You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@maven.apache.org by "Brett Porter (JIRA)" <ji...@codehaus.org> on 2005/05/18 09:39:35 UTC

[jira] Updated: (MPCHANGELOG-17) ChangeLog efficiency patch

     [ http://jira.codehaus.org/browse/MPCHANGELOG-17?page=all ]

Brett Porter updated MPCHANGELOG-17:
------------------------------------

    Fix Version: 1.8
    Description: 
The following patch is intended to speed up the generation of changelog reports, by introducing
two optional new features: 

o the re-use of an existing changelog.xml file if one exists from a previous run
o the use of compression for cvs network traffic (makes a marked difference when bandwidth is limited).

They are enabled by the properties "maven.changelog.incremental" and "maven.changelog.compression"
respectively. Both have been defaulted to "false" for the time being, though it would probably
be worth enabling compression in most cases.

If the incremental flag is set, a previous changelog.xml will be loaded and parsed into a list
of entries. These will be reused and the range of dates requested from cvs will be reduced based on
the last date found in the file. Note that if maven.changelog.range has been changed to a larger value
than the one used to generate the cached file, it isn't possible to detect this. So if you want to extend the
range back in time, then the cached file should be removed (e.g. by 'maven clean').

ChangeLog has been extended to add the functionality for parsing the xml file, retrieving the entries
from it and for combining the old and newly downloaded entries.

ChangeLogEntry has been changed to implement the Comparable interface. An equals and hashCode method have
been added. CvsChangeLogParser has been modified to make use of these methods, rather than using a differently
formatted key for map entries. 

CvsChangeLogGenerator optionally adds the "-z3" parameter to the command line if compression is enabled.





Index: plugin.jelly
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.jelly,v
retrieving revision 1.8
diff -u -w -r1.8 plugin.jelly
--- plugin.jelly  8 Jul 2003 11:01:07 -0000  1.8
+++ plugin.jelly  11 Jul 2003 17:56:09 -0000
@@ -58,6 +58,8 @@
             range="${maven.changelog.range}"
             repositoryConnection="${pom.repository.connection}"
             dateFormat="${maven.changelog.dateformat}"
+            incremental="${maven.changelog.incremental}"
+            useCompression="${maven.changelog.compression}"
             />
 
           <doc:jsl
Index: plugin.properties
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.properties,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 plugin.properties
--- plugin.properties   24 Jan 2003 03:44:50 -0000 1.1.1.1
+++ plugin.properties   11 Jul 2003 17:56:09 -0000
@@ -10,4 +10,6 @@
 maven.docs.outputencoding = ISO-8859-1
 
 maven.changelog.range = 30
+maven.changelog.incremental = false
+maven.changelog.compression = false
 maven.changelog.factory = org.apache.maven.cvslib.CvsChangeLogFactory
Index: project.xml
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/project.xml,v
retrieving revision 1.12
diff -u -w -r1.12 project.xml
--- project.xml   1 Jul 2003 10:05:46 -0000  1.12
+++ project.xml   11 Jul 2003 17:56:09 -0000
@@ -57,6 +57,10 @@
   </developers>
   <dependencies>
     <dependency>
+      <id>dom4j</id>
+      <version>1.4</version>
+    </dependency>
+    <dependency>
       <id>ant</id>
       <version>1.5.1</version>
       <properties>
Index: src/main/org/apache/maven/changelog/ChangeLog.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLog.java,v
retrieving revision 1.4
diff -u -w -r1.4 ChangeLog.java
--- src/main/org/apache/maven/changelog/ChangeLog.java   27 Feb 2003 10:30:33 -0000 1.4
+++ src/main/org/apache/maven/changelog/ChangeLog.java   11 Jul 2003 17:56:10 -0000
@@ -60,21 +60,36 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
+import java.text.ParseException;
 // commons imports
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 // maven imports
 import org.apache.maven.project.Developer;
 
+// dom4j, for parsing existing changelog.xml
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+
 /**
  * Change log task. It uses a ChangeLogGenerator and ChangeLogParser to create
  * a Collection of ChangeLogEntry objects, which are used to produce an XML
@@ -134,6 +149,15 @@
     private String outputEncoding;
 
     /**
+     * flag to indicate whether an attempt should be made to reuse existing changelog data
+     * (usually target/changelog.xml) rather than querying the server for the full log.
+     */
+    private boolean incremental = false;
+
+    /** flag to indicate that the compression should be used for network traffic if supported (e.g. cvs -z3) */
+    private boolean useCompression = false;
+
+    /**
      * Set the ChangeLogFactory class name.  If this isn't set, the factory
      * defaults to Maven's build in CVS factory.
      *
@@ -236,6 +260,34 @@
     }
     
     /**
+     * Returns the value of the incremental flag
+    * @return whether incremental changelog will be generated.
+     */
+    public boolean getIncremental()
+    {
+        return incremental;
+    }
+
+    /**
+     * Set the incremental flag. If true then an existing
+     * @param incremental set to true if existing data should be used.
+     */
+    public void setIncremental(boolean incremental)
+    {
+        this.incremental = incremental;
+    }
+
+    public boolean getUseCompression()
+    {
+        return useCompression;
+    }
+
+    public void setUseCompression(boolean flag)
+    {
+        useCompression = flag;
+    }
+
+    /**
      * Execute task.
      * @throws FileNotFoundException if {@link ChangeLog#base} doesn't exist
      * @throws IOException if there are problems running CVS
@@ -249,13 +301,99 @@
         {
             throw new NullPointerException("output must be set");
         }
+        LOG.debug("Output file is " + output.getName() + " exists = "+ output.exists());
 
+        if(incremental && output.exists())
+        {
+            loadCachedEntries();
+        }
         generateEntries();
-        replaceAuthorIdWithName();
         createDocument();
     }
    
     /**
+     * Loads the existing entries which lie within the range setting.
+     */
+    private void loadCachedEntries()
+    {
+        if(range == null || range == null || range.length() == 0)
+        {  // bail out: this should only happen if date ranges aren't supported by the VCS.
+            LOG.warn("incremental flag set for changelog, but no range specified");
+            return;
+        }
+        try
+        {
+            // assume the range is a number of days in the log (can it be anything else? L.T.)
+            int nDays = Integer.parseInt(range);
+            Calendar earliest = Calendar.getInstance();
+            earliest.clear(Calendar.HOUR_OF_DAY);
+            earliest.clear(Calendar.MINUTE);
+            earliest.add(Calendar.DATE, -nDays);  // nDays ago
+            FileInputStream in = new FileInputStream(output);
+            List cachedEntries = parseXMLEntries(in, earliest);
+            in.close();
+            if(cachedEntries.size() > 0)
+            {
+                Calendar last = Calendar.getInstance();
+                Calendar to = (Calendar) last.clone();
+                last.setTime(((ChangeLogEntry)cachedEntries.get(0)).getDate());
+                to.add(Calendar.HOUR_OF_DAY, 24);
+                // recalculate the number of days needed based on the last entry in the cached log data
+                for(nDays = 0; last.before(to); nDays++) // increment until equal to "to" date
+                {
+                    last.add(Calendar.DATE, 1);
+                }
+                range = Integer.toString(nDays);
+                LOG.info("Cached data will be used; resetting range to " + range + " days" );
+                setEntries(cachedEntries);
+            }
+        }
+        catch(Exception e)
+        {
+          LOG.warn("Exception reading existing file " + output.getName() + ": "+ e + ". Full changelog will be generated");
+        }
+    }
+
+    /**
+     * Parses the XML representation of the changelog entries.
+     * @param in the stream to read from
+     * @param earliest the start of the range. Anything prior to this will be ignored.
+     * @return a list of ChangeLogEnty objects, with the latest entry at the start.
+     * @throws DocumentException
+     * @throws ParseException
+     */
+    private List parseXMLEntries(InputStream in, Calendar earliest) throws DocumentException, ParseException
+    {
+        SAXReader reader = new SAXReader();
+
+        Document xml = reader.read( new InputStreamReader(in) );
+        Element root = xml.getRootElement();
+        Iterator eltIterator = root.elementIterator();
+        List cachedEntries = new ArrayList();
+        while(eltIterator.hasNext())
+        {
+            Element entryElt = (Element)eltIterator.next();
+            ChangeLogEntry entry = new ChangeLogEntry( entryElt.element("date").getText(),
+                entryElt.element("time").getText(),
+                entryElt.element("author").getText(),
+                entryElt.element("msg").getText() );
+            if(entry.getDate().before(earliest.getTime()))
+                continue;
+            Iterator fileEltIterator = entryElt.elementIterator("file");
+            while(fileEltIterator.hasNext())
+            {
+                Element fileElt = (Element)fileEltIterator.next();
+                ChangeLogFile clFile = new ChangeLogFile( fileElt.element("name").getText(),
+                        fileElt.element("revision").getText());
+                entry.addFile(clFile);
+            }
+            cachedEntries.add(entry);
+        }
+        LOG.info("Loaded " + cachedEntries.size() + " cached changelog entries.");
+        return cachedEntries;
+    }
+
+    /**
      * Create the change log entries.
      * @throws IOException if there is a problem creating the change log
      * entries.
@@ -271,11 +409,21 @@
 
         try
         {
-            setEntries(generator.getEntries(parser));
-            if (LOG.isInfoEnabled()) {
-                LOG.info("ChangeLog found: " + getEntries().size() 
-                    + " entries");
+            Collection newEntries = generator.getEntries(parser);
+            // The name substitution must be done here, as it will already
+            // have been applied to any cached entries and the author names
+            // must match for successful equality tests during the merging of
+            // cached and newly downloaded entries.
+            replaceAuthorIdWithName(newEntries);
+            if(incremental)
+            {
+                mergeNewEntries(newEntries);
             }
+            else
+            {
+                setEntries(newEntries);
+            }
+            LOG.info("ChangeLog found: " + getEntries().size() + " entries");
         } catch (IOException e) {
             LOG.warn(e.getLocalizedMessage(), e);
             throw e;
@@ -288,6 +436,35 @@
     }
 
     /**
+     * Merges newly downloaded changelog entries with
+     * previously the previously cached list. The latter will already
+     * be stored in the 'entries' member variable.
+     * @param newEntries
+     */
+    private void mergeNewEntries(Collection newEntries)
+    {
+        if(getEntries().isEmpty()) // there may have been a problem while loading the existing file
+        {
+            setEntries(newEntries);
+        }
+        else
+        {
+        // Build a set of the existing entries and add the elements from newEntries - the Set should take care
+        // of any duplicates.
+            SortedSet entrySet = new TreeSet(Collections.reverseOrder());
+            entrySet.addAll(getEntries());
+            Iterator newEntryIterator = newEntries.iterator();
+            while( newEntryIterator.hasNext() )
+            {
+                ChangeLogEntry cle = (ChangeLogEntry)newEntryIterator.next();
+                entrySet.add(cle);
+            }
+            setEntries(entrySet);
+        }
+    }
+
+
+    /**
      * Create a new instance of the ChangeLogFactory specified by the
      * <code>clFactory</code> member.
      *
@@ -339,12 +516,13 @@
 
     /**
      * replace all known author's id's with their maven specified names
+     * @param entrySet the set of entries to apply perform the substitution on.
      */
-    private void replaceAuthorIdWithName()
+    private void replaceAuthorIdWithName(Collection entrySet)
     {
         Properties userList = getUserList();
         ChangeLogEntry entry = null;
-        for (Iterator i = getEntries().iterator(); i.hasNext();)
+        for (Iterator i = entrySet.iterator(); i.hasNext();)
         {
             entry = (ChangeLogEntry) i.next();
             if (userList.containsKey(entry.getAuthor()))
Index: src/main/org/apache/maven/changelog/ChangeLogEntry.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLogEntry.java,v
retrieving revision 1.2
diff -u -w -r1.2 ChangeLogEntry.java
--- src/main/org/apache/maven/changelog/ChangeLogEntry.java 4 Jul 2003 16:24:46 -0000  1.2
+++ src/main/org/apache/maven/changelog/ChangeLogEntry.java 11 Jul 2003 17:56:10 -0000
@@ -70,7 +70,7 @@
  * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
  * @version $Id: ChangeLogEntry.java,v 1.2 2003/07/04 16:24:46 evenisse Exp $
  */
-public class ChangeLogEntry
+public class ChangeLogEntry implements Comparable
 {
     /**
      * Escaped <code>&lt;</code> entity
@@ -110,12 +110,18 @@
         new SimpleDateFormat("HH:mm:ss");
 
     /**
+     * Formatter used by the constructor which takes
+     * date and time arguments matching the two formats above.
+     */
+    private static final SimpleDateFormat DATE_TIME_FORMAT =
+        new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
+    /**
      * Formatter used to parse CVS date/timestamp.
      */
     private static final SimpleDateFormat CVS_TIMESTAMP_FORMAT =
         new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
 
-    /** Date the changes were committed */
+    /** Date the changes were committed (containing both date and time information) */
     private Date date;
     
     /** User who made changes */
@@ -142,6 +148,22 @@
     }
 
     /**
+     * Creates an entry from previously formatted date and time parameters.
+     * (e.g. from an output XML file).
+     * @param date an absolute date value (yyyy-MM-dd)
+     * @param time the time of day within that date (HH:mm:ss)
+     * @param author author's name
+     * @param comment the commit message for the entry
+     */
+    public ChangeLogEntry(String date, String time, String author, String comment) throws ParseException
+    {
+        Date dateTime = DATE_TIME_FORMAT.parse(date.trim() + time.trim());
+        setDate(dateTime);
+        setAuthor(author);
+        setComment(comment);
+    }
+
+    /**
      * Constructor used when attributes aren't available until later
      */
     public ChangeLogEntry() 
@@ -285,7 +307,6 @@
             throw new IllegalArgumentException("I don't understand this date: "
                 + date);
         }
-
     }
     
     /**
@@ -345,5 +366,46 @@
             } 
         } 
         return buffer.toString(); 
+    }
+
+    /**
+     * Compares for equality based on date, author and message.
+     * Note that the changelog XML file which is written by the plugin
+     * has the user Ids converted to names, so a direct comparison with
+     * the server log entries will fail.
+     *
+     * @param o the object to compare
+     * @return true if the objects have the same date/time author and message.
+     */
+    public boolean equals(Object o)
+    {
+        if(!(o instanceof ChangeLogEntry))
+            return false;
+        return (compareTo(o) == 0);
+    }
+
+    /**
+     * Implementation of {@link Comparable} interface.
+     * Uses {@link String} representations to do the comparison.
+     */
+    public int compareTo(Object o)
+    {
+        ChangeLogEntry other =(ChangeLogEntry)o;
+        return encodeAsString().compareTo(other.encodeAsString());
+    }
+
+    public int hashCode()
+    {
+        return encodeAsString().hashCode();
+    }
+
+    /**
+     * Convenience method for creating a string rep for hashcodes, comparators etc.
+     * (toString has already been written but is probably overkill for this).
+     * @return unique String representation of this object.
+     */
+    private String encodeAsString()
+    {
+        return DATE_TIME_FORMAT.format(getDate()) + getAuthor() + getComment();
     }
 }
Index: src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java,v
retrieving revision 1.6
diff -u -w -r1.6 CvsChangeLogGenerator.java
--- src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Apr 2003 18:53:19 -0000 1.6
+++ src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Jul 2003 17:56:10 -0000
@@ -113,6 +113,11 @@
         Commandline command = new Commandline();
 
         command.setExecutable("cvs");
+        // null check is needed as super.init(ChangeLog) may not have been called (as in the ExposeGenerator test class)
+        if(changeLogExecutor != null && changeLogExecutor.getUseCompression())
+        {
+            command.createArgument().setValue("-z3");
+        }
         command.createArgument().setValue("-d");
         // from format:
         // scm:cvs:pserver:anoncvs@cvs.apache.org:/home/cvspublic:jakarta-turbine-maven/src/plugins-build/changelog/
@@ -181,10 +186,7 @@
         if (ioe.getMessage().indexOf("CreateProcess") != -1 || ioe.getMessage().indexOf("cvs: not found") != -1)
         {
             // can't find CVS on Win32 or Linux...
-            if (LOG.isWarnEnabled())
-            {
                 LOG.warn("Unable to find cvs executable. " + "Changelog will be empty");
-            }
         }
         else
         {
Index: src/main/org/apache/maven/cvslib/CvsChangeLogParser.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogParser.java,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 CvsChangeLogParser.java
--- src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   24 Jan 2003 03:44:53 -0000 1.1.1.1
+++ src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   11 Jul 2003 17:56:11 -0000
@@ -81,13 +81,6 @@
 class CvsChangeLogParser implements ChangeLogParser
 {
     /**
-     * Custom date/time formatter.  Rounds ChangeLogEntry times to the nearest
-     * minute.
-     */
-    private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT = 
-        new SimpleDateFormat("yyyyMMddHHmm");
-    
-    /**
      * rcs entries, in reverse (date, time, author, comment) order
      */
     private Map entries = new TreeMap(Collections.reverseOrder());
@@ -203,17 +196,14 @@
             return;
         }
         
-        String key = ENTRY_KEY_TIMESTAMP_FORMAT.format(entry.getDate())
-            + entry.getAuthor() + entry.getComment();
-        
-        if (!entries.containsKey(key))
+        if (!entries.containsKey(entry))
         {
             entry.addFile(file);
-            entries.put(key, entry);
+            entries.put(entry, entry);
         }
         else
         {
-            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(key);
+            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(entry);
             existingEntry.addFile(file);
         }
     }
Index: src/test/org/apache/maven/changelog/ChangeLogEntryTest.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/changelog/ChangeLogEntryTest.java,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 ChangeLogEntryTest.java
--- src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   24 Jan 2003 03:44:56 -0000 1.1.1.1
+++ src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   11 Jul 2003 17:56:11 -0000
@@ -241,7 +241,6 @@
             cal.getTime(), instance.getDate());
     }
 
-    
     /** 
      * Test of getDateFormatted method
      */
@@ -260,6 +259,25 @@
             instance.getTimeFormatted());
     }
 
+    /**
+     * Tests the constructor which takes ready-formatted date and time Strings.
+     * Then checks for equality with the original instance.
+     * @throws Exception java.text.ParseException if formats are incorrect
+     */
+    public void testDateTimeConstructorAndEquals() throws Exception
+    {
+        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
+        assertEquals("Instances not equal with same data,", instance, instance2 );
+    }
+
+
+    public void testComparable() throws Exception
+    {
+        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
+        assertTrue("Comparator failed", instance2.compareTo(instance) == 0);
+        instance2 = new ChangeLogEntry("2002-04-01", "00:00:05", "dion", "comment");
+        assertTrue("Comparator failed", instance2.compareTo(instance) > 0);
+    }
     // Add test methods here, they have to start with 'test' name.
     // for example:
     // public void testHello() {}
Index: src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java,v
retrieving revision 1.5
diff -u -w -r1.5 CvsChangeLogGeneratorTest.java

  was:
The following patch is intended to speed up the generation of changelog reports, by introducing
two optional new features: 

o the re-use of an existing changelog.xml file if one exists from a previous run
o the use of compression for cvs network traffic (makes a marked difference when bandwidth is limited).

They are enabled by the properties "maven.changelog.incremental" and "maven.changelog.compression"
respectively. Both have been defaulted to "false" for the time being, though it would probably
be worth enabling compression in most cases.

If the incremental flag is set, a previous changelog.xml will be loaded and parsed into a list
of entries. These will be reused and the range of dates requested from cvs will be reduced based on
the last date found in the file. Note that if maven.changelog.range has been changed to a larger value
than the one used to generate the cached file, it isn't possible to detect this. So if you want to extend the
range back in time, then the cached file should be removed (e.g. by 'maven clean').

ChangeLog has been extended to add the functionality for parsing the xml file, retrieving the entries
from it and for combining the old and newly downloaded entries.

ChangeLogEntry has been changed to implement the Comparable interface. An equals and hashCode method have
been added. CvsChangeLogParser has been modified to make use of these methods, rather than using a differently
formatted key for map entries. 

CvsChangeLogGenerator optionally adds the "-z3" parameter to the command line if compression is enabled.





Index: plugin.jelly
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.jelly,v
retrieving revision 1.8
diff -u -w -r1.8 plugin.jelly
--- plugin.jelly  8 Jul 2003 11:01:07 -0000  1.8
+++ plugin.jelly  11 Jul 2003 17:56:09 -0000
@@ -58,6 +58,8 @@
             range="${maven.changelog.range}"
             repositoryConnection="${pom.repository.connection}"
             dateFormat="${maven.changelog.dateformat}"
+            incremental="${maven.changelog.incremental}"
+            useCompression="${maven.changelog.compression}"
             />
 
           <doc:jsl
Index: plugin.properties
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.properties,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 plugin.properties
--- plugin.properties   24 Jan 2003 03:44:50 -0000 1.1.1.1
+++ plugin.properties   11 Jul 2003 17:56:09 -0000
@@ -10,4 +10,6 @@
 maven.docs.outputencoding = ISO-8859-1
 
 maven.changelog.range = 30
+maven.changelog.incremental = false
+maven.changelog.compression = false
 maven.changelog.factory = org.apache.maven.cvslib.CvsChangeLogFactory
Index: project.xml
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/project.xml,v
retrieving revision 1.12
diff -u -w -r1.12 project.xml
--- project.xml   1 Jul 2003 10:05:46 -0000  1.12
+++ project.xml   11 Jul 2003 17:56:09 -0000
@@ -57,6 +57,10 @@
   </developers>
   <dependencies>
     <dependency>
+      <id>dom4j</id>
+      <version>1.4</version>
+    </dependency>
+    <dependency>
       <id>ant</id>
       <version>1.5.1</version>
       <properties>
Index: src/main/org/apache/maven/changelog/ChangeLog.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLog.java,v
retrieving revision 1.4
diff -u -w -r1.4 ChangeLog.java
--- src/main/org/apache/maven/changelog/ChangeLog.java   27 Feb 2003 10:30:33 -0000 1.4
+++ src/main/org/apache/maven/changelog/ChangeLog.java   11 Jul 2003 17:56:10 -0000
@@ -60,21 +60,36 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
+import java.text.ParseException;
 // commons imports
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 // maven imports
 import org.apache.maven.project.Developer;
 
+// dom4j, for parsing existing changelog.xml
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.Element;
+import org.dom4j.io.SAXReader;
+
 /**
  * Change log task. It uses a ChangeLogGenerator and ChangeLogParser to create
  * a Collection of ChangeLogEntry objects, which are used to produce an XML
@@ -134,6 +149,15 @@
     private String outputEncoding;
 
     /**
+     * flag to indicate whether an attempt should be made to reuse existing changelog data
+     * (usually target/changelog.xml) rather than querying the server for the full log.
+     */
+    private boolean incremental = false;
+
+    /** flag to indicate that the compression should be used for network traffic if supported (e.g. cvs -z3) */
+    private boolean useCompression = false;
+
+    /**
      * Set the ChangeLogFactory class name.  If this isn't set, the factory
      * defaults to Maven's build in CVS factory.
      *
@@ -236,6 +260,34 @@
     }
     
     /**
+     * Returns the value of the incremental flag
+    * @return whether incremental changelog will be generated.
+     */
+    public boolean getIncremental()
+    {
+        return incremental;
+    }
+
+    /**
+     * Set the incremental flag. If true then an existing
+     * @param incremental set to true if existing data should be used.
+     */
+    public void setIncremental(boolean incremental)
+    {
+        this.incremental = incremental;
+    }
+
+    public boolean getUseCompression()
+    {
+        return useCompression;
+    }
+
+    public void setUseCompression(boolean flag)
+    {
+        useCompression = flag;
+    }
+
+    /**
      * Execute task.
      * @throws FileNotFoundException if {@link ChangeLog#base} doesn't exist
      * @throws IOException if there are problems running CVS
@@ -249,13 +301,99 @@
         {
             throw new NullPointerException("output must be set");
         }
+        LOG.debug("Output file is " + output.getName() + " exists = "+ output.exists());
 
+        if(incremental && output.exists())
+        {
+            loadCachedEntries();
+        }
         generateEntries();
-        replaceAuthorIdWithName();
         createDocument();
     }
    
     /**
+     * Loads the existing entries which lie within the range setting.
+     */
+    private void loadCachedEntries()
+    {
+        if(range == null || range == null || range.length() == 0)
+        {  // bail out: this should only happen if date ranges aren't supported by the VCS.
+            LOG.warn("incremental flag set for changelog, but no range specified");
+            return;
+        }
+        try
+        {
+            // assume the range is a number of days in the log (can it be anything else? L.T.)
+            int nDays = Integer.parseInt(range);
+            Calendar earliest = Calendar.getInstance();
+            earliest.clear(Calendar.HOUR_OF_DAY);
+            earliest.clear(Calendar.MINUTE);
+            earliest.add(Calendar.DATE, -nDays);  // nDays ago
+            FileInputStream in = new FileInputStream(output);
+            List cachedEntries = parseXMLEntries(in, earliest);
+            in.close();
+            if(cachedEntries.size() > 0)
+            {
+                Calendar last = Calendar.getInstance();
+                Calendar to = (Calendar) last.clone();
+                last.setTime(((ChangeLogEntry)cachedEntries.get(0)).getDate());
+                to.add(Calendar.HOUR_OF_DAY, 24);
+                // recalculate the number of days needed based on the last entry in the cached log data
+                for(nDays = 0; last.before(to); nDays++) // increment until equal to "to" date
+                {
+                    last.add(Calendar.DATE, 1);
+                }
+                range = Integer.toString(nDays);
+                LOG.info("Cached data will be used; resetting range to " + range + " days" );
+                setEntries(cachedEntries);
+            }
+        }
+        catch(Exception e)
+        {
+          LOG.warn("Exception reading existing file " + output.getName() + ": "+ e + ". Full changelog will be generated");
+        }
+    }
+
+    /**
+     * Parses the XML representation of the changelog entries.
+     * @param in the stream to read from
+     * @param earliest the start of the range. Anything prior to this will be ignored.
+     * @return a list of ChangeLogEnty objects, with the latest entry at the start.
+     * @throws DocumentException
+     * @throws ParseException
+     */
+    private List parseXMLEntries(InputStream in, Calendar earliest) throws DocumentException, ParseException
+    {
+        SAXReader reader = new SAXReader();
+
+        Document xml = reader.read( new InputStreamReader(in) );
+        Element root = xml.getRootElement();
+        Iterator eltIterator = root.elementIterator();
+        List cachedEntries = new ArrayList();
+        while(eltIterator.hasNext())
+        {
+            Element entryElt = (Element)eltIterator.next();
+            ChangeLogEntry entry = new ChangeLogEntry( entryElt.element("date").getText(),
+                entryElt.element("time").getText(),
+                entryElt.element("author").getText(),
+                entryElt.element("msg").getText() );
+            if(entry.getDate().before(earliest.getTime()))
+                continue;
+            Iterator fileEltIterator = entryElt.elementIterator("file");
+            while(fileEltIterator.hasNext())
+            {
+                Element fileElt = (Element)fileEltIterator.next();
+                ChangeLogFile clFile = new ChangeLogFile( fileElt.element("name").getText(),
+                        fileElt.element("revision").getText());
+                entry.addFile(clFile);
+            }
+            cachedEntries.add(entry);
+        }
+        LOG.info("Loaded " + cachedEntries.size() + " cached changelog entries.");
+        return cachedEntries;
+    }
+
+    /**
      * Create the change log entries.
      * @throws IOException if there is a problem creating the change log
      * entries.
@@ -271,11 +409,21 @@
 
         try
         {
-            setEntries(generator.getEntries(parser));
-            if (LOG.isInfoEnabled()) {
-                LOG.info("ChangeLog found: " + getEntries().size() 
-                    + " entries");
+            Collection newEntries = generator.getEntries(parser);
+            // The name substitution must be done here, as it will already
+            // have been applied to any cached entries and the author names
+            // must match for successful equality tests during the merging of
+            // cached and newly downloaded entries.
+            replaceAuthorIdWithName(newEntries);
+            if(incremental)
+            {
+                mergeNewEntries(newEntries);
             }
+            else
+            {
+                setEntries(newEntries);
+            }
+            LOG.info("ChangeLog found: " + getEntries().size() + " entries");
         } catch (IOException e) {
             LOG.warn(e.getLocalizedMessage(), e);
             throw e;
@@ -288,6 +436,35 @@
     }
 
     /**
+     * Merges newly downloaded changelog entries with
+     * previously the previously cached list. The latter will already
+     * be stored in the 'entries' member variable.
+     * @param newEntries
+     */
+    private void mergeNewEntries(Collection newEntries)
+    {
+        if(getEntries().isEmpty()) // there may have been a problem while loading the existing file
+        {
+            setEntries(newEntries);
+        }
+        else
+        {
+        // Build a set of the existing entries and add the elements from newEntries - the Set should take care
+        // of any duplicates.
+            SortedSet entrySet = new TreeSet(Collections.reverseOrder());
+            entrySet.addAll(getEntries());
+            Iterator newEntryIterator = newEntries.iterator();
+            while( newEntryIterator.hasNext() )
+            {
+                ChangeLogEntry cle = (ChangeLogEntry)newEntryIterator.next();
+                entrySet.add(cle);
+            }
+            setEntries(entrySet);
+        }
+    }
+
+
+    /**
      * Create a new instance of the ChangeLogFactory specified by the
      * <code>clFactory</code> member.
      *
@@ -339,12 +516,13 @@
 
     /**
      * replace all known author's id's with their maven specified names
+     * @param entrySet the set of entries to apply perform the substitution on.
      */
-    private void replaceAuthorIdWithName()
+    private void replaceAuthorIdWithName(Collection entrySet)
     {
         Properties userList = getUserList();
         ChangeLogEntry entry = null;
-        for (Iterator i = getEntries().iterator(); i.hasNext();)
+        for (Iterator i = entrySet.iterator(); i.hasNext();)
         {
             entry = (ChangeLogEntry) i.next();
             if (userList.containsKey(entry.getAuthor()))
Index: src/main/org/apache/maven/changelog/ChangeLogEntry.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLogEntry.java,v
retrieving revision 1.2
diff -u -w -r1.2 ChangeLogEntry.java
--- src/main/org/apache/maven/changelog/ChangeLogEntry.java 4 Jul 2003 16:24:46 -0000  1.2
+++ src/main/org/apache/maven/changelog/ChangeLogEntry.java 11 Jul 2003 17:56:10 -0000
@@ -70,7 +70,7 @@
  * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
  * @version $Id: ChangeLogEntry.java,v 1.2 2003/07/04 16:24:46 evenisse Exp $
  */
-public class ChangeLogEntry
+public class ChangeLogEntry implements Comparable
 {
     /**
      * Escaped <code>&lt;</code> entity
@@ -110,12 +110,18 @@
         new SimpleDateFormat("HH:mm:ss");
 
     /**
+     * Formatter used by the constructor which takes
+     * date and time arguments matching the two formats above.
+     */
+    private static final SimpleDateFormat DATE_TIME_FORMAT =
+        new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
+    /**
      * Formatter used to parse CVS date/timestamp.
      */
     private static final SimpleDateFormat CVS_TIMESTAMP_FORMAT =
         new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
 
-    /** Date the changes were committed */
+    /** Date the changes were committed (containing both date and time information) */
     private Date date;
     
     /** User who made changes */
@@ -142,6 +148,22 @@
     }
 
     /**
+     * Creates an entry from previously formatted date and time parameters.
+     * (e.g. from an output XML file).
+     * @param date an absolute date value (yyyy-MM-dd)
+     * @param time the time of day within that date (HH:mm:ss)
+     * @param author author's name
+     * @param comment the commit message for the entry
+     */
+    public ChangeLogEntry(String date, String time, String author, String comment) throws ParseException
+    {
+        Date dateTime = DATE_TIME_FORMAT.parse(date.trim() + time.trim());
+        setDate(dateTime);
+        setAuthor(author);
+        setComment(comment);
+    }
+
+    /**
      * Constructor used when attributes aren't available until later
      */
     public ChangeLogEntry() 
@@ -285,7 +307,6 @@
             throw new IllegalArgumentException("I don't understand this date: "
                 + date);
         }
-
     }
     
     /**
@@ -345,5 +366,46 @@
             } 
         } 
         return buffer.toString(); 
+    }
+
+    /**
+     * Compares for equality based on date, author and message.
+     * Note that the changelog XML file which is written by the plugin
+     * has the user Ids converted to names, so a direct comparison with
+     * the server log entries will fail.
+     *
+     * @param o the object to compare
+     * @return true if the objects have the same date/time author and message.
+     */
+    public boolean equals(Object o)
+    {
+        if(!(o instanceof ChangeLogEntry))
+            return false;
+        return (compareTo(o) == 0);
+    }
+
+    /**
+     * Implementation of {@link Comparable} interface.
+     * Uses {@link String} representations to do the comparison.
+     */
+    public int compareTo(Object o)
+    {
+        ChangeLogEntry other =(ChangeLogEntry)o;
+        return encodeAsString().compareTo(other.encodeAsString());
+    }
+
+    public int hashCode()
+    {
+        return encodeAsString().hashCode();
+    }
+
+    /**
+     * Convenience method for creating a string rep for hashcodes, comparators etc.
+     * (toString has already been written but is probably overkill for this).
+     * @return unique String representation of this object.
+     */
+    private String encodeAsString()
+    {
+        return DATE_TIME_FORMAT.format(getDate()) + getAuthor() + getComment();
     }
 }
Index: src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java,v
retrieving revision 1.6
diff -u -w -r1.6 CvsChangeLogGenerator.java
--- src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Apr 2003 18:53:19 -0000 1.6
+++ src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Jul 2003 17:56:10 -0000
@@ -113,6 +113,11 @@
         Commandline command = new Commandline();
 
         command.setExecutable("cvs");
+        // null check is needed as super.init(ChangeLog) may not have been called (as in the ExposeGenerator test class)
+        if(changeLogExecutor != null && changeLogExecutor.getUseCompression())
+        {
+            command.createArgument().setValue("-z3");
+        }
         command.createArgument().setValue("-d");
         // from format:
         // scm:cvs:pserver:anoncvs@cvs.apache.org:/home/cvspublic:jakarta-turbine-maven/src/plugins-build/changelog/
@@ -181,10 +186,7 @@
         if (ioe.getMessage().indexOf("CreateProcess") != -1 || ioe.getMessage().indexOf("cvs: not found") != -1)
         {
             // can't find CVS on Win32 or Linux...
-            if (LOG.isWarnEnabled())
-            {
                 LOG.warn("Unable to find cvs executable. " + "Changelog will be empty");
-            }
         }
         else
         {
Index: src/main/org/apache/maven/cvslib/CvsChangeLogParser.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogParser.java,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 CvsChangeLogParser.java
--- src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   24 Jan 2003 03:44:53 -0000 1.1.1.1
+++ src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   11 Jul 2003 17:56:11 -0000
@@ -81,13 +81,6 @@
 class CvsChangeLogParser implements ChangeLogParser
 {
     /**
-     * Custom date/time formatter.  Rounds ChangeLogEntry times to the nearest
-     * minute.
-     */
-    private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT = 
-        new SimpleDateFormat("yyyyMMddHHmm");
-    
-    /**
      * rcs entries, in reverse (date, time, author, comment) order
      */
     private Map entries = new TreeMap(Collections.reverseOrder());
@@ -203,17 +196,14 @@
             return;
         }
         
-        String key = ENTRY_KEY_TIMESTAMP_FORMAT.format(entry.getDate())
-            + entry.getAuthor() + entry.getComment();
-        
-        if (!entries.containsKey(key))
+        if (!entries.containsKey(entry))
         {
             entry.addFile(file);
-            entries.put(key, entry);
+            entries.put(entry, entry);
         }
         else
         {
-            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(key);
+            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(entry);
             existingEntry.addFile(file);
         }
     }
Index: src/test/org/apache/maven/changelog/ChangeLogEntryTest.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/changelog/ChangeLogEntryTest.java,v
retrieving revision 1.1.1.1
diff -u -w -r1.1.1.1 ChangeLogEntryTest.java
--- src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   24 Jan 2003 03:44:56 -0000 1.1.1.1
+++ src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   11 Jul 2003 17:56:11 -0000
@@ -241,7 +241,6 @@
             cal.getTime(), instance.getDate());
     }
 
-    
     /** 
      * Test of getDateFormatted method
      */
@@ -260,6 +259,25 @@
             instance.getTimeFormatted());
     }
 
+    /**
+     * Tests the constructor which takes ready-formatted date and time Strings.
+     * Then checks for equality with the original instance.
+     * @throws Exception java.text.ParseException if formats are incorrect
+     */
+    public void testDateTimeConstructorAndEquals() throws Exception
+    {
+        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
+        assertEquals("Instances not equal with same data,", instance, instance2 );
+    }
+
+
+    public void testComparable() throws Exception
+    {
+        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
+        assertTrue("Comparator failed", instance2.compareTo(instance) == 0);
+        instance2 = new ChangeLogEntry("2002-04-01", "00:00:05", "dion", "comment");
+        assertTrue("Comparator failed", instance2.compareTo(instance) > 0);
+    }
     // Add test methods here, they have to start with 'test' name.
     // for example:
     // public void testHello() {}
Index: src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java
===================================================================
RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java,v
retrieving revision 1.5
diff -u -w -r1.5 CvsChangeLogGeneratorTest.java


> ChangeLog efficiency patch
> --------------------------
>
>          Key: MPCHANGELOG-17
>          URL: http://jira.codehaus.org/browse/MPCHANGELOG-17
>      Project: maven-changelog-plugin
>         Type: Improvement
>  Environment: n/a
>     Reporter: Luke Taylor
>     Priority: Minor
>      Fix For: 1.8
>  Attachments: patch
>
>
> The following patch is intended to speed up the generation of changelog reports, by introducing
> two optional new features: 
> o the re-use of an existing changelog.xml file if one exists from a previous run
> o the use of compression for cvs network traffic (makes a marked difference when bandwidth is limited).
> They are enabled by the properties "maven.changelog.incremental" and "maven.changelog.compression"
> respectively. Both have been defaulted to "false" for the time being, though it would probably
> be worth enabling compression in most cases.
> If the incremental flag is set, a previous changelog.xml will be loaded and parsed into a list
> of entries. These will be reused and the range of dates requested from cvs will be reduced based on
> the last date found in the file. Note that if maven.changelog.range has been changed to a larger value
> than the one used to generate the cached file, it isn't possible to detect this. So if you want to extend the
> range back in time, then the cached file should be removed (e.g. by 'maven clean').
> ChangeLog has been extended to add the functionality for parsing the xml file, retrieving the entries
> from it and for combining the old and newly downloaded entries.
> ChangeLogEntry has been changed to implement the Comparable interface. An equals and hashCode method have
> been added. CvsChangeLogParser has been modified to make use of these methods, rather than using a differently
> formatted key for map entries. 
> CvsChangeLogGenerator optionally adds the "-z3" parameter to the command line if compression is enabled.
> Index: plugin.jelly
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.jelly,v
> retrieving revision 1.8
> diff -u -w -r1.8 plugin.jelly
> --- plugin.jelly  8 Jul 2003 11:01:07 -0000  1.8
> +++ plugin.jelly  11 Jul 2003 17:56:09 -0000
> @@ -58,6 +58,8 @@
>              range="${maven.changelog.range}"
>              repositoryConnection="${pom.repository.connection}"
>              dateFormat="${maven.changelog.dateformat}"
> +            incremental="${maven.changelog.incremental}"
> +            useCompression="${maven.changelog.compression}"
>              />
>  
>            <doc:jsl
> Index: plugin.properties
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/plugin.properties,v
> retrieving revision 1.1.1.1
> diff -u -w -r1.1.1.1 plugin.properties
> --- plugin.properties   24 Jan 2003 03:44:50 -0000 1.1.1.1
> +++ plugin.properties   11 Jul 2003 17:56:09 -0000
> @@ -10,4 +10,6 @@
>  maven.docs.outputencoding = ISO-8859-1
>  
>  maven.changelog.range = 30
> +maven.changelog.incremental = false
> +maven.changelog.compression = false
>  maven.changelog.factory = org.apache.maven.cvslib.CvsChangeLogFactory
> Index: project.xml
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/project.xml,v
> retrieving revision 1.12
> diff -u -w -r1.12 project.xml
> --- project.xml   1 Jul 2003 10:05:46 -0000  1.12
> +++ project.xml   11 Jul 2003 17:56:09 -0000
> @@ -57,6 +57,10 @@
>    </developers>
>    <dependencies>
>      <dependency>
> +      <id>dom4j</id>
> +      <version>1.4</version>
> +    </dependency>
> +    <dependency>
>        <id>ant</id>
>        <version>1.5.1</version>
>        <properties>
> Index: src/main/org/apache/maven/changelog/ChangeLog.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLog.java,v
> retrieving revision 1.4
> diff -u -w -r1.4 ChangeLog.java
> --- src/main/org/apache/maven/changelog/ChangeLog.java   27 Feb 2003 10:30:33 -0000 1.4
> +++ src/main/org/apache/maven/changelog/ChangeLog.java   11 Jul 2003 17:56:10 -0000
> @@ -60,21 +60,36 @@
>  import java.io.File;
>  import java.io.FileNotFoundException;
>  import java.io.FileOutputStream;
> +import java.io.FileInputStream;
> +import java.io.InputStream;
> +import java.io.InputStreamReader;
>  import java.io.IOException;
>  import java.io.OutputStreamWriter;
>  import java.io.PrintWriter;
>  import java.io.UnsupportedEncodingException;
>  import java.util.Arrays;
> +import java.util.ArrayList;
> +import java.util.Calendar;
>  import java.util.Collection;
> +import java.util.Collections;
> +import java.util.SortedSet;
> +import java.util.TreeSet;
>  import java.util.Iterator;
>  import java.util.List;
>  import java.util.Properties;
> +import java.text.ParseException;
>  // commons imports
>  import org.apache.commons.logging.Log;
>  import org.apache.commons.logging.LogFactory;
>  // maven imports
>  import org.apache.maven.project.Developer;
>  
> +// dom4j, for parsing existing changelog.xml
> +import org.dom4j.Document;
> +import org.dom4j.DocumentException;
> +import org.dom4j.Element;
> +import org.dom4j.io.SAXReader;
> +
>  /**
>   * Change log task. It uses a ChangeLogGenerator and ChangeLogParser to create
>   * a Collection of ChangeLogEntry objects, which are used to produce an XML
> @@ -134,6 +149,15 @@
>      private String outputEncoding;
>  
>      /**
> +     * flag to indicate whether an attempt should be made to reuse existing changelog data
> +     * (usually target/changelog.xml) rather than querying the server for the full log.
> +     */
> +    private boolean incremental = false;
> +
> +    /** flag to indicate that the compression should be used for network traffic if supported (e.g. cvs -z3) */
> +    private boolean useCompression = false;
> +
> +    /**
>       * Set the ChangeLogFactory class name.  If this isn't set, the factory
>       * defaults to Maven's build in CVS factory.
>       *
> @@ -236,6 +260,34 @@
>      }
>      
>      /**
> +     * Returns the value of the incremental flag
> +    * @return whether incremental changelog will be generated.
> +     */
> +    public boolean getIncremental()
> +    {
> +        return incremental;
> +    }
> +
> +    /**
> +     * Set the incremental flag. If true then an existing
> +     * @param incremental set to true if existing data should be used.
> +     */
> +    public void setIncremental(boolean incremental)
> +    {
> +        this.incremental = incremental;
> +    }
> +
> +    public boolean getUseCompression()
> +    {
> +        return useCompression;
> +    }
> +
> +    public void setUseCompression(boolean flag)
> +    {
> +        useCompression = flag;
> +    }
> +
> +    /**
>       * Execute task.
>       * @throws FileNotFoundException if {@link ChangeLog#base} doesn't exist
>       * @throws IOException if there are problems running CVS
> @@ -249,13 +301,99 @@
>          {
>              throw new NullPointerException("output must be set");
>          }
> +        LOG.debug("Output file is " + output.getName() + " exists = "+ output.exists());
>  
> +        if(incremental && output.exists())
> +        {
> +            loadCachedEntries();
> +        }
>          generateEntries();
> -        replaceAuthorIdWithName();
>          createDocument();
>      }
>     
>      /**
> +     * Loads the existing entries which lie within the range setting.
> +     */
> +    private void loadCachedEntries()
> +    {
> +        if(range == null || range == null || range.length() == 0)
> +        {  // bail out: this should only happen if date ranges aren't supported by the VCS.
> +            LOG.warn("incremental flag set for changelog, but no range specified");
> +            return;
> +        }
> +        try
> +        {
> +            // assume the range is a number of days in the log (can it be anything else? L.T.)
> +            int nDays = Integer.parseInt(range);
> +            Calendar earliest = Calendar.getInstance();
> +            earliest.clear(Calendar.HOUR_OF_DAY);
> +            earliest.clear(Calendar.MINUTE);
> +            earliest.add(Calendar.DATE, -nDays);  // nDays ago
> +            FileInputStream in = new FileInputStream(output);
> +            List cachedEntries = parseXMLEntries(in, earliest);
> +            in.close();
> +            if(cachedEntries.size() > 0)
> +            {
> +                Calendar last = Calendar.getInstance();
> +                Calendar to = (Calendar) last.clone();
> +                last.setTime(((ChangeLogEntry)cachedEntries.get(0)).getDate());
> +                to.add(Calendar.HOUR_OF_DAY, 24);
> +                // recalculate the number of days needed based on the last entry in the cached log data
> +                for(nDays = 0; last.before(to); nDays++) // increment until equal to "to" date
> +                {
> +                    last.add(Calendar.DATE, 1);
> +                }
> +                range = Integer.toString(nDays);
> +                LOG.info("Cached data will be used; resetting range to " + range + " days" );
> +                setEntries(cachedEntries);
> +            }
> +        }
> +        catch(Exception e)
> +        {
> +          LOG.warn("Exception reading existing file " + output.getName() + ": "+ e + ". Full changelog will be generated");
> +        }
> +    }
> +
> +    /**
> +     * Parses the XML representation of the changelog entries.
> +     * @param in the stream to read from
> +     * @param earliest the start of the range. Anything prior to this will be ignored.
> +     * @return a list of ChangeLogEnty objects, with the latest entry at the start.
> +     * @throws DocumentException
> +     * @throws ParseException
> +     */
> +    private List parseXMLEntries(InputStream in, Calendar earliest) throws DocumentException, ParseException
> +    {
> +        SAXReader reader = new SAXReader();
> +
> +        Document xml = reader.read( new InputStreamReader(in) );
> +        Element root = xml.getRootElement();
> +        Iterator eltIterator = root.elementIterator();
> +        List cachedEntries = new ArrayList();
> +        while(eltIterator.hasNext())
> +        {
> +            Element entryElt = (Element)eltIterator.next();
> +            ChangeLogEntry entry = new ChangeLogEntry( entryElt.element("date").getText(),
> +                entryElt.element("time").getText(),
> +                entryElt.element("author").getText(),
> +                entryElt.element("msg").getText() );
> +            if(entry.getDate().before(earliest.getTime()))
> +                continue;
> +            Iterator fileEltIterator = entryElt.elementIterator("file");
> +            while(fileEltIterator.hasNext())
> +            {
> +                Element fileElt = (Element)fileEltIterator.next();
> +                ChangeLogFile clFile = new ChangeLogFile( fileElt.element("name").getText(),
> +                        fileElt.element("revision").getText());
> +                entry.addFile(clFile);
> +            }
> +            cachedEntries.add(entry);
> +        }
> +        LOG.info("Loaded " + cachedEntries.size() + " cached changelog entries.");
> +        return cachedEntries;
> +    }
> +
> +    /**
>       * Create the change log entries.
>       * @throws IOException if there is a problem creating the change log
>       * entries.
> @@ -271,11 +409,21 @@
>  
>          try
>          {
> -            setEntries(generator.getEntries(parser));
> -            if (LOG.isInfoEnabled()) {
> -                LOG.info("ChangeLog found: " + getEntries().size() 
> -                    + " entries");
> +            Collection newEntries = generator.getEntries(parser);
> +            // The name substitution must be done here, as it will already
> +            // have been applied to any cached entries and the author names
> +            // must match for successful equality tests during the merging of
> +            // cached and newly downloaded entries.
> +            replaceAuthorIdWithName(newEntries);
> +            if(incremental)
> +            {
> +                mergeNewEntries(newEntries);
>              }
> +            else
> +            {
> +                setEntries(newEntries);
> +            }
> +            LOG.info("ChangeLog found: " + getEntries().size() + " entries");
>          } catch (IOException e) {
>              LOG.warn(e.getLocalizedMessage(), e);
>              throw e;
> @@ -288,6 +436,35 @@
>      }
>  
>      /**
> +     * Merges newly downloaded changelog entries with
> +     * previously the previously cached list. The latter will already
> +     * be stored in the 'entries' member variable.
> +     * @param newEntries
> +     */
> +    private void mergeNewEntries(Collection newEntries)
> +    {
> +        if(getEntries().isEmpty()) // there may have been a problem while loading the existing file
> +        {
> +            setEntries(newEntries);
> +        }
> +        else
> +        {
> +        // Build a set of the existing entries and add the elements from newEntries - the Set should take care
> +        // of any duplicates.
> +            SortedSet entrySet = new TreeSet(Collections.reverseOrder());
> +            entrySet.addAll(getEntries());
> +            Iterator newEntryIterator = newEntries.iterator();
> +            while( newEntryIterator.hasNext() )
> +            {
> +                ChangeLogEntry cle = (ChangeLogEntry)newEntryIterator.next();
> +                entrySet.add(cle);
> +            }
> +            setEntries(entrySet);
> +        }
> +    }
> +
> +
> +    /**
>       * Create a new instance of the ChangeLogFactory specified by the
>       * <code>clFactory</code> member.
>       *
> @@ -339,12 +516,13 @@
>  
>      /**
>       * replace all known author's id's with their maven specified names
> +     * @param entrySet the set of entries to apply perform the substitution on.
>       */
> -    private void replaceAuthorIdWithName()
> +    private void replaceAuthorIdWithName(Collection entrySet)
>      {
>          Properties userList = getUserList();
>          ChangeLogEntry entry = null;
> -        for (Iterator i = getEntries().iterator(); i.hasNext();)
> +        for (Iterator i = entrySet.iterator(); i.hasNext();)
>          {
>              entry = (ChangeLogEntry) i.next();
>              if (userList.containsKey(entry.getAuthor()))
> Index: src/main/org/apache/maven/changelog/ChangeLogEntry.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/changelog/ChangeLogEntry.java,v
> retrieving revision 1.2
> diff -u -w -r1.2 ChangeLogEntry.java
> --- src/main/org/apache/maven/changelog/ChangeLogEntry.java 4 Jul 2003 16:24:46 -0000  1.2
> +++ src/main/org/apache/maven/changelog/ChangeLogEntry.java 11 Jul 2003 17:56:10 -0000
> @@ -70,7 +70,7 @@
>   * @author <a href="mailto:dion@multitask.com.au">dIon Gillard</a>
>   * @version $Id: ChangeLogEntry.java,v 1.2 2003/07/04 16:24:46 evenisse Exp $
>   */
> -public class ChangeLogEntry
> +public class ChangeLogEntry implements Comparable
>  {
>      /**
>       * Escaped <code>&lt;</code> entity
> @@ -110,12 +110,18 @@
>          new SimpleDateFormat("HH:mm:ss");
>  
>      /**
> +     * Formatter used by the constructor which takes
> +     * date and time arguments matching the two formats above.
> +     */
> +    private static final SimpleDateFormat DATE_TIME_FORMAT =
> +        new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
> +    /**
>       * Formatter used to parse CVS date/timestamp.
>       */
>      private static final SimpleDateFormat CVS_TIMESTAMP_FORMAT =
>          new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
>  
> -    /** Date the changes were committed */
> +    /** Date the changes were committed (containing both date and time information) */
>      private Date date;
>      
>      /** User who made changes */
> @@ -142,6 +148,22 @@
>      }
>  
>      /**
> +     * Creates an entry from previously formatted date and time parameters.
> +     * (e.g. from an output XML file).
> +     * @param date an absolute date value (yyyy-MM-dd)
> +     * @param time the time of day within that date (HH:mm:ss)
> +     * @param author author's name
> +     * @param comment the commit message for the entry
> +     */
> +    public ChangeLogEntry(String date, String time, String author, String comment) throws ParseException
> +    {
> +        Date dateTime = DATE_TIME_FORMAT.parse(date.trim() + time.trim());
> +        setDate(dateTime);
> +        setAuthor(author);
> +        setComment(comment);
> +    }
> +
> +    /**
>       * Constructor used when attributes aren't available until later
>       */
>      public ChangeLogEntry() 
> @@ -285,7 +307,6 @@
>              throw new IllegalArgumentException("I don't understand this date: "
>                  + date);
>          }
> -
>      }
>      
>      /**
> @@ -345,5 +366,46 @@
>              } 
>          } 
>          return buffer.toString(); 
> +    }
> +
> +    /**
> +     * Compares for equality based on date, author and message.
> +     * Note that the changelog XML file which is written by the plugin
> +     * has the user Ids converted to names, so a direct comparison with
> +     * the server log entries will fail.
> +     *
> +     * @param o the object to compare
> +     * @return true if the objects have the same date/time author and message.
> +     */
> +    public boolean equals(Object o)
> +    {
> +        if(!(o instanceof ChangeLogEntry))
> +            return false;
> +        return (compareTo(o) == 0);
> +    }
> +
> +    /**
> +     * Implementation of {@link Comparable} interface.
> +     * Uses {@link String} representations to do the comparison.
> +     */
> +    public int compareTo(Object o)
> +    {
> +        ChangeLogEntry other =(ChangeLogEntry)o;
> +        return encodeAsString().compareTo(other.encodeAsString());
> +    }
> +
> +    public int hashCode()
> +    {
> +        return encodeAsString().hashCode();
> +    }
> +
> +    /**
> +     * Convenience method for creating a string rep for hashcodes, comparators etc.
> +     * (toString has already been written but is probably overkill for this).
> +     * @return unique String representation of this object.
> +     */
> +    private String encodeAsString()
> +    {
> +        return DATE_TIME_FORMAT.format(getDate()) + getAuthor() + getComment();
>      }
>  }
> Index: src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java,v
> retrieving revision 1.6
> diff -u -w -r1.6 CvsChangeLogGenerator.java
> --- src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Apr 2003 18:53:19 -0000 1.6
> +++ src/main/org/apache/maven/cvslib/CvsChangeLogGenerator.java   11 Jul 2003 17:56:10 -0000
> @@ -113,6 +113,11 @@
>          Commandline command = new Commandline();
>  
>          command.setExecutable("cvs");
> +        // null check is needed as super.init(ChangeLog) may not have been called (as in the ExposeGenerator test class)
> +        if(changeLogExecutor != null && changeLogExecutor.getUseCompression())
> +        {
> +            command.createArgument().setValue("-z3");
> +        }
>          command.createArgument().setValue("-d");
>          // from format:
>          // scm:cvs:pserver:anoncvs@cvs.apache.org:/home/cvspublic:jakarta-turbine-maven/src/plugins-build/changelog/
> @@ -181,10 +186,7 @@
>          if (ioe.getMessage().indexOf("CreateProcess") != -1 || ioe.getMessage().indexOf("cvs: not found") != -1)
>          {
>              // can't find CVS on Win32 or Linux...
> -            if (LOG.isWarnEnabled())
> -            {
>                  LOG.warn("Unable to find cvs executable. " + "Changelog will be empty");
> -            }
>          }
>          else
>          {
> Index: src/main/org/apache/maven/cvslib/CvsChangeLogParser.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/main/org/apache/maven/cvslib/CvsChangeLogParser.java,v
> retrieving revision 1.1.1.1
> diff -u -w -r1.1.1.1 CvsChangeLogParser.java
> --- src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   24 Jan 2003 03:44:53 -0000 1.1.1.1
> +++ src/main/org/apache/maven/cvslib/CvsChangeLogParser.java   11 Jul 2003 17:56:11 -0000
> @@ -81,13 +81,6 @@
>  class CvsChangeLogParser implements ChangeLogParser
>  {
>      /**
> -     * Custom date/time formatter.  Rounds ChangeLogEntry times to the nearest
> -     * minute.
> -     */
> -    private static final SimpleDateFormat ENTRY_KEY_TIMESTAMP_FORMAT = 
> -        new SimpleDateFormat("yyyyMMddHHmm");
> -    
> -    /**
>       * rcs entries, in reverse (date, time, author, comment) order
>       */
>      private Map entries = new TreeMap(Collections.reverseOrder());
> @@ -203,17 +196,14 @@
>              return;
>          }
>          
> -        String key = ENTRY_KEY_TIMESTAMP_FORMAT.format(entry.getDate())
> -            + entry.getAuthor() + entry.getComment();
> -        
> -        if (!entries.containsKey(key))
> +        if (!entries.containsKey(entry))
>          {
>              entry.addFile(file);
> -            entries.put(key, entry);
> +            entries.put(entry, entry);
>          }
>          else
>          {
> -            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(key);
> +            ChangeLogEntry existingEntry = (ChangeLogEntry) entries.get(entry);
>              existingEntry.addFile(file);
>          }
>      }
> Index: src/test/org/apache/maven/changelog/ChangeLogEntryTest.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/changelog/ChangeLogEntryTest.java,v
> retrieving revision 1.1.1.1
> diff -u -w -r1.1.1.1 ChangeLogEntryTest.java
> --- src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   24 Jan 2003 03:44:56 -0000 1.1.1.1
> +++ src/test/org/apache/maven/changelog/ChangeLogEntryTest.java   11 Jul 2003 17:56:11 -0000
> @@ -241,7 +241,6 @@
>              cal.getTime(), instance.getDate());
>      }
>  
> -    
>      /** 
>       * Test of getDateFormatted method
>       */
> @@ -260,6 +259,25 @@
>              instance.getTimeFormatted());
>      }
>  
> +    /**
> +     * Tests the constructor which takes ready-formatted date and time Strings.
> +     * Then checks for equality with the original instance.
> +     * @throws Exception java.text.ParseException if formats are incorrect
> +     */
> +    public void testDateTimeConstructorAndEquals() throws Exception
> +    {
> +        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
> +        assertEquals("Instances not equal with same data,", instance, instance2 );
> +    }
> +
> +
> +    public void testComparable() throws Exception
> +    {
> +        ChangeLogEntry instance2 = new ChangeLogEntry("2002-04-01", "00:00:00", "dion", "comment");
> +        assertTrue("Comparator failed", instance2.compareTo(instance) == 0);
> +        instance2 = new ChangeLogEntry("2002-04-01", "00:00:05", "dion", "comment");
> +        assertTrue("Comparator failed", instance2.compareTo(instance) > 0);
> +    }
>      // Add test methods here, they have to start with 'test' name.
>      // for example:
>      // public void testHello() {}
> Index: src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java
> ===================================================================
> RCS file: /home/cvspublic/maven/src/plugins-build/changelog/src/test/org/apache/maven/cvslib/CvsChangeLogGeneratorTest.java,v
> retrieving revision 1.5
> diff -u -w -r1.5 CvsChangeLogGeneratorTest.java

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://jira.codehaus.org/secure/Administrators.jspa
-
For more information on JIRA, see:
   http://www.atlassian.com/software/jira


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@maven.apache.org
For additional commands, e-mail: dev-help@maven.apache.org