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/06/04 07:09:21 UTC

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

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

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

    Fix Version:     (was: 1.8)

> 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
>  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