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.wc;
013:
014: import java.io.ByteArrayOutputStream;
015: import java.io.File;
016: import java.io.IOException;
017: import java.io.InputStream;
018: import java.io.OutputStream;
019: import java.io.OutputStreamWriter;
020: import java.io.Writer;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.TreeMap;
026:
027: import org.tmatesoft.svn.core.SVNErrorCode;
028: import org.tmatesoft.svn.core.SVNErrorMessage;
029: import org.tmatesoft.svn.core.SVNException;
030: import org.tmatesoft.svn.core.SVNProperty;
031: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
032: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
033: import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
034:
035: import de.regnis.q.sequence.line.diff.QDiffGenerator;
036: import de.regnis.q.sequence.line.diff.QDiffGeneratorFactory;
037: import de.regnis.q.sequence.line.diff.QDiffManager;
038: import de.regnis.q.sequence.line.diff.QDiffUniGenerator;
039:
040: /**
041: * <b>DefaultSVNDiffGenerator</b> is a default implementation of
042: * <b>ISVNDiffGenerator</b>.
043: * <p>
044: * By default, if there's no any specified implementation of the diff generator's
045: * interface, SVNKit uses this default implementation. To set a custom
046: * diff driver use {@link SVNDiffClient#setDiffGenerator(ISVNDiffGenerator) setDiffGenerator()}.
047: *
048: * @version 1.1.1
049: * @author TMate Software Ltd.
050: */
051: public class DefaultSVNDiffGenerator implements ISVNDiffGenerator {
052:
053: protected static final byte[] PROPERTIES_SEPARATOR = "___________________________________________________________________"
054: .getBytes();
055: protected static final byte[] HEADER_SEPARATOR = "==================================================================="
056: .getBytes();
057: protected static final byte[] EOL = SVNTranslator.getEOL("native");
058: protected static final String WC_REVISION_LABEL = "(working copy)";
059: protected static final InputStream EMPTY_FILE_IS = SVNFileUtil.DUMMY_IN;
060:
061: private boolean myIsForcedBinaryDiff;
062: private String myAnchorPath1;
063: private String myAnchorPath2;
064:
065: private String myEncoding;
066: private boolean myIsDiffDeleted;
067: private boolean myIsDiffAdded;
068: private boolean myIsDiffCopied;
069: private File myBasePath;
070: private boolean myIsDiffUnversioned;
071: private SVNDiffOptions myDiffOptions;
072:
073: /**
074: * Constructs a <b>DefaultSVNDiffGenerator</b>.
075: *
076: */
077: public DefaultSVNDiffGenerator() {
078: myIsDiffDeleted = true;
079: myAnchorPath1 = "";
080: myAnchorPath2 = "";
081: }
082:
083: public void init(String anchorPath1, String anchorPath2) {
084: myAnchorPath1 = anchorPath1.replace(File.separatorChar, '/');
085: myAnchorPath2 = anchorPath2.replace(File.separatorChar, '/');
086: }
087:
088: /**
089: * Sets diff options containing diff rules.
090: *
091: * @param options diff options
092: */
093: public void setDiffOptions(SVNDiffOptions options) {
094: myDiffOptions = options;
095: }
096:
097: public void setBasePath(File basePath) {
098: myBasePath = basePath;
099: }
100:
101: public void setDiffDeleted(boolean isDiffDeleted) {
102: myIsDiffDeleted = isDiffDeleted;
103: }
104:
105: public boolean isDiffDeleted() {
106: return myIsDiffDeleted;
107: }
108:
109: public void setDiffAdded(boolean isDiffAdded) {
110: myIsDiffAdded = isDiffAdded;
111: }
112:
113: public boolean isDiffAdded() {
114: return myIsDiffAdded;
115: }
116:
117: public void setDiffCopied(boolean isDiffCopied) {
118: myIsDiffCopied = isDiffCopied;
119: }
120:
121: public boolean isDiffCopied() {
122: return myIsDiffCopied;
123: }
124:
125: /**
126: * Gets the diff options that are used by this generator.
127: * Creates a new one if none was used before.
128: *
129: * @return diff options
130: */
131: public SVNDiffOptions getDiffOptions() {
132: if (myDiffOptions == null) {
133: myDiffOptions = new SVNDiffOptions();
134: }
135: return myDiffOptions;
136: }
137:
138: protected String getDisplayPath(String path) {
139: if (myBasePath == null) {
140: return path;
141: }
142: if (path == null) {
143: path = "";
144: }
145: if (path.indexOf("://") > 0) {
146: return path;
147: }
148: // treat as file path.
149: String basePath = myBasePath.getAbsolutePath().replace(
150: File.separatorChar, '/');
151: if (path.equals(basePath)) {
152: return ".";
153: }
154: if (path.startsWith(basePath + "/")) {
155: path = path.substring(basePath.length() + 1);
156: if (path.startsWith("./")) {
157: path = path.substring("./".length());
158: }
159: }
160: return path;
161: }
162:
163: public void setForcedBinaryDiff(boolean forced) {
164: myIsForcedBinaryDiff = forced;
165: }
166:
167: public boolean isForcedBinaryDiff() {
168: return myIsForcedBinaryDiff;
169: }
170:
171: public void displayPropDiff(String path, Map baseProps, Map diff,
172: OutputStream result) throws SVNException {
173: baseProps = baseProps != null ? baseProps
174: : Collections.EMPTY_MAP;
175: diff = diff != null ? diff : Collections.EMPTY_MAP;
176: for (Iterator changedPropNames = diff.keySet().iterator(); changedPropNames
177: .hasNext();) {
178: String name = (String) changedPropNames.next();
179: String originalValue = (String) baseProps.get(name);
180: String newValue = (String) diff.get(name);
181: if ((originalValue != null && originalValue
182: .equals(newValue))
183: || originalValue == newValue) {
184: changedPropNames.remove();
185: }
186: }
187: if (diff.isEmpty()) {
188: return;
189: }
190: path = getDisplayPath(path);
191: ByteArrayOutputStream bos = new ByteArrayOutputStream();
192: diff = new TreeMap(diff);
193: try {
194: bos.write(EOL);
195: bos
196: .write(("Property changes on: " + (useLocalFileSeparatorChar() ? path
197: .replace('/', File.separatorChar)
198: : path)).getBytes(getEncoding()));
199: bos.write(EOL);
200: bos.write(PROPERTIES_SEPARATOR);
201: bos.write(EOL);
202: for (Iterator changedPropNames = diff.keySet().iterator(); changedPropNames
203: .hasNext();) {
204: String name = (String) changedPropNames.next();
205: String originalValue = baseProps != null ? (String) baseProps
206: .get(name)
207: : null;
208: String newValue = (String) diff.get(name);
209: bos.write(("Name: " + name).getBytes(getEncoding()));
210: bos.write(EOL);
211: if (originalValue != null) {
212: bos.write(" - ".getBytes(getEncoding()));
213: bos.write(originalValue.getBytes(getEncoding()));
214: bos.write(EOL);
215: }
216: if (newValue != null) {
217: bos.write(" + ".getBytes(getEncoding()));
218: bos.write(newValue.getBytes(getEncoding()));
219: bos.write(EOL);
220: }
221: }
222: bos.write(EOL);
223: } catch (IOException e) {
224: SVNErrorMessage err = SVNErrorMessage.create(
225: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
226: SVNErrorManager.error(err, e);
227: } finally {
228: try {
229: bos.close();
230: bos.writeTo(result);
231: } catch (IOException e) {
232: }
233: }
234: }
235:
236: protected File getBasePath() {
237: return myBasePath;
238: }
239:
240: public void displayFileDiff(String path, File file1, File file2,
241: String rev1, String rev2, String mimeType1,
242: String mimeType2, OutputStream result) throws SVNException {
243: path = getDisplayPath(path);
244: int i = 0;
245: for (; i < myAnchorPath1.length() && i < myAnchorPath2.length()
246: && myAnchorPath1.charAt(i) == myAnchorPath2.charAt(i); i++) {
247: }
248: if (i < myAnchorPath1.length() || i < myAnchorPath2.length()) {
249: if (i == myAnchorPath1.length()) {
250: i = myAnchorPath1.length() - 1;
251: }
252: for (; i > 0 && myAnchorPath1.charAt(i) != '/'; i--) {
253: }
254: }
255: String p1 = myAnchorPath1.substring(i);
256: String p2 = myAnchorPath2.substring(i);
257:
258: if (p1.length() == 0) {
259: p1 = path;
260: } else if (p1.charAt(0) == '/') {
261: p1 = path + "\t(..." + p1 + ")";
262: } else {
263: p1 = path + "\t(.../" + p1 + ")";
264: }
265: if (p2.length() == 0) {
266: p2 = path;
267: } else if (p2.charAt(0) == '/') {
268: p2 = path + "\t(..." + p2 + ")";
269: } else {
270: p2 = path + "\t(.../" + p2 + ")";
271: }
272:
273: // if anchor1 is the same as anchor2 just use path.
274: // if anchor1 differs from anchor2 =>
275: // condence anchors (get common root and remainings).
276:
277: rev1 = rev1 == null ? WC_REVISION_LABEL : rev1;
278: rev2 = rev2 == null ? WC_REVISION_LABEL : rev2;
279: ByteArrayOutputStream bos = new ByteArrayOutputStream();
280: try {
281: if (displayHeader(bos, path, file2 == null)) {
282: bos.close();
283: bos.writeTo(result);
284: return;
285: }
286: if (isHeaderForced(file1, file2)) {
287: bos.writeTo(result);
288: bos.reset();
289: }
290: } catch (IOException e) {
291: try {
292: bos.close();
293: bos.writeTo(result);
294: } catch (IOException inner) {
295: }
296: SVNErrorMessage err = SVNErrorMessage.create(
297: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
298: SVNErrorManager.error(err, e);
299: }
300: if (!isForcedBinaryDiff()
301: && (SVNProperty.isBinaryMimeType(mimeType1) || SVNProperty
302: .isBinaryMimeType(mimeType2))) {
303: try {
304: displayBinary(bos, mimeType1, mimeType2);
305: } catch (IOException e) {
306: SVNErrorMessage err = SVNErrorMessage.create(
307: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
308: SVNErrorManager.error(err, e);
309: } finally {
310: try {
311: bos.close();
312: bos.writeTo(result);
313: } catch (IOException e) {
314: }
315: }
316: return;
317: }
318: if (file1 == file2 && file1 == null) {
319: try {
320: bos.close();
321: bos.writeTo(result);
322: } catch (IOException e) {
323: }
324: return;
325: }
326: // put header fields.
327: try {
328: displayHeaderFields(bos, p1, rev1, p2, rev2);
329: } catch (IOException e) {
330: try {
331: bos.close();
332: bos.writeTo(result);
333: } catch (IOException inner) {
334: }
335: SVNErrorMessage err = SVNErrorMessage.create(
336: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
337: SVNErrorManager.error(err, e);
338: }
339:
340: String header;
341: try {
342: bos.close();
343: header = bos.toString();
344: } catch (IOException inner) {
345: header = "";
346: }
347:
348: InputStream is1 = null;
349: InputStream is2 = null;
350: try {
351: is1 = file1 == null ? EMPTY_FILE_IS : SVNFileUtil
352: .openFileForReading(file1);
353: is2 = file2 == null ? EMPTY_FILE_IS : SVNFileUtil
354: .openFileForReading(file2);
355:
356: QDiffUniGenerator.setup();
357: Map properties = new HashMap();
358:
359: properties.put(QDiffGeneratorFactory.IGNORE_EOL_PROPERTY,
360: Boolean
361: .valueOf(getDiffOptions()
362: .isIgnoreEOLStyle()));
363: if (getDiffOptions().isIgnoreAllWhitespace()) {
364: properties.put(
365: QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY,
366: QDiffGeneratorFactory.IGNORE_ALL_SPACE);
367: } else if (getDiffOptions().isIgnoreAmountOfWhitespace()) {
368: properties.put(
369: QDiffGeneratorFactory.IGNORE_SPACE_PROPERTY,
370: QDiffGeneratorFactory.IGNORE_SPACE_CHANGE);
371: }
372: QDiffGenerator generator = new QDiffUniGenerator(
373: properties, header);
374: Writer writer = new OutputStreamWriter(result,
375: getEncoding());
376: QDiffManager.generateTextDiff(is1, is2, getEncoding(),
377: writer, generator);
378: writer.flush();
379: } catch (IOException e) {
380: SVNErrorMessage err = SVNErrorMessage.create(
381: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
382: SVNErrorManager.error(err, e);
383: } finally {
384: SVNFileUtil.closeFile(is1);
385: SVNFileUtil.closeFile(is2);
386: }
387: }
388:
389: public void setEncoding(String encoding) {
390: myEncoding = encoding;
391: }
392:
393: public String getEncoding() {
394: if (myEncoding != null) {
395: return myEncoding;
396: }
397: return System.getProperty("file.encoding");
398: }
399:
400: public File createTempDirectory() throws SVNException {
401: return SVNFileUtil.createTempDirectory("diff");
402: }
403:
404: /**
405: * Says if unversioned files are also diffed or ignored.
406: *
407: * <p>
408: * By default unversioned files are ignored.
409: *
410: * @return <span class="javakeyword">true</span> if diffed,
411: * <span class="javakeyword">false</span> if ignored
412: * @see #setDiffUnversioned(boolean)
413: *
414: */
415:
416: public boolean isDiffUnversioned() {
417: return myIsDiffUnversioned;
418: }
419:
420: /**
421: * Includes or not unversioned files into diff processing.
422: *
423: * <p>
424: * If a diff operation is invoked on a versioned directory and
425: * <code>diffUnversioned</code> is <span class="javakeyword">true</span>
426: * then all unversioned files that may be met in the directory will
427: * be processed as added. Otherwise if <code>diffUnversioned</code>
428: * is <span class="javakeyword">false</span> such files are ignored.
429: *
430: * <p>
431: * By default unversioned files are ignored.
432: *
433: * @param diffUnversioned controls whether to diff unversioned files
434: * or not
435: * @see #isDiffUnversioned()
436: */
437: public void setDiffUnversioned(boolean diffUnversioned) {
438: myIsDiffUnversioned = diffUnversioned;
439: }
440:
441: /**
442: * Does nothing.
443: *
444: * @param path a directory path
445: * @param rev1 the first diff revision
446: * @param rev2 the second diff revision
447: * @throws SVNException
448: */
449: public void displayDeletedDirectory(String path, String rev1,
450: String rev2) throws SVNException {
451: // not implemented.
452: }
453:
454: /**
455: * Does nothing.
456: *
457: * @param path a directory path
458: * @param rev1 the first diff revision
459: * @param rev2 the second diff revision
460: * @throws SVNException
461: */
462: public void displayAddedDirectory(String path, String rev1,
463: String rev2) throws SVNException {
464: // not implemented.
465: }
466:
467: protected void displayBinary(OutputStream os, String mimeType1,
468: String mimeType2) throws IOException {
469: os.write("Cannot display: file marked as a binary type."
470: .getBytes(getEncoding()));
471: os.write(EOL);
472: if (SVNProperty.isBinaryMimeType(mimeType1)
473: && !SVNProperty.isBinaryMimeType(mimeType2)) {
474: os.write("svn:mime-type = ".getBytes(getEncoding()));
475: os.write(mimeType1.getBytes(getEncoding()));
476: os.write(EOL);
477: } else if (!SVNProperty.isBinaryMimeType(mimeType1)
478: && SVNProperty.isBinaryMimeType(mimeType2)) {
479: os.write("svn:mime-type = ".getBytes(getEncoding()));
480: os.write(mimeType2.getBytes(getEncoding()));
481: os.write(EOL);
482: } else if (SVNProperty.isBinaryMimeType(mimeType1)
483: && SVNProperty.isBinaryMimeType(mimeType2)) {
484: if (mimeType1.equals(mimeType2)) {
485: os.write("svn:mime-type = ".getBytes(getEncoding()));
486: os.write(mimeType2.getBytes(getEncoding()));
487: os.write(EOL);
488: } else {
489: os.write("svn:mime-type = (".getBytes(getEncoding()));
490: os.write(mimeType1.getBytes(getEncoding()));
491: os.write(", ".getBytes(getEncoding()));
492: os.write(mimeType2.getBytes(getEncoding()));
493: os.write(")".getBytes(getEncoding()));
494: os.write(EOL);
495: }
496: }
497: }
498:
499: protected boolean displayHeader(OutputStream os, String path,
500: boolean deleted) throws IOException {
501: if (deleted && !isDiffDeleted()) {
502: os.write("Index: ".getBytes(getEncoding()));
503: os.write(path.getBytes(getEncoding()));
504: os.write(" (deleted)".getBytes(getEncoding()));
505: os.write(EOL);
506: os.write(HEADER_SEPARATOR);
507: os.write(EOL);
508: return true;
509: }
510: os.write("Index: ".getBytes(getEncoding()));
511: os.write(path.getBytes(getEncoding()));
512: os.write(EOL);
513: os.write(HEADER_SEPARATOR);
514: os.write(EOL);
515: return false;
516: }
517:
518: protected void displayHeaderFields(OutputStream os, String path1,
519: String rev1, String path2, String rev2) throws IOException {
520: os.write("--- ".getBytes(getEncoding()));
521: os.write(path1.getBytes(getEncoding()));
522: os.write("\t".getBytes(getEncoding()));
523: os.write(rev1.getBytes(getEncoding()));
524: os.write(EOL);
525: os.write("+++ ".getBytes(getEncoding()));
526: os.write(path2.getBytes(getEncoding()));
527: os.write("\t".getBytes(getEncoding()));
528: os.write(rev2.getBytes(getEncoding()));
529: os.write(EOL);
530: }
531:
532: protected boolean isHeaderForced(File file1, File file2) {
533: return (file1 == null && file2 != null);
534: }
535:
536: protected boolean useLocalFileSeparatorChar() {
537: return true;
538: }
539: }
|