FileDocCategorySizeDatePackage
DataFactory.javaAPI DocAndroid 1.5 API29047Wed May 06 22:41:16 BST 2009com.vladium.emma.data

DataFactory

public abstract class DataFactory extends Object
author
Vlad Roubtsov, (C) 2003

Fields Summary
public static final byte
TYPE_METADATA
public static final byte
TYPE_COVERAGEDATA
private static final int
NULL_ARRAY_LENGTH
private static final int
MAGIC
private static final long
UNKNOWN
private static final int
FILE_HEADER_LENGTH
private static final int
ENTRY_HEADER_LENGTH
private static final boolean
DO_FSYNC
private static final int
IO_BUF_SIZE
Constructors Summary
private DataFactory()

Methods Summary
public static IMergeable[]load(java.io.File file)

 // must be consistent with mergeload()
    
    
           
         
    
        if (file == null) throw new IllegalArgumentException ("null input: file");
        
        return mergeload (file);
    
private static java.lang.StringmakeAppVersion(int major, int minor, int build)

        final StringBuffer buf = new StringBuffer ();
        
        buf.append (major);
        buf.append ('.");
        buf.append (minor);
        buf.append ('.");
        buf.append (build);
        
        return buf.toString ();
    
private static IMergeable[]mergeload(java.io.File file)

        final Logger log = Logger.getLogger ();
        final boolean trace1 = log.atTRACE1 ();
        final boolean trace2 = log.atTRACE2 ();
        final String method = "mergeload";
        
        long start = 0, end;
        
        if (trace1) start = System.currentTimeMillis ();
        
        final IMergeable [] result = new IMergeable [2];
        
        if (! file.exists ())
        {
            throw new IOException ("input file does not exist: [" + file.getAbsolutePath () +  "]");
        }
        else
        {
            RandomAccessFile raf = null;
            try
            {
                raf = new RandomAccessFile (file, "r");
                
                // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
                final long length = raf.length ();
                if (trace1) log.trace1 (method, "[" + file + "]: file length = " + length);
                
                if (length < FILE_HEADER_LENGTH)
                {
                    throw new IOException ("file [" + file.getAbsolutePath () + "] is corrupt or was not created by " + IAppConstants.APP_NAME);
                }
                else
                {
                    // TODO: data version checks parallel to persist()
                    
                    if (length > FILE_HEADER_LENGTH) // return {null, null} in case of equality
                    {
                        raf.seek (FILE_HEADER_LENGTH);
                                
                        // [assertion: file length > FILE_HEADER_LENGTH]
                        
                        // read entries until the first corrupt entry or the end of the file:
                        
                        long position = FILE_HEADER_LENGTH;
                        long entryLength;
                        
                        long entrystart = 0;
                        
                        while (true)
                        {
                            if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
                            if (position >= length) break;
                            
                            entryLength = raf.readLong ();
                            
                            if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
                                break;
                            else
                            {
                                final byte type = raf.readByte ();
                                if ((type < 0) || (type >= result.length))
                                    break;
                                    
                                if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength + " and type " + type);
                                {
                                    if (trace2) entrystart = System.currentTimeMillis ();
                                    final IMergeable data = readEntry (raf, type, entryLength);
                                    if (trace2) log.trace2 (method, "entry read in " + (System.currentTimeMillis () - entrystart) + " ms");                                    
                                    
                                    final IMergeable current = result [type];
                                    
                                    if (current == null)
                                        result [type] = data;
                                    else
                                        result [type] = current.merge (data); // note: later entries overrides earlier entries
                                }
                                
                                position += entryLength + ENTRY_HEADER_LENGTH;
                                
                                if ($assert.ENABLED) $assert.ASSERT (raf.getFD ().valid (), "FD invalid"); 
                                raf.seek (position);
                            }
                        }                        
                    }
                }
            }
            finally
            {
                if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
                raf = null;
            }
        }
        
        if (trace1)
        {
            end = System.currentTimeMillis ();
            
            log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms"); 
        }
        
        return result;
    
public static ICoverageDatanewCoverageData()

        return new CoverageData (); 
    
public static IMetaDatanewMetaData(CoverageOptions options)

        return new MetaData (options);
    
private static voidpersist(IMergeable data, byte type, java.io.File file)

        final Logger log = Logger.getLogger ();
        final boolean trace1 = log.atTRACE1 ();
        final boolean trace2 = log.atTRACE2 ();
        final String method = "persist";
        
        long start = 0, end;
        
        if (trace1) start = System.currentTimeMillis ();
        
        // TODO: 1.4 adds some interesting RAF open mode options as well
        // TODO: will this benefit from extra buffering?
        
        // TODO: data version checks

        RandomAccessFile raf = null;
        try
        {
            boolean overwrite = false;
            boolean truncate = false;
                        
            if (file.exists ())
            {
                // 'file' exists:
                
                if (! file.isFile ()) throw new IOException ("can persist in normal files only: " + file.getAbsolutePath ());
                
                raf = new RandomAccessFile (file, "rw");
                
                // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
                final long length = raf.length ();
                if (trace1) log.trace1 (method, "[" + file + "]: existing file length = " + length);
                
                
                if (length < 4)
                {
                    overwrite = true;
                    truncate = (length > 0);
                }
                else
                {
                    // [assertion: file length >= 4]
                    
                    // check header info before reading further:
                    final int magic = raf.readInt ();
                    if (magic != MAGIC)
                        throw new IOException ("cannot overwrite [" + file.getAbsolutePath () + "]: not created by " + IAppConstants.APP_NAME);
                    
                    if (length < FILE_HEADER_LENGTH)
                    {
                        // it's our file, but the header is corrupt: overwrite
                        overwrite = true;
                        truncate = true;
                    }  
                    else
                    {
                        // [assertion: file length >= FILE_HEADER_LENGTH]
                        
//                        if (! append)
//                        {
//                            // overwrite any existing data:
//                            
//                            raf.seek (FILE_HEADER_LENGTH);
//                            writeEntry (raf, FILE_HEADER_LENGTH, data, type);
//                        }
//                        else
                        {
                            // check data format version info:
                            final long dataVersion = raf.readLong ();
                            
                            if (dataVersion != IAppConstants.DATA_FORMAT_VERSION)
                            {
                                // read app version info for the error message:
                                
                                int major = 0, minor = 0, build = 0;
                                boolean gotAppVersion = false;
                                try
                                {
                                    major = raf.readInt ();
                                    minor = raf.readInt ();
                                    build = raf.readInt ();
                                    
                                    gotAppVersion = true;
                                }
                                catch (Throwable ignore) {}
                                
                                // TODO: error code here?
                                if (gotAppVersion)
                                {
                                    throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version [" + makeAppVersion (major, minor, build) + "]");
                                }
                                else
                                {
                                    throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version"); 
                                }
                            }
                            else
                            {
                                // [assertion: file header is valid and data format version is consistent]
                                
                                raf.seek (FILE_HEADER_LENGTH);
                                
                                if (length == FILE_HEADER_LENGTH)
                                {
                                    // no previous data entries: append 'data'
                                    
                                    writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
                                }
                                else
                                {
                                    // [assertion: file length > FILE_HEADER_LENGTH]
                                    
                                    // write 'data' starting with the first corrupt entry or the end of the file:
                                    
                                    long position = FILE_HEADER_LENGTH;
                                    long entryLength;
                                    
                                    while (true)
                                    {
                                        if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
                                        if (position >= length) break;
                                        
                                        entryLength = raf.readLong ();
                                        
                                        if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
                                            break;
                                        else
                                        {
                                            if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength);
                                            
                                            position += entryLength + ENTRY_HEADER_LENGTH; 
                                            raf.seek (position);
                                        }
                                    }
                                    
                                    if (trace2) log.trace2 (method, "[" + file + "]: adding entry at position " + position);
                                    writeEntry (log, raf, position, data, type);
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                // 'file' does not exist:
                
                if (trace1) log.trace1 (method, "[" + file + "]: creating a new file");
                
                final File parent = file.getParentFile ();
                if (parent != null) parent.mkdirs ();
                
                raf = new RandomAccessFile (file, "rw");
                
                overwrite = true;
            }
            
            
            if (overwrite)
            {
                // persist starting from 0 offset:
                
                if ($assert.ENABLED) $assert.ASSERT (raf != null, "raf = null");
                
                if (truncate) raf.seek (0);
                writeFileHeader (raf);
                if ($assert.ENABLED) $assert.ASSERT (raf.getFilePointer () == FILE_HEADER_LENGTH, "invalid header length: " + raf.getFilePointer ());
                
                writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
            }
        }
        finally
        {
            if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
            raf = null;
        }
        
        if (trace1)
        {
            end = System.currentTimeMillis ();
            
            log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms"); 
        }
    
public static voidpersist(IMetaData data, java.io.File file, boolean merge)

        if (data == null) throw new IllegalArgumentException ("null input: data");
        if (file == null) throw new IllegalArgumentException ("null input: file");
        
        if (! merge && file.exists ())
        {
            if (! file.delete ())
                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
        }
        
        persist (data, TYPE_METADATA, file);
    
public static voidpersist(ICoverageData data, java.io.File file, boolean merge)

        if (data == null) throw new IllegalArgumentException ("null input: data");
        if (file == null) throw new IllegalArgumentException ("null input: file");
        
        if (! merge && file.exists ())
        {
            if (! file.delete ())
                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
        }
        
        persist (data, TYPE_COVERAGEDATA, file);
    
public static voidpersist(ISessionData data, java.io.File file, boolean merge)

        if (data == null) throw new IllegalArgumentException ("null input: data");
        if (file == null) throw new IllegalArgumentException ("null input: file");
        
        if (! merge && file.exists ())
        {
            if (! file.delete ())
                throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
        }
        
        persist (data.getMetaData (), TYPE_METADATA, file); 
        persist (data.getCoverageData (), TYPE_COVERAGEDATA, file);
    
public static boolean[]readBooleanArray(java.io.DataInput in)

        final int length = in.readInt ();
        if (length == NULL_ARRAY_LENGTH)
            return null;
        else
        {
            final boolean [] result = new boolean [length];
            
            // read array in reverse order:
            for (int i = length; -- i >= 0; )
            {
                result [i] = in.readBoolean ();
            }
            
            return result;
        }
    
public static ICoverageDatareadCoverageData(java.net.URL url)

        ObjectInputStream oin = null;
        
        try
        {
            oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
            
            return (ICoverageData) oin.readObject ();
        }
        finally
        {
            if (oin != null) try { oin.close (); } catch (Exception ignore) {} 
        }
    
private static IMergeablereadEntry(java.io.RandomAccessFile raf, byte type, long entryLength)

        final Object data;
        
        RandomAccessFileInputStream rafin = new RandomAccessFileInputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
        {
//           ObjectInputStream oin = new ObjectInputStream (rafin);
//            
//            try
//            {
//                data = oin.readObject ();
//            }
//            catch (ClassNotFoundException cnfe)
//            {
//                // TODO: EMMA exception here
//                throw new IOException ("could not read data entry: " + cnfe.toString ());
//            }

            DataInputStream din = new DataInputStream (rafin);
            switch (type)
            {
                case TYPE_METADATA: data = MetaData.readExternal (din);
                    break;
                    
                default /* TYPE_COVERAGEDATA */: data = CoverageData.readExternal (din);
                    break;
                    
            } // end of switch
        }
        
        if ($assert.ENABLED) $assert.ASSERT (rafin.getCount () == entryLength, "entry length mismatch: " + rafin.getCount () + " != " + entryLength);
        
        return (IMergeable) data;
    
public static int[]readIntArray(java.io.DataInput in)

        final int length = in.readInt ();
        if (length == NULL_ARRAY_LENGTH)
            return null;
        else
        {
            final int [] result = new int [length];
            
            // read array in reverse order:
            for (int i = length; -- i >= 0; )
            {
                result [i] = in.readInt ();
            }
            
            return result;
        }
    
public static IMetaDatareadMetaData(java.net.URL url)

        ObjectInputStream oin = null;
        
        try
        {
            oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
            
            return (IMetaData) oin.readObject ();
        }
        finally
        {
            if (oin != null) try { oin.close (); } catch (Exception ignore) {} 
        }
    
public static voidwriteBooleanArray(boolean[] array, java.io.DataOutput out)

        if (array == null)
            out.writeInt (NULL_ARRAY_LENGTH);
        else
        {
            final int length = array.length;
            out.writeInt (length);
            
            // write array in reverse order:
            for (int i = length; -- i >= 0; )
            {
                out.writeBoolean (array [i]);
            }
        }
    
public static voidwriteCoverageData(ICoverageData data, java.io.OutputStream out)

        // TODO: prevent concurrent modification problems here
        
        ObjectOutputStream oout = new ObjectOutputStream (out);
        oout.writeObject (data);
    
private static voidwriteEntry(com.vladium.logging.Logger log, java.io.RandomAccessFile raf, long marker, IMergeable data, byte type)

        // [unfinished] entry header:
        writeEntryHeader (raf, type);
        
        // serialize 'data' starting with the current raf position:
        RandomAccessFileOutputStream rafout = new RandomAccessFileOutputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
        {
//            ObjectOutputStream oout = new ObjectOutputStream (rafout);
//            
//            oout.writeObject (data);
//            oout.flush ();
//            oout = null;
            
            DataOutputStream dout = new DataOutputStream (rafout);
            switch (type)
            {
                case TYPE_METADATA: MetaData.writeExternal ((MetaData) data, dout);
                    break;
                    
                default /* TYPE_COVERAGEDATA */: CoverageData.writeExternal ((CoverageData) data, dout);
                    break;
                    
            } // end of switch
            dout.flush ();
            dout = null;
            
            // truncate:
            raf.setLength (raf.getFilePointer ());
        }

        // transact this entry [finish the header]:
        raf.seek (marker);
        raf.writeLong (rafout.getCount ());
        if (DO_FSYNC) raf.getFD ().sync ();
        
        if (log.atTRACE2 ()) log.trace2 ("writeEntry", "entry [" + data.getClass ().getName () + "] length: " + rafout.getCount ());
    
private static voidwriteEntryHeader(java.io.DataOutput out, byte type)

        out.writeLong (UNKNOWN); // length placeholder
        out.writeByte (type);
    
private static voidwriteFileHeader(java.io.DataOutput out)

        out.writeInt (MAGIC);
        
        out.writeLong (IAppConstants.DATA_FORMAT_VERSION);
        
        out.writeInt (IAppConstants.APP_MAJOR_VERSION);
        out.writeInt (IAppConstants.APP_MINOR_VERSION);
        out.writeInt (IAppConstants.APP_BUILD_ID);
    
public static voidwriteIntArray(int[] array, java.io.DataOutput out)

        if (array == null)
            out.writeInt (NULL_ARRAY_LENGTH);
        else
        {
            final int length = array.length;
            out.writeInt (length);
            
            // write array in reverse order:
            for (int i = length; -- i >= 0; )
            {
                out.writeInt (array [i]);
            }
        }
    
public static voidwriteMetaData(IMetaData data, java.io.OutputStream out)

        ObjectOutputStream oout = new ObjectOutputStream (out);
        oout.writeObject (data);
    
public static voidwriteMetaData(IMetaData data, java.net.URL url)

        final URLConnection connection = url.openConnection ();
        connection.setDoOutput (true);
        
        OutputStream out = null;
        try
        {
            out = connection.getOutputStream ();
            
            writeMetaData (data, out);
            out.flush ();
        }
        finally
        {
            if (out != null) try { out.close (); } catch (Exception ignore) {} 
        }