package org.codehaus.plexus.components.io.attributes;

/*
 * Copyright 2007 The Codehaus Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import junit.framework.Assert;
import org.codehaus.plexus.components.io.attributes.AttributeParser.NumericUserIDAttributeParser;
import org.codehaus.plexus.util.Os;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Locale;
import java.util.Map;

import junit.framework.TestCase;

import static org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributeUtils.getFileAttributes;

public class PlexusIoResourceAttributeUtilsTest
    extends TestCase
{

    private Locale origSystemLocale;

    @Override
    protected void setUp()
        throws Exception
    {
       this.origSystemLocale = Locale.getDefault();
       // sample ls output files have US date format and we use SimpleDateFormt with system locale for ls date format parsing 
       // otherwise test could fail on systems with non-US locales
       Locale.setDefault( Locale.US );
    }
    
    @Override
    protected void tearDown()
        throws Exception
    {
        Locale.setDefault( origSystemLocale );
    }
    
    public void testGetAttributesForThisTestClass()
        throws IOException
    {
        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
        {
            System.out.println( "WARNING: Unsupported OS, skipping test" );
            return;
        }

        URL resource = Thread.currentThread().getContextClassLoader().getResource(
            getClass().getName().replace( '.', '/' ) + ".class" );

        if ( resource == null )
        {
            throw new IllegalStateException(
                "SOMETHING IS VERY WRONG. CANNOT FIND THIS TEST CLASS IN THE CLASSLOADER." );
        }

        File f = new File( resource.getPath().replaceAll( "%20", " " ) );

        Map attrs =
            PlexusIoResourceAttributeUtils.getFileAttributesByPath( f, true, true );

        PlexusIoResourceAttributes fileAttrs = (PlexusIoResourceAttributes) attrs.get( f.getAbsolutePath() );

        System.out.println( "Got attributes for: " + f.getAbsolutePath() + fileAttrs );

        assertNotNull( fileAttrs );
        assertTrue( fileAttrs.isOwnerReadable() );
        assertEquals( System.getProperty( "user.name" ), fileAttrs.getUserName() );
    }

    public void testFolderJava7()
            throws IOException, CommandLineException {

        if (Os.isFamily( Os.FAMILY_WINDOWS ) || Os.isFamily( Os.FAMILY_WIN9X )){
            return; // Nothing to do here.
        }

        URL resource = Thread.currentThread().getContextClassLoader().getResource(
            getClass().getName().replace( '.', '/' ) + ".class" );

        if ( resource == null )
        {
            throw new IllegalStateException(
                "SOMETHING IS VERY WRONG. CANNOT FIND THIS TEST CLASS IN THE CLASSLOADER." );
        }

        File f = new File( resource.getPath().replaceAll( "%20", " " ) );
        final File aDir = f.getParentFile().getParentFile().getParentFile();

        Commandline commandLine = new Commandline("chmod");
        commandLine.addArguments(new String[]{"763", f.getAbsolutePath()});

        CommandLineUtils.executeCommandLine(commandLine, null , null);
        Map attrs = PlexusIoResourceAttributeUtils.getFileAttributesByPath( aDir, true, true );

        PlexusIoResourceAttributes fileAttrs = (PlexusIoResourceAttributes) attrs.get( f.getAbsolutePath() );

        assertTrue( fileAttrs.isGroupReadable());
        assertTrue( fileAttrs.isGroupWritable());
        assertFalse( fileAttrs.isGroupExecutable());

        assertTrue( fileAttrs.isOwnerExecutable());
        assertTrue( fileAttrs.isOwnerReadable());
        assertTrue( fileAttrs.isOwnerWritable());

        assertTrue( fileAttrs.isWorldExecutable());
        assertFalse( fileAttrs.isWorldReadable());
        assertTrue( fileAttrs.isWorldWritable());

        assertNotNull(fileAttrs);
    }


    public void testAttributeParsers()
    {
        assertTrue( PlexusIoResourceAttributeUtils.totalLinePattern.matcher( "totalt 420" ).matches() );
        assertTrue( PlexusIoResourceAttributeUtils.totalLinePattern.matcher( "total 420" ).matches() );
        assertTrue( PlexusIoResourceAttributeUtils.totalLinePattern.matcher( "JSHS 420" ).matches() );
    }

    // to make a new testcase
    // Checkout plexus-io, and from the root of the module type the following:
    // ls -1nlaR src/main/java/org/codehaus/plexus/components >src/test/resources/`uname`-p1.txt
    // ls -1laR src/main/java/org/codehaus/plexus/components >src/test/resources/`uname`-p2.txt
    // Then a test-method that tests the output-

    public void ignoredTestParserUbuntu10_04_en()
        throws Exception
    {
        final Map map = checkStream( "Linux" );

        final FileAttributes o = (FileAttributes) map.get(
            "src/main/java/org/codehaus/plexus/components/io/attributes/AttributeConstants.java" );

        // -rw-r--r--  1 1020 1030   11108 Mar 16 22:42 build.xml
        assertEquals( "-rw-rw-r--", new String( o.getLsModeParts() ) );
        assertEquals( 1020, o.getUserId().intValue() );
        assertEquals( 1030, o.getGroupId().intValue() );
        // Should probably test pass 2 too...
    }

    public void testANotherOneWithTrouble()
        throws Exception
    {
        String output = "drwxr-xr-x+ 13 kristian  staff  442 Oct 11 14:14 ..";
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( output.getBytes() );
        AttributeParser parser = getNameBasedParser1la();
        parse( byteArrayInputStream, parser );

    }
    public void testSingleLine()
        throws Exception
    {
        String output =
            "-rw-r--r-- 1 1003 1002 1533 2010-04-23 14:34 /home/bamboo/agent1/xml-data/build-dir/PARALLEL-CH1W/checkout/spi/pom.xml";
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( output.getBytes() );
        AttributeParser parser = getNumericParser1nla();
        parse( byteArrayInputStream, parser );
}

    public void testSrcResource()
        throws IOException
    {
        File dir = new File("src/test/resources/symlinks");
        final Map<String, PlexusIoResourceAttributes> fileAttributesByPathScreenScrape =
            PlexusIoResourceAttributeUtils.getFileAttributesByPathScreenScrape( dir, true, true );
        assertNotNull( fileAttributesByPathScreenScrape);
        PlexusIoResourceAttributes pr = null;
        for ( String s : fileAttributesByPathScreenScrape.keySet() )
        {
            if (s.endsWith( "targetFile.txt" )) pr = fileAttributesByPathScreenScrape.get( s);
        }
        assertNotNull(pr);

        assertTrue(pr.getOctalMode() > 0);
    }
    public void testPermissionDenied()
    {
        File dir = new File("src/test/lsPerms");
        try {
            PlexusIoResourceAttributeUtils.getFileAttributesByPathScreenScrape( dir, true,
                                                                                true );
            // We could search for permission denied here and throw some kind of specialized exception.
            fail("We were supposed to get an io exceptions");
        } catch (IOException ignore)
        {
            ignore.printStackTrace();
        }
    }

    public void testReversedMonthDayOrder()
        throws Exception
    {
        String output = //
            "-rw-r--r--   1 501  80  7683 31 May 10:06 pom_newer.xml\n" + //
            "-rwxr--r--   1 502  81  7683  1 Jun 2010  pom_older.xml";
        InputStream byteArrayInputStream = new ByteArrayInputStream( output.getBytes() );
        NumericUserIDAttributeParser parser = getNumericParser1nla();
        parse( byteArrayInputStream, parser );
        Map<String, PlexusIoResourceAttributes> map = parser.getAttributesByPath();
        
        // 6 months or newer ls date format
        FileAttributes newerFileAttr = (FileAttributes) map.get( "pom_newer.xml" );
        assertNotNull( newerFileAttr );
        assertEquals( "-rw-r--r--", new String( newerFileAttr.getLsModeParts() ) );
        assertEquals( 501, newerFileAttr.getUserId().intValue() );
        assertEquals( 80, newerFileAttr.getGroupId().intValue() );
        
        // older than 6 months ls date format
        FileAttributes olderFileAttr = (FileAttributes) map.get( "pom_older.xml" );
        assertNotNull( olderFileAttr );
        assertEquals( "-rwxr--r--", new String( olderFileAttr.getLsModeParts() ) );
        assertEquals( 502, olderFileAttr.getUserId().intValue() );
        assertEquals( 81, olderFileAttr.getGroupId().intValue() );
    }

    public void testOddLinuxFormatWithExtermelyLargeNumericsSingleLine()
        throws Exception
    {
        String output =
            "-rw-rw-r-- 1 4294967294 4294967294 7901 2011-06-07 18:39 /mnt/work/src/maven-plugins-trunk/maven-compiler-plugin/pom.xml";
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( output.getBytes() );
        AttributeParser parser = getNumericParser1nla();
        parse( byteArrayInputStream, parser );
    }

    public void testMacOsWithSpace() throws Exception {
        String output =
                "-rwxrw--wx  1 kristian  DOMZZZ\\Domain Users  15387 Oct 20 15:30 /Users/kristian/lsrc/plexus/plexus-io/target/test-classes/org/codehaus/plexus/components/io/attributes/PlexusIoResourceAttributeUtilsTest.class";
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( output.getBytes() );
        AttributeParser parser = getNameBasedParser1la();
        parse( byteArrayInputStream, parser );
        final Map<String, PlexusIoResourceAttributes> attributesByPath = parser.getAttributesByPath();
        String path = "/Users/kristian/lsrc/plexus/plexus-io/target/test-classes/org/codehaus/plexus/components/io/attributes/";
    }

    public void testMacOsLine()
        throws Exception
    {
        String line = "drwxr-xr-x+ 3 1000  20  102 Oct 11 14:24 ..";
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( line.getBytes() );
        AttributeParser parser = getNumericParser1nla();
        parse( byteArrayInputStream, parser );

    }
    public void ignoredTestParserCygwin()
        throws Exception
    {
        final Map map = checkStream( "CYGWIN_NT-5.1" );
        final FileAttributes o = (FileAttributes) map.get(
            "src/main/java/org/codehaus/plexus/components/io/attributes/AttributeConstants.java" );

        // -rw-r--r--  1 1020 1030   11108 Mar 16 22:42 build.xml
        assertEquals( "-rw-r--r--", new String( o.getLsModeParts() ) );
        assertEquals( 203222, o.getUserId().intValue() );
        assertEquals( 10513, o.getGroupId().intValue() );
    }

    public void testParserSolaris()
        throws Exception
    {
        checkStream( "SunOS" );
    }

    public void testMisc()
        throws Exception
    {
        checkStream( "Test" );
    }

    public void testParserFreeBsd()
        throws Exception
    {
        checkStream( "FreeBSD" );
    }

    public void testParserb26()
        throws Exception
    {
        final Map<String, PlexusIoResourceAttributes> map = checkStream2( "b26-linux" );
        PlexusIoResourceAttributes attrs = null;
        for ( String s : map.keySet() )
        {
            if (s.endsWith( "App.java" )) attrs = map.get(s);
        }
        Assert.assertEquals( 0664, attrs.getOctalMode() );

    }

    public void testMergeAttributesWithNullBase()
    {
        PlexusIoResourceAttributes override =
            new SimpleResourceAttributes( 1001, "myUser", 1001, "test", 0 );
        PlexusIoResourceAttributes defaults =
            new SimpleResourceAttributes( 1000, "defaultUser", 1000, "defaultTest", 0 );

        PlexusIoResourceAttributes attributes;
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, null, defaults );

        assertEquals( Integer.valueOf( 1001 ), attributes.getGroupId() );
        assertEquals( Integer.valueOf( 1001 ), attributes.getUserId() );
    }

    public void testMergeAttributesWithNullOverrideGroup()
    {
        final PlexusIoResourceAttributes override =
            new SimpleResourceAttributes( 1001, "myUser", -1, null, 0 );
        final PlexusIoResourceAttributes defaults =
            new SimpleResourceAttributes( 1000, "defaultUser", 1000, "defaultGroup", 0 );

        PlexusIoResourceAttributes attributes
            = PlexusIoResourceAttributeUtils.mergeAttributes( override, null, defaults );

        assertEquals( attributes.getGroupId(), Integer.valueOf( 1000 ) );
        assertEquals( attributes.getUserId(), Integer.valueOf( 1001 ) );
    }

    public void testMergeAttributesOverride()
    {
        final PlexusIoResourceAttributes blank = new SimpleResourceAttributes();
        final PlexusIoResourceAttributes invalid = new SimpleResourceAttributes( -1, null, -1, null, -1 );
        final PlexusIoResourceAttributes override =
            new SimpleResourceAttributes( 1111, "testUser", 2222, "testGroup", 0777 );
        final PlexusIoResourceAttributes defaults =
            new SimpleResourceAttributes( 3333, "defaultUser", 4444, "defaultGroup", 0444 );
        final PlexusIoResourceAttributes base =
            new SimpleResourceAttributes( 5555, "baseUser", 6666, "baseGroup", 0111 );

        PlexusIoResourceAttributes attributes;

        // When override is null, base is returned verbatim
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( null, null, null );
        assertNull( attributes );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( null, null, defaults );
        assertNull( attributes );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( null, base, null );
        assertSame( base, attributes );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( null, base, defaults );
        assertSame( base, attributes );

        // Test cases when override is non-null
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, null, null );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, base, null );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, null, defaults );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, base, defaults );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes(
            override, blank, null );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( override, invalid, null );

        assertEquals( Integer.valueOf( 1111 ), attributes.getUserId() );
        assertEquals( "testUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 2222 ), attributes.getGroupId() );
        assertEquals( "testGroup", attributes.getGroupName() );
        assertEquals( 0777, attributes.getOctalMode() );

        // Test cases when override has only blank values
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( blank, base, null );

        assertEquals( Integer.valueOf( 5555 ), attributes.getUserId() );
        assertEquals( "baseUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 6666 ), attributes.getGroupId() );
        assertEquals( "baseGroup", attributes.getGroupName() );
        assertEquals( 0111, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( invalid, base, null );

        assertEquals( Integer.valueOf( 5555 ), attributes.getUserId() );
        assertEquals( "baseUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 6666 ), attributes.getGroupId() );
        assertEquals( "baseGroup", attributes.getGroupName() );
        assertEquals( 0111, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( blank, base, defaults );

        assertEquals( Integer.valueOf( 5555 ), attributes.getUserId() );
        assertEquals( "baseUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 6666 ), attributes.getGroupId() );
        assertEquals( "baseGroup", attributes.getGroupName() );
        assertEquals( 0111, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( invalid, base, defaults );

        assertEquals( Integer.valueOf( 5555 ), attributes.getUserId() );
        assertEquals( "baseUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 6666 ), attributes.getGroupId() );
        assertEquals( "baseGroup", attributes.getGroupName() );
        assertEquals( 0111, attributes.getOctalMode() );
    }

    public void testFileAttributes()
        throws IOException
    {
        PlexusIoResourceAttributes attrs = getFileAttributes( new File( "src/test/resources/symlinks/src/fileW.txt" ) );
        assertFalse( attrs.isSymbolicLink() );
        assertTrue( StringUtils.isNotEmpty( attrs.getUserName()));
        if (! Os.isFamily(Os.FAMILY_WINDOWS))
        {
            assertTrue( StringUtils.isNotEmpty( attrs.getGroupName()));
            assertNotNull(  attrs.getGroupId() );
            assertNotNull(  attrs.getUserId() );
        }
    }

    public void testMergeAttributesDefault()
    {
        final PlexusIoResourceAttributes blank = new SimpleResourceAttributes(null, null, null, null, 0);
        final PlexusIoResourceAttributes invalid = new SimpleResourceAttributes( -1, null, -1, null, -1 );
        final PlexusIoResourceAttributes defaults =
            new SimpleResourceAttributes( 3333, "defaultUser", 4444, "defaultGroup", 0444 );

        PlexusIoResourceAttributes attributes;

        // Test cases when override and base have blank values
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( blank, blank, defaults );

        assertEquals( Integer.valueOf( 3333 ), attributes.getUserId() );
        assertEquals( "defaultUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 4444 ), attributes.getGroupId() );
        assertEquals( "defaultGroup", attributes.getGroupName() );
        // 0 is a borderline case, for backwards compatibility it is not overridden by value from defaults
        assertEquals( 0, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( invalid, blank, defaults );

        assertEquals( Integer.valueOf( 3333 ), attributes.getUserId() );
        assertEquals( "defaultUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 4444 ), attributes.getGroupId() );
        assertEquals( "defaultGroup", attributes.getGroupName() );
        // 0 is a borderline case, for backwards compatibility it is not overridden by value from defaults
        // Not just that, but 0 is correct.
        assertEquals( 0, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( blank, invalid, defaults );

        assertEquals( Integer.valueOf( 3333 ), attributes.getUserId() );
        assertEquals( "defaultUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 4444 ), attributes.getGroupId() );
        assertEquals( "defaultGroup", attributes.getGroupName() );
        assertEquals( 0444, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( invalid, invalid, defaults );

        assertEquals( Integer.valueOf( 3333 ), attributes.getUserId() );
        assertEquals( "defaultUser", attributes.getUserName() );
        assertEquals( Integer.valueOf( 4444 ), attributes.getGroupId() );
        assertEquals( "defaultGroup", attributes.getGroupName() );
        assertEquals( 0444, attributes.getOctalMode() );

        // Test cases when invalid defaults should not override blank values
        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( blank, blank, invalid );

        assertNull( attributes.getUserId() );
        assertNull( attributes.getUserName() );
        assertNull( attributes.getGroupId() );
        assertNull( attributes.getGroupName() );
        assertEquals( 0, attributes.getOctalMode() );

        attributes = PlexusIoResourceAttributeUtils.mergeAttributes( invalid, blank, invalid );

        assertNull( attributes.getUserId() );
        assertNull( attributes.getUserName() );
        assertNull( attributes.getGroupId() );
        assertNull( attributes.getGroupName() );
        assertEquals( 0, attributes.getOctalMode() );
    }

    private InputStream getStream( String s )
    {
        return this.getClass().getClassLoader().getResourceAsStream( s );
    }

    private Map checkStream( String baseName )
        throws Exception
    {


        AttributeParser.NumericUserIDAttributeParser numericParser = getNumericParser1nla();
        InputStream phase1 = getStream( baseName + "-p1.txt" );
        parse( phase1, numericParser );


        final AttributeParser.SymbolicUserIDAttributeParser nameBasedParser = getNameBasedParser1la();
        InputStream phase2 = getStream( baseName + "-p2.txt" );
        parse( phase2, nameBasedParser );

        return nameBasedParser.merge( numericParser );
    }

    private Map<String, PlexusIoResourceAttributes> checkStream2( String baseName )
        throws Exception
    {


        AttributeParser.NumericUserIDAttributeParser numericParser = getNumericParser1nla();
        InputStream phase1 = getStream( baseName + "-1nlaR.txt" );
        parse( phase1, numericParser );


        final AttributeParser.SymbolicUserIDAttributeParser nameBasedParser = getNameBasedParser1la();
        InputStream phase2 = getStream( baseName + "-1laR.txt" );
        parse( phase2, nameBasedParser );

        return nameBasedParser.merge( numericParser );
    }

    private AttributeParser.NumericUserIDAttributeParser getNumericParser1nla()
    {
        return new AttributeParser.NumericUserIDAttributeParser( createConsoleLogger() );
    }

    private AttributeParser.SymbolicUserIDAttributeParser getNameBasedParser1la()
    {
        return new AttributeParser.SymbolicUserIDAttributeParser( createConsoleLogger() );
    }

    private StreamConsumer createConsoleLogger()
    {
        return new StreamConsumer()
        {
            public void consumeLine( String line )
            {
                System.out.println(line);
            }
        };
    }

    private void parse( InputStream stream, AttributeParser parser )
        throws Exception
    {
        BufferedReader bufferedReader = new BufferedReader( new InputStreamReader( stream ) );
        String line = bufferedReader.readLine();
        int lineNum = 0;
        try
        {
            while ( line != null )
            {
                parser.consumeLine( line );
                line = bufferedReader.readLine();
                lineNum++;
            }
        }
        catch ( Exception e )
        {
            Exception exception = new Exception( "At line " + lineNum + "in source:" + line );
            exception.initCause( e );
            throw e;
        }
    }
}
