001: /*
002: * ====================================================================
003: *
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 1999-2003 The Apache Software Foundation.
007: * All rights reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowledgement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowledgement may appear in the software itself,
026: * if and wherever such third-party acknowledgements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Software Foundation.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: *
056: */
057:
058: package org.apache.commons.jrcs.rcs;
059:
060: import java.text.DateFormat;
061: import java.text.Format;
062: import java.text.MessageFormat;
063: import java.text.SimpleDateFormat;
064: import java.util.Arrays;
065: import java.util.Calendar;
066: import java.util.Date;
067: import java.util.GregorianCalendar;
068: import java.util.Iterator;
069: import java.util.LinkedList;
070: import java.util.List;
071: import java.util.StringTokenizer;
072: import java.util.TreeMap;
073:
074: import org.apache.commons.jrcs.diff.AddDelta;
075: import org.apache.commons.jrcs.diff.Chunk;
076: import org.apache.commons.jrcs.diff.DeleteDelta;
077: import org.apache.commons.jrcs.diff.Diff;
078: import org.apache.commons.jrcs.diff.Revision;
079: import org.apache.commons.jrcs.util.ToString;
080: import org.apache.commons.jrcs.diff.PatchFailedException;
081:
082: /**
083: * Ancestor to all nodes in a version control Archive.
084: * <p>Nodes store the deltas between two revisions of the text.</p>
085: *
086: * This class is NOT thread safe.
087: *
088: * @see TrunkNode
089: * @see BranchNode
090: * @see Archive
091: *
092: * @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
093: * @version $Id: Node.java 2967 2005-10-26 10:52:33Z ian@caret.cam.ac.uk $
094: */
095: public abstract class Node extends ToString implements Comparable {
096:
097: /**
098: * The version number for this node.
099: */
100: protected final Version version;
101: protected Date date = new Date();
102: protected String author = System.getProperty("user.name");
103: protected String state = "Exp";
104: protected String log = "";
105: protected String locker = "";
106: protected Object[] text;
107: protected Node rcsnext;
108: protected Node parent;
109: protected Node child;
110: protected TreeMap branches = null;
111: protected Phrases phrases = null;
112:
113: protected static final Format dateFormatter = new MessageFormat(
114: "\t{0,number,##00}." + "{1,number,00}." + "{2,number,00}."
115: + "{3,number,00}." + "{4,number,00}."
116: + "{5,number,00}");
117: protected static final DateFormat dateFormat = new SimpleDateFormat(
118: "yy.MM.dd.HH.mm.ss");
119: protected static final DateFormat dateFormat2K = new SimpleDateFormat(
120: "yyyy.MM.dd.HH.mm.ss");
121:
122: /**
123: * Creates a copy of a node. Only used internally.
124: * @param other The node to copy.
125: */
126: protected Node(Node other) {
127: this (other.version, null);
128: this .date = other.date;
129: this .author = other.author;
130: this .state = other.state;
131: this .log = other.log;
132: this .locker = other.locker;
133: }
134:
135: /**
136: * Creates a node with the given version number.
137: * @param vernum The version number for the node.
138: * @param rcsnext The next node in the RCS logical hierarchy.
139: */
140: protected Node(Version vernum, Node rcsnext) {
141: if (vernum == null) {
142: throw new IllegalArgumentException(vernum.toString());
143: }
144: this .version = (Version) vernum.clone();
145: this .setRCSNext(rcsnext);
146: }
147:
148: /**
149: * Creates a new node of the adequate type for the given version number.
150: * @param vernum The version number for the node.
151: * @param rcsnext The next node in the RCS logical hierarchy.
152: * @return The newly created node.
153: */
154: static Node newNode(Version vernum, Node rcsnext)
155: throws InvalidVersionNumberException {
156: if (vernum.isTrunk()) {
157: return new TrunkNode(vernum, (TrunkNode) rcsnext);
158: } else {
159: return new BranchNode(vernum, (BranchNode) rcsnext);
160: }
161: }
162:
163: /**
164: * Creates a new node of the adequate type for the given version number.
165: * @param vernum The version number for the node.
166: * @return The newly created node.
167: */
168: static Node newNode(Version vernum)
169: throws InvalidVersionNumberException {
170: return newNode(vernum, null);
171: }
172:
173: /**
174: * Compares the version number of this node to that of another node.
175: * @param other The node to compare two.
176: * @return 0 if versions are equal, 1 if this version greather than the other,
177: * and -1 otherwise.
178: */
179: public int compareTo(Object other) {
180: if (other == this ) {
181: return 0;
182: } else if (!(other instanceof Node)) {
183: return -1;
184: } else {
185: return version.compareTo(((Node) other).version);
186: }
187: }
188:
189: /**
190: * Returns true if the node is a "ghost" node.
191: * Ghost nodes have no associated text ot deltas. CVS uses
192: * them to mark certain points in the node hierarchy.
193: */
194: public boolean isGhost() {
195: return version.isGhost() || text == null;
196: }
197:
198: /**
199: * Retrieve the branch node identified with
200: * the given numer.
201: * @param no The branch number.
202: * @return The branch node.
203: * @see BranchNode
204: */
205: public BranchNode getBranch(int no) {
206: if (branches == null) {
207: return null;
208: } else if (no == 0) {
209: Integer branchNo = (Integer) branches.lastKey();
210: return (BranchNode) (branchNo == null ? null : branches
211: .get(branchNo));
212: } else {
213: return (BranchNode) branches.get(new Integer(no));
214: }
215: }
216:
217: /**
218: * Return the root node of the node hierarchy.
219: * @return The root node.
220: */
221: public Node root() {
222: Node result = this ;
223: while (result.parent != null) {
224: result = result.parent;
225: }
226: return result;
227: }
228:
229: /**
230: * Set the locker.
231: * @param user A symbol that identifies the locker.
232: */
233: public void setLocker(String user) {
234: locker = user.intern();
235: }
236:
237: /**
238: * Set the author of the node's revision.
239: * @param user A symbol that identifies the author.
240: */
241: public void setAuthor(String user) {
242: author = user.intern();
243: }
244:
245: /**
246: * Set the date of the node's revision.
247: * @param value an array of 6 integers, corresponding to the
248: * year, month, day, hour, minute, and second of this revision.<br>
249: * If the year has two digits, it is interpreted as belonging to the 20th
250: * century.<br>
251: * The month is a number from 1 to 12.
252: */
253: public void setDate(int[] value) {
254: this .date = new GregorianCalendar(value[0]
255: + (value[0] <= 99 ? 1900 : 0), value[1] - 1, value[2],
256: value[3], value[4], value[5]).getTime();
257: }
258:
259: /**
260: * Sets the state of the node's revision.
261: * @param value A symbol that identifies the state. The most commonly
262: * used value is Exp.
263: */
264: public void setState(String value) {
265: state = value;
266: }
267:
268: /**
269: * Sets the next node in the RCS logical hierarchy.
270: * In the RCS hierarchy, a {@link TrunkNode TrunkNode} points
271: * to the previous revision, while a {@link BranchNode BranchNode}
272: * points to the next revision.
273: * @param node The next node in the RCS logical hierarchy.
274: */
275: public void setRCSNext(Node node) {
276: rcsnext = node;
277: }
278:
279: /**
280: * Sets the log message for the node's revision.
281: * The log message is usually used to explain why the revision took place.
282: * @param value The message.
283: */
284: public void setLog(String value) {
285: // the last newline belongs to the file format
286: if (value.endsWith(Archive.RCS_NEWLINE))
287: log = value.substring(0, value.length() - 1);
288: else
289: log = value;
290: }
291:
292: /**
293: * Sets the text for the node's revision.
294: * <p>For archives containing binary information, the text is an image
295: * of the revision contents.</p>
296: * <p>For ASCII archives, the text contains the delta between the
297: * current revision and the next revision in the RCS logical hierarchy.
298: * The deltas are codified in a format similar to the one used by Unix diff.</p>
299: * <p> The passed string is converted to an array of objects
300: * befored being stored as the revision's text</p>
301: * @param value The revision's text.
302: * @see ArchiveParser
303: */
304: public void setText(String value) {
305: this .text = org.apache.commons.jrcs.diff.Diff
306: .stringToArray(value);
307: }
308:
309: /**
310: * Sets the text for the node's revision.
311: * <p>For archives containing binary information, the text is an image
312: * of the revision contents.</p>
313: * <p>For ASCII archives, the text contains the delta between the
314: * current revision and the next revision in the RCS logical hierarchy.
315: * The deltas are codified in a format similar to the one used by Unix diff.
316: * @param value The revision's text.
317: * @see ArchiveParser
318: */
319: public void setText(Object[] value) {
320: this .text = Arrays.asList(value).toArray();
321: }
322:
323: /**
324: * Adds a branch node to the current node.
325: * @param node The branch node.
326: * @throws InvalidVersionNumberException if the version number
327: * is not a valid branch version number for the current node
328: */
329: public void addBranch(BranchNode node)
330: throws InvalidVersionNumberException {
331: if (node.version.isLessThan(this .version)
332: || node.version.size() != (this .version.size() + 2)) {
333: throw new InvalidVersionNumberException(
334: "version must be grater");
335: }
336:
337: int branchno = node.version.at(this .version.size());
338: if (branches == null) {
339: branches = new TreeMap();
340: }
341: branches.put(new Integer(branchno), node);
342: node.parent = this ;
343: }
344:
345: /**
346: * Returns the version number that should correspond to
347: * the revision folowing this node.
348: * @return The next version number.
349: */
350: public Version nextVersion() {
351: return this .version.next();
352: }
353:
354: /**
355: * Returns the version number that should correspond to a newly
356: * created branch of this node.
357: * @return the new branch's version number.
358: */
359: public Version newBranchVersion() {
360: Version result = new Version(this .version);
361: if (branches == null || branches.size() <= 0) {
362: result.__addBranch(1);
363: } else {
364: result.__addBranch(((Integer) branches.lastKey())
365: .intValue());
366: }
367: result.__addBranch(1);
368: return result;
369: }
370:
371: /**
372: * Return the next node in the RCS logical hierarchy.
373: * @return the next node
374: */
375: public Node getRCSNext() {
376: return rcsnext;
377: }
378:
379: /**
380: * Returns the path from the current node to the node
381: * identified by the given version.
382: * @param vernum The version number of the last node in the path.
383: * @return The path
384: * @throws NodeNotFoundException if a node with the given version number
385: * doesn't exist, or is not reachable following the RCS-next chain
386: * from this node.
387: * @see Path
388: */
389: public Path pathTo(Version vernum) throws NodeNotFoundException {
390: return pathTo(vernum, false);
391: }
392:
393: /**
394: * Returns the path from the current node to the node
395: * identified by the given version.
396: * @param vernum The version number of the last node in the path.
397: * @param soft If true, no error is thrown if a node with the given
398: * version doesn't exist. Use soft=true to find a apth to where a new
399: * node should be added.
400: * @return The path
401: * @throws NodeNotFoundException if a node with the given version number
402: * is not reachable following the RCS-next chain from this node.
403: * If soft=false the exception is also thrown if a node with the given
404: * version number doesn't exist.
405: * @see Path
406: */
407: public Path pathTo(Version vernum, boolean soft)
408: throws NodeNotFoundException {
409: Path path = new Path();
410:
411: Node target = this ;
412: do {
413: path.add(target);
414: target = target.nextInPathTo(vernum, soft);
415: } while (target != null);
416: return path;
417: }
418:
419: /**
420: * Returns the next node in the path from the current node to the node
421: * identified by the given version.
422: * @param vernum The version number of the last node in the path.
423: * @param soft If true, no error is thrown if a node with the given
424: * version doesn't exist. Use soft=true to find a apth to where a new
425: * node should be added.
426: * @return The path
427: * @throws NodeNotFoundException if a node with the given version number
428: * is not reachable following the RCS-next chain from this node.
429: * If soft=false the exception is also thrown if a node with the given
430: * version number doesn't exist.
431: * @see Path
432: */
433: public abstract Node nextInPathTo(Version vernum, boolean soft)
434: throws NodeNotFoundException;
435:
436: /**
437: * Returns the Node with the version number that corresponds to
438: * the revision to be obtained after the deltas in the current node
439: * are applied.
440: * <p>For a {@link BranchNode BranchNode} the deltaRevision is the
441: * current revision; that is, after the deltas are applied, the text for
442: * the current revision is obtained.</p>
443: * <p>For a {@link TrunkNode TrunkNode} the deltaRevision is the
444: * next revision; that is, after the deltas are applied, the text obtained
445: * corresponds to the next revision in the chain.</p>
446: * @return The node for the delta revision.
447: */
448: public abstract Node deltaRevision();
449:
450: /**
451: * Apply the deltas in the current node to the given text.
452: * @param original the text to be patched
453: * @throws InvalidFileFormatException if the deltas cannot be parsed.
454: * @throws PatchFailedException if the diff engine determines that
455: * the deltas cannot apply to the given text.
456: */
457: public void patch(List original) throws InvalidFileFormatException,
458: PatchFailedException {
459: patch(original, false);
460: }
461:
462: /**
463: * Apply the deltas in the current node to the given text.
464: * @param original the text to be patched
465: * @param annotate set to true to have each text line be a
466: * {@link Line Line} object that identifies the revision in which
467: * the line was changed or added.
468: * @throws InvalidFileFormatException if the deltas cannot be parsed.
469: * @throws PatchFailedException if the diff engine determines that
470: * the deltas cannot apply to the given text.
471: */
472: public void patch(List original, boolean annotate)
473: throws InvalidFileFormatException,
474: org.apache.commons.jrcs.diff.PatchFailedException {
475: Revision revision = new Revision();
476: for (int it = 0; it < text.length; it++) {
477: String cmd = text[it].toString();
478:
479: java.util.StringTokenizer t = new StringTokenizer(cmd,
480: "ad ", true);
481: char action;
482: int n;
483: int count;
484:
485: try {
486: action = t.nextToken().charAt(0);
487: n = Integer.parseInt(t.nextToken());
488: t.nextToken(); // skip the space
489: count = Integer.parseInt(t.nextToken());
490: } catch (Exception e) {
491: throw new InvalidFileFormatException(version + ":line:"
492: + ":" + e.getMessage());
493: }
494:
495: if (action == 'd') {
496: revision.addDelta(new DeleteDelta(new Chunk(n - 1,
497: count)));
498: } else if (action == 'a') {
499: revision.addDelta(new AddDelta(n, new Chunk(
500: getTextLines(it + 1, it + 1 + count), 0, count,
501: n - 1)));
502: it += count;
503: } else {
504: throw new InvalidFileFormatException(version.toString());
505: }
506: }
507: revision.applyTo(original);
508: }
509:
510: /**
511: * Conver the current node and all of its branches
512: * to their RCS string representation and
513: * add it to the given StringBuffer.
514: * @param s The string buffer to add the node's image to.
515: */
516: public void toString(StringBuffer s) {
517: toString(s, Archive.RCS_NEWLINE);
518: }
519:
520: /**
521: * Conver the current node and all of its branches
522: * to their RCS string representation and
523: * add it to the given StringBuffer using the given marker as
524: * line separator.
525: * @param s The string buffer to add the node's image to.
526: * @param EOL The line separator to use.
527: */
528: public void toString(StringBuffer s, String EOL) {
529: String EOI = ";" + EOL;
530: String NLT = EOL + "\t";
531:
532: s.append(EOL);
533: s.append(version.toString() + EOL);
534:
535: s.append("date");
536: if (date != null) {
537: DateFormat formatter = dateFormat;
538: Calendar cal = new GregorianCalendar();
539: cal.setTime(date);
540: if (cal.get(Calendar.YEAR) > 1999) {
541: formatter = dateFormat2K;
542: }
543: s.append("\t" + formatter.format(date));
544: }
545: s.append(";\tauthor");
546: if (author != null) {
547: s.append(" " + author);
548: }
549: s.append(";\tstate");
550: if (state != null) {
551: s.append(" ");
552: s.append(state);
553: }
554: s.append(EOI);
555:
556: s.append("branches");
557: if (branches != null) {
558: for (Iterator i = branches.values().iterator(); i.hasNext();) {
559: Node n = (Node) i.next();
560: if (n != null) {
561: s.append(NLT + n.version);
562: }
563: }
564: }
565: s.append(EOI);
566:
567: s.append("next\t");
568: if (rcsnext != null) {
569: s.append(rcsnext.version.toString());
570: }
571: s.append(EOI);
572: }
573:
574: /**
575: * Conver the urrent node to its RCS string representation.
576: * @return The string representation
577: */
578: public String toText() {
579: final StringBuffer s = new StringBuffer();
580: toText(s, Archive.RCS_NEWLINE);
581: return s.toString();
582: }
583:
584: /**
585: * Conver the urrent node to its RCS string representation and
586: * add it to the given StringBuffer using the given marker as
587: * line separator.
588: * @param s The string buffer to add the node's image to.
589: * @param EOL The line separator to use.
590: */
591: public void toText(StringBuffer s, String EOL) {
592: s.append(EOL + EOL);
593: s.append(version.toString() + EOL);
594:
595: s.append("log" + EOL);
596: if (log.length() == 0)
597: s.append(Archive.quoteString(""));
598: else
599: // add a newline after the comment
600: s.append(Archive.quoteString(log + EOL));
601: s.append(EOL);
602:
603: if (phrases != null) {
604: s.append(phrases.toString());
605: }
606:
607: s.append("text" + EOL);
608: s.append(Archive.quoteString(Diff.arrayToString(text, EOL)
609: + EOL));
610: s.append(EOL);
611:
612: if (branches != null) {
613: for (Iterator i = branches.values().iterator(); i.hasNext();) {
614: Node n = (Node) i.next();
615: if (n != null) {
616: n.toText(s, EOL);
617: }
618: }
619: }
620: }
621:
622: /**
623: * Return a list with the lines of the node's text.
624: * @return The list
625: */
626: public List getTextLines() {
627: return getTextLines(new LinkedList());
628: }
629:
630: /**
631: * Return a list with a subset of the lines of the node's text.
632: * @param from The offset of the first line to retrieve.
633: * @param to The offset of the line after the last one to retrieve.
634: * @return The list
635: */
636: public List getTextLines(int from, int to) {
637: return getTextLines(new LinkedList(), from, to);
638: }
639:
640: /**
641: * Add a subset of the lines of the node's text to the given list.
642: * @return The given list after the additions have been made.
643: */
644: public List getTextLines(List lines) {
645: return getTextLines(lines, 0, text.length);
646: }
647:
648: /**
649: * Add a subset of the lines of the node's text to the given list.
650: * @param from The offset of the first line to retrieve.
651: * @param to The offset of the line after the last one to retrieve.
652: * @return The given list after the additions have been made.
653: */
654: public List getTextLines(List lines, int from, int to) {
655: for (int i = from; i < to; i++) {
656: lines.add(new Line(deltaRevision(), text[i]));
657: }
658: return lines;
659: }
660:
661: public final Date getDate() {
662: return date;
663: }
664:
665: public final String getAuthor() {
666: return author;
667: }
668:
669: public final String getState() {
670: return state;
671: }
672:
673: public final String getLog() {
674: return log;
675: }
676:
677: public final String getLocker() {
678: return locker;
679: }
680:
681: public final Object[] getText() {
682: return text;
683: }
684:
685: public final Node getChild() {
686: return child;
687: }
688:
689: public final TreeMap getBranches() {
690: return branches;
691: }
692:
693: public final Node getParent() {
694: return parent;
695: }
696:
697: public final Version getVersion() {
698: return version;
699: }
700:
701: public Phrases getPhrases() {
702: return phrases;
703: }
704:
705: }
|