001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.wc;
013:
014: import java.io.ByteArrayInputStream;
015: import java.io.ByteArrayOutputStream;
016: import java.io.File;
017: import java.io.FileOutputStream;
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.OutputStream;
021: import java.io.UnsupportedEncodingException;
022: import java.util.Collection;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.TreeSet;
027: import java.util.Arrays;
028:
029: import org.tmatesoft.svn.core.SVNErrorCode;
030: import org.tmatesoft.svn.core.SVNErrorMessage;
031: import org.tmatesoft.svn.core.SVNException;
032:
033: /**
034: * @version 1.1.1
035: * @author TMate Software Ltd.
036: */
037: public class SVNProperties {
038: public static final String SVN_HASH_TERMINATOR = "END";
039:
040: private File myFile;
041:
042: private String myPath;
043:
044: public SVNProperties(File properitesFile, String path) {
045: myFile = properitesFile;
046: myPath = path;
047: }
048:
049: public File getFile() {
050: return myFile;
051: }
052:
053: public String getPath() {
054: return myPath;
055: }
056:
057: public Collection properties(Collection target) throws SVNException {
058: target = target == null ? new TreeSet() : target;
059: if (isEmpty()) {
060: return target;
061: }
062: ByteArrayOutputStream nameOS = new ByteArrayOutputStream();
063: InputStream is = SVNFileUtil.openFileForReading(getFile());
064: try {
065: while (readProperty('K', is, nameOS)) {
066: target.add(new String(nameOS.toByteArray(), "UTF-8"));
067: nameOS.reset();
068: readProperty('V', is, null);
069: }
070: } catch (IOException e) {
071: SVNErrorMessage err = SVNErrorMessage.create(
072: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
073: SVNErrorManager.error(err, e);
074: } finally {
075: SVNFileUtil.closeFile(is);
076: }
077: return target;
078: }
079:
080: public Map asMap() throws SVNException {
081: Map result = new HashMap();
082: if (isEmpty()) {
083: return result;
084: }
085: ByteArrayOutputStream nameOS = new ByteArrayOutputStream();
086: InputStream is = SVNFileUtil.openFileForReading(getFile());
087: try {
088: while (readProperty('K', is, nameOS)) {
089: String name = new String(nameOS.toByteArray(), "UTF-8");
090: nameOS.reset();
091: readProperty('V', is, nameOS);
092: String value = new String(nameOS.toByteArray(), "UTF-8");
093: result.put(name, value);
094: nameOS.reset();
095: }
096: } catch (IOException e) {
097: SVNErrorMessage err = SVNErrorMessage
098: .create(SVNErrorCode.IO_ERROR,
099: "Cannot read properties file ''{0}'': {1}",
100: new Object[] { getFile(),
101: e.getLocalizedMessage() });
102: SVNErrorManager.error(err, e);
103: } finally {
104: SVNFileUtil.closeFile(is);
105: }
106: return result;
107: }
108:
109: public boolean compareTo(SVNProperties properties,
110: ISVNPropertyComparator comparator) throws SVNException {
111: boolean equals = true;
112: Collection props1 = properties(null);
113: Collection props2 = properties.properties(null);
114:
115: // missed in props2.
116: Collection tmp = new TreeSet(props1);
117: tmp.removeAll(props2);
118: for (Iterator props = tmp.iterator(); props.hasNext();) {
119: String missing = (String) props.next();
120: comparator.propertyDeleted(missing);
121: equals = false;
122: }
123:
124: // added in props2.
125: tmp = new TreeSet(props2);
126: tmp.removeAll(props1);
127:
128: File tmpFile = null;
129: File tmpFile1 = null;
130: File tmpFile2 = null;
131: OutputStream os = null;
132: InputStream is = null;
133: InputStream is1 = null;
134: InputStream is2 = null;
135:
136: for (Iterator props = tmp.iterator(); props.hasNext();) {
137: String added = (String) props.next();
138: try {
139: tmpFile = SVNFileUtil.createUniqueFile(getFile()
140: .getParentFile(), getFile().getName(), ".tmp");
141:
142: os = SVNFileUtil.openFileForWriting(tmpFile);
143: properties.getPropertyValue(added, os);
144: SVNFileUtil.closeFile(os);
145:
146: is = SVNFileUtil.openFileForReading(tmpFile);
147: comparator.propertyAdded(added, is, (int) tmpFile
148: .length());
149: equals = false;
150: SVNFileUtil.closeFile(is);
151: } finally {
152: if (tmpFile != null) {
153: tmpFile.delete();
154: }
155: SVNFileUtil.closeFile(os);
156: SVNFileUtil.closeFile(is);
157: tmpFile = null;
158: is = null;
159: os = null;
160: }
161: }
162:
163: // changed in props2
164: props2.retainAll(props1);
165: for (Iterator props = props2.iterator(); props.hasNext();) {
166: String changed = (String) props.next();
167:
168: try {
169: tmpFile1 = SVNFileUtil.createUniqueFile(getFile()
170: .getParentFile(), getFile().getName(), ".tmp1");
171: tmpFile2 = SVNFileUtil.createUniqueFile(getFile()
172: .getParentFile(), getFile().getName(), ".tmp2");
173:
174: os = new FileOutputStream(tmpFile1);
175: getPropertyValue(changed, os);
176: os.close();
177: os = new FileOutputStream(tmpFile2);
178: properties.getPropertyValue(changed, os);
179: os.close();
180: if (tmpFile2.length() != tmpFile1.length()) {
181: is = SVNFileUtil.openFileForReading(tmpFile2);
182: comparator.propertyChanged(changed, is,
183: (int) tmpFile2.length());
184: equals = false;
185: SVNFileUtil.closeFile(is);
186: } else {
187: is1 = SVNFileUtil.openFileForReading(tmpFile1);
188: is2 = SVNFileUtil.openFileForReading(tmpFile2);
189: boolean differs = false;
190: for (int i = 0; i < tmpFile1.length(); i++) {
191: if (is1.read() != is2.read()) {
192: differs = true;
193: break;
194: }
195: }
196: SVNFileUtil.closeFile(is1);
197: SVNFileUtil.closeFile(is2);
198: if (differs) {
199: is2 = SVNFileUtil.openFileForReading(tmpFile2);
200: comparator.propertyChanged(changed, is2,
201: (int) tmpFile2.length());
202: equals = false;
203: SVNFileUtil.closeFile(is2);
204: }
205: }
206: } catch (IOException e) {
207: SVNErrorMessage err = SVNErrorMessage.create(
208: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
209: SVNErrorManager.error(err, e);
210: } finally {
211: if (tmpFile2 != null) {
212: tmpFile2.delete();
213: }
214: if (tmpFile2 != null) {
215: tmpFile1.delete();
216: }
217: SVNFileUtil.closeFile(os);
218: SVNFileUtil.closeFile(is);
219: SVNFileUtil.closeFile(is1);
220: SVNFileUtil.closeFile(is2);
221: os = null;
222: tmpFile1 = tmpFile2 = null;
223: is = is1 = is2 = null;
224: }
225: }
226: return equals;
227: }
228:
229: public String getPropertyValue(String name) throws SVNException {
230: if (isEmpty()) {
231: return null;
232: }
233: ByteArrayOutputStream os = new ByteArrayOutputStream();
234: os = (ByteArrayOutputStream) getPropertyValue(name, os);
235: if (os != null && os.size() >= 0) {
236: byte[] bytes = os.toByteArray();
237: try {
238: return new String(bytes, "UTF-8");
239: } catch (UnsupportedEncodingException e) {
240: return new String(bytes);
241: }
242: }
243: return null;
244: }
245:
246: public OutputStream getPropertyValue(String name, OutputStream os)
247: throws SVNException {
248: if (isEmpty()) {
249: return null;
250: }
251: ByteArrayOutputStream nameOS = new ByteArrayOutputStream();
252: InputStream is = SVNFileUtil.openFileForReading(getFile());
253: try {
254: while (readProperty('K', is, nameOS)) {
255: String currentName = new String(nameOS.toByteArray(),
256: "UTF-8");
257: nameOS.reset();
258: if (currentName.equals(name)) {
259: readProperty('V', is, os);
260: return os;
261: }
262: readProperty('V', is, null);
263: }
264: } catch (IOException e) {
265: SVNErrorMessage err = SVNErrorMessage.create(
266: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
267: SVNErrorManager.error(err, e);
268: } finally {
269: SVNFileUtil.closeFile(is);
270: }
271: return null;
272: }
273:
274: public void setPropertyValue(String name, String value)
275: throws SVNException {
276: byte[] bytes = null;
277: if (value != null) {
278: try {
279: bytes = value.getBytes("UTF-8");
280: } catch (IOException e) {
281: bytes = value.getBytes();
282: }
283: }
284: int length = bytes != null && bytes.length >= 0 ? bytes.length
285: : -1;
286: setPropertyValue(name,
287: bytes != null ? new ByteArrayInputStream(bytes) : null,
288: length);
289: }
290:
291: public void setPropertyValue(String name, InputStream is, int length)
292: throws SVNException {
293: InputStream src = null;
294: OutputStream dst = null;
295: File tmpFile = null;
296: boolean empty = false;
297: try {
298: tmpFile = SVNFileUtil.createUniqueFile(getFile()
299: .getParentFile(), getFile().getName(), ".tmp");
300: if (!isEmpty()) {
301: src = SVNFileUtil.openFileForReading(getFile());
302: }
303: dst = SVNFileUtil.openFileForWriting(tmpFile);
304: empty = !copyProperties(src, dst, name, is, length);
305: } finally {
306: SVNFileUtil.closeFile(src);
307: SVNFileUtil.closeFile(dst);
308: }
309: if (tmpFile != null) {
310: if (!empty) {
311: SVNFileUtil.rename(tmpFile, getFile());
312: SVNFileUtil.setReadonly(getFile(), true);
313: } else {
314: SVNFileUtil.deleteFile(tmpFile);
315: SVNFileUtil.deleteFile(getFile());
316: }
317: }
318: }
319:
320: public void setProperties(Map properties) throws SVNException {
321: if (properties != null) {
322: for (Iterator names = properties.keySet().iterator(); names
323: .hasNext();) {
324: String name = (String) names.next();
325: String value = (String) properties.get(name);
326: setPropertyValue(name, value);
327: }
328: }
329: }
330:
331: public Map compareTo(SVNProperties properties) throws SVNException {
332: final Map locallyChangedProperties = new HashMap();
333: compareTo(properties, new ISVNPropertyComparator() {
334: public void propertyAdded(String name, InputStream value,
335: int length) {
336: propertyChanged(name, value, length);
337: }
338:
339: public void propertyChanged(String name,
340: InputStream newValue, int length) {
341: ByteArrayOutputStream os = new ByteArrayOutputStream(
342: length);
343: for (int i = 0; i < length; i++) {
344: try {
345: os.write(newValue.read());
346: } catch (IOException e) {
347: }
348: }
349: byte[] bytes = os.toByteArray();
350: try {
351: locallyChangedProperties.put(name, new String(
352: bytes, "UTF-8"));
353: } catch (UnsupportedEncodingException e) {
354: locallyChangedProperties.put(name,
355: new String(bytes));
356: }
357: }
358:
359: public void propertyDeleted(String name) {
360: locallyChangedProperties.put(name, null);
361: }
362: });
363: return locallyChangedProperties;
364: }
365:
366: public void copyTo(SVNProperties destination) throws SVNException {
367: if (isEmpty()) {
368: SVNFileUtil.deleteFile(destination.getFile());
369: } else {
370: SVNFileUtil.copyFile(getFile(), destination.getFile(),
371: false);
372: }
373: }
374:
375: public void delete() throws SVNException {
376: SVNFileUtil.deleteFile(getFile());
377: }
378:
379: public static void setProperties(Map namesToValues, File target,
380: File tmpFile, String terminator) throws SVNException {
381: OutputStream dst = null;
382: try {
383: //tmpFile = SVNFileUtil.createUniqueFile(target.getParentFile(), target.getName(), ".tmp");
384: dst = SVNFileUtil.openFileForWriting(tmpFile);
385: setProperties(namesToValues, dst, terminator);
386: } finally {
387: SVNFileUtil.closeFile(dst);
388: }
389: if (tmpFile != null) {
390: SVNFileUtil.rename(tmpFile, target);
391: SVNFileUtil.setReadonly(target, true);
392: }
393: }
394:
395: public static void setProperties(Map namesToValues,
396: OutputStream target, String terminator) throws SVNException {
397: try {
398: Object[] keys = namesToValues.keySet().toArray();
399: Arrays.sort(keys);
400: for (int i = 0; i < keys.length; i++) {
401: String propertyName = (String) keys[i];
402: writeProperty(target, 'K', propertyName
403: .getBytes("UTF-8"));
404: String propertyValue = (String) namesToValues
405: .get(propertyName);
406: writeProperty(target, 'V', propertyValue
407: .getBytes("UTF-8"));
408: }
409: if (terminator != null) {
410: target.write(terminator.getBytes("UTF-8"));
411: target.write('\n');
412: }
413: } catch (IOException ioe) {
414: SVNErrorMessage err = SVNErrorMessage.create(
415: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
416: SVNErrorManager.error(err, ioe);
417: }
418: }
419:
420: public static void appendProperty(String name, String value,
421: OutputStream target) throws SVNException {
422: if (name == null || value == null) {
423: return;
424: }
425: try {
426: writeProperty(target, 'K', name.getBytes("UTF-8"));
427: writeProperty(target, 'V', value.getBytes("UTF-8"));
428: } catch (IOException ioe) {
429: SVNErrorMessage err = SVNErrorMessage.create(
430: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
431: SVNErrorManager.error(err, ioe);
432: }
433: }
434:
435: public static void appendPropertyDeleted(String name,
436: OutputStream target) throws SVNException {
437: if (name == null) {
438: return;
439: }
440: try {
441: writeProperty(target, 'D', name.getBytes("UTF-8"));
442: } catch (IOException ioe) {
443: SVNErrorMessage err = SVNErrorMessage.create(
444: SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
445: SVNErrorManager.error(err, ioe);
446: }
447: }
448:
449: /** @noinspection ResultOfMethodCallIgnored */
450: private static boolean copyProperties(InputStream is,
451: OutputStream os, String name, InputStream value, int length)
452: throws SVNException {
453: // read names, till name is met, then insert value or skip this
454: // property.
455: int propCount = 0;
456: try {
457: if (is != null) {
458: int l = 0;
459: while ((l = readLength(is, 'K')) > 0) {
460: byte[] nameBytes = new byte[l];
461: is.read(nameBytes);
462: is.read();
463: if (name.equals(new String(nameBytes, "UTF-8"))) {
464: // skip property, will be appended.
465: readProperty('V', is, null);
466: continue;
467: }
468: // save name
469: writeProperty(os, 'K', nameBytes);
470: l = readLength(is, 'V');
471: writeProperty(os, 'V', is, l);
472: is.read();
473: propCount++;
474: }
475: }
476: if (value != null && length >= 0) {
477: byte[] nameBytes = name.getBytes("UTF-8");
478: writeProperty(os, 'K', nameBytes);
479: writeProperty(os, 'V', value, length);
480: propCount++;
481: }
482: if (propCount > 0) {
483: os.write(new byte[] { 'E', 'N', 'D', '\n' });
484: }
485: } catch (IOException e) {
486: SVNErrorMessage err = SVNErrorMessage.create(
487: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
488: SVNErrorManager.error(err, e);
489: }
490: return propCount > 0;
491: }
492:
493: private static boolean readProperty(char type, InputStream is,
494: OutputStream os) throws IOException {
495: int length = readLength(is, type);
496: if (length < 0) {
497: return false;
498: }
499: if (os != null) {
500: byte[] value = new byte[length];
501: int r = is.read(value);
502: os.write(value, 0, r);
503: } else {
504: while (length > 0) {
505: length -= is.skip(length);
506: }
507: }
508: return is.read() == '\n';
509: }
510:
511: private static void writeProperty(OutputStream os, char type,
512: byte[] value) throws IOException {
513: os.write((byte) type);
514: os.write(' ');
515: os.write(Integer.toString(value.length).getBytes());
516: os.write('\n');
517: os.write(value);
518: os.write('\n');
519: }
520:
521: private static void writeProperty(OutputStream os, char type,
522: InputStream value, int length) throws IOException {
523: os.write((byte) type);
524: os.write(' ');
525: os.write(Integer.toString(length).getBytes());
526: os.write('\n');
527: for (int i = 0; i < length; i++) {
528: int r = value.read();
529: os.write(r);
530: }
531: os.write('\n');
532: }
533:
534: private static int readLength(InputStream is, char type)
535: throws IOException {
536: byte[] buffer = new byte[255];
537: int r = is.read(buffer, 0, 4);
538: if (r != 4) {
539: throw new IOException("invalid properties file format");
540: }
541: // either END\n or K x\n
542: if (buffer[0] == 'E' && buffer[1] == 'N' && buffer[2] == 'D'
543: && buffer[3] == '\n') {
544: return -1;
545: } else if (buffer[0] == type && buffer[1] == ' ') {
546: int i = 4;
547: if (buffer[3] != '\n') {
548: while (true) {
549: int b = is.read();
550: if (b < 0) {
551: throw new IOException(
552: "invalid properties file format");
553: } else if (b == '\n') {
554: break;
555: }
556: buffer[i] = (byte) (0xFF & b);
557: i++;
558: }
559: } else {
560: i = 3;
561: }
562: String length = new String(buffer, 2, i - 2);
563: return Integer.parseInt(length.trim());
564: }
565: throw new IOException("invalid properties file format");
566: }
567:
568: public boolean isEmpty() {
569: return !getFile().exists() || getFile().length() <= 4;
570: }
571: }
|