001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/rwiki/tags/sakai_2-4-1/rwiki-tool/tool/src/java/uk/ac/cam/caret/sakai/rwiki/tool/bean/GenericDiffBean.java $
003: * $Id: GenericDiffBean.java 20354 2007-01-17 10:30:57Z ian@caret.cam.ac.uk $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package uk.ac.cam.caret.sakai.rwiki.tool.bean;
021:
022: import org.apache.commons.jrcs.diff.AddDelta;
023: import org.apache.commons.jrcs.diff.ChangeDelta;
024: import org.apache.commons.jrcs.diff.Chunk;
025: import org.apache.commons.jrcs.diff.DeleteDelta;
026: import org.apache.commons.jrcs.diff.DifferentiationFailedException;
027: import org.apache.commons.jrcs.diff.Revision;
028: import org.apache.commons.jrcs.diff.RevisionVisitor;
029: import org.apache.commons.jrcs.diff.myers.MyersDiff;
030:
031: import uk.ac.cam.caret.sakai.rwiki.utils.XmlEscaper;
032:
033: /**
034: * Bean Helper class that for creating diffs. Line endings for the left and
035: * right content are both normalized to Unix Line endings by the simple regular
036: * expression replace "\r\n?" with "\n"
037: *
038: * @version $Id: GenericDiffBean.java 7664 2006-04-12 15:27:23Z
039: * ian@caret.cam.ac.uk $
040: * @author andrew
041: */
042:
043: public class GenericDiffBean {
044:
045: /**
046: * <code>RevisionVisitor</code> that will generate rows for a diff table.
047: * Notes:
048: * <ul>
049: * <li>Each line in the revision is given a it's own row.</li>
050: * <li>The left and the right columns have the attribute width set at 50%</li>
051: * <li>If the line is unchanged, the left column will have the class
052: * unchangedLeft and the right: the class unchangedRight.</li>
053: * <li>If the line was changed, the left column will have the class:
054: * deletedLeft and the right will have: changedRight.</li>
055: * <li>If the line was deleted, the left column will have the class:
056: * deletedLeft and the right will have: deletedRight. In this case the right
057: * column will contain &#160; a non-breaking space</li>
058: * <li>If the line was added, the left column will have the class:
059: * addedLeft and the right will have: addedRight. In this case the left
060: * column will contain &#160; a non-breaking space</li>
061: * n
062: * </ul>
063: *
064: * @author andrew
065: */
066: public class ColorDiffTableRevisionVisitor implements
067: RevisionVisitor {
068:
069: private StringBuffer sb = null;
070:
071: private int leftLineNum = 0, rightLineNum = 0;
072:
073: private boolean needToEnd = false;
074:
075: /*
076: * (non-Javadoc)
077: *
078: * @see org.apache.commons.jrcs.diff.RevisionVisitor#visit(org.apache.commons.jrcs.diff.Revision)
079: */
080: public final void visit(final Revision revision) {
081: sb = new StringBuffer();
082: leftLineNum = 0;
083: rightLineNum = 0;
084: needToEnd = true;
085: }
086:
087: private void doRow(final int left, final int right,
088: final String baseClass) {
089: while (leftLineNum < left || rightLineNum < right) {
090: sb.append("<tr><td width=\"50%\" class=\"");
091: sb.append(baseClass);
092: sb.append("Left\">");
093: if (leftLineNum < left) {
094: sb
095: .append(XmlEscaper
096: .xmlEscape((String) objectifiedLeft[leftLineNum++]));
097: } else {
098: sb.append(" ");
099: }
100: sb.append("</td><td width=\"50%\" class=\"");
101: sb.append(baseClass);
102: sb.append("Right\">");
103: if (rightLineNum < right) {
104: sb
105: .append(XmlEscaper
106: .xmlEscape((String) objectifiedRight[rightLineNum++]));
107: } else {
108: sb.append(" ");
109: }
110: sb.append("</td></tr>");
111: }
112: }
113:
114: public void visit(final DeleteDelta delta) {
115: Chunk targetLeft = delta.getOriginal();
116: Chunk targetRight = delta.getRevised();
117:
118: doRow(targetLeft.first(), targetRight.first(), "unchanged");
119: doRow(targetLeft.last() + 1, targetRight.last() + 1,
120: "deleted");
121:
122: }
123:
124: public void visit(final ChangeDelta delta) {
125: Chunk targetLeft = delta.getOriginal();
126: Chunk targetRight = delta.getRevised();
127:
128: doRow(targetLeft.first(), targetRight.first(), "unchanged");
129: doRow(targetLeft.last() + 1, targetRight.last() + 1,
130: "changed");
131:
132: }
133:
134: public void visit(final AddDelta delta) {
135: Chunk targetLeft = delta.getOriginal();
136: Chunk targetRight = delta.getRevised();
137:
138: doRow(targetLeft.first(), targetRight.first(), "unchanged");
139: doRow(targetLeft.last() + 1, targetRight.last() + 1,
140: "added");
141: }
142:
143: /**
144: * Called once we have been visited by the revision and it's deltas. The
145: * visitor methods will have been used to generate rows for an XHTML
146: * table representation of the revision deltas.
147: *
148: * @return string containing XHTML rows, each row should be XML
149: */
150: public String getTableRows() {
151: if (needToEnd) {
152: doRow(objectifiedLeft.length, objectifiedRight.length,
153: "unchanged");
154: needToEnd = false;
155: }
156:
157: return sb.toString();
158: }
159: }
160:
161: /**
162: * <code>RevisionVisitor</code> that will generate a listed diff view
163: * Notes:
164: * <ul>
165: * <li>Each line is given it's own div.</li>
166: * <li>A deleted line has the class deleted.</li>
167: * <li>An added line has the class added.</li>
168: * <li>An unchanged line has the class unchanged.</li>
169: * <li>A changed line is display as an div with class original followed by
170: * a div with class changed</li>
171: * </ul>
172: *
173: * @author andrew
174: */
175: public class ColorDiffRevisionVisitor implements RevisionVisitor {
176: private StringBuffer sb = null;
177:
178: private int leftLineNum = 0, rightLineNum = 0;
179:
180: private boolean needToEnd = false;
181:
182: public void visit(final Revision revision) {
183: sb = new StringBuffer();
184: leftLineNum = 0;
185: rightLineNum = 0;
186: needToEnd = true;
187: }
188:
189: private void appendLine(final Object line, final String cssclass) {
190: sb.append("<div class=\"");
191: sb.append(cssclass);
192: sb.append("\">");
193: sb.append(XmlEscaper.xmlEscape((String) line));
194: sb.append("</div>");
195: }
196:
197: private void doUnchanged(final int left, final int right) {
198: if ((left - leftLineNum) != (right - rightLineNum)) {
199: throw new IllegalArgumentException(
200: "Left and Right lines out of sync!");
201: }
202: while (leftLineNum < left) {
203: appendLine(objectifiedLeft[leftLineNum++], "unchanged");
204: }
205: rightLineNum = right;
206: }
207:
208: private void doSomeChange(final int left, final int right,
209: final String original, final String change) {
210: while (leftLineNum < left) {
211: appendLine(objectifiedLeft[leftLineNum++], original);
212: }
213: while (rightLineNum < right) {
214: appendLine(objectifiedRight[rightLineNum++], change);
215: }
216: }
217:
218: public void visit(final DeleteDelta delta) {
219: Chunk targetLeft = delta.getOriginal();
220: Chunk targetRight = delta.getRevised();
221: doUnchanged(targetLeft.first(), targetRight.first());
222: doSomeChange(targetLeft.last() + 1, targetRight.last() + 1,
223: "deleted", "bug");
224: }
225:
226: public void visit(final ChangeDelta delta) {
227: Chunk targetLeft = delta.getOriginal();
228: Chunk targetRight = delta.getRevised();
229: doUnchanged(targetLeft.first(), targetRight.first());
230: doSomeChange(targetLeft.last() + 1, targetRight.last() + 1,
231: "original", "changed");
232: }
233:
234: public void visit(final AddDelta delta) {
235: Chunk targetLeft = delta.getOriginal();
236: Chunk targetRight = delta.getRevised();
237: doUnchanged(targetLeft.first(), targetRight.first());
238: doSomeChange(targetLeft.last() + 1, targetRight.last() + 1,
239: "bug", "added");
240: }
241:
242: /**
243: * Called once we have been visited by the revision and it's deltas. The
244: * visitor methods will have been used to generate an XHTML
245: * representation of the revision deltas.
246: *
247: * @return xhtml string
248: */
249: public String getDiffString() {
250: if (needToEnd) {
251: doUnchanged(objectifiedLeft.length,
252: objectifiedRight.length);
253: needToEnd = false;
254: }
255:
256: return sb.toString();
257: }
258:
259: }
260:
261: /**
262: * The content of the left revision i.e. the old revision
263: */
264: private String leftContent;
265:
266: /**
267: * The content of the right revision i.e. the new revision
268: */
269: private String rightContent;
270:
271: /**
272: * A JRCS <code>Revision</code> representing the difference between the
273: * left and right content
274: */
275: private Revision difference;
276:
277: /**
278: * an Object[] of the right content, each line as a separate object.
279: */
280: private Object[] objectifiedRight;
281:
282: /**
283: * an Object[] of the left content, each line as a separate object.
284: */
285: private Object[] objectifiedLeft;
286:
287: /**
288: * Create a <code>GenericDiffBean</code> for the given left and right
289: * content
290: *
291: * @param leftContent
292: * the left content i.e. the old content
293: * @param rightContent
294: * the right content i.e. the new content
295: */
296: public GenericDiffBean(String leftContent, String rightContent) {
297: this .setLeftContent(leftContent);
298: this .setRightContent(rightContent);
299: this .init();
300: }
301:
302: public GenericDiffBean() {
303: // REQUIRED TO BE A BEAN!
304: }
305:
306: /**
307: * initialise the bean. To be called after the leftContent and rightContent
308: * have been set.
309: */
310: public void init() {
311: MyersDiff diffAlgorithm = new MyersDiff();
312:
313: try {
314: objectifiedLeft = leftContent.split("\n");
315:
316: objectifiedRight = rightContent.split("\n");
317:
318: difference = diffAlgorithm.diff(objectifiedLeft,
319: objectifiedRight);
320:
321: } catch (DifferentiationFailedException e) {
322: // Quite why JRCS has this I don't know!
323: throw new RuntimeException(
324: "DifferentiationFailedException occured: "
325: + "This should never happen!", e);
326: }
327:
328: }
329:
330: /**
331: * Creates the rows for an XHTML table representing the differences between
332: * the left and right revision contents. This table is created using
333: * <code>ColorDiffTableRevisionVisitor</code>
334: *
335: * @see ColorDiffTableRevisionVisitor
336: * @return String representing rows of an XHTML table
337: */
338: public String getColorDiffTable() {
339: ColorDiffTableRevisionVisitor rv = new ColorDiffTableRevisionVisitor();
340: difference.accept(rv);
341:
342: return rv.getTableRows();
343: }
344:
345: /**
346: * Creates a string containing XHTML representing the differences between
347: * the left and right revision contents. This method uses the
348: * <code>ColorDiffRevisionVisitor</code>
349: *
350: * @see ColorDiffRevisionVisitor
351: * @return String representation of XHTML div
352: */
353: public String getColorDiffString() {
354: ColorDiffRevisionVisitor rv = new ColorDiffRevisionVisitor();
355: difference.accept(rv);
356: return rv.getDiffString();
357: }
358:
359: /**
360: * Gets the difference between the left and right as a Unix Diff
361: *
362: * @return String representing the unix diff
363: */
364: public String getUnixDiffString() {
365: return difference.toString();
366: }
367:
368: /**
369: * Get the left content
370: *
371: * @return the left content (with normalized line endings)
372: */
373: public String getLeftContent() {
374: return leftContent;
375: }
376:
377: /**
378: * Sets the left content i.e. the old content.
379: *
380: * @param leftContent
381: * A non null string representing the left content
382: */
383: public void setLeftContent(String leftContent) {
384: this .leftContent = leftContent.replaceAll("\r\n?", "\n");
385: }
386:
387: /**
388: * Gets the right content
389: *
390: * @return the right content (with normalized line endings)
391: */
392: public String getRightContent() {
393: return rightContent;
394: }
395:
396: /**
397: * Sets the right content i.e. the new content
398: *
399: * @param rightContent
400: * A non null string representing the right content
401: */
402: public void setRightContent(String rightContent) {
403: this .rightContent = rightContent.replaceAll("\r\n?", "\n");
404: }
405:
406: /**
407: * Gets a JRCS <code>Revision</code> representing the difference between
408: * the left and right content
409: *
410: * @return non-null <code>Revision</code> (if init has been called)
411: */
412: public Revision getDifference() {
413: return difference;
414: }
415:
416: }
|