Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/trapezium/chisel/ProcessedFile.java


1   /*
2    * @(#)ProcessedFile.java
3    *
4    * Copyright (c) 1998-2000 by Trapezium Development LLC.  All Rights Reserved.
5    *
6    * The information in this file is the property of Trapezium Development LLC
7    * and may be used only in accordance with the terms of the license granted
8    * by Trapezium.
9    */
10  package com.trapezium.chisel;
11  
12  import java.awt.*;
13  import java.awt.event.*;
14  import java.io.*;
15  import java.net.URL;
16  import java.util.*;
17  import java.util.zip.*;
18  
19  import com.trapezium.factory.FactoryData;
20  import com.trapezium.factory.FactoryChain;
21  import com.trapezium.factory.QueuedRequestFactory;
22  import com.trapezium.factory.TokenStreamFactory;
23  import com.trapezium.factory.FactoryResponseListener;
24  import com.trapezium.factory.ParserFactory;
25  import com.trapezium.util.GlobalProgressIndicator;
26  import com.trapezium.util.ProgressIndicator;
27  import com.trapezium.vrml.*;
28  import com.trapezium.vrml.grammar.*;
29  import com.trapezium.edit.TokenEditor;
30  import com.trapezium.edit.EditLintVisitor;
31  import com.trapezium.parse.TokenEnumerator;
32  import com.trapezium.chisel.gui.ChiselController;
33  import com.trapezium.html.HTMLgenerator;
34  import com.trapezium.vrml.visitor.X3dWriter;
35  
36  import com.trapezium.edit.Document;
37  
38  /** a ProcessedFile object is a FactoryData object that knows how to load
39      itself, reload itself from its original source, and save itself. */
40  public class ProcessedFile extends FactoryData implements Serializable {
41  
42      /** URL for the source; null if source is a local file */
43      //URL url;
44  
45      /** file to save to; if the source is a remote URL, this is a locally cached
46          copy of the retrieved file.  If the source is a local URL, then this
47          File is just the URL opened as a file */
48      //File file;
49  
50      /** way to create the data given the source */
51      FactoryChain loader;
52  
53    // for the moment, ParserFactory is not thread-safe, only have one for entire system
54      QueuedRequestFactory parser;
55      QueuedRequestFactory tokenFactory;
56      Session session;
57  
58      public void wipeout() {
59          super.wipeout();
60          if ( parser != null ) {
61              parser.wipeout();
62              parser = null;
63          }
64          if ( tokenFactory != null ) {
65              tokenFactory.wipeout();
66              tokenFactory = null;
67          }
68          if ( loader != null ) {
69              loader.wipeout();
70              loader = null;
71          }
72          session = null;
73      }
74      
75    public QueuedRequestFactory getParserFactory() {
76      return( parser );
77    }
78  
79    public QueuedRequestFactory getTokenFactory() {
80        return( tokenFactory );
81    }
82  
83      public ProcessedFile() {
84          this(null);
85      }
86  
87    /*  create a ProcessedFile, load it and parse it */
88    static int id = 1;
89    int myId;
90  
91    boolean newlyOpened;
92    public boolean isNewlyOpened() {
93        return( newlyOpened );
94    }
95  
96    public void notNewlyOpened() {
97        newlyOpened = false;
98    }
99  
100 
101   int versionNumber;
102     public ProcessedFile(String source) {
103         myId = id;
104         newlyOpened = true;
105         id++;
106         versionNumber = 1;
107         //System.out.println( "Creating processedFile " + myId );
108         setUrl(source);
109         loader = new FactoryChain( ChiselSet.VALIDATORS );
110         tokenFactory = new TokenStreamFactory();
111         parser = new ParserFactory();
112         loader.addFactory(tokenFactory);
113         loader.addFactory(parser);
114     }
115 
116     /** Get the version number for the file */
117     public int getVersion() {
118         return( versionNumber );
119     }
120 
121     /** Get the version number as a String */
122     public String getVersionString() {
123         return( "v" + versionNumber + ":  " );
124     }
125 
126     /** Set the version number for the file */
127     public void setVersion( int versionNo ) {
128         versionNumber = versionNo;
129         markVersion( versionNumber );
130         Scene scene = getScene();
131         if ( scene != null ) {
132             GlobalProgressIndicator.setProgressIndicator( ChiselSet.getProgressIndicator( ChiselSet.VALIDATORS ), "Serializing...", scene.getVrmlElementCount() );
133             getDocument().setViewerVersion( versionNo, scene );
134         }
135     }
136 
137     public void initProgressIndicator( String title ) {
138         Scene scene = getScene();
139         GlobalProgressIndicator.setProgressIndicator( ChiselSet.getProgressIndicator( ChiselSet.VALIDATORS ), title, scene.getVrmlElementCount() );
140     }
141 
142 
143     /** Update a single listener text field, sets the text and checks box off.
144      *
145      *  @param count count used to update
146      *  @param strheader  String identifying type of data
147      *  @param frl FactoryResponseListener, where update is sent
148      *  @param testProfile text is appended here for progress check
149      *
150      *  @return true if the file is not totally clean, otherwise false
151      */
152     boolean updateListener( int count, String strheader, FactoryResponseListener frl, StringBuffer testProfile ) {
153         String txt = count + strheader;
154       frl.setText( txt );
155       boolean flag = (count > 0);
156       if ( frl instanceof ChiselRow ) {
157           ChiselRow cr = (ChiselRow)frl;
158           if ( cr.isAutomaticallyChecked() ) {
159               setRowInfo(cr, flag);
160           }
161       }
162       if ( testProfile != null ) {
163           testProfile.append( txt );
164       }
165       return( flag );
166   }
167 
168     /** Update the summary info in all the chisel rows.
169      *
170      *  @param cleanCount number of times file cleaned, some chisels automatically updated
171      *     only on first pass through (where cleanCount==0)
172      *
173      *  @return true if the file is not totally cleaned and the info profile has
174      *     changed.  The info profile is necessary to prevent useless repeated
175      *     attempts to clean a file, which cannot be cleaned for whatever reason.
176      */
177     String infoProfile = null;
178     int cleanCount = 0;
179     public void setCleanCount( int ccval ) {
180         cleanCount = ccval;
181     }
182     public int getCleanCount() {
183         return( cleanCount );
184     }
185     public boolean updateInfo() {
186         boolean needSignal = false;
187         StringBuffer testProfile = new StringBuffer();
188         EditLintVisitor lv = getLintInfo();
189         if ( lv != null ) {
190             FactoryResponseListener frl = ChiselSet.getSpecializedListener( ChiselSet.InlineCountListener );
191         if ( frl != null ) {
192             frl.setText( lv.getInlineCount() + " inline" );
193         }
194 
195         // unused DEF's don't set the signal
196         frl = ChiselSet.getSpecializedListener( ChiselSet.UnusedDEFCountListener );
197         if ( frl != null ) {
198             frl.setText( lv.getUnusedDEFCount() + " unused" );
199  /*           boolean flag = (lv.getUnusedDEFCount() > 0);
200         // turn it off it was on
201             if ( frl instanceof ChiselRow ) {
202                 setRowInfo((ChiselRow)frl, flag);
203             }*/
204         }
205 
206         frl = ChiselSet.getSpecializedListener( ChiselSet.DefaultFieldCountListener );
207         if ( frl != null ) {
208             needSignal = updateListener( lv.getDefaultFieldCount(), " default", frl, testProfile ) || needSignal;
209         }
210         frl = ChiselSet.getSpecializedListener( ChiselSet.UnusedCoordCountListener );
211         FactoryResponseListener unusedListener = frl;
212         if ( frl != null ) {
213             needSignal = updateListener( lv.getUnusedCoordCount(), " unused", frl, testProfile ) || needSignal;
214         }
215         frl = ChiselSet.getSpecializedListener( ChiselSet.UnusedPROTOinterfaceCountListener );
216         if ( frl != null ) {
217             updateListener( lv.getUnusedPROTOinterfaceCount(), " unused", frl, testProfile );
218         }
219         frl = ChiselSet.getSpecializedListener( ChiselSet.DuplicateFieldCountListener );
220         if ( frl != null ) {
221             needSignal = updateListener( lv.getDuplicateFieldCount(), " repeated", frl, testProfile ) || needSignal;
222         }
223         frl = ChiselSet.getSpecializedListener( ChiselSet.SingleColorIFScountListener );
224         if ( frl != null ) {
225             needSignal = updateListener( lv.getSingleColorIFScount(), " single colored IFS", frl, testProfile ) || needSignal;
226         }
227         frl = ChiselSet.getSpecializedListener( ChiselSet.EmptyIndexedFaceSetCountListener );
228         if ( frl != null ) {
229             needSignal = updateListener( lv.getEmptyIndexedFaceSetCount(), " empty", frl, testProfile ) || needSignal;
230         }
231         frl = ChiselSet.getSpecializedListener( ChiselSet.ElevationGridCountListener );
232         if ( frl != null ) {
233             if ( lv.getElevationGridCount() == 1 ) {
234                 frl.setText( "1 grid" );
235             } else {
236                 frl.setText( lv.getElevationGridCount() + " grids" );
237             }
238         }
239         frl = ChiselSet.getSpecializedListener( ChiselSet.TransformCountListener );
240         if ( frl != null ) {
241             if ( lv.getTransformCount() == 1 ) {
242                 frl.setText( "1 transform" );
243             } else {
244                 frl.setText( lv.getTransformCount() + " transforms" );
245             }
246         }
247          frl = ChiselSet.getSpecializedListener( ChiselSet.DEFUSECountListener );
248         if ( frl != null ) {
249             int defcount = lv.getDEFcount();
250             int usecount = lv.getUSEcount();
251             frl.setText( defcount + " DEF, " + usecount + " USE" );
252         }
253          frl = ChiselSet.getSpecializedListener( ChiselSet.DEFUSECountListener2 );
254         if ( frl != null ) {
255             int defcount = lv.getDEFcount();
256             int usecount = lv.getUSEcount();
257             frl.setText( defcount + " DEF, " + usecount + " USE" );
258         }
259         frl = ChiselSet.getSpecializedListener( ChiselSet.DupIndexCountListener );
260         if ( frl != null ) {
261             needSignal = updateListener( lv.getDupIndexCount(), " repeated", frl, testProfile ) || needSignal;
262         }
263         frl = ChiselSet.getSpecializedListener( ChiselSet.BadFaceCountListener );
264         if ( frl != null ) {
265             needSignal = updateListener( lv.getBadFaceCount(), " bad", frl, testProfile ) || needSignal;
266         }
267         frl = ChiselSet.getSpecializedListener( ChiselSet.RepeatedValueCountListener );
268         if ( frl != null ) {
269             boolean flag = updateListener( lv.getRepeatedValueCount(), " repeated", frl, testProfile );
270             if ( frl instanceof ChiselRow ) {
271                 if ( flag ) {
272                     setRowInfo((ChiselRow)unusedListener, flag );
273                 }
274             }
275                needSignal = needSignal || flag;
276         }
277         frl = ChiselSet.getSpecializedListener( ChiselSet.BadRouteListener );
278         if ( frl != null ) {
279             updateListener( lv.getBadRouteCount(), " bad", frl, null );
280         }
281         frl = ChiselSet.getSpecializedListener( ChiselSet.NormalCountListener );
282         if ( frl != null ) {
283             updateListener( lv.getNormalCount(), " normal", frl, null );
284         }
285         frl = ChiselSet.getSpecializedListener( ChiselSet.UnnecessaryKeyValueListener );
286         if ( frl != null ) {
287             if ( cleanCount == 0 ) {
288                 needSignal = updateListener( lv.getUnnecessaryKeyValueCount(), " unnecessary", frl, testProfile ) || needSignal;
289             } else {
290                 updateListener( lv.getUnnecessaryKeyValueCount(), " unnecessary", frl, testProfile );
291                 if ( frl instanceof ChiselRow ) {
292                     setRowInfo( (ChiselRow)frl, false );
293                 }
294             }
295         }
296         frl = ChiselSet.getSpecializedListener( ChiselSet.UnindexedValueListener );
297         if ( frl != null ) {
298             updateListener( lv.getUnindexedValueCount(), " unindexed values", frl, null );
299         }
300         frl = ChiselSet.getSpecializedListener( ChiselSet.InterpolatorCountListener );
301         if ( frl != null ) {
302             if ( lv.getInterpolatorCount() == 1 ) {
303                 frl.setText( "1 interpolator" );
304             } else {
305                 frl.setText( lv.getInterpolatorCount() + " interpolators" );
306             }
307         }
308         frl = ChiselSet.getSpecializedListener( ChiselSet.InterpolatorCountListener2 );
309         if ( frl != null ) {
310             if ( lv.getInterpolatorCount() == 1 ) {
311                 frl.setText( "1 interpolator" );
312             } else {
313                 frl.setText( lv.getInterpolatorCount() + " interpolators" );
314             }
315         }
316         frl = ChiselSet.getSpecializedListener( ChiselSet.UselessNodeCountListener );
317         if ( frl != null ) {
318             needSignal = updateListener( lv.getUselessNodeCount(), " useless", frl, testProfile ) || needSignal;
319         }
320         frl = ChiselSet.getSpecializedListener( ChiselSet.ValueNodeCountListener );
321         if ( frl != null ) {
322             if ( lv.getValueNodeCount() == 1 ) {
323                 frl.setText( "1 value node" );
324             } else {
325                 frl.setText( lv.getValueNodeCount() + " value nodes" );
326             }
327         }
328         frl = ChiselSet.getSpecializedListener( ChiselSet.PROTOInstanceCountListener );
329         if ( frl != null ) {
330             if ( lv.getPROTOInstanceCount() == 1 ) {
331                 frl.setText( "1 PROTO instance" );
332             } else {
333                 frl.setText( lv.getPROTOInstanceCount() + " PROTO instances" );
334             }
335         }
336         }
337         if ( infoProfile == null ) {
338             infoProfile = new String( testProfile );
339         } else {
340             String infoTestProfile = new String( testProfile );
341             if ( infoTestProfile.compareTo( infoProfile ) == 0 ) {
342                 needSignal = false;
343             } else {
344                 infoProfile = infoTestProfile;
345             }
346         }
347         return( needSignal );
348   }
349 
350   void setRowInfo(ChiselRow cr, boolean flag) {
351         cr.rowReady();
352         ChiselController cc = cr.getChiselController();
353         cr.setEnabled( flag );
354         cc.setValue( String.valueOf( flag ));
355         cc.repaint();
356     }
357 
358     /** DocumentLoader interface */
359     public void reload( Object scene ) {
360         System.out.println( "Reloading scene..." );
361         if ( scene instanceof Scene ) {
362             Scene s = (Scene)scene;
363             TokenEnumerator te = s.getTokenEnumerator();
364             if ( te instanceof TokenEditor ) {
365                 getDocument().setLines( (TokenEditor)te );
366             }
367             setScene( s );
368             cleanCount = 0;
369             updateInfo();
370         }
371     }
372 
373     FactoryResponseListener parsingListener = null;
374     public void setParsingListener( FactoryResponseListener f ) {
375         parsingListener = f;
376         tokenFactory.setListener( f );
377         parser.addListener( f );
378     }
379 
380     public void mergeErrors( BitSet errorMarks, BitSet warningMarks, BitSet nonconformanceMarks ) {
381         mergeLintErrors( parsingListener, errorMarks, warningMarks, nonconformanceMarks );
382     }
383 
384     /** Set who is going to be called when each stage of the loader is done */
385     public void setDoneListener( FactoryResponseListener f ) {
386         //loader.setListener( f );
387         loader.addListener( f );    // should be safe, since it rejects duplicates
388     }
389 
390 
391     //
392     //  The ProcessedFile FactoryChain consists of a large number of chisels, which
393     //  may be independently activated/deactivated via the GUI, or may be deactivated
394     //  if there are validation errors.  The latter case means that we do not allow
395     //  chiselling of files which contain errors.
396     //
397     //  The "enableChisel" method turns on chisels by name, based on the
398     //  name associated with the row visualization.
399     //
400     public void enableChisel( String chiselName, FactoryResponseListener theListener ) {
401         loader.enableChisel( chiselName, theListener );
402     }
403 
404     public void disableChisel( String chiselName ) {
405         loader.disableChisel( chiselName );
406     }
407 
408     public void removeFactory( String factoryName ) {
409         loader.removeFactory( factoryName );
410     }
411 
412     public int getId() {
413         return( myId );
414     }
415 
416     /** Generate local name for file */
417     public String generateName() {
418         return( generateName( false ));
419     }
420 
421     /** Generate a name for file.
422      *
423      *  @param stripNumber strip off "_<N>" portion of name.
424      */
425     public String generateName( boolean stripNumber ) {
426         File file = getFile();
427         if ( file != null ) {
428             String name = file.getName();
429             if (( saveAsCounter > 1 ) && stripNumber ) {
430                 int underscoreIdx = name.lastIndexOf( "_" );
431                 int dotIdx = name.lastIndexOf( "." );
432                 if (( underscoreIdx != -1 ) && ( dotIdx != -1 ) && ( underscoreIdx < dotIdx )) {
433                     String beforeUnderscore = name.substring( 0, underscoreIdx );
434                     String dotAndAfter = name.substring( dotIdx );
435                     return( beforeUnderscore + dotAndAfter );
436                 }
437             }
438             return( name );
439         } else {
440             return( "unknown.wrl" );
441         }
442     }
443     
444     public String generateX3dName() {
445         File file = getFile();
446         if ( file != null ) {
447             String name = file.getName();
448             int dotIdx = name.lastIndexOf( "." );
449             if ( dotIdx != -1 ) {
450                 String beforeDot = name.substring( 0, dotIdx );
451                 return( beforeDot + ".x3d" );
452             } else {
453                 return( name + ".x3d" );
454             }
455         }
456         return( "unknown.x3d" );
457     }
458 
459     public String generateChiseledName() {
460         File file = getFile();
461         if ( file != null ) {
462             String name = file.getName();
463             int dotIdx = name.lastIndexOf( "." );
464             if ( dotIdx != -1 ) {
465                 String beforeDot = name.substring( 0, dotIdx );
466                 return( beforeDot + ".chiseled.wrl" );
467             } else {
468                 return( name + ".chiseled.wrl" );
469             }
470         } else {
471             return( "unknown.chiseled.wrl" );
472         }
473     }
474 
475     public String getLocalName() {
476         File file = getFile();
477         if (file != null) {
478             return file.getName();
479         } else {
480             return null;
481         }
482     }
483 
484     /** generate a name for "save as", just the file name, with an appended
485      *  save as counter (local to the processed file).
486      */
487     int saveAsCounter = 1;
488     public String generateSaveAsName() {
489         String name = generateName( true );
490         if ( name.indexOf( "." ) == -1 ) {
491             name = name + ".wrl";
492         }
493         String beforeDot = name.substring( 0, name.indexOf( "." ));
494         String dotAndAfter = name.substring( name.indexOf( "." ));
495         saveAsCounter++;
496         String newName = beforeDot + "_" + saveAsCounter + dotAndAfter;
497         return( newName );
498     }
499     int X3DsaveAsCounter = 1;
500     public String generateX3dSaveAsName() {
501         String name = generateName( true );
502         if ( name.indexOf( "." ) == -1 ) {
503             name = name + ".x3d";
504         }
505         String beforeDot = name.substring( 0, name.indexOf( "." ));
506         String dotAndAfter = name.substring( name.indexOf( "." ));
507         X3DsaveAsCounter++;
508         String newName = beforeDot + "_" + X3DsaveAsCounter + dotAndAfter;
509         return( newName );
510     }
511 
512     /** generate a name for gzip, name.chiseled.wrz
513      */
514     public String generateGzipName() {
515         return( generateGzipName( false ));
516     }
517 
518     /** Generate a name for gzip, name.chiseled.wrz.
519      *
520      *  @param stripNumber true if the "_<N>" part of the name is to be stripped
521      *     out.
522      */
523     public String generateGzipName( boolean stripNumber ) {
524         String name = getLocalName();
525         if ( name == null ) {
526             name = "unknown.wrz";
527         }
528         if ( name.indexOf( "." ) == -1 ) {
529             name = name + ".wrz";
530         }
531         String beforeDot = name.substring( 0, name.indexOf( "." ));
532         if ( stripNumber && ( saveAsCounter > 1 )) {
533             if ( beforeDot.indexOf( "_" ) > 0 ) {
534                 beforeDot = beforeDot.substring( 0, beforeDot.lastIndexOf( "_" ));
535             }
536         }
537         String newName = beforeDot + ".chiseled.wrz";
538         return( newName );
539     }
540 
541     /** generate a name for gzip save as, name_<N>.chiseled.wrz
542      */
543     public String generateGzipSaveAsName() {
544         String name = generateGzipName( true );
545         String beforeChiseled = name.substring( 0, name.indexOf( ".chiseled" ));
546         saveAsCounter++;
547         String newName = beforeChiseled + "_" + saveAsCounter + ".chiseled.wrz";
548         return( newName );
549     }
550 
551     
552     public void addFactory( QueuedRequestFactory f ) {
553     loader.addFactory( f );
554   }
555 
556   public void addOptimizer( Optimizer o ) {
557       loader.addOptimizer( o, parser, getBaseFilePath(), getNameWithoutPath() );
558   }
559 
560     public void addFactory( QueuedRequestFactory f, QueuedRequestFactory prev ) {
561     loader.insertFactory( f, prev );
562     //load();
563   }
564 
565   public void insertFactory( QueuedRequestFactory f ) {
566       loader.insertFactory( f, tokenFactory );
567   }
568 
569     public String getBaseFilePath() {
570         String name = getUrl();
571         if ( name != null ) {
572             if ( name.lastIndexOf( '/' ) != -1 ) {
573                 name = name.substring( 0, name.lastIndexOf( '/' ));
574             } else if ( name.lastIndexOf( '\\' ) != -1 ) {
575                 name = name.substring( 0, name.lastIndexOf( '\\' ));
576             } else {
577                 name = null;
578             }
579         }
580         //System.out.println( "ProcessedFile base name is '" + name + "'" );
581         return( name );
582 
583     }
584   public String getNameWithoutPath() {
585     String name = getUrl();
586     if ( name != null ) {
587       if ( name.lastIndexOf( '/' ) != -1 ) {
588         return( name.substring( name.lastIndexOf( '/' ) + 1 ));
589       } else if ( name.lastIndexOf( '\\' ) != -1 ) {
590         return( name.substring( name.lastIndexOf( '\\' ) + 1 ));
591       } else {
592         return( name );
593       }
594     }
595     return( null );
596   }
597 
598     public String getNameWithoutVersion() {
599         String name = getUrl();
600         File file = getFile();
601         if (file != null) {
602             name = file.getName();
603         }
604         return name;
605     }
606 
607     public String getName() {
608         String name = getNameWithoutVersion();
609         return getVersionString() + name;
610     }
611 
612     Document doc;
613     MenuItem undoItem;
614     MenuItem redoItem;
615     public void setUndoItem( MenuItem undoItem ) {
616         this.undoItem = undoItem;
617         undoItem.setEnabled( false );
618     }
619 
620     public void setRedoItem( MenuItem redoItem ) {
621         this.redoItem = redoItem;
622         redoItem.setEnabled( false );
623     }
624     public void clear_undo() {
625         if ( doc != null ) {
626             doc.clear_undo();
627         }
628     }
629 
630     /** Remove all resialized files */
631     public void removeSerialFiles() {
632         if ( doc != null ) {
633             doc.removeSerialFiles( versionNumber );
634         }
635     }
636 
637     /** Get the Document associated with this ProcessedFile */
638     public Document getDocument() {
639         return( doc );
640     }
641 
642     /** Set the Document associated with this ProcessedFile */
643     public void setDocument( Document doc ) {
644         this.doc = doc;
645         if ( doc != null ) {
646             doc.setUndoItem( undoItem );
647             doc.setRedoItem( redoItem );
648             doc.setNodeSelector( getScene() );
649         }
650     }
651 
652   /** kick off the loader chain of processing */
653   Date startTime;
654     public void load() {
655         load( true );
656     }
657 
658     /** Start running loader factories.
659      *
660      *  @param resetStartTime if true, "startTime" is set to current time
661      */
662     void load( boolean resetStartTime ) {
663         if ( resetStartTime ) {
664             startTime = new Date();
665         }
666         loader.submit(this);
667     }
668 
669     public Date getStartTime() {
670         return( startTime );
671     }
672 
673     /** Save the file in text format
674      */
675   public void asciiSave() {
676       asciiSave( getFile(), false, false );
677   }
678 
679     /** Save the file in gzip format
680      */
681   public void gzipSave() {
682     asciiSave( getFile(), true, false );
683   }
684   
685   public void x3dSave() {
686       asciiSave( getFile(), false, true );
687   }
688 
689 
690     class SaveThread extends Thread {
691         boolean gzip;
692         boolean x3d;
693         int numberLines;
694         PrintStream out;
695         TokenEditor sceneTokenEditor;
696 
697         SaveThread( TokenEditor sceneTokenEditor, boolean gzip, boolean x3d, int numberLines, PrintStream out ) {
698             this.sceneTokenEditor = sceneTokenEditor;
699             this.gzip = gzip;
700             this.x3d = x3d;
701             this.numberLines = numberLines;
702             this.out = out;
703         }
704 
705         public void run() {
706            Properties props = ChiselProperties.getProperties();
707             String unixFormatProperty = props.getProperty("workspace.saveInUnixFormat");
708             boolean saveInUnixFormat = (unixFormatProperty == null) ? false : ("true".equalsIgnoreCase(unixFormatProperty));
709             if ( x3d ) {
710                 Scene s = getScene();
711                 X3dWriter x3dw = new X3dWriter( out, sceneTokenEditor );
712                 s.twoPassTraverse( x3dw );
713                 x3dw.finished();
714             } else if ( gzip ) {
715                 for ( int i = 0; i < numberLines; i++ ) {
716                     GlobalProgressIndicator.markProgress();
717                     if ( saveInUnixFormat ) {
718                         out.print( sceneTokenEditor.getNospaceLineAt( i ));
719                         out.print( '\n' );
720                     } else {
721                         out.println( sceneTokenEditor.getNospaceLineAt( i ));
722                     }
723                 }
724             } else {
725                 for ( int i = 0; i < numberLines; i++ ) {
726                     GlobalProgressIndicator.markProgress();
727                     if ( saveInUnixFormat ) {
728                         out.print( sceneTokenEditor.getTabLineAt( i ));
729                         out.print( '\n' );
730                     } else {
731                         out.println( sceneTokenEditor.getTabLineAt( i ));
732                     }
733                 }
734             }
735             out.flush();
736             out.close();
737             System.out.println( "Finished saving file, saved " + numberLines + " lines" );
738             GlobalProgressIndicator.reset();
739             if ( Chisel.singletonChisel != null ) {
740                 Chisel.singletonChisel.updateHeaderLine();
741             }
742             saving = false;
743         }
744     }
745 
746     boolean saving;
747     SaveThread saver;
748 
749     /** Save the file in text format
750      *
751      *  @param file file to save it to
752      *  @param gzip if true, save in gzip format
753      *  @param x3d if true, save in experimental X3d format 
754      */
755     public void asciiSave( File file, boolean gzip, boolean x3d ) {
756         if ( saving ) {
757             return;
758         }
759         saving = true;
760         setGzip( gzip );
761         Scene scene = getScene();
762         System.out.println( "Saving file..." );
763         try {
764             FileOutputStream fos = new FileOutputStream(file);
765             OutputStream fo = fos;
766             if ( gzip ) {
767                 fo = new GZIPOutputStream( fo );
768             }
769             PrintStream out = new PrintStream( fo );
770             TokenEditor sceneTokenEditor = (TokenEditor) scene.getTokenEnumerator();
771             int numberLines = sceneTokenEditor.getNumberLines();
772             String progressStr = "Saving " + file.getName() + " ";
773             if ( gzip ) {
774                 progressStr = "GZIP saving " + file.getName() + " ";
775             }
776             GlobalProgressIndicator.setProgressIndicator( ChiselSet.getProgressIndicator( ChiselSet.VALIDATORS ), progressStr, numberLines );
777             SaveThread saveThread = new SaveThread( sceneTokenEditor, gzip, x3d, numberLines, out );
778             saveThread.start();
779             saver = saveThread;
780         } catch( Exception e ) {
781             e.printStackTrace();
782             saving = false;
783         }
784     }
785 
786     public void waitForSave() {
787         if ( saver != null ) {
788             try {
789                 saver.join();
790             } catch ( Exception e ) {
791             }
792         }
793     }
794 
795     public void synchTokenEditor() {
796         Scene scene = getScene();
797         TokenEditor sceneTokenEditor = (TokenEditor)scene.getTokenEnumerator();
798         setTokenEditor( sceneTokenEditor );
799         if ( sceneTokenEditor.isDirty() ) {
800             sceneTokenEditor.retokenize();
801             Scene newScene = new Scene( scene.getUrl(), sceneTokenEditor );
802             VRML97parser parser = new VRML97parser();
803             parser.Build( sceneTokenEditor, newScene );
804             setScene( newScene );
805         }
806     }
807 
808     void dump( String loc ) {
809         Scene s = getScene();
810         if ( s != null ) {
811             TokenEnumerator te = s.getTokenEnumerator();
812             if ( te != null ) {
813                 System.out.println( loc + ", have " + te.getNumberLines() + " lines" );
814             } else {
815                 System.out.println( loc + ", token enumerator is null" );
816             }
817         } else {
818             System.out.println( loc + ", scene is null" );
819         }
820     }
821 
822   /** store info about which chisel is operating on this file
823    *
824    *  @param chisel the chisel that is going to operate on the file
825    */
826   public void chiselWith( Optimizer chisel )  {
827       if ( session == null ) {
828           session = new Session();
829       }
830       session.chiselWith( chisel );
831   }
832 
833   /** Save the current version in the session object */
834   void markVersion( int version ) {
835       if ( session == null ) {
836           session = new Session();
837       }
838       session.markVersion( version );
839   }
840 
841   /** Save the current percent reduction in the session object */
842   public void markPercent() {
843       if ( session == null ) {
844           session = new Session();
845       }
846       session.markPercent( getPercentX100() );
847   }
848 
849   /** Generate an HTML report for the chisel session.
850    *
851    *  @param htmlFileName destination of the HTML report
852    *  @param wrlFileName name of the file that was chiseled
853    *  @param originalName original name of the file
854    */
855   public void generateHtml( String htmlFileName, String wrlFileName, String originalName ) {
856       if ( session == null ) {
857           session = new Session();
858       }
859       session.generateHtml( htmlFileName, wrlFileName, originalName );
860   }
861 
862     /**
863      *  The Session class tracks all the operations done on the file being
864      *  processed.
865      */
866     class Session {
867         /** the list of operations */
868         Vector entries;
869         ChiselSessionEntry lastChisel;
870         public Session() {
871             entries = new Vector();
872         }
873 
874         /** store info about the chisel operating on the file */
875         void chiselWith( Optimizer chisel ) {
876             lastChisel = new ChiselSessionEntry( chisel );
877             entries.addElement( lastChisel );
878             if ( chisel.getNumberOptions() > 0 ) {
879                 entries.addElement( new ChiselOptionSettings( chisel.getNumberOptions(), chisel ));
880             }
881 //            dump();
882         }
883 
884         /** store info when a new version is made */
885         void markVersion( int version ) {
886             entries.addElement( new VersionSessionEntry( version ));
887 //            dump();
888         }
889         void markPercent( int percentX100 ) {
890             if ( lastChisel != null ) {
891                 lastChisel.markPercent( percentX100 );
892             }
893 //            dump();
894         }
895 
896         /** Generate an HTML report to a specific file.
897          *
898          *  @param htmlFileName the name of the file to contain the html
899          *  @param wrlFileName current name of the chiseled file
900          *  @param originalName original name of the chiseled file, only
901          *    used if different from the current name.
902          */
903         void generateHtml( String htmlFileName, String wrlFileName, String originalName ) {
904             try {
905                 OutputStream os = new FileOutputStream( htmlFileName );
906                 HTMLgenerator htmlGen = new HTMLgenerator( os );
907                 htmlGen.header( "Chisel " + Chisel.version + ": " + wrlFileName );
908                 if (( originalName != null ) && ( originalName.compareTo( wrlFileName ) != 0 )) {
909                     htmlGen.locateText( "Original file: <A HREF=\"" + originalName + "\">" + originalName + "</A>", "B" );
910                 }
911                 htmlGen.pText( "<BR>" );
912                 htmlGen.locateText( "Chiseled file: <A HREF=\"" + wrlFileName + "\">" + wrlFileName + "</A>", "B" );
913 
914                 htmlGen.startTable( null, "H4", 1, 4, 4 );
915                 htmlGen.startRow();
916                 htmlGen.columnHeader( "Chisel", "CENTER" );
917                 htmlGen.columnHeader( "% reduction", "CENTER" );
918                 htmlGen.columnHeader( "Total Reduced", "CENTER" );
919                 htmlGen.endRow();
920                 ChiselSessionEntry prevCSE = null;
921 
922                 int numberEntries = entries.size();
923                 for ( int i = 0; i < numberEntries; i++ ) {
924                     SessionEntry s = (SessionEntry)entries.elementAt( i );
925                     if ( s instanceof ChiselSessionEntry ) {
926                         ChiselSessionEntry cse = (ChiselSessionEntry)s;
927                         SessionEntry nextEntry = null;
928                         if ( i < (numberEntries - 1 )) {
929                             nextEntry = (SessionEntry)entries.elementAt( i + 1 );
930                         }
931                         cse.genRow( htmlGen, prevCSE, nextEntry );
932                         prevCSE = cse;
933                     }
934                 }
935                 htmlGen.terminate();
936                 System.out.println( "HTML report in " + htmlFileName );
937             } catch ( Exception e ) {
938                 System.out.println( "Could not generate HTML" );
939                 e.printStackTrace();
940             }
941         }
942 
943         void dump() {
944             for ( int i = 0; i < entries.size(); i++ ) {
945                 SessionEntry s = (SessionEntry)entries.elementAt( i );
946                 System.out.println( s.getName() );
947                 if ( s instanceof ChiselSessionEntry ) {
948                     ChiselSessionEntry cs = (ChiselSessionEntry)s;
949                     System.out.println( "reduction " + cs.getTotalReduction() );
950                 }
951             }
952         }
953     }
954 
955     /** ChiselOptionSettings saves the settings for a specific chisel */
956     class ChiselOptionSettings extends SessionEntry {
957         Vector optionValues;
958         Optimizer chisel;
959 
960         ChiselOptionSettings( int numberOptions, Optimizer chisel ) {
961             super( "settings" );
962             this.chisel = chisel;
963             optionValues = new Vector( numberOptions );
964             for ( int i = 0; i < numberOptions; i++ ) {
965                 optionValues.addElement( chisel.getOptionValue( i ));
966             }
967         }
968 
969         /** Generate a row entry, which is a table-within-a-table */
970         void genRow( HTMLgenerator htmlGen ) {
971             htmlGen.startTable( null, "H4", 1, 0, 0 );
972             for ( int i = 0; i < optionValues.size(); i++ ) {
973                 htmlGen.startRow( "RIGHT" );
974                 htmlGen.genColumn( (String)chisel.getOptionLabel( i ) );
975                 Object x = optionValues.elementAt( i );
976                 if ( x instanceof String ) {
977                     htmlGen.genColumn( (String)x );
978                 } else {
979                     htmlGen.genColumn( "unknown" );
980                 }
981                 htmlGen.endRow();
982             }
983             htmlGen.endTable();
984         }
985     }
986 
987     /** SessionEntry is the base class for all entries */
988     class SessionEntry {
989         String name;
990         public SessionEntry( String name ) {
991             this.name = name;
992         }
993         public String getName()  {
994             return( name );
995         }
996     }
997 
998     class ChiselSessionEntry extends SessionEntry{
999         int numberOptions;
1000        Object[] options;
1001        int percentX100;
1002        Optimizer chisel;
1003
1004        public ChiselSessionEntry( Optimizer chisel ) {
1005            super( chisel.getClass().getName() );
1006            this.chisel = chisel;
1007            numberOptions = chisel.getNumberOptions();
1008            percentX100 = 10000;
1009            if ( numberOptions > 0 ) {
1010                options = new Object[ numberOptions ];
1011                for ( int i = 0; i < numberOptions; i++ ) {
1012                    options[i] = chisel.getOptionValue( i );
1013                }
1014            }
1015        }
1016
1017        /** Generate a row of text for a particular chisel */
1018        public void genRow( HTMLgenerator htmlGen, ChiselSessionEntry prevEntry, SessionEntry nextEntry ) {
1019            htmlGen.startRow( "LEFT" );
1020            if ( nextEntry instanceof ChiselOptionSettings ) {
1021                ChiselOptionSettings cos = (ChiselOptionSettings)nextEntry;
1022                htmlGen.genColumnStart( chisel.getActionMessage() );
1023                cos.genRow( htmlGen );
1024                htmlGen.genColumnEnd();
1025            } else {
1026                htmlGen.genColumn( chisel.getActionMessage() );
1027            }
1028            htmlGen.genColumn( getDelta( prevEntry ));
1029            htmlGen.genColumn( getTotalReduction() );
1030            htmlGen.endRow();
1031        }
1032
1033        void markPercent( int percentX100 ) {
1034//            System.out.println( "markPercent " + percentX100 );
1035            this.percentX100 = percentX100;
1036        }
1037        String redStr;
1038
1039        /** Get the string representing the percent reduction */
1040        String getTotalReduction()  {
1041//            if ( redStr != null ) {
1042//                System.out.println( "Reusing redStr, percentX100 is " + percentX100 );
1043//                return( redStr );
1044//            }
1045            System.out.println( "percentX100 is " + percentX100 );
1046            int px = 10000 - percentX100;
1047            int percent = px/100;
1048            int hundredths = px%100;
1049            if ( hundredths < 10 ) {
1050                redStr = new String( percent + ".0" + hundredths + "%" );
1051            } else {
1052                redStr = new String( percent + "." + hundredths + "%" );
1053            }
1054            return( redStr );
1055        }
1056
1057        public int getX100() {
1058            return( percentX100 );
1059        }
1060
1061        String getDelta( ChiselSessionEntry prevEntry ) {
1062            int prevX100 = 10000;
1063            if ( prevEntry != null ) {
1064                prevX100 = prevEntry.getX100();
1065            }
1066            int diff = prevX100 - percentX100;
1067            if ( diff < 0 ) {
1068                return( "*" );
1069            } else {
1070                int percent = diff/100;
1071                int hundredths = diff%100;
1072                if ( hundredths < 10 ) {
1073                    return( new String( percent + ".0" + hundredths + "%" ));
1074                } else {
1075                    return( new String( percent + "." + hundredths + "%" ));
1076                }
1077            }
1078        }
1079    }
1080
1081    class VersionSessionEntry extends SessionEntry {
1082        int version;
1083        public VersionSessionEntry( int version ) {
1084            super( "Version " + version );
1085            this.version = version;
1086        }
1087    }
1088}
1089
1090