Home | History | Annotate | Download | only in data
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: DataFactory.java,v 1.1.1.1.2.3 2004/07/16 23:32:29 vlad_r Exp $
      8  */
      9 package com.vladium.emma.data;
     10 
     11 import java.io.BufferedInputStream;
     12 import java.io.BufferedOutputStream;
     13 import java.io.DataInput;
     14 import java.io.DataInputStream;
     15 import java.io.DataOutput;
     16 import java.io.DataOutputStream;
     17 import java.io.File;
     18 import java.io.FileDescriptor;
     19 import java.io.FileInputStream;
     20 import java.io.FileOutputStream;
     21 import java.io.IOException;
     22 import java.io.ObjectInputStream;
     23 import java.io.ObjectOutputStream;
     24 import java.io.OutputStream;
     25 import java.io.RandomAccessFile;
     26 import java.net.URL;
     27 import java.net.URLConnection;
     28 
     29 import com.vladium.logging.Logger;
     30 import com.vladium.util.asserts.$assert;
     31 import com.vladium.emma.IAppConstants;
     32 
     33 // ----------------------------------------------------------------------------
     34 /**
     35  * @author Vlad Roubtsov, (C) 2003
     36  */
     37 public
     38 abstract class DataFactory
     39 {
     40     // public: ................................................................
     41 
     42     // TODO: file compaction
     43     // TODO: file locking
     44 
     45     // TODO: what's the best place for these?
     46 
     47     public static final byte TYPE_METADATA          = 0x0; // must start with 0
     48     public static final byte TYPE_COVERAGEDATA      = 0x1; // must be consistent with mergeload()
     49 
     50 
     51     public static IMergeable [] load (final File file)
     52         throws IOException
     53     {
     54         if (file == null) throw new IllegalArgumentException ("null input: file");
     55 
     56         return mergeload (file);
     57     }
     58 
     59     public static void persist (final IMetaData data, final File file, final boolean merge)
     60         throws IOException
     61     {
     62         if (data == null) throw new IllegalArgumentException ("null input: data");
     63         if (file == null) throw new IllegalArgumentException ("null input: file");
     64 
     65         if (! merge && file.exists ())
     66         {
     67             if (! file.delete ())
     68                 throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
     69         }
     70 
     71         persist (data, TYPE_METADATA, file);
     72     }
     73 
     74     public static void persist (final ICoverageData data, final File file, final boolean merge)
     75         throws IOException
     76     {
     77         if (data == null) throw new IllegalArgumentException ("null input: data");
     78         if (file == null) throw new IllegalArgumentException ("null input: file");
     79 
     80         if (! merge && file.exists ())
     81         {
     82             if (! file.delete ())
     83                 throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
     84         }
     85 
     86         persist (data, TYPE_COVERAGEDATA, file);
     87     }
     88 
     89     public static void persist (final ISessionData data, final File file, final boolean merge)
     90         throws IOException
     91     {
     92         if (data == null) throw new IllegalArgumentException ("null input: data");
     93         if (file == null) throw new IllegalArgumentException ("null input: file");
     94 
     95         if (! merge && file.exists ())
     96         {
     97             if (! file.delete ())
     98                 throw new IOException ("could not delete file [" + file.getAbsolutePath () + "]");
     99         }
    100 
    101         persist (data.getMetaData (), TYPE_METADATA, file);
    102         persist (data.getCoverageData (), TYPE_COVERAGEDATA, file);
    103     }
    104 
    105 
    106     public static IMetaData newMetaData (final CoverageOptions options)
    107     {
    108         return new MetaData (options);
    109     }
    110 
    111     public static ICoverageData newCoverageData ()
    112     {
    113         return new CoverageData ();
    114     }
    115 
    116     public static IMetaData readMetaData (final URL url)
    117         throws IOException, ClassNotFoundException
    118     {
    119         ObjectInputStream oin = null;
    120 
    121         try
    122         {
    123             oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
    124 
    125             return (IMetaData) oin.readObject ();
    126         }
    127         finally
    128         {
    129             if (oin != null) try { oin.close (); } catch (Exception ignore) {}
    130         }
    131     }
    132 
    133     public static void writeMetaData (final IMetaData data, final OutputStream out)
    134         throws IOException
    135     {
    136         ObjectOutputStream oout = new ObjectOutputStream (out);
    137         oout.writeObject (data);
    138     }
    139 
    140     public static void writeMetaData (final IMetaData data, final URL url)
    141         throws IOException
    142     {
    143         final URLConnection connection = url.openConnection ();
    144         connection.setDoOutput (true);
    145 
    146         OutputStream out = null;
    147         try
    148         {
    149             out = connection.getOutputStream ();
    150 
    151             writeMetaData (data, out);
    152             out.flush ();
    153         }
    154         finally
    155         {
    156             if (out != null) try { out.close (); } catch (Exception ignore) {}
    157         }
    158     }
    159 
    160     public static ICoverageData readCoverageData (final URL url)
    161         throws IOException, ClassNotFoundException
    162     {
    163         ObjectInputStream oin = null;
    164 
    165         try
    166         {
    167             oin = new ObjectInputStream (new BufferedInputStream (url.openStream (), 32 * 1024));
    168 
    169             return (ICoverageData) oin.readObject ();
    170         }
    171         finally
    172         {
    173             if (oin != null) try { oin.close (); } catch (Exception ignore) {}
    174         }
    175     }
    176 
    177     public static void writeCoverageData (final ICoverageData data, final OutputStream out)
    178         throws IOException
    179     {
    180         // TODO: prevent concurrent modification problems here
    181 
    182         ObjectOutputStream oout = new ObjectOutputStream (out);
    183         oout.writeObject (data);
    184     }
    185 
    186     public static int [] readIntArray (final DataInput in)
    187         throws IOException
    188     {
    189         final int length = in.readInt ();
    190         if (length == NULL_ARRAY_LENGTH)
    191             return null;
    192         else
    193         {
    194             final int [] result = new int [length];
    195 
    196             // read array in reverse order:
    197             for (int i = length; -- i >= 0; )
    198             {
    199                 result [i] = in.readInt ();
    200             }
    201 
    202             return result;
    203         }
    204     }
    205 
    206     public static boolean [] readBooleanArray (final DataInput in)
    207         throws IOException
    208     {
    209         final int length = in.readInt ();
    210         if (length == NULL_ARRAY_LENGTH)
    211             return null;
    212         else
    213         {
    214             final boolean [] result = new boolean [length];
    215 
    216             // read array in reverse order:
    217             for (int i = length; -- i >= 0; )
    218             {
    219                 result [i] = in.readBoolean ();
    220             }
    221 
    222             return result;
    223         }
    224     }
    225 
    226     public static void writeIntArray (final int [] array, final DataOutput out)
    227         throws IOException
    228     {
    229         if (array == null)
    230             out.writeInt (NULL_ARRAY_LENGTH);
    231         else
    232         {
    233             final int length = array.length;
    234             out.writeInt (length);
    235 
    236             // write array in reverse order:
    237             for (int i = length; -- i >= 0; )
    238             {
    239                 out.writeInt (array [i]);
    240             }
    241         }
    242     }
    243 
    244     public static void writeBooleanArray (final boolean [] array, final DataOutput out)
    245         throws IOException
    246     {
    247         if (array == null)
    248             out.writeInt (NULL_ARRAY_LENGTH);
    249         else
    250         {
    251             final int length = array.length;
    252             out.writeInt (length);
    253 
    254             // write array in reverse order:
    255             for (int i = length; -- i >= 0; )
    256             {
    257                 out.writeBoolean (array [i]);
    258             }
    259         }
    260     }
    261 
    262     // protected: .............................................................
    263 
    264     // package: ...............................................................
    265 
    266     // private: ...............................................................
    267 
    268 
    269     private static final class UCFileInputStream extends FileInputStream
    270     {
    271         public void close ()
    272         {
    273         }
    274 
    275         UCFileInputStream (final FileDescriptor fd)
    276         {
    277             super (fd);
    278 
    279             if ($assert.ENABLED) $assert.ASSERT (fd.valid (), "UCFileInputStream.<init>: FD invalid");
    280         }
    281 
    282     } // end of nested class
    283 
    284     private static final class UCFileOutputStream extends FileOutputStream
    285     {
    286         public void close ()
    287         {
    288         }
    289 
    290         UCFileOutputStream (final FileDescriptor fd)
    291         {
    292             super (fd);
    293 
    294             if ($assert.ENABLED) $assert.ASSERT (fd.valid (), "UCFileOutputStream.<init>: FD invalid");
    295         }
    296 
    297     } // end of nested class
    298 
    299 
    300     private static final class RandomAccessFileInputStream extends BufferedInputStream
    301     {
    302         public final int read () throws IOException
    303         {
    304             final int rc = super.read ();
    305             if (rc >= 0) ++ m_count;
    306 
    307             return rc;
    308         }
    309 
    310         public final int read (final byte [] b, final int off, final int len)
    311             throws IOException
    312         {
    313             final int rc = super.read (b, off, len);
    314             if (rc >= 0) m_count += rc;
    315 
    316             return rc;
    317         }
    318 
    319         public final int read (final byte [] b) throws IOException
    320         {
    321             final int rc = super.read (b);
    322             if (rc >= 0) m_count += rc;
    323 
    324             return rc;
    325         }
    326 
    327         public void close ()
    328         {
    329         }
    330 
    331 
    332         RandomAccessFileInputStream (final RandomAccessFile raf, final int bufSize)
    333             throws IOException
    334         {
    335             super (new UCFileInputStream (raf.getFD ()), bufSize);
    336         }
    337 
    338         final long getCount ()
    339         {
    340             return m_count;
    341         }
    342 
    343         private long m_count;
    344 
    345     } // end of nested class
    346 
    347     private static final class RandomAccessFileOutputStream extends BufferedOutputStream
    348     {
    349         public final void write (final byte [] b, final int off, final int len) throws IOException
    350         {
    351             super.write (b, off, len);
    352             m_count += len;
    353         }
    354 
    355         public final void write (final byte [] b) throws IOException
    356         {
    357             super.write (b);
    358             m_count += b.length;
    359         }
    360 
    361         public final void write (final int b) throws IOException
    362         {
    363             super.write (b);
    364             ++ m_count;
    365         }
    366 
    367         public void close ()
    368         {
    369         }
    370 
    371 
    372         RandomAccessFileOutputStream (final RandomAccessFile raf, final int bufSize)
    373             throws IOException
    374         {
    375             super (new UCFileOutputStream (raf.getFD ()), bufSize);
    376         }
    377 
    378         final long getCount ()
    379         {
    380             return m_count;
    381         }
    382 
    383         private long m_count;
    384 
    385     } // end of nested class
    386 
    387 
    388     private DataFactory () {} // prevent subclassing
    389 
    390     /*
    391      * input checked by the caller
    392      */
    393     private static IMergeable [] mergeload (final File file)
    394         throws IOException
    395     {
    396         final Logger log = Logger.getLogger ();
    397         final boolean trace1 = log.atTRACE1 ();
    398         final boolean trace2 = log.atTRACE2 ();
    399         final String method = "mergeload";
    400 
    401         long start = 0, end;
    402 
    403         if (trace1) start = System.currentTimeMillis ();
    404 
    405         final IMergeable [] result = new IMergeable [2];
    406 
    407         if (! file.exists ())
    408         {
    409             throw new IOException ("input file does not exist: [" + file.getAbsolutePath () +  "]");
    410         }
    411         else
    412         {
    413             RandomAccessFile raf = null;
    414             try
    415             {
    416                 raf = new RandomAccessFile (file, "r");
    417 
    418                 // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
    419                 final long length = raf.length ();
    420                 if (trace1) log.trace1 (method, "[" + file + "]: file length = " + length);
    421 
    422                 if (length < FILE_HEADER_LENGTH)
    423                 {
    424                     throw new IOException ("file [" + file.getAbsolutePath () + "] is corrupt or was not created by " + IAppConstants.APP_NAME);
    425                 }
    426                 else
    427                 {
    428                     // TODO: data version checks parallel to persist()
    429 
    430                     if (length > FILE_HEADER_LENGTH) // return {null, null} in case of equality
    431                     {
    432                         raf.seek (FILE_HEADER_LENGTH);
    433 
    434                         // [assertion: file length > FILE_HEADER_LENGTH]
    435 
    436                         // read entries until the first corrupt entry or the end of the file:
    437 
    438                         long position = FILE_HEADER_LENGTH;
    439                         long entryLength;
    440 
    441                         long entrystart = 0;
    442 
    443                         while (true)
    444                         {
    445                             if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
    446                             if (position >= length) break;
    447 
    448                             entryLength = raf.readLong ();
    449 
    450                             if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
    451                                 break;
    452                             else
    453                             {
    454                                 final byte type = raf.readByte ();
    455                                 if ((type < 0) || (type >= result.length))
    456                                     break;
    457 
    458                                 if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength + " and type " + type);
    459                                 {
    460                                     if (trace2) entrystart = System.currentTimeMillis ();
    461                                     final IMergeable data = readEntry (raf, type, entryLength);
    462                                     if (trace2) log.trace2 (method, "entry read in " + (System.currentTimeMillis () - entrystart) + " ms");
    463 
    464                                     final IMergeable current = result [type];
    465 
    466                                     if (current == null)
    467                                         result [type] = data;
    468                                     else
    469                                         result [type] = current.merge (data); // note: later entries overrides earlier entries
    470                                 }
    471 
    472                                 position += entryLength + ENTRY_HEADER_LENGTH;
    473 
    474                                 if ($assert.ENABLED) $assert.ASSERT (raf.getFD ().valid (), "FD invalid");
    475                                 raf.seek (position);
    476                             }
    477                         }
    478                     }
    479                 }
    480             }
    481             finally
    482             {
    483                 if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
    484                 raf = null;
    485             }
    486         }
    487 
    488         if (trace1)
    489         {
    490             end = System.currentTimeMillis ();
    491 
    492             log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms");
    493         }
    494 
    495         return result;
    496     }
    497 
    498 
    499     /*
    500      * input checked by the caller
    501      */
    502     private static void persist (final IMergeable data, final byte type, final File file)
    503         throws IOException
    504     {
    505         final Logger log = Logger.getLogger ();
    506         final boolean trace1 = log.atTRACE1 ();
    507         final boolean trace2 = log.atTRACE2 ();
    508         final String method = "persist";
    509 
    510         long start = 0, end;
    511 
    512         if (trace1) start = System.currentTimeMillis ();
    513 
    514         // TODO: 1.4 adds some interesting RAF open mode options as well
    515         // TODO: will this benefit from extra buffering?
    516 
    517         // TODO: data version checks
    518 
    519         RandomAccessFile raf = null;
    520         try
    521         {
    522             boolean overwrite = false;
    523             boolean truncate = false;
    524 
    525             if (file.exists ())
    526             {
    527                 // 'file' exists:
    528 
    529                 if (! file.isFile ()) throw new IOException ("can persist in normal files only: " + file.getAbsolutePath ());
    530 
    531                 raf = new RandomAccessFile (file, "rw");
    532 
    533                 // 'file' is a valid existing file, but it could still be of 0 length or otherwise corrupt:
    534                 final long length = raf.length ();
    535                 if (trace1) log.trace1 (method, "[" + file + "]: existing file length = " + length);
    536 
    537 
    538                 if (length < 4)
    539                 {
    540                     overwrite = true;
    541                     truncate = (length > 0);
    542                 }
    543                 else
    544                 {
    545                     // [assertion: file length >= 4]
    546 
    547                     // check header info before reading further:
    548                     final int magic = raf.readInt ();
    549                     if (magic != MAGIC)
    550                         throw new IOException ("cannot overwrite [" + file.getAbsolutePath () + "]: not created by " + IAppConstants.APP_NAME);
    551 
    552                     if (length < FILE_HEADER_LENGTH)
    553                     {
    554                         // it's our file, but the header is corrupt: overwrite
    555                         overwrite = true;
    556                         truncate = true;
    557                     }
    558                     else
    559                     {
    560                         // [assertion: file length >= FILE_HEADER_LENGTH]
    561 
    562 //                        if (! append)
    563 //                        {
    564 //                            // overwrite any existing data:
    565 //
    566 //                            raf.seek (FILE_HEADER_LENGTH);
    567 //                            writeEntry (raf, FILE_HEADER_LENGTH, data, type);
    568 //                        }
    569 //                        else
    570                         {
    571                             // check data format version info:
    572                             final long dataVersion = raf.readLong ();
    573 
    574                             if (dataVersion != IAppConstants.DATA_FORMAT_VERSION)
    575                             {
    576                                 // read app version info for the error message:
    577 
    578                                 int major = 0, minor = 0, build = 0;
    579                                 boolean gotAppVersion = false;
    580                                 try
    581                                 {
    582                                     major = raf.readInt ();
    583                                     minor = raf.readInt ();
    584                                     build = raf.readInt ();
    585 
    586                                     gotAppVersion = true;
    587                                 }
    588                                 catch (Throwable ignore) {}
    589 
    590                                 // TODO: error code here?
    591                                 if (gotAppVersion)
    592                                 {
    593                                     throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version [" + makeAppVersion (major, minor, build) + "]");
    594                                 }
    595                                 else
    596                                 {
    597                                     throw new IOException ("cannot merge new data into [" + file.getAbsolutePath () + "]: created by another " + IAppConstants.APP_NAME + " version");
    598                                 }
    599                             }
    600                             else
    601                             {
    602                                 // [assertion: file header is valid and data format version is consistent]
    603 
    604                                 raf.seek (FILE_HEADER_LENGTH);
    605 
    606                                 if (length == FILE_HEADER_LENGTH)
    607                                 {
    608                                     // no previous data entries: append 'data'
    609 
    610                                     writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
    611                                 }
    612                                 else
    613                                 {
    614                                     // [assertion: file length > FILE_HEADER_LENGTH]
    615 
    616                                     // write 'data' starting with the first corrupt entry or the end of the file:
    617 
    618                                     long position = FILE_HEADER_LENGTH;
    619                                     long entryLength;
    620 
    621                                     while (true)
    622                                     {
    623                                         if (trace2) log.trace2 (method, "[" + file + "]: position " + raf.getFilePointer ());
    624                                         if (position >= length) break;
    625 
    626                                         entryLength = raf.readLong ();
    627 
    628                                         if ((entryLength <= 0) || (position + entryLength + ENTRY_HEADER_LENGTH > length))
    629                                             break;
    630                                         else
    631                                         {
    632                                             if (trace2) log.trace2 (method, "[" + file + "]: found valid entry of size " + entryLength);
    633 
    634                                             position += entryLength + ENTRY_HEADER_LENGTH;
    635                                             raf.seek (position);
    636                                         }
    637                                     }
    638 
    639                                     if (trace2) log.trace2 (method, "[" + file + "]: adding entry at position " + position);
    640                                     writeEntry (log, raf, position, data, type);
    641                                 }
    642                             }
    643                         }
    644                     }
    645                 }
    646             }
    647             else
    648             {
    649                 // 'file' does not exist:
    650 
    651                 if (trace1) log.trace1 (method, "[" + file + "]: creating a new file");
    652 
    653                 final File parent = file.getParentFile ();
    654                 if (parent != null) parent.mkdirs ();
    655 
    656                 raf = new RandomAccessFile (file, "rw");
    657 
    658                 overwrite = true;
    659             }
    660 
    661 
    662             if (overwrite)
    663             {
    664                 // persist starting from 0 offset:
    665 
    666                 if ($assert.ENABLED) $assert.ASSERT (raf != null, "raf = null");
    667 
    668                 if (truncate) raf.seek (0);
    669                 writeFileHeader (raf);
    670                 if ($assert.ENABLED) $assert.ASSERT (raf.getFilePointer () == FILE_HEADER_LENGTH, "invalid header length: " + raf.getFilePointer ());
    671 
    672                 writeEntry (log, raf, FILE_HEADER_LENGTH, data, type);
    673             }
    674         }
    675         finally
    676         {
    677             if (raf != null) try { raf.close (); } catch (Throwable ignore) {}
    678             raf = null;
    679         }
    680 
    681         if (trace1)
    682         {
    683             end = System.currentTimeMillis ();
    684 
    685             log.trace1 (method, "[" + file + "]: file processed in " + (end - start) + " ms");
    686         }
    687     }
    688 
    689     private static void writeFileHeader (final DataOutput out)
    690         throws IOException
    691     {
    692         out.writeInt (MAGIC);
    693 
    694         out.writeLong (IAppConstants.DATA_FORMAT_VERSION);
    695 
    696         out.writeInt (IAppConstants.APP_MAJOR_VERSION);
    697         out.writeInt (IAppConstants.APP_MINOR_VERSION);
    698         out.writeInt (IAppConstants.APP_BUILD_ID);
    699     }
    700 
    701     private static void writeEntryHeader (final DataOutput out, final byte type)
    702         throws IOException
    703     {
    704         out.writeLong (UNKNOWN); // length placeholder
    705         out.writeByte (type);
    706     }
    707 
    708     private static void writeEntry (final Logger log, final RandomAccessFile raf, final long marker, final IMergeable data, final byte type)
    709         throws IOException
    710     {
    711         // [unfinished] entry header:
    712         writeEntryHeader (raf, type);
    713 
    714         // serialize 'data' starting with the current raf position:
    715         RandomAccessFileOutputStream rafout = new RandomAccessFileOutputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
    716         {
    717 //            ObjectOutputStream oout = new ObjectOutputStream (rafout);
    718 //
    719 //            oout.writeObject (data);
    720 //            oout.flush ();
    721 //            oout = null;
    722 
    723             DataOutputStream dout = new DataOutputStream (rafout);
    724             switch (type)
    725             {
    726                 case TYPE_METADATA: MetaData.writeExternal ((MetaData) data, dout);
    727                     break;
    728 
    729                 default /* TYPE_COVERAGEDATA */: CoverageData.writeExternal ((CoverageData) data, dout);
    730                     break;
    731 
    732             } // end of switch
    733             dout.flush ();
    734             dout = null;
    735 
    736             // truncate:
    737             raf.setLength (raf.getFilePointer ());
    738         }
    739 
    740         // transact this entry [finish the header]:
    741         raf.seek (marker);
    742         raf.writeLong (rafout.getCount ());
    743         if (DO_FSYNC) raf.getFD ().sync ();
    744 
    745         if (log.atTRACE2 ()) log.trace2 ("writeEntry", "entry [" + data.getClass ().getName () + "] length: " + rafout.getCount ());
    746     }
    747 
    748     private static IMergeable readEntry (final RandomAccessFile raf, final byte type, final long entryLength)
    749         throws IOException
    750     {
    751         final Object data;
    752 
    753         RandomAccessFileInputStream rafin = new RandomAccessFileInputStream (raf, IO_BUF_SIZE); // note: no new file descriptors created here
    754         {
    755 //           ObjectInputStream oin = new ObjectInputStream (rafin);
    756 //
    757 //            try
    758 //            {
    759 //                data = oin.readObject ();
    760 //            }
    761 //            catch (ClassNotFoundException cnfe)
    762 //            {
    763 //                // TODO: EMMA exception here
    764 //                throw new IOException ("could not read data entry: " + cnfe.toString ());
    765 //            }
    766 
    767             DataInputStream din = new DataInputStream (rafin);
    768             switch (type)
    769             {
    770                 case TYPE_METADATA: data = MetaData.readExternal (din);
    771                     break;
    772 
    773                 default /* TYPE_COVERAGEDATA */: data = CoverageData.readExternal (din);
    774                     break;
    775 
    776             } // end of switch
    777         }
    778 
    779         if ($assert.ENABLED) $assert.ASSERT (rafin.getCount () == entryLength, "entry length mismatch: " + rafin.getCount () + " != " + entryLength);
    780 
    781         return (IMergeable) data;
    782     }
    783 
    784 
    785     /*
    786      * This is cloned from EMMAProperties by design, to eliminate a CONSTANT_Class_info
    787      * dependency between this and EMMAProperties classes.
    788      */
    789     private static String makeAppVersion (final int major, final int minor, final int build)
    790     {
    791         final StringBuffer buf = new StringBuffer ();
    792 
    793         buf.append (major);
    794         buf.append ('.');
    795         buf.append (minor);
    796         buf.append ('.');
    797         buf.append (build);
    798 
    799         return buf.toString ();
    800     }
    801 
    802 
    803     private static final int NULL_ARRAY_LENGTH = -1;
    804 
    805     private static final int MAGIC = 0x454D4D41; // "EMMA"
    806     private static final long UNKNOWN = 0L;
    807     private static final int FILE_HEADER_LENGTH = 4 + 8 + 3 * 4; // IMPORTANT: update on writeFileHeader() changes
    808     private static final int ENTRY_HEADER_LENGTH = 8 + 1; // IMPORTANT: update on writeEntryHeader() changes
    809     private static final boolean DO_FSYNC = true;
    810     private static final int IO_BUF_SIZE = 32 * 1024;
    811 
    812 } // end of class
    813 // ----------------------------------------------------------------------------
    814