2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
41
package org.netbeans.modules.subversion.client.parser;
43
import java.io.BufferedInputStream;
44
import java.io.BufferedReader;
46
import java.io.IOException;
47
import java.io.InputStream;
48
import java.text.ParseException;
49
import java.util.ArrayList;
50
import java.util.Arrays;
51
import java.util.Date;
52
import java.util.List;
54
import org.netbeans.modules.subversion.config.KVFile;
55
import org.openide.ErrorManager;
61
public class WorkingCopyDetails {
62
static final String FILE_ATTRIBUTE_VALUE = "file"; // NOI18
63
static final String IS_HANDLED = "handled";
64
private static final char SLASH_N = '\n';
65
private static final char SLASH_R = '\r';
67
static final String VERSION_ATTR_KEY = "wc-version";
68
static final String VERSION_UNKNOWN = "";
69
static final String VERSION_13 = "1.3";
70
static final String VERSION_14 = "1.4";
72
private final File file;
73
//These Map stores the values in the SVN entities file
74
//for the file and its parent directory
75
private final Map<String, String> attributes;
76
//private Properties parentProps;
77
//These Properties store the working and base versions of the
78
//SVN properties for the file
79
private Map<String, byte[]> workingSvnProperties = null;
80
private Map<String, byte[]> baseSvnProperties = null;
82
protected File propertiesFile = null;
83
private File basePropertiesFile = null;
84
private File textBaseFile = null;
86
/** Creates a new instance of WorkingCopyDetails */
87
private WorkingCopyDetails(File file, Map<String, String> attributes) {
89
this.attributes = attributes;
92
public static WorkingCopyDetails createWorkingCopy(File file, Map<String, String> attributes) {
93
String version = attributes != null ? attributes.get(VERSION_ATTR_KEY) : VERSION_UNKNOWN;
95
if(version.equals(VERSION_13)) {
97
return new WorkingCopyDetails(file, attributes);
99
} else if(version.equals(VERSION_14)) {
101
return new WorkingCopyDetails(file, attributes) {
102
public boolean propertiesExist() throws IOException {
103
return getAttributes().containsKey("has-props"); // NOI18N
105
public boolean propertiesModified() throws IOException {
106
return getAttributes().containsKey("has-prop-mods"); // NOI18N
108
File getPropertiesFile() throws IOException {
109
if (propertiesFile == null) {
110
// unchanged properties have only the base file
111
boolean modified = getBooleanValue("has-prop-mods"); // NOI18N
112
propertiesFile = SvnWcUtils.getPropertiesFile(getFile(), modified ? false : true);
114
return propertiesFile;
118
} else if(version.equals(VERSION_UNKNOWN)) {
120
WorkingCopyDetails wcd = new WorkingCopyDetails(file, attributes);
121
if(!wcd.isHandled()) {
124
// how is this possible?
125
throw new UnsupportedOperationException("Unknown SVN working copy version: " + version); // NOI18N
129
throw new UnsupportedOperationException("Unknown SVN working copy version: " + version); // NOI18N
133
ErrorManager.getDefault().log(ErrorManager.WARNING, "Could not determine the SVN working copy version for " + file + ". Falling back on 1.3"); // NOI18N
134
return new WorkingCopyDetails(file, attributes);
138
protected Map<String, String> getAttributes() {
142
protected File getFile() {
146
public String getValue(String propertyName, String defaultValue) {
147
String returnValue = getValue(propertyName);
148
return returnValue != null ? returnValue : defaultValue;
151
public String getValue(String key) {
152
if(key==null) return null;
153
return getAttributes() != null ? getAttributes().get(key) : null;
156
public long getLongValue(String key) throws LocalSubversionException {
158
String value = getValue(key);
159
if(value==null) return -1;
160
return Long.parseLong(value);
161
} catch (NumberFormatException ex) {
162
throw new LocalSubversionException(ex);
166
public Date getDateValue(String key) throws LocalSubversionException {
168
String value = getValue(key);
169
if(value==null || value.equals("")) return null;
170
return SvnWcUtils.parseSvnDate(value);
171
} catch (ParseException ex) {
172
throw new LocalSubversionException(ex);
176
public boolean getBooleanValue(String key) {
177
String value = getValue(key);
178
if(value==null) return false;
179
return Boolean.valueOf(value).booleanValue();
182
public boolean isHandled() {
183
return getBooleanValue(IS_HANDLED);
186
public boolean isFile() {
187
return getAttributes() !=null ? FILE_ATTRIBUTE_VALUE.equals(getAttributes().get("kind")) : false; // NOI18N
190
File getPropertiesFile() throws IOException {
191
if (propertiesFile == null) {
192
propertiesFile = SvnWcUtils.getPropertiesFile(file, false);
194
return propertiesFile;
197
File getBasePropertiesFile() throws IOException {
198
if (basePropertiesFile == null) {
199
basePropertiesFile = SvnWcUtils.getPropertiesFile(file, true);
201
return basePropertiesFile;
204
private File getTextBaseFile() throws IOException {
205
if (textBaseFile == null) {
206
textBaseFile = SvnWcUtils.getTextBaseFile(file);
211
private Map<String, byte[]> getWorkingSvnProperties() throws IOException {
212
if (workingSvnProperties == null) {
213
workingSvnProperties = loadProperties(getPropertiesFile());
215
return workingSvnProperties;
218
private Map<String, byte[]> getBaseSvnProperties() throws IOException {
219
if (baseSvnProperties == null) {
220
baseSvnProperties = loadProperties(getBasePropertiesFile());
222
return baseSvnProperties;
225
public boolean propertiesExist() throws IOException {
226
boolean returnValue = false;
227
File propsFile = getPropertiesFile();
228
returnValue = propsFile != null ? propsFile.exists() : false;
230
//A size of 4 bytes is equivalent to empty properties
231
InputStream inputStream = new java.io.FileInputStream(propsFile);
234
int retval = inputStream.read();
235
while ((retval != -1) && (size < 5)) {
237
retval = inputStream.read();
239
returnValue = (size > 4);
248
public boolean propertiesModified() throws IOException {
249
Map<String, byte[]> baseProps = getBaseSvnProperties();
250
Map<String, byte[]> props = getWorkingSvnProperties();
251
if ((baseProps == null) && (props != null)) {
254
if ((baseProps != null) && (props == null)) {
257
if ((baseProps == null) && (props == null)) {
260
if(baseProps.size() != props.size()) {
263
for(Map.Entry<String, byte[]> baseEntry : baseProps.entrySet()) {
264
byte[] propsValue = props.get(baseEntry.getKey());
265
if(propsValue == null || !Arrays.equals(propsValue, baseEntry.getValue())) {
272
private Map<String, byte[]> loadProperties(File propsFile) throws IOException {
273
if(propsFile == null || !propsFile.exists()) {
276
KVFile kv = new KVFile(propsFile);
277
return kv.getNormalizedMap();
280
public boolean textModified() throws IOException {
282
File baseFile = getTextBaseFile();
283
if ((file == null) && (baseFile != null)) {
286
if ((file != null) && (baseFile == null)) {
289
if ((file == null) && (baseFile == null)) {
292
Map<String, byte[]> workingSvnProps = getWorkingSvnProperties();
293
if(workingSvnProps != null) {
294
String svnSpecial = getPropertyValue(workingSvnProps, "svn:special"); // NOI18N
295
if (svnSpecial != null && svnSpecial.equals("*")) { // NOI18N
296
if (isSymbolicLink()) {
300
String svnKeywords = getPropertyValue(workingSvnProps, "svn:keywords"); // NOI18N
301
if (svnKeywords != null) {
302
return isModifiedByLine(svnKeywords.trim());
305
return isModifiedByByte();
310
private String getPropertyValue(Map<String, byte[]> props, String key) {
311
byte[] byteValue = props.get(key);
312
if(byteValue != null && byteValue.length > 0) {
313
return new String(byteValue);
318
private boolean isSymbolicLink() throws IOException {
319
boolean returnValue = false;
321
File baseFile = getTextBaseFile();
322
if (baseFile != null) {
323
BufferedReader reader = null;
325
reader = new BufferedReader(new java.io.FileReader(baseFile));
326
String firstLine = reader.readLine();
327
returnValue = firstLine.startsWith("link"); // NOI18N
329
if (reader != null) {
338
* Assumes that textBaseFile exists
340
private boolean isModifiedByByte() throws IOException {
341
boolean returnValue = false;
343
InputStream baseStream = null;
344
InputStream fileStream = null;
346
baseStream = new BufferedInputStream(new java.io.FileInputStream(textBaseFile));
347
fileStream = new BufferedInputStream(new java.io.FileInputStream(file));
349
int baseRetVal = baseStream.read();
350
int fileRetVal = fileStream.read();
352
while (baseRetVal != -1) {
353
if (baseRetVal != fileRetVal) {
354
//Check for line endings... ignore if need be
355
boolean isLineEnding = false;
356
if ((SLASH_N == ((char) baseRetVal)) && SLASH_R == ((char) fileRetVal)) {
357
fileRetVal = fileStream.read();
358
isLineEnding = (SLASH_N == ((char) fileRetVal));
359
} else if ((SLASH_N == ((char) fileRetVal)) && SLASH_R == ((char) baseRetVal)) {
360
baseRetVal = baseStream.read();
361
isLineEnding = (SLASH_N == ((char) baseRetVal));
364
if (!(isLineEnding)) {
368
baseRetVal = baseStream.read();
369
fileRetVal = fileStream.read();
372
//If we're here, then baseRetVal is -1. So, returnValue
373
//should be true is fileRetVal != -1
374
returnValue = (fileRetVal != -1);
376
if(fileStream != null) {
379
if(baseStream != null) {
388
* Assumes that textBaseFile exists
390
private boolean isModifiedByLine(String rawKeywords) throws IOException {
391
if(rawKeywords == null || rawKeywords.equals("")) {
394
boolean returnValue = false;
396
List<String> keywordsList = new ArrayList<String>();
398
rawKeywords = rawKeywords.replaceAll("\n", " ");
399
rawKeywords = rawKeywords.replaceAll("\t", " ");
400
keywordsList.addAll(normalizeKeywords(rawKeywords.split(" "))); // NOI18N
402
String[] keywords = keywordsList.toArray(new String[keywordsList.size()]);
404
BufferedReader baseReader = null;
405
BufferedReader fileReader = null;
408
baseReader = new BufferedReader(new java.io.FileReader(textBaseFile));
409
fileReader = new BufferedReader(new java.io.FileReader(file));
411
String baseLine = baseReader.readLine();
412
String fileLine = fileReader.readLine();
414
while (baseLine != null && fileLine != null) {
416
if (!fileLine.equals(baseLine)) {
417
boolean equal = false;
418
for (int i = 0; i < keywords.length; i++) {
419
String headerPattern = "$" + keywords[i]; // NOI18N
420
if(fileLine.indexOf(headerPattern) > -1) {
421
equal = compareKeywordLines(fileLine, baseLine, keywords);
430
baseLine = baseReader.readLine();
431
fileLine = fileReader.readLine();
434
returnValue = baseLine != null || fileLine != null;
437
if (fileReader != null) {
441
if (baseReader != null) {
449
private List<String> normalizeKeywords(String[] keywords) {
450
List<String> keywordsList = new ArrayList<String>();
451
for (int i = 0; i < keywords.length; i++) {
452
String kw = keywords[i].toLowerCase().trim();
453
if(kw.equals("date") || kw.equals("lastchangeddate")) { // NOI18N
454
keywordsList.add("LastChangedDate"); // NOI18N
455
keywordsList.add("Date"); // NOI18N
456
} else if(kw.equals("revision") || kw.equals("rev") || kw.equals("lastchangedrevision")) { // NOI18N
457
keywordsList.add("LastChangedRevision"); // NOI18N
458
keywordsList.add("Revision"); // NOI18N
459
keywordsList.add("Rev"); // NOI18N
460
} else if(kw.equals("author") || kw.equals("lastchangedby")) { // NOI18N
461
keywordsList.add("LastChangedBy"); // NOI18N
462
keywordsList.add("Author"); // NOI18N
463
} else if(kw.equals("url") || kw.equals("headurl")) { // NOI18N
464
keywordsList.add("HeadURL"); // NOI18N
465
keywordsList.add("URL"); // NOI18N
466
} else if(kw.equals("id")) { // NOI18N
467
keywordsList.add("Id"); // NOI18N
468
} else if(!kw.equals("")){
469
keywordsList.add(keywords[i]);
475
private boolean compareKeywordLines(String modifiedLine, String baseLine, String[] keywords) {
478
for (int fileIdx = 0; fileIdx < baseLine.length(); fileIdx++) {
480
if(baseLine.charAt(fileIdx) == '$') {
481
// 1. could be a keyword ...
482
for (int keywordsIdx = 0; keywordsIdx < keywords.length; keywordsIdx++) {
484
String keyword = keywords[keywordsIdx]; // NOI18N
486
boolean gotHeader = false;
487
for (int keyIdx = 0; keyIdx < keyword.length(); keyIdx++) {
488
if(fileIdx + keyIdx + 1 > baseLine.length() - 1 || // we are already at the end of the baseline
489
keyword.charAt(keyIdx) != baseLine.charAt(fileIdx + keyIdx + 1)) // the chars are not equal
492
break; // 2. it's not a keyword -> try the next one
500
fileIdx += keyword.length();
502
// 3. now check if there is somthing like "$", ":$" after the keyword
503
if(checkFollowingString(baseLine, fileIdx + 1, "$")) {
505
} else if(checkFollowingString(baseLine, fileIdx + 1, ":$")) {
507
} else if(checkFollowingString(baseLine, fileIdx + 1, "::")) {
508
int spaces = getSpacesCount(baseLine, fileIdx + 3);
512
fileIdx += spaces + 3;
514
// it's not a keyword -> rollback the index and keep comparing
515
fileIdx -= keyword.length();
519
// 4. it was a correctly closed keyword -> skip the chars until the next '$'
520
// for the modified file - '$Id: '
521
modifiedIdx += keyword.length() + 1; //
522
while(++modifiedIdx < modifiedLine.length() && modifiedLine.charAt(modifiedIdx) != '$');
524
if(modifiedIdx >= modifiedLine.length()) {
525
// modified line is done but we found a keyword -> wrong
532
if(modifiedLine.charAt(modifiedIdx) != baseLine.charAt(fileIdx)) {
536
if(modifiedIdx >= modifiedLine.length()) {
537
// if the modified line is done then must be also the base line done
538
return fileIdx == baseLine.length() - 1;
541
return modifiedIdx == modifiedLine.length() - 2;
544
private boolean checkFollowingString(String baseLine, int offset, String str) {
545
if(baseLine.length() < offset + str.length()) {
548
for (int idx = 0; idx < str.length(); idx++) {
549
if(baseLine.charAt(offset + idx) != str.charAt(idx)) {
556
private int getSpacesCount(String baseLine, int offset) {
557
if(baseLine.length() <= offset) {
560
for (int idx = 0; idx < baseLine.length(); idx++) {
561
char c = baseLine.charAt(offset + idx);
564
} else if(c == '$') {