0001: /*
0002: * ====================================================================
0003: *
0004: * The Apache Software License, Version 1.1
0005: *
0006: * Copyright (c) 1999-2003 The Apache Software Foundation.
0007: * All rights reserved.
0008: *
0009: * Redistribution and use in source and binary forms, with or without
0010: * modification, are permitted provided that the following conditions
0011: * are met:
0012: *
0013: * 1. Redistributions of source code must retain the above copyright
0014: * notice, this list of conditions and the following disclaimer.
0015: *
0016: * 2. Redistributions in binary form must reproduce the above copyright
0017: * notice, this list of conditions and the following disclaimer in
0018: * the documentation and/or other materials provided with the
0019: * distribution.
0020: *
0021: * 3. The end-user documentation included with the redistribution, if
0022: * any, must include the following acknowledgement:
0023: * "This product includes software developed by the
0024: * Apache Software Foundation (http://www.apache.org/)."
0025: * Alternately, this acknowledgement may appear in the software itself,
0026: * if and wherever such third-party acknowledgements normally appear.
0027: *
0028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
0029: * Foundation" must not be used to endorse or promote products derived
0030: * from this software without prior written permission. For written
0031: * permission, please contact apache@apache.org.
0032: *
0033: * 5. Products derived from this software may not be called "Apache"
0034: * nor may "Apache" appear in their names without prior written
0035: * permission of the Apache Software Foundation.
0036: *
0037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0048: * SUCH DAMAGE.
0049: * ====================================================================
0050: *
0051: * This software consists of voluntary contributions made by many
0052: * individuals on behalf of the Apache Software Foundation. For more
0053: * information on the Apache Software Foundation, please see
0054: * <http://www.apache.org/>.
0055: *
0056: */
0057:
0058: package org.apache.commons.jrcs.rcs;
0059:
0060: import java.io.*;
0061: import java.util.*;
0062:
0063: import org.apache.commons.jrcs.diff.Diff;
0064: import org.apache.commons.jrcs.diff.DiffException;
0065: import org.apache.commons.jrcs.diff.PatchFailedException;
0066: import org.apache.commons.jrcs.util.ToString;
0067:
0068: /**
0069: * Handling of RCS/CVS style version control archives.
0070: *
0071: *
0072: * <p>JRCS is a library that knows how to manipulate the archive files produced
0073: * by the RCS and CVS version control systems. JRCS is not intended to replace
0074: * neither tool. JRCS was written to be able create archive analysis tools
0075: * that can do things like identify hot spots in the source code,
0076: * measure the contributions by each developer,
0077: * or assess how bugs make it in.</p>
0078: *
0079: * <p>The reasons why JRCS has the ability do do check-ins and save archives
0080: * is API symmetry, and to simplify the writing of unit tests.</p>
0081: *
0082: * <p><b>CAVEAT UTILITOR:</b> Do not make modifications to your archives with JRCS.
0083: * There needs to be an important amount of additional testing
0084: * before it's safe to do that.</p>
0085: *
0086: * <p>The {@link org.apache.commons.jrcs.rcs rcs} package implements the
0087: * archive handling functionality. The entry point to the library is class
0088: * {@link org.apache.commons.jrcs.rcs.Archive Archive}.</p>
0089: *
0090: *
0091: * <p>The {@link org.apache.commons.jrcs.diff diff} package implements
0092: * the differencing engine that JRCS uses. The engine has the power of Unix diff,
0093: * is simple to understand, and can be used independently of the archive handling
0094: * functionality. The entry point to the differencing engine is class
0095: * {@link org.apache.commons.jrcs.diff.Diff Diff}.</p>
0096: *
0097: * <p>Within this library, the word <i>text</i> means a unit of information
0098: * subject to version control. The word <i>revision</i> means a particular
0099: * version of a text. Each <i>revision</i> has a <i>version number</i>
0100: * associated to it. <i>Version numbers</i> are dot-separated lists of numbers.
0101: * Version numbers with an odd number of dots indicate revisions, while those
0102: * with an even number of dots (including zero dots) designate branches.</p>
0103: *
0104: * <p>Revisions of a text are represented as <code>Object[]</code> because
0105: * the diff engine is capable of handling more than plain text. In fact,
0106: * arrays of any type that implements
0107: * {@link java.lang.Object#hashCode hashCode()} and
0108: * {@link java.lang.Object#equals equals()}
0109: * correctly can be subject to differencing and version control using this
0110: * library.</p>
0111: *
0112: * <p>To create an empty archive use:
0113: * <code><pre>
0114: * Archive archive = new Archive();
0115: * </pre></code>
0116: * </p>
0117: *
0118: * <p>To read an archive from the file system, use:
0119: * <code><pre>
0120: * Archive archive = new Archive("/path/to/archive,v");
0121: * </pre></code>
0122: * </p>
0123: *
0124: * <p>You can also initialize archives from streams.</p>
0125: *
0126: * <p>To retreive a revision from an archive use:
0127: * <code><pre>
0128: * String versionNumber = "1.2";
0129: * Object[] text = archive.getRevision(versionNumber);
0130: * </pre></code>
0131: * </p>
0132: *
0133: * <p>You can also retreive revisions in such a way that each item
0134: * is annotated with the version number of the revision in which it was
0135: * last changed or added. To retrieve annotated text use:
0136: * <code><pre>
0137: * String versionNumber = "1.2";
0138: * {@link Line Line[]} text = archive.getRevision(versionNumber);
0139: * for(int i = 0; i < text.length(); i++)
0140: * System.out.println(text[i].revision.version);
0141: * </pre></code>
0142: * </p>
0143: *
0144: * <p>This class is NOT thread safe.</p>
0145: * @see org.apache.commons.jrcs.diff
0146: *
0147: * @version $Id: Archive.java 2967 2005-10-26 10:52:33Z ian@caret.cam.ac.uk $
0148: * @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
0149: */
0150: public class Archive extends ToString {
0151: public static final String RCS_NEWLINE = "\n";
0152:
0153: protected TrunkNode head;
0154: protected Version branch;
0155: protected Map nodes = new TreeMap(); //!!! check Node.compareTo for correct RCS order
0156: protected Set users = new TreeSet();
0157: protected Set locked = new TreeSet();
0158: protected Map symbols = new TreeMap();
0159: protected Phrases phrases = new Phrases();
0160: protected String desc = new String();
0161: protected boolean strictLocking = true;
0162: protected String expand;
0163: protected String comment = "# ";
0164: protected String filename = "__unknown__,v";
0165:
0166: // synchronize this if this has to be used in MT !
0167: private static final KeywordsFormat FORMATTER = new KeywordsFormat();
0168:
0169: /**
0170: * Creates a new archive and sets the text of the initial revision.
0171: * @param text The text of the initial revision.
0172: * @param desc The archives description (not the log message).
0173: */
0174: public Archive(Object[] text, String desc) {
0175: this (text, desc, new Version(1, 1));
0176: }
0177:
0178: /**
0179: * Creates a new archive with the specified initial version number
0180: * and sets the text of the initial revision.
0181: * The initial revision must be of the form "n.m" (i.e. a trunk revision).
0182: * @param text The text of the initial revision.
0183: * @param desc The archives description (not the log message).
0184: * @param vernum The initial revision number.
0185: */
0186: public Archive(Object[] text, String desc, String vernum) {
0187: this (text, desc, new Version(vernum));
0188: }
0189:
0190: /**
0191: * Creates a new archive with the specified initial version number
0192: * and sets the text of the initial revision.
0193: * The initial revision must be of the form "n.m" (i.e. a trunk revision).
0194: * @param text The text of the initial revision.
0195: * @param desc The archives description (not the log message).
0196: * @param vernum The initial revision number.
0197: */
0198: public Archive(Object[] text, String desc, Version vernum) {
0199: // can only add a trunk version
0200: if (vernum.size() > 2) {
0201: throw new InvalidVersionNumberException(vernum
0202: + " must be a trunk version");
0203: }
0204: while (vernum.size() < 2) {
0205: vernum = vernum.newBranch(1);
0206: }
0207: // now add the _head node
0208: this .head = (TrunkNode) newNode(vernum, null);
0209: this .head.setText(text);
0210: this .head.setLog(desc);
0211: }
0212:
0213: /**
0214: * Load an archive from an input stream.
0215: * Parses the archive given by the input stream, and gives it the provided name.
0216: * @param fname The name to give to the archive.
0217: * @param input Where to read the archive from
0218: */
0219: public Archive(String fname, InputStream input)
0220: throws ParseException {
0221: this .filename = fname;
0222: ArchiveParser.load(this , input);
0223: }
0224:
0225: /**
0226: * Load an archive from an a file given by name.
0227: * @param path The path to the file wher the archive resides.
0228: */
0229: public Archive(String path) throws ParseException,
0230: FileNotFoundException {
0231: this .filename = new File(path).getPath();
0232: ArchiveParser.load(this , this .filename);
0233: }
0234:
0235: /**
0236: * Create an unitialized Archive.
0237: * Used internally by the ArchiveParser.
0238: * @see ArchiveParser
0239: */
0240: Archive() {
0241: }
0242:
0243: /**
0244: * Set the name of the file for this archive
0245: * @param path The full path name.
0246: */
0247: public void setFileName(String path) {
0248: this .filename = path;
0249: }
0250:
0251: /**
0252: * Save the archive to the provided stream.
0253: * @param output The stream to save the archive to.
0254: */
0255: public void save(OutputStream output) throws IOException {
0256: output.write(toByteArray());
0257: }
0258:
0259: /**
0260: * Save the archive to a file and the the Archives filename
0261: * accordingly.
0262: * @param path The file's path.
0263: */
0264: public void save(String path) throws IOException {
0265: OutputStream output = new FileOutputStream(path);
0266: try {
0267: save(output);
0268: this .filename = new File(path).getPath();
0269: } finally {
0270: output.close();
0271: }
0272: }
0273:
0274: /**
0275: * Add a head node with the given version number.
0276: * @param vernum The version number to use.
0277: */
0278: protected void setHead(Version vernum)
0279: throws InvalidVersionNumberException {
0280: if (head != null) {
0281: throw new HeadAlreadySetException(head.getVersion());
0282: }
0283: head = new TrunkNode(vernum, null);
0284: nodes.put(vernum, head);
0285: }
0286:
0287: /**
0288: * Set the active branch to the one identified by the given version number.
0289: * Incomplete version numbers of the form "1" or "2.1.3" are accepted.
0290: * @param v The version number.
0291: */
0292: public void setBranch(String v)
0293: throws InvalidBranchVersionNumberException {
0294: setBranch(new Version(v));
0295: }
0296:
0297: /**
0298: * Set the active branch to the one identified by the given version number.
0299: * @param vernum The version number.
0300: */
0301: public void setBranch(Version vernum)
0302: throws InvalidBranchVersionNumberException {
0303: if (!vernum.isBranch()) {
0304: throw new InvalidBranchVersionNumberException(vernum);
0305: }
0306: if (head == null
0307: || vernum.getBase(2).isGreaterThan(head.getVersion())) {
0308: throw new InvalidBranchVersionNumberException(vernum
0309: + "is greater than _head version "
0310: + head.getVersion());
0311: }
0312: branch = vernum;
0313: }
0314:
0315: /** Add a user name to the list of archive users.
0316: * @param name The user name.
0317: */
0318: public void addUser(String name) {
0319: users.add(name);
0320: }
0321:
0322: /**
0323: * Tag a given version with a symbol.
0324: * @param sym The tag.
0325: * @param vernum The version to tag.
0326: */
0327: public void addSymbol(String sym, Version vernum)
0328: throws InvalidVersionNumberException {
0329: //@TODO: Verify if the symbol is valid
0330: symbols.put(sym, vernum);
0331: }
0332:
0333: /**
0334: * Returns a Map of the symbols (tags) associated with each revision.
0335: * The symbols are the keys and the revision numbers are the values.
0336: * @return A map of symbol/revision number pairs.
0337: */
0338: public Map getSymbols() {
0339: return symbols;
0340: }
0341:
0342: /**
0343: * Add a lock over a revison.
0344: * @param user The user that locks the revision.
0345: * @param vernum The version number of the revision to lock.
0346: */
0347: public void addLock(String user, Version vernum)
0348: throws InvalidVersionNumberException, NodeNotFoundException {
0349: addUser(user);
0350: Node node = newNode(vernum);
0351: node.setLocker(user);
0352: if (user == null) {
0353: locked.remove(node);
0354: } else {
0355: locked.add(node);
0356: }
0357: }
0358:
0359: /**
0360: * Set the strict locking flag for the archive.
0361: * @param value Indicates if strict locking should be on or off.
0362: */
0363: public void setStrictLocking(boolean value) {
0364: strictLocking = value;
0365: }
0366:
0367: /**
0368: * Set the keyword expansion flag for the archive.
0369: * @param value The keyword expansion value. It should be one of:
0370: * <ul>
0371: * <li> kv (Default) Substitue keyword and value.
0372: * <li> kvl Substitute keyword, value, and locker (if any).
0373: * <li> k Substitute keyword only.
0374: * <li> o Preserve original string.
0375: * <li> b Like o, but mark file as binary.
0376: * <li> v Substitue value only.
0377: * </ul>
0378: */
0379: public void setExpand(String value) {
0380: expand = value;
0381: }
0382:
0383: /**
0384: * Set the archive's comment.
0385: * @param value The comment.
0386: */
0387: public void setComment(String value) {
0388: comment = value;
0389: }
0390:
0391: /**
0392: * Set the archives description.
0393: * @param value The descriptions text.
0394: */
0395: public void setDesc(String value) {
0396: desc = value;
0397: }
0398:
0399: /**
0400: * Add a new phrase to the archive.
0401: * Phrases are used to provide for extensions of the archive format.
0402: * Each phrase has a key and a list of values associated with it.
0403: * @param key The phrases key.
0404: * @param values The values under the key.
0405: */
0406: public void addPhrase(String key, Collection values) {
0407: phrases.put(key, values);
0408: }
0409:
0410: protected Node newNode(Version vernum) {
0411: return newNode(vernum, null);
0412: }
0413:
0414: protected Node newNode(Version vernum, Node prev)
0415: throws InvalidVersionNumberException, NodeNotFoundException {
0416: if (!vernum.isRevision()) {
0417: throw new InvalidVersionNumberException(vernum);
0418: }
0419: Node node = (Node) nodes.get(vernum);
0420: if (node == null) {
0421: node = Node.newNode(vernum, prev);
0422: nodes.put(vernum, node);
0423: }
0424: return node;
0425: }
0426:
0427: protected TrunkNode newTrunkNode(Version vernum)
0428: throws InvalidVersionNumberException, NodeNotFoundException {
0429: if (!vernum.isTrunk()) {
0430: throw new InvalidTrunkVersionNumberException(vernum);
0431: }
0432: return (TrunkNode) newNode(vernum);
0433: }
0434:
0435: protected BranchNode newBranchNode(Version vernum)
0436: throws InvalidVersionNumberException, NodeNotFoundException {
0437: if (!vernum.isBranch()) {
0438: throw new InvalidBranchVersionNumberException(vernum);
0439: }
0440: return (BranchNode) newNode(vernum);
0441: }
0442:
0443: protected Node getNode(Version vernum)
0444: throws InvalidVersionNumberException, NodeNotFoundException {
0445: if (!vernum.isRevision()) {
0446: throw new InvalidVersionNumberException(vernum);
0447: }
0448: Node node = (Node) nodes.get(vernum);
0449: if (node == null) {
0450: throw new NodeNotFoundException(vernum);
0451: }
0452: return node;
0453: }
0454:
0455: /**
0456: * Return the node with the version number that matches the one provided.
0457: * The given version number may be partial.
0458: * @param vernum the version number to match.
0459: * @return the node, or null if no match found.
0460: */
0461: public Node findNode(Version vernum) {
0462: Path path = getRevisionPath(vernum);
0463: return (path == null ? null : path.last());
0464: }
0465:
0466: /**
0467: * Place a string image of the archive in the given StringBuffer.
0468: * @param s Where the image shoul go.
0469: */
0470: public void toString(StringBuffer s) {
0471: toString(s, RCS_NEWLINE);
0472: }
0473:
0474: /**
0475: * Return a text image of the archive.
0476: * @param EOL The token to use as line separator.
0477: * @return The text image of the archive.
0478: */
0479: public String toString(String EOL) {
0480: StringBuffer s = new StringBuffer();
0481: toString(s, EOL);
0482: return s.toString();
0483: }
0484:
0485: /**
0486: * Return a text image of the archive as a char array.
0487: * This is useful for writing the archive to a file without
0488: * having the characters be interpreted by the writer.
0489: * @return The archive image.
0490: */
0491: public char[] toCharArray() {
0492: return toString(Archive.RCS_NEWLINE).toCharArray();
0493: }
0494:
0495: /**
0496: * Return a text image of the archive as a char array.
0497: * This is useful for writing the archive to a file without
0498: * having the characters be interpreted by the writer.
0499: * @return The archive image.
0500: */
0501: public byte[] toByteArray() {
0502: return toString(Archive.RCS_NEWLINE).getBytes();
0503: }
0504:
0505: /**
0506: * Returns the path from the head node to the node identified
0507: * by the given version number.
0508: * @param vernum The version number that identifies the final node.
0509: * Partial version numbers are OK.
0510: * @return The path to the node, or null if not found.
0511: */
0512: protected Path getRevisionPath(Version vernum) {
0513: if (head == null) {
0514: return null;
0515: }
0516: try {
0517: Path path = head.pathTo(vernum, true);
0518: Node revisionFound = path.last();
0519: if (revisionFound == null) {
0520: return null;
0521: }
0522: if (revisionFound.getVersion().isLessThan(vernum)) {
0523: return null;
0524: }
0525: return path;
0526: } catch (NodeNotFoundException e) {
0527: return null;
0528: }
0529: }
0530:
0531: /**
0532: * Return the actual revision number of the node identified
0533: * by the given version number.
0534: * @param vernum The version number that identifies the node.
0535: * Partial version numbers are OK.
0536: * @return The actual version, or null if a node is not found.
0537: */
0538: public Version getRevisionVersion(Version vernum) {
0539: Path path = getRevisionPath(vernum);
0540: return (path == null ? null : path.last().getVersion());
0541: }
0542:
0543: /**
0544: * Return the actual revision number of the node identified
0545: * by the given version number.
0546: * @param vernum The version number that identifies the node.
0547: * Partial version numbers are OK.
0548: * @return The actual version, or null if a node is not found.
0549: */
0550: public Version getRevisionVersion(String vernum) {
0551: return getRevisionVersion(new Version(vernum));
0552: }
0553:
0554: /**
0555: * Return the actual revision number of the active revision.
0556: * The revision will be the tip of the branch identified as
0557: * active, or the head revision of the trunk if no branch is set
0558: * as active.
0559: * @return The version number of the active revision, or null if
0560: * there is none.
0561: */
0562: public Version getRevisionVersion() {
0563: if (branch != null) {
0564: return getRevisionVersion(branch);
0565: } else if (head != null) {
0566: return head.getVersion();
0567: } else {
0568: return null;
0569: }
0570: }
0571:
0572: /**
0573: * Append a text image of the archive to the given buffer using
0574: * the given token as line separator.
0575: * @param s where to append the image.
0576: * @param EOL the line separator.
0577: */
0578: public void toString(StringBuffer s, String EOL) {
0579: String EOI = ";" + EOL;
0580: String NLT = EOL + "\t";
0581:
0582: s.append("head");
0583: if (head != null) {
0584: s.append("\t");
0585: head.getVersion().toString(s);
0586: }
0587: s.append(EOI);
0588:
0589: if (branch != null) {
0590: s.append("branch\t");
0591: s.append(branch.toString());
0592: s.append(EOI);
0593: }
0594:
0595: s.append("access");
0596: for (Iterator i = users.iterator(); i.hasNext();) {
0597: s.append(EOL);
0598: s.append("\t");
0599: s.append(i.next());
0600: }
0601: s.append(EOI);
0602:
0603: s.append("symbols");
0604: for (Iterator i = symbols.entrySet().iterator(); i.hasNext();) {
0605: Map.Entry e = (Map.Entry) i.next();
0606: s.append(NLT);
0607: s.append(e.getKey().toString());
0608: s.append(":");
0609: s.append(e.getValue().toString());
0610: }
0611: s.append(EOI);
0612:
0613: s.append("locks");
0614: for (Iterator i = locked.iterator(); i.hasNext();) {
0615: String locker = ((Node) i.next()).getLocker();
0616: s.append(NLT);
0617: s.append(locker);
0618: }
0619: if (strictLocking) {
0620: s.append("; strict");
0621: }
0622: s.append(EOI);
0623:
0624: if (comment != null) {
0625: s.append("comment\t");
0626: s.append(Archive.quoteString(comment));
0627: s.append(EOI);
0628: }
0629:
0630: if (expand != null) {
0631: s.append("expand\t");
0632: s.append(Archive.quoteString(expand));
0633: s.append(EOI);
0634: }
0635:
0636: if (phrases != null) {
0637: phrases.toString(s, EOL);
0638: }
0639: s.append(EOL);
0640:
0641: for (Iterator i = nodes.values().iterator(); i.hasNext();) {
0642: Node n = (Node) i.next();
0643: if (!n.getVersion().isGhost() && n.getText() != null) {
0644: n.toString(s, EOL);
0645: }
0646: }
0647:
0648: s.append(EOL + EOL);
0649: s.append("desc");
0650: s.append(EOL);
0651: s.append(quoteString(desc));
0652: s.append(EOL);
0653:
0654: Node n = head;
0655: while (n != null) {
0656: n.toText(s, EOL);
0657: n = n.getRCSNext();
0658: }
0659: }
0660:
0661: /**
0662: * Quote a string.
0663: * RCS strings are quoted using @. Any @ in the original
0664: * string is doubled to @@.
0665: * @param s the string to quote.
0666: * @return The string quoted in RCS style.
0667: */
0668: static public String quoteString(String s) {
0669: //!!! use org.apache.commons.jrcs.RegExp here !!!
0670: StringBuffer result = new StringBuffer(s);
0671: for (int i = 0; i < s.length(); i++) {
0672: if (result.charAt(i) == '@') {
0673: result.insert(i++, '@');
0674: }
0675: }
0676: result.insert(0, '@');
0677: result.append('@');
0678: return new String(result);
0679: }
0680:
0681: /**
0682: * Unquote a string quoted in RCS style.
0683: * @param s the quoted string.
0684: * @return s the string unquoted.
0685: */
0686: static public String unquoteString(String s) {
0687: return unquoteString(s, true);
0688: }
0689:
0690: /**
0691: * Unquote a string quoted in RCS style.
0692: * @param s the quoted string.
0693: * @param removeExtremes Determines if the enclosing @ quotes
0694: * should be removed.
0695: * @return s the string unquoted.
0696: */
0697: static public String unquoteString(String s, boolean removeExtremes) {
0698: //!!! use org.apache.commons.jrcs.RegExp here !!!
0699: //!!! always ignore extremes. Check they are @'s, though.
0700: StringBuffer result = new StringBuffer();
0701: int start = 0;
0702: int end = s.length();
0703: if (removeExtremes) {
0704: start += 1;
0705: end -= 1;
0706: }
0707: for (int i = start; i < end; i++) {
0708: char c = s.charAt(i);
0709: result.append(c);
0710: if (c == '@') {
0711: i++;
0712: }
0713: }
0714: return new String(result);
0715: }
0716:
0717: /**
0718: * Get the text belonging to the head revision.
0719: * @return The text of the head revision
0720: * @throws NodeNotFoundException if the revision could not be found.
0721: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0722: * @throws PatchFailedException if any of the deltas could not be applied
0723: */
0724: public Object[] getRevision() throws InvalidFileFormatException,
0725: PatchFailedException, NodeNotFoundException {
0726: return getRevision(false);
0727: }
0728:
0729: /**
0730: * Get the text belonging to the head revision.
0731: * Set annotate to true to have the lines be annotated with the
0732: * number of the revision in which they were added or changed.
0733: * @param annotate set to true to have the text be annotated
0734: * @return The text of the head revision
0735: * @throws NodeNotFoundException if the revision could not be found.
0736: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0737: * @throws PatchFailedException if any of the deltas could not be applied
0738: * to produce a new revision.
0739: */
0740: public Object[] getRevision(boolean annotate)
0741: throws InvalidFileFormatException, PatchFailedException,
0742: NodeNotFoundException {
0743: if (branch != null) {
0744: return getRevision(branch);
0745: } else if (head != null) {
0746: return getRevision(head.getVersion());
0747: } else {
0748: throw new IllegalStateException("no head node");
0749: }
0750: }
0751:
0752: /**
0753: * Get the text belonging to the revision identified by the
0754: * given version number.
0755: * Partial version numbers are OK.
0756: * @param vernum the version number.
0757: * @return The text of the revision if found.
0758: * @throws InvalidVersionNumberException if the version number cannot be parsed.
0759: * @throws NodeNotFoundException if the revision could not be found.
0760: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0761: * @throws PatchFailedException if any of the deltas could not be applied
0762: */
0763: public Object[] getRevision(String vernum)
0764: throws InvalidFileFormatException, PatchFailedException,
0765: InvalidVersionNumberException, NodeNotFoundException {
0766: return getRevision(vernum, false);
0767: }
0768:
0769: /**
0770: * Get the text belonging to the revision identified by the
0771: * given version number.
0772: * Partial version numbers are OK.
0773: * Set annotate to true to have the lines be annotated with the
0774: * number of the revision in which they were added or changed.
0775: * @param vernum the version number.
0776: * @param annotate set to true to have the text be annotated
0777: * @return The text of the revision if found.
0778: * @throws InvalidVersionNumberException if the version number cannot be parsed.
0779: * @throws NodeNotFoundException if the revision could not be found.
0780: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0781: * @throws PatchFailedException if any of the deltas could not be applied
0782: */
0783: public Object[] getRevision(String vernum, boolean annotate)
0784: throws InvalidVersionNumberException,
0785: NodeNotFoundException, InvalidFileFormatException,
0786: PatchFailedException {
0787: return getRevision(new Version(vernum), annotate);
0788: }
0789:
0790: /**
0791: * Get the text belonging to the revision identified by the
0792: * given version number.
0793: * Partial version numbers are OK.
0794: * @param vernum the version number.
0795: * @return The text of the revision if found.
0796: * @throws NodeNotFoundException if the revision could not be found.
0797: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0798: * @throws PatchFailedException if any of the deltas could not be applied
0799: */
0800: public Object[] getRevision(Version vernum)
0801: throws InvalidFileFormatException, PatchFailedException,
0802: NodeNotFoundException {
0803: return getRevision(vernum, false);
0804: }
0805:
0806: /**
0807: * Get the text belonging to the revision identified by the
0808: * given version number.
0809: * Partial version numbers are OK.
0810: * Set annotate to true to have the lines be annotated with the
0811: * number of the revision in which they were added or changed.
0812: * @param vernum the version number.
0813: * @param annotate set to true to have the text be annotated
0814: * @return The text of the revision if found.
0815: * @throws NodeNotFoundException if the revision could not be found.
0816: * @throws InvalidFileFormatException if any of the deltas cannot be parsed.
0817: * @throws PatchFailedException if any of the deltas could not be applied
0818: */
0819: public Object[] getRevision(Version vernum, boolean annotate)
0820: throws InvalidFileFormatException, PatchFailedException,
0821: NodeNotFoundException {
0822: Path path = getRevisionPath(vernum);
0823: if (path == null) {
0824: throw new NodeNotFoundException(vernum);
0825: }
0826: Lines lines = new Lines();
0827: Node revisionFound = path.last();
0828: path.patch(lines, annotate);
0829:
0830: return doKeywords(lines.toArray(), revisionFound);
0831: }
0832:
0833: /**
0834: * Add the given revision to the active branch on the archive.
0835: * @param text the text of the revision.
0836: * @param log the log: a short note explaining what the revision is.
0837: * @return The version number assigned to the revision.
0838: */
0839: public Version addRevision(Object[] text, String log)
0840: throws InvalidFileFormatException, DiffException,
0841: InvalidVersionNumberException, NodeNotFoundException {
0842: if (branch != null) {
0843: return addRevision(text, branch, log);
0844: } else {
0845: return addRevision(text, head.getVersion().next(), log);
0846: }
0847: }
0848:
0849: /**
0850: * Add the given revision to the the archive using the given version
0851: * number.
0852: * The version number may be partial. If so, the rules used by RCS/CVS
0853: * are used to decide which branch the revision should be added to. A
0854: * new branch may be created if required.
0855: * @param text the text of the revision.
0856: * @param vernum is the version number wanted, or, if partial, identifies
0857: * the target branch.
0858: * @param log the log: a short note explaining what the revision is.
0859: * @return The version number assigned to the revision.
0860: */
0861: public Version addRevision(Object[] text, String vernum, String log)
0862: throws InvalidFileFormatException, DiffException,
0863: InvalidVersionNumberException, NodeNotFoundException {
0864: return addRevision(text, new Version(vernum), log);
0865: }
0866:
0867: /**
0868: * Add the given revision to the the archive using the given version
0869: * number.
0870: * The version number may be partial. If so, the rules used by RCS/CVS
0871: * are used to decide which branch the revision should be added to. A
0872: * new branch may be created if required.
0873: * @param text the text of the revision.
0874: * @param vernum is the version number wanted, or, if partial, identifies
0875: * the target branch.
0876: * @param log the log: a short note explaining what the revision is.
0877: * @return The version number assigned to the revision.
0878: */
0879: public Version addRevision(Object[] text, Version vernum, String log)
0880: throws InvalidFileFormatException, DiffException,
0881: NodeNotFoundException, InvalidVersionNumberException {
0882: if (head == null) {
0883: throw new IllegalStateException("no head node");
0884: }
0885:
0886: Path path = head.pathTo(vernum, true);
0887: Node target = path.last();
0888:
0889: if (vernum.size() < target.getVersion().size()) {
0890: vernum = target.nextVersion();
0891: } else if (!vernum.isGreaterThan(target.getVersion())) {
0892: throw new InvalidVersionNumberException(vernum
0893: + " revision must be higher than "
0894: + target.getVersion());
0895: } else if (vernum.odd()) {
0896: if (vernum.last() == 0) {
0897: vernum = target.newBranchVersion();
0898: } else {
0899: vernum = vernum.newBranch(1);
0900: }
0901: } else if (vernum.last() == 0) {
0902: vernum = vernum.next();
0903: }
0904:
0905: boolean headAdd = (target == head && !vernum.isBranch());
0906:
0907: text = removeKeywords(text);
0908: String deltaText;
0909: if (headAdd) {
0910: deltaText = Diff.diff(text, head.getText()).toRCSString(
0911: RCS_NEWLINE);
0912: } else {
0913: Object[] oldText = path.patch().toArray();
0914: deltaText = Diff.diff(oldText, text).toRCSString(
0915: RCS_NEWLINE);
0916: }
0917: if (deltaText.length() == 0) {
0918: return null;
0919: } // no changes, no new version
0920:
0921: Node newNode = null;
0922: if (headAdd) {
0923: newNode = newNode(vernum, head);
0924: newNode.setText(text);
0925: head.setText(deltaText);
0926: head = (TrunkNode) newNode;
0927: } else { // adding a branch node
0928: newNode = newNode(vernum);
0929: newNode.setText(deltaText);
0930: if (vernum.size() > target.getVersion().size()) {
0931: target.addBranch((BranchNode) newNode);
0932: } else {
0933: target.setRCSNext(newNode);
0934: }
0935: }
0936: newNode.setLog(log);
0937: return newNode.getVersion();
0938: }
0939:
0940: /**
0941: * Returns the given text with values added to CVS-style keywords.
0942: * @param text the text on which substitutions will be applied.
0943: * @param rev a node that identifies the revision to which the
0944: * given text belongs.
0945: * @return the text with substitutions performed.
0946: */
0947: public Object[] doKeywords(Object[] text, Node rev)
0948: throws PatchFailedException {
0949:
0950: //!!! this is used specifically for the way
0951: //!!! in which the keyword replacer works. Should be moved there.
0952: //!!! Write a Format.format(Object[], Node rev) instead.
0953: Object[] revisionInfo = new Object[] { filename,
0954: new File(filename).getName(),
0955: rev.getVersion().toString(), rev.getDate(),
0956: rev.getAuthor(), rev.getState(), rev.getLocker() };
0957:
0958: Object[] result = new Object[text.length];
0959: for (int i = 0; i < text.length; i++) {
0960: result[i] = FORMATTER.update(text[i].toString(),
0961: revisionInfo);
0962: }
0963: return result;
0964: }
0965:
0966: /**
0967: * Returns the given text removing the values of any CVS-style
0968: * keywords.
0969: * @param text the text on which substitutions will be applied.
0970: * @return the text with substitutions performed.
0971: */
0972: protected static Object[] removeKeywords(Object[] text)
0973: throws PatchFailedException {
0974: Object[] result = new Object[text.length];
0975: for (int i = 0; i < text.length; i++) {
0976: result[i] = FORMATTER.reset(text[i].toString());
0977: }
0978: return result;
0979: }
0980:
0981: /**
0982: * Return the list of nodes between the head revision and
0983: * the root revision.
0984: */
0985: public Node[] changeLog() {
0986: return changeLog(head.version);
0987: }
0988:
0989: /**
0990: * Return the list of nodes between the the given revision
0991: * and the root revision.
0992: * @param latest the version of the last revision in the log.
0993: */
0994: public Node[] changeLog(Version latest) {
0995: return changeLog(latest, head.root().version);
0996: }
0997:
0998: /**
0999: * Return the list of nodes between the the given two revisions.
1000: * @param latest the version of the last revision in the log.
1001: * @param earliest the version of the first revision in the log.
1002: */
1003: public Node[] changeLog(Version latest, Version earliest) {
1004: Node last = findNode(latest);
1005: if (last == null) {
1006: throw new NodeNotFoundException(latest.toString());
1007: }
1008:
1009: Node first = findNode(earliest);
1010: if (first == null) {
1011: throw new NodeNotFoundException(earliest.toString());
1012: }
1013:
1014: List result = new LinkedList();
1015:
1016: Node node = last;
1017: while (node != null) {
1018: result.add(0, node);
1019: if (node == first) {
1020: break;
1021: }
1022: node = node.parent;
1023: }
1024:
1025: if (node == null) {
1026: throw new NodeNotFoundException(earliest.toString());
1027: }
1028:
1029: return (Node[]) result.toArray(new Node[result.size()]);
1030: }
1031:
1032: /**
1033: * Returns the description associated with the archive.
1034: * @return the description
1035: */
1036: public String getDesc() {
1037: return desc;
1038: }
1039:
1040: /** Returns the log message associated with the given revision.
1041: * @param version - the version to get the log message for
1042: * @return the log message for the version.
1043: * @exception - if the version does not exist for the archive.
1044: */
1045: public String getLog(Version version) throws NodeNotFoundException {
1046: Node node = this .findNode(version);
1047: if (node == null) {
1048: throw new NodeNotFoundException("There's no version "
1049: + version);
1050: }
1051: return node.getLog();
1052: }
1053:
1054: /** Returns the log message associated with the given revision.
1055: * @param version - the version to get the log message for
1056: * @return the log message for the version.
1057: * @exception - if the version does not exist for the archive.
1058: */
1059: public String getLog(String vernum)
1060: throws InvalidVersionNumberException, NodeNotFoundException {
1061: return getLog(new Version(vernum));
1062: }
1063:
1064: }
|