001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.subversion.client.parser;
042:
043: import java.io.BufferedInputStream;
044: import java.io.BufferedReader;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.text.ParseException;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.Date;
052: import java.util.List;
053: import java.util.Map;
054: import org.netbeans.modules.subversion.Subversion;
055: import org.netbeans.modules.subversion.config.KVFile;
056:
057: /**
058: *
059: * @author Ed Hillmann
060: */
061: public class WorkingCopyDetails {
062: static final String FILE_ATTRIBUTE_VALUE = "file"; // NOI18
063: static final String IS_HANDLED = "handled";
064: private static final char SLASH_N = '\n';
065: private static final char SLASH_R = '\r';
066:
067: static final String VERSION_ATTR_KEY = "wc-version";
068: static final String VERSION_UNKNOWN = "";
069: static final String VERSION_13 = "1.3";
070: static final String VERSION_14 = "1.4";
071:
072: private final File file;
073: //These Map stores the values in the SVN entities file
074: //for the file and its parent directory
075: private final Map<String, String> attributes;
076: //private Properties parentProps;
077: //These Properties store the working and base versions of the
078: //SVN properties for the file
079: private Map<String, byte[]> workingSvnProperties = null;
080: private Map<String, byte[]> baseSvnProperties = null;
081:
082: protected File propertiesFile = null;
083: private File basePropertiesFile = null;
084: private File textBaseFile = null;
085:
086: /** Creates a new instance of WorkingCopyDetails */
087: private WorkingCopyDetails(File file, Map<String, String> attributes) {
088: this .file = file;
089: this .attributes = attributes;
090: }
091:
092: public static WorkingCopyDetails createWorkingCopy(File file,
093: Map<String, String> attributes) {
094: String version = attributes != null ? attributes
095: .get(VERSION_ATTR_KEY) : VERSION_UNKNOWN;
096: if (version != null) {
097: if (version.equals(VERSION_13)) {
098:
099: return new WorkingCopyDetails(file, attributes);
100:
101: } else if (version.equals(VERSION_14)) {
102:
103: return new WorkingCopyDetails(file, attributes) {
104: public boolean propertiesExist() throws IOException {
105: return getAttributes().containsKey("has-props"); // NOI18N
106: }
107:
108: public boolean propertiesModified()
109: throws IOException {
110: return getAttributes().containsKey(
111: "has-prop-mods"); // NOI18N
112: }
113:
114: File getPropertiesFile() throws IOException {
115: if (propertiesFile == null) {
116: // unchanged properties have only the base file
117: boolean modified = getBooleanValue("has-prop-mods"); // NOI18N
118: propertiesFile = SvnWcUtils
119: .getPropertiesFile(getFile(),
120: modified ? false : true);
121: }
122: return propertiesFile;
123: }
124: };
125:
126: } else if (version.equals(VERSION_UNKNOWN)) {
127:
128: WorkingCopyDetails wcd = new WorkingCopyDetails(file,
129: attributes);
130: if (!wcd.isHandled()) {
131: return wcd;
132: }
133: // how is this possible?
134: throw new UnsupportedOperationException(
135: "Unknown SVN working copy version: " + version); // NOI18N
136:
137: } else {
138:
139: throw new UnsupportedOperationException(
140: "Unknown SVN working copy version: " + version); // NOI18N
141:
142: }
143: } else {
144: Subversion.LOG
145: .warning("Could not determine the SVN working copy version for "
146: + file + ". Falling back on 1.3"); // NOI18N
147: return new WorkingCopyDetails(file, attributes);
148: }
149: }
150:
151: protected Map<String, String> getAttributes() {
152: return attributes;
153: }
154:
155: protected File getFile() {
156: return file;
157: }
158:
159: public String getValue(String propertyName, String defaultValue) {
160: String returnValue = getValue(propertyName);
161: return returnValue != null ? returnValue : defaultValue;
162: }
163:
164: public String getValue(String key) {
165: if (key == null)
166: return null;
167: return getAttributes() != null ? getAttributes().get(key)
168: : null;
169: }
170:
171: public long getLongValue(String key)
172: throws LocalSubversionException {
173: try {
174: String value = getValue(key);
175: if (value == null)
176: return -1;
177: return Long.parseLong(value);
178: } catch (NumberFormatException ex) {
179: throw new LocalSubversionException(ex);
180: }
181: }
182:
183: public Date getDateValue(String key)
184: throws LocalSubversionException {
185: try {
186: String value = getValue(key);
187: if (value == null || value.equals(""))
188: return null;
189: return SvnWcUtils.parseSvnDate(value);
190: } catch (ParseException ex) {
191: throw new LocalSubversionException(ex);
192: }
193: }
194:
195: public boolean getBooleanValue(String key) {
196: String value = getValue(key);
197: if (value == null)
198: return false;
199: return Boolean.valueOf(value).booleanValue();
200: }
201:
202: public boolean isHandled() {
203: return getBooleanValue(IS_HANDLED);
204: }
205:
206: public boolean isFile() {
207: return getAttributes() != null ? FILE_ATTRIBUTE_VALUE
208: .equals(getAttributes().get("kind")) : false; // NOI18N
209: }
210:
211: File getPropertiesFile() throws IOException {
212: if (propertiesFile == null) {
213: propertiesFile = SvnWcUtils.getPropertiesFile(file, false);
214: }
215: return propertiesFile;
216: }
217:
218: File getBasePropertiesFile() throws IOException {
219: if (basePropertiesFile == null) {
220: basePropertiesFile = SvnWcUtils.getPropertiesFile(file,
221: true);
222: }
223: return basePropertiesFile;
224: }
225:
226: private File getTextBaseFile() throws IOException {
227: if (textBaseFile == null) {
228: textBaseFile = SvnWcUtils.getTextBaseFile(file);
229: }
230: return textBaseFile;
231: }
232:
233: private Map<String, byte[]> getWorkingSvnProperties()
234: throws IOException {
235: if (workingSvnProperties == null) {
236: workingSvnProperties = loadProperties(getPropertiesFile());
237: }
238: return workingSvnProperties;
239: }
240:
241: private Map<String, byte[]> getBaseSvnProperties()
242: throws IOException {
243: if (baseSvnProperties == null) {
244: baseSvnProperties = loadProperties(getBasePropertiesFile());
245: }
246: return baseSvnProperties;
247: }
248:
249: public boolean propertiesExist() throws IOException {
250: boolean returnValue = false;
251: File propsFile = getPropertiesFile();
252: returnValue = propsFile != null ? propsFile.exists() : false;
253: if (returnValue) {
254: //A size of 4 bytes is equivalent to empty properties
255: InputStream inputStream = new java.io.FileInputStream(
256: propsFile);
257: try {
258: int size = 0;
259: int retval = inputStream.read();
260: while ((retval != -1) && (size < 5)) {
261: size++;
262: retval = inputStream.read();
263: }
264: returnValue = (size > 4);
265: } finally {
266: inputStream.close();
267: }
268: }
269:
270: return returnValue;
271: }
272:
273: public boolean propertiesModified() throws IOException {
274: Map<String, byte[]> baseProps = getBaseSvnProperties();
275: Map<String, byte[]> props = getWorkingSvnProperties();
276: if ((baseProps == null) && (props != null)) {
277: return true;
278: }
279: if ((baseProps != null) && (props == null)) {
280: return true;
281: }
282: if ((baseProps == null) && (props == null)) {
283: return false;
284: }
285: if (baseProps.size() != props.size()) {
286: return true;
287: }
288: for (Map.Entry<String, byte[]> baseEntry : baseProps.entrySet()) {
289: byte[] propsValue = props.get(baseEntry.getKey());
290: if (propsValue == null
291: || !Arrays.equals(propsValue, baseEntry.getValue())) {
292: return true;
293: }
294: }
295: return false;
296: }
297:
298: private Map<String, byte[]> loadProperties(File propsFile)
299: throws IOException {
300: if (propsFile == null || !propsFile.exists()) {
301: return null;
302: }
303: KVFile kv = new KVFile(propsFile);
304: return kv.getNormalizedMap();
305: }
306:
307: public boolean textModified() throws IOException {
308: if (file.exists()) {
309: File baseFile = getTextBaseFile();
310: if ((file == null) && (baseFile != null)) {
311: return true;
312: }
313: if ((file != null) && (baseFile == null)) {
314: return true;
315: }
316: if ((file == null) && (baseFile == null)) {
317: return false;
318: }
319: Map<String, byte[]> workingSvnProps = getWorkingSvnProperties();
320: if (workingSvnProps != null) {
321: String svnSpecial = getPropertyValue(workingSvnProps,
322: "svn:special"); // NOI18N
323: if (svnSpecial != null && svnSpecial.equals("*")) { // NOI18N
324: if (isSymbolicLink()) {
325: return false;
326: }
327: }
328: String svnKeywords = getPropertyValue(workingSvnProps,
329: "svn:keywords"); // NOI18N
330: if (svnKeywords != null) {
331: return isModifiedByLine(svnKeywords.trim());
332: }
333: }
334: return isModifiedByByte();
335: }
336: return false;
337: }
338:
339: private String getPropertyValue(Map<String, byte[]> props,
340: String key) {
341: byte[] byteValue = props.get(key);
342: if (byteValue != null && byteValue.length > 0) {
343: return new String(byteValue);
344: }
345: return null;
346: }
347:
348: private boolean isSymbolicLink() throws IOException {
349: boolean returnValue = false;
350:
351: File baseFile = getTextBaseFile();
352: if (baseFile != null) {
353: BufferedReader reader = null;
354: try {
355: reader = new BufferedReader(new java.io.FileReader(
356: baseFile));
357: String firstLine = reader.readLine();
358: returnValue = firstLine.startsWith("link"); // NOI18N
359: } finally {
360: if (reader != null) {
361: reader.close();
362: }
363: }
364: }
365: return returnValue;
366: }
367:
368: /**
369: * Assumes that textBaseFile exists
370: */
371: private boolean isModifiedByByte() throws IOException {
372: boolean returnValue = false;
373:
374: InputStream baseStream = null;
375: InputStream fileStream = null;
376: try {
377: baseStream = new BufferedInputStream(
378: new java.io.FileInputStream(textBaseFile));
379: fileStream = new BufferedInputStream(
380: new java.io.FileInputStream(file));
381:
382: int baseRetVal = baseStream.read();
383: int fileRetVal = fileStream.read();
384:
385: while (baseRetVal != -1) {
386: if (baseRetVal != fileRetVal) {
387: //Check for line endings... ignore if need be
388: boolean isLineEnding = false;
389: if ((SLASH_N == ((char) baseRetVal))
390: && SLASH_R == ((char) fileRetVal)) {
391: fileRetVal = fileStream.read();
392: isLineEnding = (SLASH_N == ((char) fileRetVal));
393: } else if ((SLASH_N == ((char) fileRetVal))
394: && SLASH_R == ((char) baseRetVal)) {
395: baseRetVal = baseStream.read();
396: isLineEnding = (SLASH_N == ((char) baseRetVal));
397: }
398:
399: if (!(isLineEnding)) {
400: return true;
401: }
402: }
403: baseRetVal = baseStream.read();
404: fileRetVal = fileStream.read();
405: }
406:
407: //If we're here, then baseRetVal is -1. So, returnValue
408: //should be true is fileRetVal != -1
409: returnValue = (fileRetVal != -1);
410: } finally {
411: if (fileStream != null) {
412: fileStream.close();
413: }
414: if (baseStream != null) {
415: baseStream.close();
416: }
417: }
418:
419: return returnValue;
420: }
421:
422: /**
423: * Assumes that textBaseFile exists
424: */
425: private boolean isModifiedByLine(String rawKeywords)
426: throws IOException {
427: if (rawKeywords == null || rawKeywords.equals("")) {
428: return false;
429: }
430: boolean returnValue = false;
431:
432: List<String> keywordsList = new ArrayList<String>();
433:
434: rawKeywords = rawKeywords.replaceAll("\n", " ");
435: rawKeywords = rawKeywords.replaceAll("\t", " ");
436: keywordsList.addAll(normalizeKeywords(rawKeywords.split(" "))); // NOI18N
437:
438: String[] keywords = keywordsList
439: .toArray(new String[keywordsList.size()]);
440:
441: BufferedReader baseReader = null;
442: BufferedReader fileReader = null;
443:
444: try {
445: baseReader = new BufferedReader(new java.io.FileReader(
446: textBaseFile));
447: fileReader = new BufferedReader(
448: new java.io.FileReader(file));
449:
450: String baseLine = baseReader.readLine();
451: String fileLine = fileReader.readLine();
452:
453: while (baseLine != null && fileLine != null) {
454:
455: if (!fileLine.equals(baseLine)) {
456: boolean equal = false;
457: for (int i = 0; i < keywords.length; i++) {
458: String headerPattern = "$" + keywords[i]; // NOI18N
459: if (fileLine.indexOf(headerPattern) > -1) {
460: equal = compareKeywordLines(fileLine,
461: baseLine, keywords);
462: break;
463: }
464: }
465: if (!equal) {
466: return true;
467: }
468: }
469:
470: baseLine = baseReader.readLine();
471: fileLine = fileReader.readLine();
472: }
473:
474: returnValue = baseLine != null || fileLine != null;
475:
476: } finally {
477: if (fileReader != null) {
478: fileReader.close();
479: }
480:
481: if (baseReader != null) {
482: baseReader.close();
483: }
484: }
485:
486: return returnValue;
487: }
488:
489: private List<String> normalizeKeywords(String[] keywords) {
490: List<String> keywordsList = new ArrayList<String>();
491: for (int i = 0; i < keywords.length; i++) {
492: String kw = keywords[i].toLowerCase().trim();
493: if (kw.equals("date") || kw.equals("lastchangeddate")) { // NOI18N
494: keywordsList.add("LastChangedDate"); // NOI18N
495: keywordsList.add("Date"); // NOI18N
496: } else if (kw.equals("revision") || kw.equals("rev")
497: || kw.equals("lastchangedrevision")) { // NOI18N
498: keywordsList.add("LastChangedRevision"); // NOI18N
499: keywordsList.add("Revision"); // NOI18N
500: keywordsList.add("Rev"); // NOI18N
501: } else if (kw.equals("author")
502: || kw.equals("lastchangedby")) { // NOI18N
503: keywordsList.add("LastChangedBy"); // NOI18N
504: keywordsList.add("Author"); // NOI18N
505: } else if (kw.equals("url") || kw.equals("headurl")) { // NOI18N
506: keywordsList.add("HeadURL"); // NOI18N
507: keywordsList.add("URL"); // NOI18N
508: } else if (kw.equals("id")) { // NOI18N
509: keywordsList.add("Id"); // NOI18N
510: } else if (!kw.equals("")) {
511: keywordsList.add(keywords[i]);
512: }
513: }
514: return keywordsList;
515: }
516:
517: private boolean compareKeywordLines(String modifiedLine,
518: String baseLine, String[] keywords) {
519:
520: int modifiedIdx = 0;
521: for (int fileIdx = 0; fileIdx < baseLine.length(); fileIdx++) {
522:
523: if (baseLine.charAt(fileIdx) == '$') {
524: // 1. could be a keyword ...
525: for (int keywordsIdx = 0; keywordsIdx < keywords.length; keywordsIdx++) {
526:
527: String keyword = keywords[keywordsIdx]; // NOI18N
528:
529: boolean gotHeader = false;
530: for (int keyIdx = 0; keyIdx < keyword.length(); keyIdx++) {
531: if (fileIdx + keyIdx + 1 > baseLine.length() - 1
532: || // we are already at the end of the baseline
533: keyword.charAt(keyIdx) != baseLine
534: .charAt(fileIdx + keyIdx + 1)) // the chars are not equal
535: {
536: gotHeader = false;
537: break; // 2. it's not a keyword -> try the next one
538: }
539: gotHeader = true;
540: }
541:
542: if (gotHeader) {
543:
544: // base file idx
545: fileIdx += keyword.length();
546:
547: // 3. now check if there is somthing like "$", ":$" after the keyword
548: if (checkFollowingString(baseLine, fileIdx + 1,
549: "$")) {
550: fileIdx += 1;
551: } else if (checkFollowingString(baseLine,
552: fileIdx + 1, ":$")) {
553: fileIdx += 2;
554: } else if (checkFollowingString(baseLine,
555: fileIdx + 1, "::")) {
556: int spaces = getSpacesCount(baseLine,
557: fileIdx + 3);
558: if (spaces <= 0) {
559: return false;
560: }
561: fileIdx += spaces + 3;
562: } else {
563: // it's not a keyword -> rollback the index and keep comparing
564: fileIdx -= keyword.length();
565: break;
566: }
567:
568: // 4. it was a correctly closed keyword -> skip the chars until the next '$'
569: // for the modified file - '$Id: '
570: modifiedIdx += keyword.length() + 1; //
571: while (++modifiedIdx < modifiedLine.length()
572: && modifiedLine.charAt(modifiedIdx) != '$')
573: ;
574:
575: if (modifiedIdx >= modifiedLine.length()) {
576: // modified line is done but we found a keyword -> wrong
577: return false;
578: }
579: break;
580: }
581: }
582: }
583: if (modifiedLine.charAt(modifiedIdx) != baseLine
584: .charAt(fileIdx)) {
585: return false;
586: }
587: modifiedIdx++;
588: if (modifiedIdx >= modifiedLine.length()) {
589: // if the modified line is done then must be also the base line done
590: return fileIdx == baseLine.length() - 1;
591: }
592: }
593: return modifiedIdx == modifiedLine.length() - 2;
594: }
595:
596: private boolean checkFollowingString(String baseLine, int offset,
597: String str) {
598: if (baseLine.length() < offset + str.length()) {
599: return false;
600: }
601: for (int idx = 0; idx < str.length(); idx++) {
602: if (baseLine.charAt(offset + idx) != str.charAt(idx)) {
603: return false;
604: }
605: }
606: return true;
607: }
608:
609: private int getSpacesCount(String baseLine, int offset) {
610: if (baseLine.length() <= offset) {
611: return -1;
612: }
613: for (int idx = 0; idx < baseLine.length(); idx++) {
614: char c = baseLine.charAt(offset + idx);
615: if (c == ' ') {
616: continue;
617: } else if (c == '$') {
618: return idx;
619: }
620: return -1;
621: }
622: return -1;
623: }
624:
625: }
|