001package org.apache.maven.scm.provider.accurev.cli;
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.io.IOException;
023import java.io.Reader;
024import java.io.Writer;
025import java.nio.channels.Channels;
026import java.nio.channels.Pipe;
027import java.nio.channels.Pipe.SinkChannel;
028import java.nio.channels.Pipe.SourceChannel;
029import java.nio.charset.Charset;
030import java.util.ArrayList;
031import java.util.Arrays;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035
036import org.apache.maven.scm.log.ScmLogger;
037import org.codehaus.plexus.util.cli.StreamConsumer;
038import org.codehaus.plexus.util.xml.pull.MXParser;
039import org.codehaus.plexus.util.xml.pull.XmlPullParser;
040import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
041
042/**
043 * This class is required because Plexus command line won't let you get to the process stream output process.
044 * 
045 * @author ggardner
046 */
047public abstract class XppStreamConsumer
048    extends Thread
049    implements StreamConsumer
050
051{
052    public ScmLogger getLogger()
053    {
054        return logger;
055    }
056
057    private Writer writer;
058
059    private XmlPullParser parser = new MXParser();
060
061    private volatile boolean complete = false;
062
063    private ScmLogger logger;
064
065    private int lineCount = 0;
066
067    private Reader reader;
068
069    public XppStreamConsumer( ScmLogger logger )
070    {
071
072        super();
073        this.logger = logger;
074        try
075        {
076            Pipe p = Pipe.open();
077            SinkChannel sink = p.sink();
078            SourceChannel source = p.source();
079            writer = Channels.newWriter( sink, Charset.defaultCharset().name() );
080            reader = Channels.newReader( source, Charset.defaultCharset().name() );
081            parser.setInput( reader );
082
083        }
084        catch ( Exception e )
085        {
086            logger.error( "Exception initialising pipe", e );
087        }
088
089    }
090
091    public final void consumeLine( String line )
092    {
093        // Do not debug line here - as CommandOutputConsumer wraps this and uses
094        // the same logger
095        try
096        {
097            writer.append( line );
098            if ( lineCount == 0 )
099            {
100                this.start();
101            }
102            lineCount++;
103            writer.flush();
104        }
105        catch ( IOException e )
106        {
107            throw new RuntimeException( "error pumping line to pipe", e );
108        }
109
110    }
111
112    @Override
113    public void run()
114    {
115
116        try
117        {
118            parse( parser );
119        }
120        catch ( Exception e )
121        {
122            caughtParseException( e );
123        }
124        finally
125        {
126            synchronized ( this )
127            {
128                
129                try
130                {
131                    reader.close();
132                }
133                catch ( IOException e )
134                {
135                    getLogger().warn( "Error closing pipe reader", e );
136                }
137                
138                complete = true;
139                this.notifyAll();
140            }
141        }
142    }
143
144    protected void caughtParseException( Exception e )
145    {
146        logger.warn( "Exception parsing input", e );
147
148    }
149
150    protected void parse( XmlPullParser p )
151        throws XmlPullParserException, IOException
152    {
153        List<String> tagPath = new ArrayList<String>();
154        int eventType = p.getEventType();
155        if ( logger.isDebugEnabled() )
156        {
157            logger.debug( "Event " + eventType );
158        }
159
160        while ( eventType != XmlPullParser.END_DOCUMENT )
161        {
162            int lastIndex = tagPath.size() - 1;
163            String tagName;
164            switch ( eventType )
165            {
166                case XmlPullParser.START_DOCUMENT:
167
168                    break;
169
170                case XmlPullParser.START_TAG:
171                    tagName = p.getName();
172                    if ( tagName != null )
173                    {
174                        tagPath.add( tagName );
175                        int attributeCount = p.getAttributeCount();
176                        Map<String, String> attributes = new HashMap<String, String>( Math.max( attributeCount, 0 ) );
177                        for ( int i = 0; i < attributeCount; i++ )
178                        {
179                            attributes.put( p.getAttributeName( i ), p.getAttributeValue( i ) );
180                        }
181
182                        startTag( tagPath, attributes );
183                    }
184                    break;
185
186                case XmlPullParser.TEXT:
187                    if ( !p.isWhitespace() )
188                    {
189                        String text = p.getText();
190                        text( tagPath, text );
191                    }
192                    break;
193
194                case XmlPullParser.END_TAG:
195                    tagName = p.getName();
196
197                    if ( lastIndex < 0 || !tagName.equals( tagPath.get( lastIndex ) ) )
198                    {
199                        logger.warn( "Bad tag path: " + Arrays.toString( tagPath.toArray() ) );
200                    }
201                    endTag( tagPath );
202                    tagPath.remove( lastIndex );
203                    break;
204
205                default:
206                    logger.warn( "Unexpected event type " + eventType );
207                    break;
208            }
209            p.next();
210            eventType = p.getEventType();
211            if ( logger.isDebugEnabled() )
212            {
213                logger.debug( "Event " + eventType );
214            }
215        }
216    }
217
218    /**
219     * close the input and wait for parsing to complete
220     */
221    public void waitComplete()
222    {
223        Thread.yield();
224        try
225        {
226            writer.close();
227        }
228        catch ( IOException e1 )
229        {
230            logger.warn( "Exception flushing output", e1 );
231        }
232
233        while ( !isComplete() )
234        {
235            synchronized ( this )
236            {
237                try
238                {
239                    if ( !isComplete() )
240                    {
241                        this.wait( 1000 );
242                    }
243                }
244                catch ( Exception e )
245                {
246                    logger.warn( e );
247                }
248            }
249        }
250
251    }
252
253    private boolean isComplete()
254    {
255        return complete || lineCount == 0;
256    }
257
258    protected void startTag( List<String> tagPath, Map<String, String> attributes )
259    {
260        if ( logger.isDebugEnabled() )
261        {
262            String tagName = getTagName( tagPath );
263            logger.debug( "START_TAG: " + tagName + "(" + attributes.size() + ")" );
264        }
265    }
266
267    protected static String getTagName( List<String> tagPath )
268    {
269        return tagPath.size() == 0 ? null : tagPath.get( tagPath.size() - 1 );
270    }
271
272    protected void endTag( List<String> tagPath )
273    {
274        if ( logger.isDebugEnabled() )
275        {
276            logger.debug( "END_TAG: " + getTagName( tagPath ) );
277        }
278    }
279
280    protected void text( List<String> tagPath, String text )
281    {
282        if ( logger.isDebugEnabled() )
283        {
284            logger.debug( "TEXT: " + text );
285        }
286    }
287
288}