001/**
002 * Portions Copyright 2003 Sun Microsystems, Inc.
003 * Portions Copyright 1999-2001 Language Technologies Institute, 
004 * Carnegie Mellon University.
005 * All Rights Reserved.  Use is subject to license terms.
006 * 
007 * See the file "license.terms" for information on usage and
008 * redistribution of this file, and for a DISCLAIMER OF ALL 
009 * WARRANTIES.
010 */
011package com.sun.speech.freetts.clunits;
012
013import java.io.BufferedOutputStream;
014import java.io.BufferedReader;
015import java.io.DataInputStream;
016import java.io.DataOutputStream;
017import java.io.FileInputStream;
018import java.io.FileNotFoundException;
019import java.io.FileOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.InputStreamReader;
023import java.net.URL;
024import java.nio.ByteBuffer;
025import java.nio.MappedByteBuffer;
026import java.nio.channels.FileChannel;
027import java.util.ArrayList;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Map;
032import java.util.NoSuchElementException;
033import java.util.StringTokenizer;
034
035import com.sun.speech.freetts.cart.CART;
036import com.sun.speech.freetts.cart.CARTImpl;
037import com.sun.speech.freetts.relp.SampleInfo;
038import com.sun.speech.freetts.relp.SampleSet;
039import com.sun.speech.freetts.util.BulkTimer;
040import com.sun.speech.freetts.util.Utilities;
041
042
043/**
044 * Provides support for the cluster unit database. The use of the
045 * cluster unit database is confined to this clunits package. This
046 * class provides a main program that can be used to convert from a
047 * text version of the database to a binary version of the database.
048 *
049 * The ClusterUnitDataBase can be loaded from a text or a binary
050 * source. The binary form of the database loads much faster and
051 * therefore is generally used in a deployed system.
052 *
053 */
054public class ClusterUnitDatabase {
055
056    final  static int CLUNIT_NONE = 65535;
057
058    private DatabaseClusterUnit[] units;
059    private UnitType[] unitTypes;
060    private SampleSet sts;
061    private SampleSet mcep;
062    
063    private UnitOriginInfo[] unitOrigins; // for debugging
064
065    private int continuityWeight;
066    private int optimalCoupling;
067    private int extendSelections;
068    private int joinMethod;
069    private int[] joinWeights;
070    private int joinWeightShift;
071
072    private Map cartMap = new HashMap();
073    private CART defaultCart = null;
074
075    private transient List unitList;
076    private transient int lineCount;
077    private transient List unitTypesList;
078
079    private final static int MAGIC = 0xf0cacc1a;
080    private final static int VERSION = 0x1000;
081
082
083    /**
084     * Creates the UnitDatabase from the given input stream.
085     *
086     * @param is the input stream to read the database from
087     * @param isBinary the input stream is a binary stream
088     *
089     * @throws IOException if there is trouble opening the DB
090     */
091    ClusterUnitDatabase(URL url, boolean isBinary) throws IOException {
092        BulkTimer.LOAD.start("ClusterUnitDatabase");
093        InputStream is = Utilities.getInputStream(url);
094        if (isBinary) {
095            loadBinary(is);
096        } else {
097            loadText(is);
098        }
099        is.close();
100    // Attempt to load debug info from a .debug resource.
101    // This will silently fail if no debug info is available.
102    String urlString = url.toExternalForm();
103    URL debugURL = new URL(urlString.substring(0, urlString.lastIndexOf(".")) + ".debug");
104    try {
105        InputStream debugInfoStream = Utilities.getInputStream(debugURL);
106        loadUnitOrigins(debugInfoStream);
107    } catch (IOException ioe) {
108        // Silently ignore if you cannot load the debug info
109    }
110        BulkTimer.LOAD.stop("ClusterUnitDatabase");
111    }
112
113
114    /**
115     * Retrieves the begininning sample index for the
116     * given entry.
117     *
118     * @param unitEntry the entry of interest
119     *
120     * @return the begininning sample index
121     */
122    int getStart(int unitEntry) {
123        return units[unitEntry].start;
124    }
125
126    /**
127     * Retrieves the ending sample index for the
128     * given entry.
129     *
130     * @param unitEntry the entry of interest
131     *
132     * @return the ending sample index
133     */
134    int getEnd(int unitEntry) {
135        return units[unitEntry].end;
136    }
137
138    /**
139     * Retrieves the phone for the given entry
140     *
141     * @param unitEntry the entry of interest
142     *
143     * @return the phone for the entry
144     */
145    int getPhone(int unitEntry) {
146        return units[unitEntry].phone;
147    }
148
149    /**
150     * Returns the cart of the given unit type.
151     *
152     * @param unitType the type of cart
153     *
154     * @return the cart 
155     */
156    CART getTree(String unitType) {
157        CART cart =  (CART) cartMap.get(unitType);
158
159        if (cart == null) {
160            System.err.println("ClusterUnitDatabase: can't find tree for " 
161                    + unitType);
162            return defaultCart;         // "graceful" failrue
163        }
164        return cart;
165    }
166
167    /**
168     * Retrieves the type index for the name given a name. 
169     *
170     * @param name the name
171     *
172     * @return the index for the name
173     */
174// [[[TODO: perhaps replace this with  java.util.Arrays.binarySearch]]]
175    int getUnitTypeIndex(String name) {
176        int start, end, mid, c;
177
178        start = 0;
179        end = unitTypes.length;
180
181        while (start < end) {
182            mid = (start + end) / 2;
183            c = unitTypes[mid].getName().compareTo(name);
184            if (c == 0) {
185                return mid;
186            } else if (c > 0) {
187                end = mid;
188            } else {
189                start = mid + 1;
190            }
191        }
192        return -1;
193    }
194
195    /**
196     * Retrieves the unit index given a unit type and val.
197     *
198     * @param unitType the type of the unit
199     * @param instance the value associated with the unit
200     *
201     * @return the index.
202     */
203    int getUnitIndex(String unitType, int instance) {
204        int i = getUnitTypeIndex(unitType);
205        if (i == -1) {
206            error("getUnitIndex: can't find unit type " + unitType);
207            i = 0;
208        }
209        if (instance >= unitTypes[i].getCount()) {
210            error("getUnitIndex: can't find instance " 
211                    + instance + " of " + unitType);
212            instance = 0;
213        }
214        return unitTypes[i].getStart() + instance;
215    }
216
217
218    /**
219     * Retrieves the index for the name given a name. 
220     *
221     * @param name the name
222     *
223     * @return the index for the name
224     */
225    int getUnitIndexName(String name) {
226        int lastIndex = name.lastIndexOf('_');
227        if (lastIndex == -1) {
228            error("getUnitIndexName: bad unit name " + name);
229            return -1;
230        }
231        int index = Integer.parseInt(name.substring(lastIndex + 1));
232        String type = name.substring(0, lastIndex);
233        return getUnitIndex(type, index);
234    }
235
236    /**
237     * Retrieves the extend selections setting.
238     *
239     * @return the extend selections setting
240     */
241    int getExtendSelections() {
242        return extendSelections;
243    }
244
245    /**
246     * Gets the next unit.
247     *
248     * @return the next unit
249     */
250    int getNextUnit(int which) {
251        return units[which].next;
252    }
253
254    /**
255     * Gets the previous units.
256     *
257     * @param which which unit is of interest
258     *
259     * @return the previous unit
260     */
261    int getPrevUnit(int which) {
262        return units[which].prev;
263    }
264
265
266    /**
267     * Determines if the unit types are equal.
268     *
269     * @param unitA the index of unit a
270     * @param unitB the index of unit B
271     *
272     * @return <code>true</code> if the types of units a and b are
273     *     equal; otherwise return <code>false</code> 
274     */
275    boolean isUnitTypeEqual(int unitA, int unitB)  {
276        return units[unitA].type == units[unitB].type;
277        // String nameA = units[unitA].getName();
278        // String nameB = units[unitB].getName();
279        // int lastUnderscore = nameA.lastIndexOf('_');
280        // return nameA.regionMatches(0, nameB, 0, lastUnderscore + 1);
281    }
282
283    /**
284     * Retrieves the optimal coupling setting.
285     *
286     * @return the optimal coupling setting
287     */
288    int getOptimalCoupling() {
289        return optimalCoupling;
290    }
291
292    /**
293     * Retrieves the continuity weight setting.
294     *
295     * 
296     * @return  the continuity weight setting
297     */
298    int getContinuityWeight()  {
299        return continuityWeight;
300    }
301
302    /**
303     * Retrieves the join weights.
304     *
305     * @return  the join weights
306     */
307    int[] getJoinWeights() {
308        return joinWeights;
309    }
310
311
312    /**
313     * Looks up the unit with the given name.
314     *
315     * @param unitName the name of the unit to look for
316     *
317     * @return the unit or the defaultUnit if not found.
318     */
319    DatabaseClusterUnit getUnit(String unitName) {
320        return null;
321    }
322    
323    /**
324     * Looks up the unit with the given index.
325     *
326     * @param index the index of the unit to look for
327     *
328     * @return the unit 
329     */
330    DatabaseClusterUnit getUnit(int which) {
331        return units[which];
332    }
333
334    /**
335     * Looks up the origin info for the unit with the given index.
336     *
337     * @param index the index of the unit to look for
338     *
339     * @return the origin info for the unit, or null if none is available 
340     */
341    UnitOriginInfo getUnitOriginInfo(int which) {
342        if (unitOrigins != null)
343            return unitOrigins[which];
344        else
345            return null;
346    }
347
348    
349    /**
350     * Returns the name of this UnitDatabase.
351     *
352     * @return the name of the database
353     */
354    String getName() {
355        return "ClusterUnitDatabase";
356    }
357    
358    /**
359     * Returns the sample info for this set of data.
360     *
361     * @return the sample info
362     */
363    SampleInfo getSampleInfo() {
364        return sts.getSampleInfo();
365    }
366
367
368    /**
369     * Gets the sample list.
370     *
371     * @return the sample list
372     */
373    SampleSet getSts() {
374        return sts;
375    }
376
377    /**
378     * Gets the Mel Ceptra list.
379     *
380     * @return the Mel Ceptra list
381     */
382    SampleSet getMcep() {
383        return mcep;
384    }
385
386    /**
387     * Determines if the application of the given join weights could
388     * be applied  as a simple right-shift. If so return the shift
389     * otherwise return 0.
390     *
391     * @return the amount to right shift (or zero if not possible)
392     */
393    int getJoinWeightShift() {
394        return joinWeightShift;
395    }
396
397
398    /**
399     * Calculates the join weight shift.
400     *
401     * @param joinWeights the weights to check
402     *
403     * @return the amount to right shift (or zero if not possible)
404     */
405    private int calcJoinWeightShift(int[] joinWeights) {
406        int first = joinWeights[0];
407        for (int i = 1; i < joinWeights.length; i++) {
408            if (joinWeights[i] != first) {
409                return 0;
410            }
411        }
412
413        int divisor = 65536 / first;
414        if (divisor == 2) {
415            return 1;
416        } else if (divisor == 4) {
417            return 2;
418        }
419        return 0;
420    }
421
422    /**
423     * Loads the database from the given input stream.
424     *
425     * @param is the input stream
426     */
427    private void loadText(InputStream is) {
428        BufferedReader reader;
429        String line;
430
431
432        unitList = new ArrayList();
433        unitTypesList = new ArrayList();
434
435        if (is == null) {
436            throw new Error("Can't load cluster db file.");
437        }
438
439        reader = new BufferedReader(new InputStreamReader(is));
440        try {
441            line = reader.readLine();
442            lineCount++;
443            while (line != null) {
444                if (!line.startsWith("***")) {
445                    parseAndAdd(line, reader);
446                }
447                line = reader.readLine();
448            }
449            reader.close();
450
451            units = new DatabaseClusterUnit[unitList.size()];
452            units = (DatabaseClusterUnit[]) unitList.toArray(units);
453            unitList = null;
454
455            unitTypes = new UnitType[unitTypesList.size()];
456            unitTypes = (UnitType[]) unitTypesList.toArray(unitTypes);
457            unitTypesList = null;
458
459        } catch (IOException e) {
460            throw new Error(e.getMessage() + " at line " + lineCount);
461        } finally {
462        }
463    }
464
465    /**
466     * Parses and process the given line.
467     *
468     * @param line the line to process
469     * @param reader the source for the lines
470     *
471     * @throws IOException if an error occurs while reading
472     */
473    private void parseAndAdd(String line, BufferedReader reader)
474        throws IOException {
475        try {
476            StringTokenizer tokenizer = new StringTokenizer(line," ");
477            String tag = tokenizer.nextToken();
478            if (tag.equals("CONTINUITY_WEIGHT")) {
479                continuityWeight = Integer.parseInt(tokenizer.nextToken());
480            } else if (tag.equals("OPTIMAL_COUPLING")) {
481                optimalCoupling = Integer.parseInt(tokenizer.nextToken());
482            }  else if (tag.equals("EXTEND_SELECTIONS")) {
483                extendSelections = Integer.parseInt(tokenizer.nextToken());
484            }  else if (tag.equals("JOIN_METHOD")) {
485                joinMethod = Integer.parseInt(tokenizer.nextToken());
486            }  else if (tag.equals("JOIN_WEIGHTS")) {
487                int numWeights = Integer.parseInt(tokenizer.nextToken());
488                joinWeights = new int[numWeights];
489                for (int i = 0; i < numWeights; i++) {
490                    joinWeights[i]  = Integer.parseInt(tokenizer.nextToken());
491                }
492
493                joinWeightShift = calcJoinWeightShift(joinWeights);
494
495            } else if (tag.equals("STS")) {
496                String name = tokenizer.nextToken();
497                if (name.equals("STS")) {
498                    sts = new SampleSet(tokenizer, reader);
499                } else {
500                    mcep = new SampleSet(tokenizer, reader);
501                }
502            } else if (tag.equals("UNITS")) {
503                int type = Integer.parseInt(tokenizer.nextToken());
504                int phone = Integer.parseInt(tokenizer.nextToken());
505                int start = Integer.parseInt(tokenizer.nextToken());
506                int end = Integer.parseInt(tokenizer.nextToken());
507                int prev = Integer.parseInt(tokenizer.nextToken());
508                int next = Integer.parseInt(tokenizer.nextToken());
509                DatabaseClusterUnit unit 
510                      = new DatabaseClusterUnit(type, phone, start, 
511                              end, prev, next);
512                unitList.add(unit);
513            } else if (tag.equals("CART")) {
514                String name = tokenizer.nextToken();
515                int nodes = Integer.parseInt(tokenizer.nextToken());
516                CART cart = new CARTImpl(reader, nodes);
517                cartMap.put(name, cart);
518
519                if (defaultCart == null) {
520                    defaultCart = cart;
521                }
522            } else if (tag.equals("UNIT_TYPE")) {
523                String name = tokenizer.nextToken();
524                int start = Integer.parseInt(tokenizer.nextToken());
525                int count = Integer.parseInt(tokenizer.nextToken());
526                UnitType unitType = new UnitType(name, start, count);
527                unitTypesList.add(unitType);
528            } else {
529                throw new Error("Unsupported tag " + tag + " in db line `" + line + "'");
530            }
531        } catch (NoSuchElementException nse) {
532            throw new Error("Error parsing db " + nse.getMessage());
533        } catch (NumberFormatException nfe) {
534            throw new Error("Error parsing numbers in db line `" + line + "':" + nfe.getMessage());
535        }
536    }
537
538    /**
539     * Loads a binary file from the input stream. 
540     *
541     * @param is the input stream to read the database from
542     *
543     * @throws IOException if there is trouble opening the DB
544     *
545     */
546    private void loadBinary(InputStream is) throws IOException {
547        // we get better performance if we can map the file in
548        // 1.0 seconds vs. 1.75 seconds, but we can't
549        // always guarantee that we can do that.
550        if (is instanceof FileInputStream) {
551            FileInputStream fis = (FileInputStream) is;
552            FileChannel fc = fis.getChannel();
553
554            MappedByteBuffer bb = 
555                fc.map(FileChannel.MapMode.READ_ONLY, 0, (int) fc.size());
556            bb.load();
557            loadBinary(bb);
558            is.close();
559        } else {
560            loadBinary(new DataInputStream(is));
561        }
562    }
563
564    /**
565     * Loads the database from the given byte buffer.
566     *
567     * @param bb the byte buffer to load the db from
568     *
569     * @throws IOException if there is trouble opening the DB
570     */
571    private void loadBinary(ByteBuffer bb) throws IOException {
572
573        if (bb.getInt() != MAGIC)  {
574            throw new Error("Bad magic in db");
575        }
576        if (bb.getInt() != VERSION)  {
577            throw new Error("Bad VERSION in db");
578        }
579
580        continuityWeight = bb.getInt();
581        optimalCoupling = bb.getInt();
582        extendSelections = bb.getInt();
583        joinMethod = bb.getInt();
584        joinWeightShift = bb.getInt();
585
586        int weightLength = bb.getInt();
587        joinWeights = new int[weightLength];
588        for (int i = 0; i < joinWeights.length; i++) {
589            joinWeights[i] = bb.getInt();
590        }
591
592        int unitsLength = bb.getInt();
593        units = new DatabaseClusterUnit[unitsLength];
594        for (int i = 0; i < units.length; i++) {
595            units[i] = new DatabaseClusterUnit(bb);
596        }
597
598        int unitTypesLength = bb.getInt();
599        unitTypes = new UnitType[unitTypesLength];
600        for (int i = 0; i < unitTypes.length; i++) {
601            unitTypes[i] = new UnitType(bb);
602        }
603        sts = new SampleSet(bb);
604        mcep = new SampleSet(bb);
605
606        int numCarts = bb.getInt();
607        cartMap = new HashMap();
608        for (int i = 0; i < numCarts; i++) {
609            String name = Utilities.getString(bb);
610            CART cart = CARTImpl.loadBinary(bb);
611            cartMap.put(name, cart);
612
613            if (defaultCart == null) {
614                defaultCart = cart;
615            }
616        }
617    }
618
619    /**
620     * Loads the database from the given input stream.
621     *
622     * @param is the input stream to load the db from
623     *
624     * @throws IOException if there is trouble opening the DB
625     */
626    private void loadBinary(DataInputStream is) throws IOException {
627
628        if (is.readInt() != MAGIC)  {
629            throw new Error("Bad magic in db");
630        }
631        if (is.readInt() != VERSION)  {
632            throw new Error("Bad VERSION in db");
633        }
634
635        continuityWeight = is.readInt();
636        optimalCoupling = is.readInt();
637        extendSelections = is.readInt();
638        joinMethod = is.readInt();
639        joinWeightShift = is.readInt();
640
641        int weightLength = is.readInt();
642        joinWeights = new int[weightLength];
643        for (int i = 0; i < joinWeights.length; i++) {
644            joinWeights[i] = is.readInt();
645        }
646
647        int unitsLength = is.readInt();
648        units = new DatabaseClusterUnit[unitsLength];
649        for (int i = 0; i < units.length; i++) {
650            units[i] = new DatabaseClusterUnit(is);
651        }
652
653        int unitTypesLength = is.readInt();
654        unitTypes = new UnitType[unitTypesLength];
655        for (int i = 0; i < unitTypes.length; i++) {
656            unitTypes[i] = new UnitType(is);
657        }
658        sts = new SampleSet(is);
659        mcep = new SampleSet(is);
660
661        int numCarts = is.readInt();
662        cartMap = new HashMap();
663        for (int i = 0; i < numCarts; i++) {
664            String name = Utilities.getString(is);
665            CART cart = CARTImpl.loadBinary(is);
666            cartMap.put(name, cart);
667
668            if (defaultCart == null) {
669                defaultCart = cart;
670            }
671        }
672    }
673    
674    /**
675     * Load debug info about the origin of units from the given input stream.
676     * The file format is identical to that of the Festvox .catalogue files.
677     * This is useful when creating and debugging new voices: For a selected
678     * unit, you can find out which unit from which original sound file
679     * was used.
680     * @param is the input stream from which to read the debug info.
681     * @throws IOException if a read problem occurs.
682     */
683    private void loadUnitOrigins(InputStream is)  throws IOException
684    {
685        unitOrigins = new UnitOriginInfo[units.length];
686        BufferedReader in = new BufferedReader(new InputStreamReader(is));
687        
688        String currentLine = null;
689        // Skip EST header:
690        while ((currentLine = in.readLine()) != null) {
691            if (currentLine.startsWith("EST_Header_End")) break;
692        }
693        while ((currentLine = in.readLine()) != null) {
694            String[] tokens = currentLine.split(" ");
695            String name = tokens[0];
696            int index = getUnitIndexName(name);
697            try {
698                unitOrigins[index] = new UnitOriginInfo();
699                unitOrigins[index].originFile = tokens[1];
700                unitOrigins[index].originStart = Float.valueOf(tokens[2]).floatValue();
701                unitOrigins[index].originEnd = Float.valueOf(tokens[4]).floatValue();
702            } catch (NumberFormatException nfe) {}
703        }
704        in.close();
705    }
706
707
708    /**
709     * Dumps a binary form of the database.
710     *
711     * @param path the path to dump the file to
712     */
713    void dumpBinary(String path) {
714        try {
715            FileOutputStream fos = new FileOutputStream(path);
716            DataOutputStream os = new DataOutputStream(new
717                    BufferedOutputStream(fos));
718
719            os.writeInt(MAGIC);
720            os.writeInt(VERSION);
721            os.writeInt(continuityWeight);
722            os.writeInt(optimalCoupling);
723            os.writeInt(extendSelections);
724            os.writeInt(joinMethod);
725            os.writeInt(joinWeightShift);
726            os.writeInt(joinWeights.length);
727            for (int i = 0; i < joinWeights.length; i++) {
728                os.writeInt(joinWeights[i]);
729            }
730
731            os.writeInt(units.length);
732            for (int i = 0; i < units.length; i++) {
733                units[i].dumpBinary(os);
734            }
735
736            os.writeInt(unitTypes.length);
737            for (int i = 0; i < unitTypes.length; i++) {
738                unitTypes[i].dumpBinary(os);
739            }
740            sts.dumpBinary(os);
741            mcep.dumpBinary(os);
742
743            os.writeInt(cartMap.size());
744            for (Iterator i = cartMap.keySet().iterator(); i.hasNext();) {
745                String name = (String) i.next();
746                CART cart =  (CART) cartMap.get(name);
747
748                Utilities.outString(os, name);
749                cart.dumpBinary(os);
750            }
751            os.close();
752
753            // note that we are not currently saving the state
754            // of the default cart
755
756        } catch (FileNotFoundException fe) {
757            throw new Error("Can't dump binary database " +
758                    fe.getMessage());
759        } catch (IOException ioe) {
760            throw new Error("Can't write binary database " +
761                    ioe.getMessage());
762        }
763    }
764
765
766    /**
767     * Determines if two databases are identical.
768     *
769     * @param other the database to compare this one to
770     *
771     * @return true if the databases are identical
772     */
773    public boolean compare(ClusterUnitDatabase other) {
774        System.out.println("Warning: Compare not implemented yet");
775        return false;
776    }
777
778    /**
779     *  Manipulates a ClusterUnitDatabase.  
780     *
781     * <p>
782     * <b> Usage </b>
783     * <p>
784     *  <code> java com.sun.speech.freetts.clunits.ClusterUnitDatabase
785     *  [options]</code> 
786     * <p>
787     * <b> Options </b>
788     * <p>
789     *    <ul>
790     *          <li> <code> -src path </code> provides a directory
791     *          path to the source text for the database
792     *          <li> <code> -dest path </code> provides a directory
793     *          for where to place the resulting binaries
794     *          <li> <code> -generate_binary [filename]</code> reads
795     *          in the text version of the database and generates
796     *          the binary version of the database.
797     *          <li> <code> -compare </code>  Loads the text and
798     *          binary versions of the database and compares them to
799     *          see if they are equivalent.
800     *          <li> <code> -showTimes </code> shows timings for any
801     *          loading, comparing or dumping operation
802     *    </ul>
803     * 
804     */
805    public static void main(String[] args) {
806        boolean showTimes = false;
807        String srcPath = ".";
808        String destPath = ".";
809
810        try {
811            if (args.length > 0) {
812                BulkTimer timer = new BulkTimer();
813                timer.start();
814                for (int i = 0 ; i < args.length; i++) {
815                    if (args[i].equals("-src")) {
816                        srcPath = args[++i];
817                    } else if (args[i].equals("-dest")) {
818                        destPath = args[++i];
819                    } else if (args[i].equals("-generate_binary")) {
820                         String name = "clunits.txt";
821                         if (i + 1 < args.length) {
822                             String nameArg = args[++i];
823                             if (!nameArg.startsWith("-")) {
824                                 name = nameArg;
825                             }
826                         }
827
828                         int suffixPos = name.lastIndexOf(".txt");
829
830                         String binaryName = "clunits.bin";
831                         if (suffixPos != -1) {
832                             binaryName = name.substring(0, suffixPos) + ".bin";
833                         }
834
835                         System.out.println("Loading " + name);
836                         timer.start("load_text");
837                         ClusterUnitDatabase udb = new
838                             ClusterUnitDatabase(
839                                new URL("file:" + srcPath + "/" + name),
840                                false);
841                         timer.stop("load_text");
842
843                         System.out.println("Dumping " + binaryName);
844                         timer.start("dump_binary");
845                         udb.dumpBinary(destPath + "/" + binaryName);
846                         timer.stop("dump_binary");
847
848                    } else if (args[i].equals("-compare")) {
849
850                        timer.start("load_text");
851                         ClusterUnitDatabase udb = new
852                             ClusterUnitDatabase(
853                                new URL("file:./cmu_time_awb.txt"), false);
854                        timer.stop("load_text");
855
856                        timer.start("load_binary");
857                        ClusterUnitDatabase budb = 
858                            new ClusterUnitDatabase(
859                                    new URL("file:./cmu_time_awb.bin"), true);
860                        timer.stop("load_binary");
861
862                        timer.start("compare");
863                        if (udb.compare(budb)) {
864                            System.out.println("other compare ok");
865                        } else {
866                            System.out.println("other compare different");
867                        }
868                        timer.stop("compare");
869                    } else if (args[i].equals("-showtimes")) {
870                        showTimes = true;
871                    } else {
872                        System.out.println("Unknown option " + args[i]);
873                    }
874                }
875                timer.stop();
876                if (showTimes) {
877                    timer.show("ClusterUnitDatabase");
878                }
879            } else {
880                System.out.println("Options: ");
881                System.out.println("    -src path");
882                System.out.println("    -dest path");
883                System.out.println("    -compare");
884                System.out.println("    -generate_binary");
885                System.out.println("    -showTimes");
886            }
887        } catch (IOException ioe) {
888            System.err.println(ioe);
889        }
890    }
891
892
893    /**
894     * Represents a unit  for the cluster database.
895     */
896    class DatabaseClusterUnit {
897
898        int type;
899        int phone;
900        int start;
901        int end;
902        int prev;
903        int next;
904
905        /**
906         * Constructs a unit.
907         *
908         * @param type the name of the unit
909         * @param phone the name of the unit
910         * @param start the starting frame
911         * @param end the ending frame
912         * @param prev the previous index
913         * @param next the next index
914         */
915        DatabaseClusterUnit(int type, int phone, int start, 
916                int end, int prev, int next) {
917            this.type = type;
918            this.phone = phone;
919            this.start = start;
920            this.end = end;
921            this.prev = prev;
922            this.next = next;
923        }
924
925        /**
926         * Creates a unit by reading it from the given byte buffer.
927         *
928         * @param bb source of the DatabaseClusterUnit data
929         *
930         * @throws IOException if an IO error occurs
931         */
932        DatabaseClusterUnit(ByteBuffer bb) throws IOException {
933            this.type = bb.getInt();
934            this.phone = bb.getInt();
935            this.start = bb.getInt();
936            this.end = bb.getInt();
937            this.prev = bb.getInt();
938            this.next = bb.getInt();
939        }
940
941        /**
942         * Creates a unit by reading it from the given input stream.
943         *
944         * @param is source of the DatabaseClusterUnit data
945         *
946         * @throws IOException if an IO error occurs
947         */
948        DatabaseClusterUnit(DataInputStream is) throws IOException {
949            this.type = is.readInt();
950            this.phone = is.readInt();
951            this.start = is.readInt();
952            this.end = is.readInt();
953            this.prev = is.readInt();
954            this.next = is.readInt();
955        }
956
957        /**
958         * Returns the name of the unit.
959         *
960         * @return the name
961         */
962        String getName() {
963            return unitTypes[type].getName();
964        }
965
966        /**
967         * Dumps this unit to the given output stream.
968         *
969         * @param os the output stream
970         *
971         * @throws IOException if an error occurs.
972         */
973        void dumpBinary(DataOutputStream os) throws IOException {
974            os.writeInt(type);
975            os.writeInt(phone);
976            os.writeInt(start);
977            os.writeInt(end);
978            os.writeInt(prev);
979            os.writeInt(next);
980        }
981    }
982    
983    /**
984     * Represents debug information about the origin of a unit.
985     */
986    class UnitOriginInfo {
987        String originFile;
988        float originStart;
989        float originEnd;
990    }
991
992    /**
993     * Displays an error message
994     *
995     * @param s the error message
996     */
997    private void error(String s) {
998        System.out.println("ClusterUnitDatabase Error: " + s);
999    }
1000}
1001
1002/**
1003 * Represents a unit type in the system
1004 */
1005class UnitType {
1006    private String name;
1007    private int start;
1008    private int count;
1009
1010    /**
1011     * Constructs a UnitType from the given parameters
1012     *
1013     * @param name the name of the type
1014     * @param start the starting index for this type
1015     * @param count the number of elements for this type
1016     */
1017    UnitType(String name, int start, int count) {
1018        this.name = name;
1019        this.start = start;
1020        this.count = count;
1021    }
1022
1023    /**
1024     * Creates a unit type by reading it from the given input stream.
1025     *
1026     * @param is source of the UnitType data
1027     *
1028     * @throws IOException if an IO error occurs
1029     */
1030    UnitType(DataInputStream is) throws IOException {
1031        this.name = Utilities.getString(is);
1032        this.start = is.readInt();
1033        this.count = is.readInt();
1034    }
1035
1036    /**
1037     * Creates a unit type by reading it from the given byte buffer.
1038     *
1039     * @param bb source of the UnitType  data
1040     *
1041     * @throws IOException if an IO error occurs
1042     */
1043    UnitType(ByteBuffer bb) throws IOException {
1044        this.name = Utilities.getString(bb);
1045        this.start = bb.getInt();
1046        this.count = bb.getInt();
1047    }
1048
1049    /**
1050     * Gets the name for this unit type
1051     * 
1052     * @return the name for the type
1053     */
1054    String getName() {
1055        return name;
1056    }
1057
1058    /**
1059     * Gets the start index for this type
1060     *
1061     * @return the start index
1062     */
1063    int getStart() {
1064        return start;
1065    }
1066
1067    /**
1068     * Gets the count for this type
1069     *
1070     * @return the  count for this type
1071     */
1072    int getCount() {
1073        return count;
1074    }
1075
1076    /**
1077     * Dumps this unit to the given output stream.
1078     *
1079     * @param os the output stream
1080     *
1081     * @throws IOException if an error occurs.
1082     */
1083    void dumpBinary(DataOutputStream os) throws IOException {
1084        Utilities.outString(os, name);
1085        os.writeInt(start);
1086        os.writeInt(count);
1087    }
1088}