001package org.apache.maven.scm.provider.tfs.command.consumer;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.text.DateFormat;
023import java.text.ParseException;
024import java.util.ArrayList;
025import java.util.Date;
026import java.util.List;
027import java.util.Locale;
028import java.util.TimeZone;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032import org.apache.maven.scm.ChangeFile;
033import org.apache.maven.scm.ChangeSet;
034import org.apache.maven.scm.log.ScmLogger;
035import org.apache.maven.scm.util.AbstractConsumer;
036
037/**
038 * @author Olivier Lamy
039 *
040 */
041public class TfsChangeLogConsumer
042    extends AbstractConsumer
043{
044
045    private static final String PATTERN =
046        "^[^:]*:[ \t]([0-9]*)\n" + "[^:]*:[ \t](.*)\n[^:]*:[ \t](.*)\n"
047            + "[^:]*:((?:\n.*)*)\n\n[^\n :]*:(?=\n  )((?:\n[ \t]+.*)*)";
048
049    private static final String PATTERN_ITEM = "\n  ([^$]+) (\\$/.*)";
050
051    private List<ChangeSet> logs = new ArrayList<ChangeSet>();
052
053    private String buffer = "";
054
055    boolean fed = false;
056
057    public TfsChangeLogConsumer( ScmLogger logger )
058    {
059        super( logger );
060    }
061
062    public void consumeLine( String line )
063    {
064        fed = true;
065        if ( line.startsWith( "-----" ) )
066        {
067            addChangeLog();
068        }
069        buffer += line + "\n";
070    }
071
072    public List<ChangeSet> getLogs()
073    {
074        addChangeLog();
075        return logs;
076    }
077
078    private void addChangeLog()
079    {
080        if ( !buffer.equals( "" ) )
081        {
082            Pattern p = Pattern.compile( PATTERN );
083            Matcher m = p.matcher( buffer );
084            if ( m.find() )
085            {
086                String revision = m.group( 1 ).trim();
087                String username = m.group( 2 ).trim();
088                String dateString = m.group( 3 ).trim();
089                String comment = m.group( 4 ).trim();
090                Pattern itemPattern = Pattern.compile( PATTERN_ITEM );
091                Matcher itemMatcher = itemPattern.matcher( m.group( 5 ) );
092                List<ChangeFile> files = new ArrayList<ChangeFile>();
093                while ( itemMatcher.find() )
094                {
095                    ChangeFile file = new ChangeFile( itemMatcher.group( 2 ).trim(), revision );
096                    files.add( file );
097                }
098                Date date;
099                try
100                {
101                    date = parseDate( dateString );
102                }
103                catch ( ParseException e )
104                {
105                    getLogger().error( "Date parse error", e );
106                    throw new RuntimeException( e );
107                }
108
109                ChangeSet change = new ChangeSet( date, comment, username, files );
110                logs.add( change );
111            }
112            buffer = "";
113        }
114    }
115
116    public boolean hasBeenFed()
117    {
118        return fed;
119    }
120
121    @SuppressWarnings( "deprecation" )
122    protected static Date parseDate( String dateString )
123        throws ParseException
124    {
125        Date date = null;
126        try
127        {
128            // Use the depricated Date.parse method as this is very good at
129            // detecting
130            // dates commonly output by the US and UK standard locales of
131            // dotnet that
132            // are output by the Microsoft command line client.
133            date = new Date( Date.parse( dateString ) );
134        }
135        catch ( IllegalArgumentException e )
136        {
137            // ignore - parse failed.
138        }
139        if ( date == null )
140        {
141            // The old fashioned way did not work. Let's try it using a more
142            // complex
143            // alternative.
144            DateFormat[] formats = createDateFormatsForLocaleAndTimeZone( null, null );
145            return parseWithFormats( dateString, formats );
146        }
147        return date;
148    }
149
150    private static Date parseWithFormats( String input, DateFormat[] formats )
151        throws ParseException
152    {
153        ParseException parseException = null;
154        for ( int i = 0; i < formats.length; i++ )
155        {
156            try
157            {
158                return formats[i].parse( input );
159            }
160            catch ( ParseException ex )
161            {
162                parseException = ex;
163            }
164        }
165
166        throw parseException;
167    }
168
169    /**
170     * Build an array of DateFormats that are commonly used for this locale and timezone.
171     */
172    private static DateFormat[] createDateFormatsForLocaleAndTimeZone( Locale locale, TimeZone timeZone )
173    {
174        if ( locale == null )
175        {
176            locale = Locale.getDefault();
177        }
178
179        if ( timeZone == null )
180        {
181            timeZone = TimeZone.getDefault();
182        }
183
184        List<DateFormat> formats = new ArrayList<DateFormat>();
185
186        for ( int dateStyle = DateFormat.FULL; dateStyle <= DateFormat.SHORT; dateStyle++ )
187        {
188            for ( int timeStyle = DateFormat.FULL; timeStyle <= DateFormat.SHORT; timeStyle++ )
189            {
190                DateFormat df = DateFormat.getDateTimeInstance( dateStyle, timeStyle, locale );
191                if ( timeZone != null )
192                {
193                    df.setTimeZone( timeZone );
194                }
195                formats.add( df );
196            }
197        }
198
199        for ( int dateStyle = DateFormat.FULL; dateStyle <= DateFormat.SHORT; dateStyle++ )
200        {
201            DateFormat df = DateFormat.getDateInstance( dateStyle, locale );
202            df.setTimeZone( timeZone );
203            formats.add( df );
204        }
205
206        return (DateFormat[]) formats.toArray( new DateFormat[formats.size()] );
207    }
208
209}