1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 package org.apache.tools.ant.taskdefs.optional.clearcase;
56
57 import org.apache.tools.ant.BuildException;
58 import org.apache.tools.ant.Project;
59 import org.apache.tools.ant.taskdefs.Echo;
60 import org.apache.tools.ant.taskdefs.Execute;
61 import org.apache.tools.ant.taskdefs.LoadFile;
62 import org.apache.tools.ant.taskdefs.PumpStreamHandler;
63 import org.apache.tools.ant.taskdefs.optional.clearcase.ClearCase;
64 import org.apache.tools.ant.types.Commandline;
65 import org.apache.tools.ant.util.FileUtils;
66 import org.w3c.dom.*;
67
68 import javax.xml.parsers.DocumentBuilderFactory;
69 import java.io.*;
70 import java.text.ParseException;
71 import java.text.SimpleDateFormat;
72 import java.util.Date;
73 import java.util.Random;
74 import java.util.StringTokenizer;
75 import java.util.Vector;
76
77 /***
78 * The aim of this task is to generate a ClearCase change log report under the XML format, just like
79 * <code>CVSChangeLog</code> does it.
80 * <p/>
81 * Has been tested under Windows NT V4.0 SP6 and ClearCase 2002.05.00.
82 * </p>
83 *
84 * @taskname CCChangeLog
85 * @author Edouard Mercier
86 * @version 1.0 : 2004.04.25
87 */
88 public class CCChangeLog
89 extends ClearCase
90 {
91
92 private String _from_date;
93
94 /***
95 * Sets the date the history begins from. The format is the one expected by the ClearCase <code>lshistory -since</code>
96 * option (in French, <code>01-mars-04</code> is OK for instance).
97 *
98 * @optional start_date if not set, the history is taken from the beginning of the source history
99 */
100 public void setFromDate(String start_date)
101 {
102 _from_date = start_date;
103 }
104
105 private String _from_label;
106
107 /***
108 * Enables to tell from which label the change log will be generated.
109 *
110 * @optional from_label if not set, the history is taken from the beginning of the source history
111 */
112 public void setFromLabel(String from_label)
113 {
114 _from_label = from_label;
115 }
116
117 private File _destination_file = new File("CCChangeLog.xml");
118
119 /***
120 * Sets the output file for the XML log.
121 */
122 public void setDestfile(File destination_file)
123 {
124 _destination_file = destination_file;
125 }
126
127 /***
128 * The file that will contain the log of the ClearCase command execution.
129 */
130 private File temporary_file;
131
132 /***
133 * Checks that the input parameters are OK.
134 *
135 * @throws BuildException if some parameters are not OK
136 */
137 private void checkParameters()
138 throws BuildException
139 {
140 }
141
142 /***
143 * The method that actually perform the ClearCase change history job.
144 *
145 * @throws BuildException if some unexpected error occured
146 */
147 public void execute()
148 throws BuildException
149 {
150 checkParameters();
151
152 File lstype_log_file = performLsType();
153 analyzeLsType(lstype_log_file);
154
155 File lshistory_log_file = performLsHistory();
156
157 temporary_file = FileUtils.newFileUtils().createTempFile("ccchangelog", ".xml", null);
158 temporary_file.deleteOnExit();
159
160
161 {
162 Echo echo = new Echo();
163 echo.setProject(getProject());
164 echo.setFile(temporary_file);
165 echo.setMessage("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><events>");
166 echo.execute();
167 }
168 String value = null;
169
170
171
172
173
174
175
176
177
178 {
179 value = "";
180 FileReader file_reader = null;
181 BufferedReader buffered_reader = null;
182 try
183 {
184 file_reader = new FileReader(lshistory_log_file);
185 buffered_reader = new BufferedReader(file_reader);
186 try
187 {
188 String line = null;
189 while ((line = buffered_reader.readLine()) != null)
190 {
191 value += line + "\n";
192 }
193 }
194 catch (IOException io_exception)
195 {
196 throw new BuildException("Internal error while reading file '" +
197 lshistory_log_file.getAbsolutePath() + "'", io_exception, location);
198 }
199 }
200 catch (FileNotFoundException file_not_found_exception)
201 {
202 throw new BuildException("Internal error while reading file '" +
203 lshistory_log_file.getAbsolutePath() + "'", file_not_found_exception, location);
204 }
205 finally
206 {
207 if (buffered_reader != null)
208 {
209 try
210 {
211 buffered_reader.close();
212 }
213 catch (IOException io_exception)
214 {
215
216 }
217 }
218 if (file_reader != null)
219 {
220 try
221 {
222 file_reader.close();
223 }
224 catch (IOException io_exception)
225 {
226
227 }
228 }
229 }
230 }
231 if (value == null)
232 {
233 throw new BuildException("There was an internal error while reading the temporary file '" +
234 lshistory_log_file.getAbsolutePath() + "'", location);
235 }
236 {
237 Echo echo = new Echo();
238 echo.setProject(getProject());
239 echo.setFile(temporary_file);
240 echo.setAppend(true);
241 echo.setMessage(value);
242 echo.execute();
243 }
244 {
245 Echo echo = new Echo();
246 echo.setProject(getProject());
247 echo.setFile(temporary_file);
248 echo.setAppend(true);
249 echo.setMessage("</events>");
250 echo.execute();
251 }
252
253
254 CCEntry[] entry_array = analyzeLsHistory(temporary_file);
255 dumpChangeLog(entry_array);
256 }
257
258 /***
259 * The 'lshistory' command
260 */
261 public static final String COMMAND_LSHISTORY = "lshistory";
262
263 /***
264 * The 'lstype' command
265 */
266 public static final String COMMAND_LSTYPE = "lstype";
267
268 /***
269 * The way ClearCase expresses the date format.
270 */
271 private final SimpleDateFormat clearCaseDateFormat = new SimpleDateFormat("yyyyMMdd.HHmmss");
272
273 /***
274 * Performs the call to 'lstype' and outputs the result in the provided file.
275 *
276 * @return the file into which the log of the CC command execution should be output
277 */
278 private File performLsType()
279 {
280 log("We perform a 'lstype' call in order to determine the existing labels", Project.MSG_VERBOSE);
281 File temporary_log_file = FileUtils.newFileUtils().createTempFile("ccchangelog_lstype", ".log", null);
282 temporary_log_file.deleteOnExit();
283 ArgumentProvider argument_provider = new ArgumentProvider()
284 {
285 public void provideAdditionalArguments(Commandline command_line)
286 {
287 command_line.createArgument().setLine("-kind lbtype");
288 }
289 };
290 performCCCommand(temporary_log_file, COMMAND_LSTYPE, argument_provider, "%Nd | %n\n");
291 return temporary_log_file;
292 }
293
294 private void analyzeLsType(File log_file)
295 {
296 log("We now analyze the result of the ClearCase '" + COMMAND_LSTYPE + "' call", Project.MSG_VERBOSE);
297 SimpleDateFormat clearcase_date_format = new SimpleDateFormat("dd-MMMM-yy");
298 try
299 {
300 FileReader file_reader = new FileReader(log_file);
301 BufferedReader buffered_reader = new BufferedReader(file_reader);
302 try
303 {
304 String line = null;
305 while ((line = buffered_reader.readLine()) != null)
306 {
307 StringTokenizer tokenizer = new StringTokenizer(line, "|");
308 String timestamp = ((String) tokenizer.nextElement()).trim();
309 Date date = null;
310 try
311 {
312 date = clearCaseDateFormat.parse(timestamp);
313 }
314 catch (ParseException parse_exception)
315 {
316 log(parse_exception.getMessage(), Project.MSG_ERR);
317 throw new BuildException("Parsing of the ClearCase date '" + timestamp +
318 "' failed during the 'lstype' command log analyzis", parse_exception, location);
319 }
320 String label = ((String) tokenizer.nextElement()).trim();
321 log("Found the label '" + label + "' created '" + date + "'", Project.MSG_VERBOSE);
322 if (label.equals(_from_label) == true)
323 {
324 _from_date = clearcase_date_format.format(date);
325 log("Found the date of the label '" + label + "' and its creation date is '" + _from_date +
326 "': this start date will be used", Project.MSG_INFO);
327 }
328 }
329 }
330 catch (IOException io_exception)
331 {
332 throw new BuildException("Could not analyze the log of the ClearCase command", io_exception, location);
333 }
334 }
335 catch (FileNotFoundException file_not_found_exception)
336 {
337
338 }
339 }
340
341 /***
342 * The XML element that is used internally.
343 */
344 private final String XML_EVENT = "event";
345
346 /***
347 * Performs the call to 'lshistory' and outputs the result in the provided file.
348 *
349 * @return the file into which the log of the CC command execution should be output
350 */
351 private File performLsHistory()
352 {
353 log("We perform a 'lshistory' call in order to be able to build the ClearCase change log", Project.MSG_VERBOSE);
354 File temporary_log_file = FileUtils.newFileUtils().createTempFile("ccchangelog_lshistory", ".log", null);
355 temporary_log_file.deleteOnExit();
356 ArgumentProvider argument_provider = new ArgumentProvider()
357 {
358 public void provideAdditionalArguments(Commandline command_line)
359 {
360 command_line.createArgument().setValue("-minor");
361 command_line.createArgument().setValue("-recurse");
362 command_line.createArgument().setLine("-since " + _from_date);
363 command_line.createArgument().setValue(getViewPath());
364 }
365 };
366
367 String format = "<" + XML_EVENT + "><a>%a</a><c><![CDATA[%c]]></c><d>%Nd</d><e>%e</e><f>%f</f><h>%h</h><i>%e</i>" +
368 "<l>%l</l><m>%m</m><n>%n</n>" +
369 "<o>%o</o><p>%p</p><u>%u</u></" + XML_EVENT + ">\n";
370 performCCCommand(temporary_log_file, COMMAND_LSHISTORY, argument_provider, format);
371 return temporary_log_file;
372 }
373
374 /***
375 * This defines a ClearCase change event.
376 */
377 private static class CCEntry
378 {
379 Date timeStamp;
380 String user;
381 String comment;
382 int type = UNKNOWN;
383 String resource;
384 public String eventName;
385
386 final static int UNKNOWN = -1;
387 final static int CREATE_VERSION = 1;
388 final static int CREATE_BRANCH = 2;
389 final static int CREATE_CHECKOUT = 3;
390 final static int CREATE_LABEL = 4;
391 }
392
393 /***
394 * Analyzes the log file resulting from the CC 'lshistory' command execution,
395 * and a bit modified in order to be under the XML format.
396 *
397 * @param log_file
398 */
399 private CCEntry[] analyzeLsHistory(File log_file)
400 {
401 log("We now analyze the result of the ClearCase '" + COMMAND_LSHISTORY + "' call", Project.MSG_VERBOSE);
402 Vector entry_vector = new Vector();
403 Document document = null;
404 try
405 {
406 document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(log_file);
407 }
408 catch (Exception exception)
409 {
410 throw new BuildException("Cannot analyze the log of the ClearCase command", exception, location);
411 }
412 NodeList node_list = document.getDocumentElement().getElementsByTagName(XML_EVENT);
413 for (int index = 0; index < node_list.getLength(); index++)
414 {
415 Node node = node_list.item(index);
416 CCEntry entry = new CCEntry();
417 entry_vector.add(entry);
418 NodeList child_node_list = node.getChildNodes();
419 for (int child_index = 0; child_index < child_node_list.getLength(); child_index++)
420 {
421 Node child_node = child_node_list.item(child_index);
422 String value = null;
423 if (child_node.getChildNodes().item(0) != null)
424 {
425 if (child_node.getChildNodes().item(0).getNodeType() == Node.TEXT_NODE)
426 {
427 value = ((Text) child_node.getChildNodes().item(0)).getData();
428 }
429 else if (child_node.getChildNodes().item(0).getNodeType() == Node.CDATA_SECTION_NODE)
430 {
431 value = ((CDATASection) child_node.getChildNodes().item(0)).getData();
432 }
433 }
434 if (child_node.getNodeName().equals("c") == true)
435 {
436 entry.comment = value;
437
438 }
439 else if (child_node.getNodeName().equals("d"))
440 {
441 try
442 {
443 entry.timeStamp = clearCaseDateFormat.parse(value);
444 }
445 catch (ParseException parse_exception)
446 {
447 throw new BuildException("Parsing of a ClearCase event failed", parse_exception, location);
448 }
449 }
450 else if (child_node.getNodeName().equals("u") == true)
451 {
452 entry.user = value;
453 }
454 else if (child_node.getNodeName().equals("n") == true)
455 {
456 entry.resource = value;
457 }
458 else if (child_node.getNodeName().equals("e") == true)
459 {
460 entry.eventName = value;
461 }
462 }
463 }
464 CCEntry[] entry_array = new CCEntry[entry_vector.size()];
465 entry_vector.toArray(entry_array);
466 return entry_array;
467 }
468
469 /***
470 * Analyzes the log file resulting from the CC command execution.
471 * <p/>
472 * Has been tested under Windows NTV4.0 SP6 and ClearCase 2002.05.00.
473 * </p>
474 *
475 * @param log_file
476 */
477 private CCEntry[] analyzeLsHistoryOld(File log_file)
478 {
479 log("We now analyze the result of the ClearCase '" + COMMAND_LSHISTORY + "' call", Project.MSG_VERBOSE);
480 Vector entry_vector = new Vector();
481 try
482 {
483 FileReader file_reader = new FileReader(log_file);
484 BufferedReader buffered_reader = new BufferedReader(file_reader);
485 try
486 {
487 String line = buffered_reader.readLine();
488 while (line != null)
489 {
490 log("Line read:" + line, Project.MSG_DEBUG);
491 log("We first read the timestamp and the ClearCase user on the first line", Project.MSG_DEBUG);
492 StringTokenizer timestamp_tokenizer = new StringTokenizer(line, " ");
493 String timestamp = (String) timestamp_tokenizer.nextElement();
494 Date date = null;
495 try
496 {
497 date = clearCaseDateFormat.parse(timestamp);
498 }
499 catch (ParseException parse_exception)
500 {
501 throw new BuildException("Parsing of a ClearCase event failed", parse_exception, location);
502 }
503 CCEntry entry = new CCEntry();
504 entry.timeStamp = date;
505 entry_vector.add(entry);
506 String user = line.substring(timestamp.length()).trim();
507 entry.user = user;
508 log("Then, the second line displays the ClearCase action", Project.MSG_DEBUG);
509 line = buffered_reader.readLine();
510 if (isNewEvent(line))
511 {
512 continue;
513 }
514
515 if (line.indexOf("create version") != -1 || line.indexOf("create directory version") != -1)
516 {
517 entry.type = CCEntry.CREATE_VERSION;
518 }
519 else if (line.indexOf("create branch") != -1)
520 {
521 entry.type = CCEntry.CREATE_BRANCH;
522 }
523 else if (line.indexOf("checkout version") != -1)
524 {
525 entry.type = CCEntry.CREATE_CHECKOUT;
526 }
527 else if (line.indexOf("make label") != -1)
528 {
529 entry.type = CCEntry.CREATE_LABEL;
530 }
531
532 if (entry.type != CCEntry.UNKNOWN)
533 {
534 StringTokenizer resource_tokenizer = new StringTokenizer(line, "\"");
535 resource_tokenizer.nextElement();
536 entry.resource = (String) resource_tokenizer.nextElement();
537 }
538 line = buffered_reader.readLine().trim();
539 if (isNewEvent(line))
540 {
541 continue;
542 }
543 if (line.startsWith("\"") == true)
544 {
545 log("We have reached the ClearCase comment", Project.MSG_DEBUG);
546 String comment = "";
547 line = line.substring(1);
548 boolean add_cr = false;
549 while (true)
550 {
551 if (line.endsWith("\"") == true)
552 {
553 comment += ((add_cr == true) ? "\n" : "") + line.substring(0, line.length() - 1);
554 break;
555 }
556 comment += ((add_cr == true) ? "\n" : "") + line;
557 add_cr = true;
558 line = buffered_reader.readLine().trim();
559 }
560 entry.comment = comment;
561 }
562
563 line = buffered_reader.readLine();
564 }
565 }
566 catch (IOException io_exception)
567 {
568 throw new BuildException("Could not analyze the log of the ClearCase command", io_exception, location);
569 }
570 }
571 catch (FileNotFoundException file_not_found_exception)
572 {
573
574 }
575 CCEntry[] entry_array = new CCEntry[entry_vector.size()];
576 entry_vector.toArray(entry_array);
577 return entry_array;
578 }
579
580 /***
581 * @param line the raw line to analyze
582 * @return whether the given raw line corresponds to a new ClearCase event within the log file
583 */
584 private boolean isNewEvent(String line)
585 {
586 StringTokenizer timestamp_tokenizer = new StringTokenizer(line, " ");
587 try
588 {
589 clearCaseDateFormat.parse((String) timestamp_tokenizer.nextElement());
590 }
591 catch (ParseException parse_exception)
592 {
593
594 return false;
595 }
596 return true;
597 }
598
599 /***
600 * Dumps into a file the result of the ClearCase change log analyzis.
601 *
602 * @param entry_array all ClearCase change events
603 */
604 private void dumpChangeLog(CCEntry[] entry_array)
605 throws BuildException
606 {
607 log("We dump the ClearCase change log", Project.MSG_VERBOSE);
608 FileOutputStream output = null;
609 try
610 {
611 output = new FileOutputStream(_destination_file);
612 PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, "ISO-8859-1"));
613 writer.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
614 writer.println("<changelog>");
615 SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd");
616 SimpleDateFormat time_format = new SimpleDateFormat("HH:mm");
617 for (int index = 0; index < entry_array.length; index++)
618 {
619 CCEntry entry = entry_array[index];
620 log("Handling the ClearCase event performed '" + entry.timeStamp + "' by '" + entry.user + "'", Project.MSG_VERBOSE);
621 writer.println("\t<entry>");
622 writer.println("\t\t<event>" + entry.eventName + "</event>");
623 if (entry.timeStamp != null)
624 {
625 writer.println("\t\t<date>" + date_format.format(entry.timeStamp) + "</date>");
626 writer.println("\t\t<time>" + time_format.format(entry.timeStamp) + "</time>");
627 }
628 writer.println("\t\t<author>" + entry.user + "</author>");
629 if (entry.resource != null)
630 {
631 writer.println("\t\t<file>");
632 writer.println("\t\t\t<name>" + entry.resource + "</name>");
633 writer.println("\t\t</file>");
634 }
635 if (entry.comment != null)
636 {
637 writer.println("\t\t<msg><![CDATA[" + entry.comment + "]]></msg>");
638 }
639 writer.println("\t</entry>");
640 }
641 writer.println("</changelog>");
642 writer.flush();
643 writer.close();
644 }
645 catch (final UnsupportedEncodingException unsupported_encoding_exception)
646 {
647 throw new BuildException("Does not support the encoding", unsupported_encoding_exception, location);
648 }
649 catch (final IOException io_exception)
650 {
651 throw new BuildException("Error while generating report", io_exception, location);
652 }
653 finally
654 {
655 if (output != null)
656 {
657 try
658 {
659 output.close();
660 }
661 catch (final IOException io_exception)
662 {
663
664 }
665 }
666 }
667 }
668
669 /***
670 * An interface designed in order to provide additional arguments to the ClearCase command execution.
671 */
672 interface ArgumentProvider
673 {
674 /***
675 * Enables to provide additional arguments to the provided command-line.
676 *
677 * @param command_line the command line additional arguments will be added to
678 */
679 void provideAdditionalArguments(Commandline command_line);
680 }
681
682 private void performCCCommand(File log_file, String clearcase_command, ArgumentProvider argument_provider, String format)
683 throws BuildException
684 {
685
686 Commandline command_line = new Commandline();
687
688 if (getViewPath() == null)
689 {
690 setViewPath(getProject().getBaseDir().getPath());
691 }
692 command_line.setExecutable(getClearToolCommand());
693 command_line.createArgument().setValue(clearcase_command);
694
695 log("The format used in order to extract the information from ClearCase is '" + format + "'", Project.MSG_VERBOSE);
696 command_line.createArgument().setLine("-fmt \"" + format + "\"");
697
698 argument_provider.provideAdditionalArguments(command_line);
699
700 int result = run(command_line, log_file);
701 if (result == Execute.INVALID || result != 0)
702 {
703 String msg = "Failed executing: " + command_line.toString();
704 throw new BuildException(msg, location);
705 }
706 }
707
708 /***
709 * Runs the ClearCase command via the provided command line, and outputs the log into the provided log file.
710 */
711 protected int run(Commandline command_line, File log_file)
712 throws BuildException
713 {
714 try
715 {
716 Project project = getProject();
717 final FileOutputStream output = new FileOutputStream(log_file.getPath(), false);
718
719
720 Execute execute = new Execute(new PumpStreamHandler(output, output));
721 execute.setAntRun(project);
722 String execution_path = getViewPath();
723 if (execution_path == null)
724 {
725 execution_path = project.getBaseDir().getPath();
726 }
727 execute.setWorkingDirectory(new File(execution_path));
728 execute.setCommandline(command_line.getCommandline());
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749 return execute.execute();
750 }
751 catch (java.io.IOException io_exception)
752 {
753 throw new BuildException(io_exception, location);
754 }
755 }
756
757 }