All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.quinsoft.zeidon.utils.PortableFileReader Maven / Gradle / Ivy

The newest version!
/**
    This file is part of the Zeidon Java Object Engine (Zeidon JOE).

    Zeidon JOE is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Zeidon JOE is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Zeidon JOE.  If not, see .

    Copyright 2009-2015 QuinSoft
 */
/**
 *
 */
package com.quinsoft.zeidon.utils;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.ZeidonLogger;

/**
 * Generic logic to read Zeidon Object Instances from a file or stream.  The OI is expected to
 * be in "portable file" format.
 *
 * @author DG
 *
 */
public class PortableFileReader
{
    /**
     * Long strings or strings with control chars (like '\n') are stored as blobs.
     * This char indicates that a string is stored as a blob.
     */
    public static final char   STRING_STORED_AS_BLOB = 0x1f;
    public static final String STRING_STORED_AS_BLOB_REGEX = "\\x1f";

    private static final String EMPTY_STRING = "";

    public interface PortableFileEntityHandler
    {
        void startFile( PortableFileReader reader, String lodDefName );
        PortableFileAttributeHandler createEntity( PortableFileReader reader, int level, long flags );
        void endEntityAttributes( PortableFileReader reader, String entityName, int currentLevel );
        void endEntity( PortableFileReader reader, PortableFileAttributeHandler handler, int currentLevel );
        void link( PortableFileReader reader, int target, int source );
        void endFile();

        /**
         * An entity handler that does nothing.  Makes it easier to create subclasses.
         *
         * @author DG
         */
        public abstract class NullEntityHandler implements PortableFileEntityHandler
        {
            @Override
            public void startFile(PortableFileReader reader, String lodDefName)
            {
            }

            @Override
            public PortableFileAttributeHandler createEntity(PortableFileReader reader, int level, long flags)
            {
                return null;
            }

            @Override
            public void endEntity(PortableFileReader reader, PortableFileAttributeHandler handler, int currentLevel)
            {
            }

            @Override
            public void endEntityAttributes(PortableFileReader reader, String entityName, int currentLevel)
            {
            }

            @Override
            public void endFile()
            {
            }

            @Override
            public void link(PortableFileReader reader, int target, int source)
            {
            }
        }
    }

    public interface PortableFileAttributeHandler
    {
        void setAttribute( PortableFileReader reader );

        /**
         * An attribute handler that ignores all attributes.
         * @author DG
         */
        public class NullAttributeHandler implements PortableFileAttributeHandler
        {
            @Override
            public void setAttribute(PortableFileReader reader)
            {
            }
        }
    }

    private BufferedBinaryStreamReader reader;
    private String line;
    private String attributeName;
    private String attributeValue;
    private byte[] attributeValueBytes;
    private int    lineNumber;
    private PortableFileAttributeHandler attributeHandler;
    private boolean incremental;
    private boolean compressed;
    boolean optimisticOIs;
    private boolean attribFlags;
    private long entityFlags;
    private long attributeFlags;
    private final InputStream inputStream;
    private final PortableFileEntityHandler entityHandler;
    private final ZeidonLogger logger;
    private final ObjectEngine objectEngine;
    /**
     * Instantiate a reader to parse an OI from a stream.  Note that the inputStream is *NOT* closed.
     *
     * @param logger
     * @param inputStream
     * @param entityHandler
     */
    public PortableFileReader( ObjectEngine objectEngine, ZeidonLogger logger, InputStream inputStream, PortableFileEntityHandler entityHandler )
    {
        this.entityHandler = entityHandler;
        this.inputStream = inputStream;
        this.logger = logger;
        this.objectEngine = objectEngine;
    }

    public BufferedBinaryStreamReader getStreamReader()
    {
        return reader;
    }

    /**
     * Set the buffered stream reader.  This allows a caller to read multiple OIs from
     * a single stream.  Explicitly setting the stream will prevent this logic from
     * closing the stream.
     *
     * @param reader
     */
    public void setStreamReader( BufferedBinaryStreamReader reader )
    {
        this.reader = reader;
    }

    public void readEntities()
    {
        boolean closeReader = false;
        if ( reader == null )
        {
            reader = new BufferedBinaryStreamReader( inputStream );
            closeReader = true;
        }

        lineNumber = 0;
        try
        {
            ArrayList attributeHandlerStack =
                                        new ArrayList();

            int currentLevel = 0;
            String currentEntityName = null;

            // If we are reading multiple OIs in a single stream we expect to find 'ZEND'
            // to indicate one OI has ended and the next one begins.  Set endOfCurrentOi
            // to true so we can exit activating the current OI.
            boolean endOfCurrentOi = false;

            while ( ! endOfCurrentOi && ( line = reader.readLine() ) != null )
            {
                lineNumber++;

                if ( StringUtils.isBlank( line ) )
                    continue;

                String values[] = splitLine( line, ' ' );
                attributeName = values[0];
                attributeValue = values[1];

                switch ( attributeName.charAt( 0 ) )
                {
                    case 'e':
                        attributeName = attributeName.substring( 1 );
                        values = splitLine( attributeValue, ',' );
                        int level = Integer.parseInt( values[0] );

                        if ( currentLevel > 0 )
                            entityHandler.endEntityAttributes( this, currentEntityName, currentLevel );

                        if ( values[1] != EMPTY_STRING )
                            entityFlags = Long.parseLong( values[1] );

                        currentEntityName = attributeName;

                        while ( currentLevel >= level )
                        {
                            attributeHandler = attributeHandlerStack.get( currentLevel );
                            entityHandler.endEntity( this, attributeHandler, currentLevel );
                            currentLevel--;
                        }

                        attributeHandler = entityHandler.createEntity( this, level, entityFlags );

                        if ( level >= attributeHandlerStack.size() )
                            attributeHandlerStack.add( attributeHandler );
                        else
                            attributeHandlerStack.set( level, attributeHandler );

                        currentLevel = level;
                        break;

                    case 'a':
                        if ( incremental || attribFlags )
                        {
                            values = splitLine( attributeName, ',' );
                            if ( values[1] != EMPTY_STRING )
                            {
                                attributeName = values[0];
                                attributeFlags = Long.parseLong( values[1] );
                            }
                        }
                        else
                            attributeFlags = 0;

                        attributeName = attributeName.substring( 1 );
                        if ( attributeHandler != null )
                        {
                            try
                            {
                                if ( attributeValue.length() > 0 && attributeValue.charAt( 0 ) == STRING_STORED_AS_BLOB )
                                {
                                    attributeValue = attributeValue.substring(1); // Skip past control char.
                                    int length = Integer.parseInt( attributeValue );
                                    attributeValueBytes = new byte[ length ];
                                    try
                                    {
                                        int read = reader.read( attributeValueBytes, length );
                                        assert read == length;
                                        attributeValue = new String( attributeValueBytes, "UTF8" );
                                    }
                                    catch ( IOException e )
                                    {
                                        throw new ZeidonException( "Error reading binary data from portable file" );
                                    }

                                    lineNumber += StringUtils.countMatches( attributeValue, "\n" ) + 1;
                                }
                                attributeHandler.setAttribute( this );
                            }
                            catch ( Throwable e )
                            {
                                throw ZeidonException.prependMessage( e, "Attribute Name '%s'", attributeName );
                            }
                        }

                        break;

                    case 'i':
                        int target = Integer.parseInt( attributeName.substring( 1 ) );
                        int source = Integer.parseInt( attributeValue );

                        entityHandler.link( this, target, source );
                        break;

                    case 'Z':
                        if ( ! "ZEND".equals( line ) )
                            throw new ZeidonException("Expecting 'ZEND' but got %s instead", line );

                        // Set flag to break out of reader loop.
                        endOfCurrentOi = true;
                        break;

                    case 'z':
                        boolean erDate;
                        attributeHandlerStack.add( null );

                        if ( lineNumber > 2 )
                            throw new ZeidonException( "Unexpected beginning of new OI in stream on line %d", lineNumber );

                        // Read the flags from the header.  Sample header:
                        // z1100-Zeidon    MEMPLOY  mEmploy  07/12/09   20:38:04 1.0a2
                        erDate        = line.charAt( 1 ) == '1';
                        incremental   = line.charAt( 2 ) == '1';
                        compressed    = line.charAt( 3 ) == '1';
                        optimisticOIs = line.charAt( 4 ) == '1';
                        attribFlags   = line.charAt( 5 ) == '1';
                        if ( compressed )
                            throw new ZeidonException("Reading compressed streams not supported yet.");

                        String lodDefName = StringUtils.split( line, " " )[2];
                        entityHandler.startFile( this, lodDefName );

                        if ( erDate && isIncremental() )
                        {
                            // File contains the ER date so read it.  We don't support compressed
                            // OIs yet so we don't do anything with it.
                            reader.readLine();
                        }

                        break;

                    case ';':
                        // Comment line, so just ignore.
                        break;

                    case 'm':
                        //TODO: Add support for meta flags.
                        break;

                    default:
                        throw new ZeidonException( "Line %d doesn't start with e, a, i, or z\n==> %s",
                                                    lineNumber, line );
                }
            } // while not EOF...

            if ( currentLevel > 0 )
                entityHandler.endEntityAttributes( this, currentEntityName, currentLevel );

            while ( currentLevel > 0 )
            {
                attributeHandler = attributeHandlerStack.get( currentLevel );
                entityHandler.endEntity( this, attributeHandler, currentLevel );
                currentLevel--;
            }

            entityHandler.endFile();
        }
        catch ( Throwable e )
        {
            // Add line number to the message.
            throw ZeidonException.prependMessage( e, "line:%d", lineNumber );
        }
        finally
        {
            if ( closeReader )
            {
                IOUtils.closeQuietly( reader );
                reader = null;
            }
        }
    }

    /**
     * Splits the line into two parts.  First part is non-spaces, second part is after
     * group of delimiters.
     *
     *     Ex:  "ABC   123 456 " returns ["ABC", "123 456 "]
     *
     * Note: this does not validate that 'line' is non-blank.
     *
     * @param line2
     * @return
     */
    private String[] splitLine( String line, char delimiter )
    {
        String[] array = new String[2];

        int idx = 0;

        // Skip past whitespaces at beginning of the line.
        while ( line.charAt( idx ) == ' ' )
            idx++;

        // Find first set of chars up to first delimiter.
        while ( idx < line.length() && line.charAt( idx ) != delimiter )
            idx++;

        array[0] = line.substring( 0, idx );

        // Now skip over all consecutive delimiters.
        while ( idx < line.length() && line.charAt( idx ) == delimiter )
            idx++;

        if ( idx == line.length() )
            array[1] = EMPTY_STRING;
        else
            array[1] = line.substring( idx );

        return array;
    }

    public String getLine()
    {
        return line;
    }

    public String getAttributeName()
    {
        return attributeName;
    }

    public String getAttributeValue()
    {
        return attributeValue;
    }

    /**
     * Convenience method for getAttributeValue().toUpperCase().startsWith( "..." )
     *
     * @param str string to compare.
     *
     * @return true if attribute value starts with str.
     */
    public boolean valueStartsWith( String str )
    {
        return getAttributeValue().toUpperCase().startsWith( str );
    }

    public byte[] getAttributeValueAsBytes()
    {
        return attributeValueBytes;
    }

    public long getEntityFlags()
    {
        return entityFlags;
    }

    public long getAttributeFlags()
    {
        return attributeFlags;
    }

    public boolean isIncremental()
    {
        return incremental;
    }

    public ObjectEngine geObjectEngine()
    {
        return objectEngine;
    }

    public ZeidonLogger getLogger()
    {
        return logger;
    }

    public PortableFileAttributeHandler getAttributeHandler()
    {
        return attributeHandler;
    }

    public void setAttributeHandler(PortableFileAttributeHandler attributeHandler)
    {
        this.attributeHandler = attributeHandler;
    }

    public static void readPortableFile( Task task, String filename, ZeidonLogger logger, PortableFileEntityHandler entityHandler )
    {
        try
        {
            logger.debug( "Reading portable file %s", filename );
            InputStream is = JoeUtils.getInputStream(task, filename, entityHandler.getClass().getClassLoader());
            if ( is == null )
                throw new ZeidonException( "Couldn't find file %s", filename );

            readPortableFile( task.getObjectEngine(), is, logger, entityHandler );
        }
        catch ( Exception e )
        {
            // Add filename to exception.
            throw ZeidonException.wrapException( e ).prependFilename( filename );
        }
    }

    public static void readPortableFile( ObjectEngine objectEngine, InputStream inputStream, ZeidonLogger logger, PortableFileEntityHandler entityHandler )
    {
        try
        {
            PortableFileReader reader = new PortableFileReader( objectEngine, logger, inputStream, entityHandler );
            reader.readEntities();
        }
        finally
        {
            IOUtils.closeQuietly( inputStream );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy