001: /********************************************************************************
002: * CruiseControl, a Continuous Integration Toolkit
003: * Copyright (c) 2001-2003, ThoughtWorks, Inc.
004: * 200 E. Randolph, 25th Floor
005: * Chicago, IL 60601 USA
006: * All rights reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * + Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * + Redistributions in binary form must reproduce the above
016: * copyright notice, this list of conditions and the following
017: * disclaimer in the documentation and/or other materials provided
018: * with the distribution.
019: *
020: * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the
021: * names of its contributors may be used to endorse or promote
022: * products derived from this software without specific prior
023: * written permission.
024: *
025: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
026: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
027: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
028: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
029: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
030: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
031: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
032: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
033: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
034: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036: ********************************************************************************/package net.sourceforge.cruisecontrol.sourcecontrols;
037:
038: import java.io.BufferedReader;
039: import java.io.IOException;
040: import java.io.InputStream;
041: import java.io.InputStreamReader;
042: import java.text.DateFormat;
043: import java.text.ParseException;
044: import java.text.SimpleDateFormat;
045: import java.util.ArrayList;
046: import java.util.Arrays;
047: import java.util.Date;
048: import java.util.HashMap;
049: import java.util.Iterator;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.StringTokenizer;
053:
054: import net.sourceforge.cruisecontrol.CruiseControlException;
055: import net.sourceforge.cruisecontrol.Modification;
056: import net.sourceforge.cruisecontrol.SourceControl;
057: import net.sourceforge.cruisecontrol.util.Commandline;
058: import net.sourceforge.cruisecontrol.util.CommandlineUtil;
059: import net.sourceforge.cruisecontrol.util.ValidationHelper;
060:
061: import org.apache.log4j.Logger;
062: import org.jdom.Element;
063:
064: /**
065: * This class implements the SourceControlElement methods for ClearCase UCM.
066: *
067: * @author <a href="mailto:kevin.lee@buildmeister.com">Kevin Lee</a>
068: * @author Alex Batlin
069: */
070: public class UCM implements SourceControl {
071:
072: private static final Logger LOG = Logger.getLogger(UCM.class);
073:
074: private String stream;
075: private String viewPath;
076: private boolean multiVob;
077: private boolean contributors = true;
078: private boolean rebases = false;
079:
080: private SourceControlProperties properties = new SourceControlProperties();
081:
082: private final SimpleDateFormat inputDateFormat = new SimpleDateFormat(
083: "dd-MMMM-yyyy.HH:mm:ss");
084: private final SimpleDateFormat outputDateFormat = new SimpleDateFormat(
085: "yyyyMMdd.HHmmss");
086:
087: private String pvob;
088:
089: /**
090: * Unlikely combination of characters to separate fields in a ClearCase query
091: */
092: static final String DELIMITER = "#~#";
093:
094: /**
095: * Even more unlikely combination of characters to indicate end of one line in query. Carriage return (\n) can be
096: * used in comments and so is not available to us.
097: */
098: static final String END_OF_STRING_DELIMITER = "@#@#@#@#@#@#@#@#@#@#@#@";
099:
100: public Map getProperties() {
101: return properties.getPropertiesAndReset();
102: }
103:
104: public void validate() throws CruiseControlException {
105: ValidationHelper.assertIsSet(stream, "stream", this .getClass());
106: ValidationHelper.assertIsSet(viewPath, "viewpath", this
107: .getClass());
108:
109: if (isRebases()) {
110: ValidationHelper.assertIsSet(pvob, "pvob", this .getClass());
111: }
112: }
113:
114: /**
115: * get which stream is being checked
116: *
117: * @return the name of the stream being checked
118: */
119: public String getStream() {
120: return stream;
121: }
122:
123: /**
124: * set the stream to check for changes
125: *
126: * @param stream
127: * the stream to be checked (via its underlying branch)
128: */
129: public void setStream(String stream) {
130: this .stream = stream;
131: }
132:
133: /**
134: * get the starting point path in a view to check for changes
135: *
136: * @return path inside a view
137: */
138: public String getViewPath() {
139: return viewPath;
140: }
141:
142: /**
143: * set the starting point path in a view to check for changes
144: *
145: * @param viewPath
146: * path inside a view
147: */
148: public void setViewPath(String viewPath) {
149: this .viewPath = viewPath;
150: }
151:
152: /**
153: * get whether the view contains multiple vobs
154: *
155: * @return true, if the view contains multiple vobs, else false
156: */
157: public boolean isMultiVob() {
158: return this .multiVob;
159: }
160:
161: /**
162: * set whether the view contains multiple vobs
163: *
164: * @param multiVob boolean indicating whether the view contains multiple vobs
165: */
166: public void setMultiVob(boolean multiVob) {
167: this .multiVob = multiVob;
168: }
169:
170: /**
171: * Set the name of the pvob to use for queries.
172: *
173: * @param pvob
174: * the pvob
175: */
176: public void setPvob(String pvob) {
177: this .pvob = pvob;
178: }
179:
180: /**
181: * Get the name of the pvob to use for queries.
182: *
183: * @return The name of the pvob
184: */
185: public String getPvob() {
186: return this .pvob;
187: }
188:
189: /**
190: * Gets whether rebases are to be reported as changes.
191: *
192: * @return true, if rebases are to be reported, else false
193: */
194: public boolean isRebases() {
195: return this .rebases;
196: }
197:
198: /**
199: * Sets whether rebases of the integration stream are reported as changes.
200: *
201: * @param rebases
202: * boolean indicating whether rebases are to be reported as changes
203: */
204: public void setRebases(boolean rebases) {
205: this .rebases = rebases;
206: }
207:
208: /**
209: * get whether contributors are to be found
210: *
211: * @return true, if contributors are to be found, else false
212: */
213: public boolean isContributors() {
214: return contributors;
215: }
216:
217: /**
218: * set whether contributors are to be found
219: *
220: * @param contributors
221: * boolean indicating whether contributors are to be found
222: */
223: public void setContributors(boolean contributors) {
224: this .contributors = contributors;
225: }
226:
227: /**
228: * set the name of the property that will be set if modifications are found
229: *
230: * @param property
231: * The name of the property to set
232: */
233: public void setProperty(String property) {
234: properties.assignPropertyName(property);
235: }
236:
237: /**
238: * Get a List of modifications detailing all the changes between now and the last build. Return this as an element.
239: * It is not necessary for sourcecontrols to actually do anything other than returning a chunk of XML data back.
240: *
241: * @param lastBuild
242: * time of last build
243: * @param now
244: * time this build started
245: * @return a list of XML elements that contains data about the modifications that took place. If no changes, this
246: * method returns an empty list.
247: */
248: public List getModifications(Date lastBuild, Date now) {
249: String lastBuildDate = inputDateFormat.format(lastBuild);
250: String nowDate = inputDateFormat.format(now);
251: properties.put("ucmlastbuild", lastBuildDate);
252: properties.put("ucmnow", nowDate);
253: List mods = new ArrayList();
254: try {
255: HashMap activityNames = collectActivitiesSinceLastBuild(lastBuildDate);
256: if (activityNames.size() == 0) {
257: return mods;
258: }
259: mods = describeAllActivities(activityNames);
260: } catch (Exception e) {
261: LOG.error("Command failed to execute succesfully", e);
262: }
263:
264: if (this .isRebases()) {
265: try {
266: Commandline commandline = buildDetectRebasesCommand(lastBuildDate);
267: commandline.setWorkingDirectory(viewPath);
268: InputStream cmdStream = CommandlineUtil
269: .streamOutput(commandline);
270:
271: try {
272: mods.addAll(parseRebases(cmdStream));
273: } finally {
274: cmdStream.close();
275: }
276: } catch (Exception e) {
277: LOG.error(
278: "Error in executing the Clear Case command : ",
279: e);
280: }
281: }
282:
283: // If modifications were found, set the property
284: if (!mods.isEmpty()) {
285: properties.modificationFound();
286: }
287:
288: return mods;
289: }
290:
291: /**
292: * get all the activities on the stream since the last build date
293: */
294: private HashMap collectActivitiesSinceLastBuild(String lastBuildDate) {
295:
296: LOG.debug("Last build time was: " + lastBuildDate);
297:
298: HashMap activityMap = new HashMap();
299:
300: Commandline commandLine = buildListStreamCommand(lastBuildDate);
301: LOG.debug("Executing: " + commandLine);
302:
303: try {
304: commandLine.setWorkingDirectory(viewPath);
305: InputStream cmdStream = CommandlineUtil
306: .streamOutput(commandLine);
307:
308: try {
309: InputStreamReader isr = new InputStreamReader(cmdStream);
310: BufferedReader br = new BufferedReader(isr);
311: String line;
312:
313: while (((line = br.readLine()) != null)
314: && (!br.equals(""))) {
315: String[] details = getDetails(line);
316: if (details[0].equals("mkbranch")
317: || details[0].equals("rmbranch")
318: || details[0].equals("rmver")) {
319: // if type is create/remove branch then skip
320: } else {
321: String activityName = details[1];
322: String activityDate = details[2];
323: // assume the latest change for an activity is listed first
324: if (!activityMap.containsKey(activityName)) {
325: LOG.debug("Found activity name: "
326: + activityName + "; date: "
327: + activityDate);
328: activityMap.put(activityName, activityDate);
329: }
330: }
331: }
332: } finally {
333: cmdStream.close();
334: }
335: } catch (IOException e) {
336: LOG.error("IO Error executing ClearCase lshistory command",
337: e);
338: } catch (CruiseControlException e) {
339: LOG
340: .error(
341: "Interrupt Error executing ClearCase lshistory command",
342: e);
343: }
344:
345: return activityMap;
346: }
347:
348: private String[] getDetails(String line) {
349: // replacing line.split("~#~") for jdk 1.3
350: ArrayList details = new ArrayList();
351: String delimiter = "~#~";
352: int startIndex = 0;
353: int index = 0;
354: while (index != -1) {
355: String detail;
356: index = line.indexOf(delimiter, startIndex);
357: if (index == -1) {
358: detail = line.substring(startIndex, line.length());
359: } else {
360: detail = line.substring(startIndex, index);
361: }
362: details.add(detail);
363: startIndex = index + delimiter.length();
364: }
365:
366: return (String[]) details.toArray(new String[] {});
367: }
368:
369: /**
370: * construct a command to get all the activities on the specified stream
371: */
372: public Commandline buildListStreamCommand(String lastBuildDate) {
373: Commandline commandLine = new Commandline();
374: if (isMultiVob()) {
375: try {
376: commandLine.setWorkingDirectory(getViewPath());
377: } catch (CruiseControlException e) {
378: LOG.error("Error in setting workdirectory", e);
379: }
380: }
381: commandLine.setExecutable("cleartool");
382: commandLine.createArgument("lshistory");
383: commandLine.createArguments("-branch", getStream());
384: if (isMultiVob()) {
385: commandLine.createArgument("-avobs");
386: } else {
387: commandLine.createArgument("-r");
388: }
389: commandLine.createArgument("-nco");
390: commandLine.createArguments("-since", lastBuildDate);
391: commandLine.createArguments("-fmt",
392: "%o~#~%[activity]Xp~#~%Nd\n");
393: if (!isMultiVob()) {
394: commandLine.createArgument(getViewPath());
395: }
396: return commandLine;
397: }
398:
399: /**
400: * get all the activities on the stream since the last build date
401: */
402: private List describeAllActivities(HashMap activityNames) {
403:
404: ArrayList activityList = new ArrayList();
405:
406: Iterator it = activityNames.entrySet().iterator();
407: while (it.hasNext()) {
408: Map.Entry activity = (Map.Entry) it.next();
409: String activityID = activity.getKey().toString();
410: String activityDate = activity.getValue().toString();
411: UCMModification activityMod = describeActivity(activityID,
412: activityDate);
413: activityList.add(activityMod);
414:
415: // check for contributor activities
416: if (activityMod.comment.startsWith("deliver ")
417: && isContributors()) {
418: List contribList;
419: contribList = describeContributors(activityID);
420: Iterator contribIter = contribList.iterator();
421: while (contribIter.hasNext()) {
422: String contribName = contribIter.next().toString();
423: UCMModification contribMod = describeActivity(
424: contribName, activityDate);
425: // prefix type to make it stand out in Build Results report
426: contribMod.type = "contributor";
427: LOG.debug("Found contributor name: " + contribName
428: + "; date: " + activityDate);
429: activityList.add(contribMod);
430: }
431: }
432: }
433:
434: return activityList;
435: }
436:
437: /**
438: * get all the activities on the stream since the last build date
439: */
440: private UCMModification describeActivity(String activityID,
441: String activityDate) {
442:
443: UCMModification mod = new UCMModification();
444:
445: Commandline commandLine = buildDescribeActivityCommand(activityID);
446: LOG.debug("Executing: " + commandLine);
447:
448: try {
449: commandLine.setWorkingDirectory(viewPath);
450: InputStream cmdStream = CommandlineUtil
451: .streamOutput(commandLine);
452:
453: try {
454: InputStreamReader isr = new InputStreamReader(cmdStream);
455: BufferedReader br = new BufferedReader(isr);
456: String line;
457:
458: while (((line = br.readLine()) != null)
459: && (!br.equals(""))) {
460: String[] details = getDetails(line);
461: try {
462: mod.modifiedTime = outputDateFormat
463: .parse(activityDate);
464: } catch (ParseException e) {
465: LOG.error("Error parsing modification date");
466: mod.modifiedTime = new Date();
467: }
468: mod.type = "activity";
469: // counter for UCM without ClearQuest
470: if (details[0].equals("")) {
471: mod.revision = details[3];
472: } else {
473: mod.revision = details[0];
474: }
475: mod.crmtype = details[1];
476: mod.userName = details[2];
477: mod.comment = details[3];
478: }
479: } finally {
480: cmdStream.close();
481: }
482: } catch (IOException e) {
483: LOG.error("IO Error executing ClearCase describe command",
484: e);
485: } catch (CruiseControlException e) {
486: LOG
487: .error(
488: "Interrupt error executing ClearCase describe command",
489: e);
490: }
491:
492: return mod;
493: }
494:
495: /**
496: * construct a command to get all the activities on the specified stream
497: */
498: public Commandline buildDescribeActivityCommand(String activityID) {
499: Commandline commandLine = new Commandline();
500: commandLine.setExecutable("cleartool");
501: commandLine.createArgument("describe");
502: commandLine
503: .createArguments("-fmt",
504: "%[crm_record_id]p~#~%[crm_record_type]p~#~%u~#~%[headline]p~#~");
505: commandLine.createArgument(activityID);
506: return commandLine;
507: }
508:
509: /**
510: * get all the activities on the stream since the last build date
511: */
512: private List describeContributors(String activityName) {
513:
514: ArrayList contribList = new ArrayList();
515: Commandline commandLine = buildListContributorsCommand(activityName);
516: LOG.debug("Executing: " + commandLine);
517:
518: try {
519: commandLine.setWorkingDirectory(viewPath);
520: InputStream cmdStream = CommandlineUtil
521: .streamOutput(commandLine);
522:
523: try {
524: InputStreamReader isr = new InputStreamReader(cmdStream);
525: BufferedReader br = new BufferedReader(isr);
526: String line;
527:
528: while ((line = br.readLine()) != null) {
529: String[] contribs = splitOnSpace(line);
530: for (int i = 0; i < contribs.length; i++) {
531: contribList.add(contribs[i]);
532: }
533: }
534: } finally {
535: cmdStream.close();
536: }
537: } catch (IOException e) {
538: LOG
539: .error(
540: "IO Error executing ClearCase describe contributors command",
541: e);
542: } catch (CruiseControlException e) {
543: LOG
544: .error(
545: "Interrupt Error executing ClearCase describe contributors command",
546: e);
547: }
548:
549: return contribList;
550: }
551:
552: private String[] splitOnSpace(String string) {
553: return string.split(" ");
554: }
555:
556: /**
557: * construct a command to get all the activities on the specified stream
558: */
559: public Commandline buildListContributorsCommand(String activityID) {
560: Commandline commandLine = new Commandline();
561: commandLine.setExecutable("cleartool");
562: commandLine.createArgument("describe");
563: commandLine.createArguments("-fmt", "\"%[contrib_acts]Xp\"");
564: commandLine.createArgument(activityID);
565: return commandLine;
566: }
567:
568: protected Commandline buildDetectRebasesCommand(String lastBuildDate) {
569: Commandline commandLine = new Commandline();
570: commandLine.setExecutable("cleartool");
571: commandLine.createArgument().setValue("lshistory");
572: commandLine.createArgument().setValue("-since");
573: commandLine.createArgument().setValue(lastBuildDate);
574: commandLine.createArgument().setValue("-minor");
575: commandLine.createArgument().setValue("-fmt");
576: String format = "%u" + DELIMITER + "%Nd" + DELIMITER + "%o"
577: + DELIMITER + "%Nc" + END_OF_STRING_DELIMITER + "\\n";
578: commandLine.createArgument().setValue(format);
579: commandLine.createArgument().setValue(
580: "stream:" + stream + "@" + pvob);
581: return commandLine;
582: }
583:
584: /**
585: * Parses the given input stream to construct the modifications list. The stream is expected to be the result of
586: * listing the history of a UCM stream. Rebases are then detected by delegating to
587: * {@link #parseRebaseEntry(String)}.
588: * Package-private to make it available to the unit test.
589: *
590: * @param input
591: * the stream to parse
592: * @return a list of modification elements
593: * @exception IOException
594: */
595: List parseRebases(InputStream input) throws IOException {
596: ArrayList modifications = new ArrayList();
597: BufferedReader reader = new BufferedReader(
598: new InputStreamReader(input));
599: String ls = System.getProperty("line.separator");
600:
601: String line;
602: StringBuffer lines = new StringBuffer();
603:
604: while ((line = reader.readLine()) != null) {
605: if (lines.length() != 0) {
606: lines.append(ls);
607: }
608: lines.append(line);
609: Modification mod = null;
610:
611: if (lines.indexOf(END_OF_STRING_DELIMITER) > -1) {
612: mod = parseRebaseEntry(lines.substring(0, lines
613: .indexOf(END_OF_STRING_DELIMITER)));
614: lines = new StringBuffer();
615: }
616:
617: if (mod != null) {
618: modifications.add(mod);
619: }
620: }
621:
622: return modifications;
623: }
624:
625: /**
626: * Parses a single line from the reader. Each line contains a signe revision with the format : <br>
627: * username#~#date_of_revision#~#operation_type#~#comments <br>
628: * <p>
629: * This method looks for operations of type rmhlink and mkhlink where the hyperlink name begins with "UseBaseline".
630: * These represent changes in the baseline dependencies of the stream.
631: * </p>
632: *
633: * @param line
634: * the line to parse
635: * @return a modification element corresponding to the given line
636: */
637: Modification parseRebaseEntry(String line) {
638: LOG.debug("parsing entry: " + line);
639: String[] tokens = tokenizeEntry(line);
640: if (tokens == null) {
641: return null;
642: }
643: String username = tokens[0].trim();
644: String timeStamp = tokens[1].trim();
645: String operationType = tokens[2].trim();
646: String comment = tokens[3].trim();
647:
648: Modification mod = null;
649:
650: // Rebases show up as mkhlink and rmhlink operations
651: if (operationType.equals("mkhlink")
652: || operationType.equals("rmhlink")) {
653: // Parse the hyperlink name out of the comment field, then
654: // get more information on that hyperlink
655: mod = new Modification();
656: String linkName = parseLinkName(comment);
657:
658: // If this isn't a "UseBaseline" hyperlink, we're not interested
659: if (!linkName.startsWith("UseBaseline")) {
660: return null;
661: }
662:
663: Hyperlink link = getHyperlink(linkName);
664: StringBuffer modComment = new StringBuffer();
665: mod.type = "ucmdependency";
666:
667: if (operationType.equals("mkhlink")) {
668: modComment.append("Added dependency");
669: } else {
670: modComment.append("Removed dependency");
671: }
672:
673: if (link.getFrom().length() > 0) {
674: modComment.append(" of ");
675: modComment.append(link.getFrom());
676: }
677:
678: if (link.getTo().length() > 0) {
679: modComment.append(" on ");
680: modComment.append(link.getTo());
681: mod.revision = link.getTo();
682: } else {
683: // Don't know what the revision was to
684: mod.revision = "";
685: }
686:
687: mod.comment = modComment.toString();
688: mod.userName = username;
689:
690: try {
691: mod.modifiedTime = outputDateFormat.parse(timeStamp);
692: } catch (ParseException e) {
693: LOG.error("Error parsing modification date", e);
694: mod.modifiedTime = new Date();
695: }
696:
697: properties.modificationFound();
698: }
699:
700: return mod;
701: }
702:
703: private Hyperlink getHyperlink(String linkName) {
704: Commandline commandline = buildGetHyperlinkCommandline(linkName);
705:
706: try {
707: commandline.setWorkingDirectory(viewPath);
708: InputStream cmdStream = CommandlineUtil
709: .streamOutput(commandline);
710: Hyperlink link = null;
711:
712: try {
713: link = parseHyperlinkDescription(cmdStream);
714: } finally {
715: cmdStream.close();
716: }
717:
718: return link;
719: } catch (Exception e) {
720: LOG
721: .error(
722: "Error in executing the Clear Case command : ",
723: e);
724: return new Hyperlink();
725: }
726: }
727:
728: protected Commandline buildGetHyperlinkCommandline(String linkName) {
729: Commandline commandline = new Commandline();
730: commandline.setExecutable("cleartool");
731: commandline.createArgument().setValue("describe");
732: commandline.createArgument().setValue(
733: "hlink:" + linkName + "@" + pvob);
734: return commandline;
735: }
736:
737: Hyperlink parseHyperlinkDescription(InputStream input)
738: throws IOException {
739: BufferedReader reader = new BufferedReader(
740: new InputStreamReader(input));
741:
742: String lastLine = "";
743: String line = reader.readLine();
744:
745: // If the hyperlink wasn't found, cleartool will return no output, giving
746: // us an empty stream. This will end up returning an empty string.
747: while (line != null) {
748: lastLine = line;
749: line = reader.readLine();
750: }
751:
752: Hyperlink link = new Hyperlink();
753: StringTokenizer tokens = new StringTokenizer(lastLine, " ");
754:
755: if (!tokens.hasMoreTokens()) {
756: return link;
757: }
758:
759: // Discard the first one, that's the link name
760: tokens.nextToken();
761:
762: if (!tokens.hasMoreTokens()) {
763: return link;
764: }
765:
766: link.setFrom(tokens.nextToken());
767:
768: // Discard "->"
769: if (!tokens.hasMoreTokens()) {
770: return link;
771: }
772:
773: tokens.nextToken();
774:
775: if (!tokens.hasMoreTokens()) {
776: return link;
777: }
778:
779: link.setTo(tokens.nextToken());
780:
781: return link;
782: }
783:
784: String parseLinkName(String comment) {
785: // Parse on spaces and quotes (to eliminate them)
786: StringTokenizer tokens = new StringTokenizer(comment, " \"");
787: String link = "";
788: String token = "";
789:
790: // The next-to-last token should contain the hyperlink name
791: while (tokens.hasMoreTokens()) {
792: link = token;
793: token = tokens.nextToken();
794: }
795:
796: int index = link.lastIndexOf('@');
797:
798: if (index != -1) {
799: // Remove the VOB-qualifier from the end. We'll add it back ourselves
800: return link.substring(0, link.lastIndexOf('@'));
801: } else {
802: // Return it unmodified
803: return link;
804: }
805: }
806:
807: private String[] tokenizeEntry(String line) {
808: int maxTokens = 4;
809: int minTokens = maxTokens - 1; // comment may be absent.
810: String[] tokens = new String[maxTokens];
811: Arrays.fill(tokens, "");
812: int tokenIndex = 0;
813: for (int oldIndex = 0, i = line.indexOf(DELIMITER, 0); true; oldIndex = i
814: + DELIMITER.length(), i = line.indexOf(DELIMITER,
815: oldIndex), tokenIndex++) {
816: if (tokenIndex > maxTokens) {
817: LOG.debug("Too many tokens; skipping entry");
818: return null;
819: }
820: if (i == -1) {
821: tokens[tokenIndex] = line.substring(oldIndex);
822: break;
823: } else {
824: tokens[tokenIndex] = line.substring(oldIndex, i);
825: }
826: }
827: if (tokenIndex < minTokens) {
828: LOG.debug("Not enough tokens; skipping entry");
829: return null;
830: }
831: return tokens;
832: }
833:
834: /**
835: * Class to represent ClearCase hyperlinks.
836: */
837: class Hyperlink {
838: private String from = "";
839: private String to = "";
840:
841: public String getFrom() {
842: return this .from;
843: }
844:
845: public void setFrom(String from) {
846: this .from = from;
847: }
848:
849: public String getTo() {
850: return this .to;
851: }
852:
853: public void setTo(String to) {
854: this .to = to;
855: }
856: }
857:
858: /**
859: * class to hold UCMModifications
860: */
861: private static class UCMModification extends Modification {
862: private static final String TAGNAME_CRMTYPE = "crmtype";
863:
864: public String crmtype;
865:
866: public int compareTo(Object o) {
867: UCMModification modification = (UCMModification) o;
868: return getActivitityNumber()
869: - modification.getActivitityNumber();
870: }
871:
872: public boolean equals(Object o) {
873: if (o == null || !(o instanceof UCMModification)) {
874: return false;
875: }
876:
877: UCMModification modification = (UCMModification) o;
878: return getActivitityNumber() == modification
879: .getActivitityNumber();
880: }
881:
882: public int hashCode() {
883: return getActivitityNumber();
884: }
885:
886: private int getActivitityNumber() {
887: return Integer.parseInt(revision);
888: }
889:
890: UCMModification() {
891: super ("ucm");
892: }
893:
894: public Element toElement(DateFormat formatter) {
895: Element modificationElement = super .toElement(formatter);
896: Element crmtypeElement = new Element(TAGNAME_CRMTYPE);
897: crmtypeElement.addContent(crmtype);
898: modificationElement.addContent(crmtypeElement);
899: return modificationElement;
900: }
901:
902: }
903:
904: }
|