Source code: com/trapezium/vrml/VrmlElement.java
1 /*
2 * @(#)VrmlElement.java
3 *
4 * Copyright (c) 1998 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 */
11 package com.trapezium.vrml;
12
13 import com.trapezium.parse.TokenEnumerator;
14 import com.trapezium.pattern.Visitor;
15 import com.trapezium.pattern.VisitorPattern;
16 import com.trapezium.vrml.visitor.DumpVisitor;
17 import com.trapezium.vrml.visitor.ComplexityVisitor;
18 import com.trapezium.vrml.visitor.AdjustmentVisitor;
19 import com.trapezium.util.GlobalProgressIndicator;
20 import java.util.Vector;
21
22 /**
23 * A VrmlElement is the base class for any object in the VRML 2.0 object hierarchy.
24 * Specific subclasses get created as a ".wrl" file is parsed. The grammar determines
25 * which type of element to create during parsing.
26 *
27 * Each VrmlElement contains zero or more children, which are also VrmlElement.
28 * The object hierarchy for this is very simple. The root level object is a Scene, with
29 * children that are Nodes or ROUTEs. Node children are fields. This object hierarchy
30 * parallels the VRML syntax.
31 *
32 * The VrmlElement implements the visitor pattern, which allows the object structure
33 * to be traversed after it is created. See LintVisitor for how parsing errors get
34 * displayed.
35 *
36 * @author Johannes N. Johannsen
37 * @version 1.21, field bug fix, self reference PROTO USE
38 * 1.12, 29 May 1998, base profile nonconformance category
39 * 1.12, 7 May 1998, make Serializable
40 * 1.12, 29 March 1998, added "adjust" method
41 * @version 1.1, 14 Jan 1998
42 *
43 * @since 1.0
44 */
45 abstract public class VrmlElement implements VisitorPattern, java.io.Serializable {
46 /** Option, disable warnings entirely */
47 static public boolean nowarning = false;
48 /** Option, disable unused DEF warnings */
49 static public boolean noUnusedDEFwarning = false;
50 /** Option, only record nonconformance messages */
51 static public boolean baseProfile = false;
52 /** Option, disable nonconformance messages */
53 static public boolean disableBaseProfile = false;
54 /** Used to track number of VrmlElements created during parsing, not thread safe */
55 static public int createCount = 0;
56
57 /** any error creating a specific element is noted here, see setError, getError */
58 public transient String errorString = null;
59
60 /** parent, necessary for scoping searches */
61 VrmlElement parent = null;
62
63 /** children are seen only through the Visitor pattern */
64 Object children = null;
65
66 /** default constructor */
67 public VrmlElement() {
68 createCount++;
69 }
70
71 /** Write out the object for serialization, mark progress if GlobalProgressIndicator
72 * is set up.
73 */
74 private void writeObject(java.io.ObjectOutputStream oos) throws java.io.IOException {
75 GlobalProgressIndicator.markProgress();
76 oos.defaultWriteObject();
77 }
78
79 /** Read in serialized object, mark progress if GlobalProgressIndicator is set up.
80 */
81 private void readObject(java.io.ObjectInputStream oos) throws java.io.IOException {
82 try {
83 GlobalProgressIndicator.markProgress();
84 oos.defaultReadObject();
85 } catch ( ClassNotFoundException e ) {
86 e.printStackTrace();
87 throw new java.io.IOException();
88 }
89 }
90
91
92 /** get the class name without package */
93 public String getBaseName() {
94 String className = getClass().getName();
95 int firstIndex = className.lastIndexOf( '.' ) + 1;
96 int lastIndex = className.length();
97 return( className.substring( firstIndex, lastIndex ));
98 }
99
100 /* Set first token */
101 abstract public void setFirstTokenOffset( int tokenOffset );
102
103 /** Get first token */
104 abstract public int getFirstTokenOffset();
105
106 /** Set last token */
107 abstract public void setLastTokenOffset( int tokenOffset ) throws FunctionCallException;
108
109 /** Get last token */
110 abstract public int getLastTokenOffset();
111
112 /** Adjust token values */
113 abstract public void adjust( int boundary, int amount );
114
115 public VrmlElement vrmlClone( VrmlElement protoInstance ) {
116 return( null );
117 }
118
119 /** Add a child VrmlElement */
120 public void addChild( Object child ) {
121
122 if ( child == null ) {
123 return;
124 }
125 // optimization, one child added directly, if more than one
126 // create Vector
127 if ( children == null ) {
128 children = child;
129 } else if ( children instanceof Vector ) {
130 ((Vector)children).addElement( child );
131 } else {
132 Object original = children;
133 children = new Vector();
134 ((Vector)children).addElement( original );
135 ((Vector)children).addElement( child );
136 }
137 if ( child instanceof VrmlElement ) {
138 VrmlElement pChild = (VrmlElement)child;
139 pChild.setParent( this );
140 }
141 }
142
143 /** Remove a child.
144 *
145 * @param child child to remove
146 * @throws VrmlElementNotFoundException if the child is not part
147 * of this VrmlElement
148 */
149 public void removeChild( Object child ) throws VrmlElementNotFoundException {
150 if ( !contains( child )) {
151 throw new VrmlElementNotFoundException();
152 }
153 if ( children instanceof Vector ) {
154 ((Vector)children).removeElement( child );
155 } else {
156 children = null;
157 }
158 }
159
160 /** Check if a specific child exists.
161 *
162 * @param child child to check for
163 * @return true if child found, otherwise false
164 */
165 public boolean contains( Object child ) {
166 if ( children != null ) {
167 if ( children instanceof Vector ) {
168 return( ((Vector)children).contains( child ));
169 } else {
170 return( children == child );
171 }
172 } else {
173 return( false );
174 }
175 }
176
177 /** does nothing unless DEFUSENode */
178 public void deregisterSelf() {
179 }
180
181
182 /** Remove a child, and update the text as well.
183 *
184 * @param f the VrmlElement to remove
185 * @throws VrmlElementNotFoundException if the VrmlElement parameter does not have this
186 * VrmlElement as one of its ancestors.
187 */
188 public void removeVrmlElement( VrmlElement f ) throws VrmlElementNotFoundException {
189 f.deregisterSelf();
190 VrmlElement scanner = f.getParent();
191 VrmlElement container = null;
192 if ( contains( f )) {
193 container = this;
194 }
195 while (( scanner != null ) && ( scanner != this )) {
196 if ( scanner.contains( f )) {
197 container = scanner;
198 }
199 scanner = scanner.getParent();
200 }
201 if (( scanner == null ) || ( container == null )) {
202 throw new VrmlElementNotFoundException();
203 }
204 container.removeChild( f );
205 int numberTokens = f.getLastTokenOffset() - f.getFirstTokenOffset() + 1;
206 Scene s = (Scene)f.getScene();
207 TokenEnumerator tokenEnumerator = s.getTokenEnumerator();
208 tokenEnumerator.startLineWith( f.getFirstTokenOffset() );
209 tokenEnumerator.startLineWith( f.getLastTokenOffset() + 1 );
210 AdjustmentVisitor av = new AdjustmentVisitor( tokenEnumerator, f.getFirstTokenOffset() + numberTokens - 1, -numberTokens );
211 s.traverse( av );
212 int firstLineNumber = tokenEnumerator.getLineNumber( f.getFirstTokenOffset() );
213 int numberLines = tokenEnumerator.getLineNumber( f.getLastTokenOffset() ) - firstLineNumber + 1;
214 int nTokens = f.getLastTokenOffset() - f.getFirstTokenOffset() + 1;
215 tokenEnumerator.removeTokens( f.getFirstTokenOffset(), nTokens );
216 tokenEnumerator.removeLines( firstLineNumber, numberLines );
217 }
218
219 /** Get the specific child.
220 *
221 * @param offset the offset of the child, from 0 to (numberChlidren-1)
222 * @return the child VrmlElement, null if none or the offset value out
223 * of range.
224 */
225 public VrmlElement getChildAt( int offset ) {
226 if ( children == null ) {
227 return( null );
228 } else if ( children instanceof Vector ) {
229 Vector vchildren = (Vector)children;
230 if ( vchildren.size() <= offset ) {
231 return( null );
232 } else {
233 return( (VrmlElement)vchildren.elementAt( offset ));
234 }
235 } else if ( offset == 0 ) {
236 return( (VrmlElement)children );
237 } else {
238 return( null );
239 }
240 }
241
242 /** Get the last child of this VrmlElement */
243 public VrmlElement getLastChild() {
244 if ( children == null ) {
245 return( null );
246 } else if ( children instanceof Vector ) {
247 Vector vchildren = (Vector)children;
248 return( getChildAt( vchildren.size() - 1 ));
249 } else {
250 return( (VrmlElement)children );
251 }
252 }
253
254 /** Get the number of children */
255 public int numberChildren() {
256 if ( children == null ) {
257 return( 0 );
258 } else if ( children instanceof Vector ) {
259 Vector vchildren = (Vector)children;
260 return( vchildren.size() );
261 } else {
262 return( 1 );
263 }
264 }
265
266 /** set a child's parent */
267 public void setParent( VrmlElement p ) {
268 parent = p;
269 }
270
271 /** get this element's parent */
272 public VrmlElement getParent() {
273 return( parent );
274 }
275
276 /** set the error string */
277 public void setError( String s ) {
278 if ( s == null ) {
279 errorString = null;
280 return;
281 }
282 if ( nowarning ) {
283 if ( s.indexOf( "Warning" ) == 0 ) {
284 return;
285 }
286 }
287 if ( noUnusedDEFwarning ) {
288 if ( s.indexOf( "DEF is not used" ) > 0 ) {
289 return;
290 }
291 }
292 if ( baseProfile ) {
293 if ( s.indexOf( "Nonconformance" ) == -1 ) {
294 return;
295 }
296 }
297 if ( disableBaseProfile ) {
298 if ( s.indexOf( "Nonconformance" ) == 0 ) {
299 return;
300 }
301 }
302 if ( errorString == null ) {
303 errorString = s;
304 } else {
305 if ( errorString.indexOf( s ) >= 0 ) {
306 return;
307 }
308 StringBuffer b = null;
309 if (( errorString.indexOf( "Warning" ) == 0 ) || ( errorString.indexOf( "Nonconformance" ) == 0 )) {
310 b = new StringBuffer( s );
311 b.append( ", " + errorString );
312 } else {
313 b = new StringBuffer( errorString );
314 b.append( ", " + s );
315 }
316 errorString = new String( b );
317 }
318 }
319
320 /** get the error string */
321 public String getError() {
322 return( errorString );
323 }
324
325 /** is this VrmlElement traversable */
326 abstract public boolean isTraversable();
327
328 /** template method, Field objects override this to traverse their
329 * field value.
330 */
331 public void fieldValueTraverse( Visitor v ) {
332 }
333
334 /** Visitor pattern, traverse structure with a particular visitor */
335 public void twoPassTraverse( Visitor v ) {
336 // the visitor controls whether children get visited or not
337 if ( v.visit( this )) {
338 fieldValueTraverse( v );
339 if ( children != null ) {
340 int nChildren = numberChildren();
341 for ( int i = 0; i < nChildren; i++ ) {
342 VrmlElement child = getChildAt( i );
343 if ( v.acceptsPassOne( child )) {
344 child.twoPassTraverse( v );
345 }
346 }
347 for ( int i = 0; i < nChildren; i++ ) {
348 VrmlElement child = getChildAt( i );
349 if ( v.acceptsPassTwo( child )) {
350 child.twoPassTraverse( v );
351 }
352 }
353 }
354 }
355 // used to keep track of how many levels down we are visiting
356 v.done();
357 }
358
359 /** Visitor pattern, traverse structure with a particular visitor */
360 public void traverse( Visitor v ) {
361 // the visitor controls whether children get visited or not
362 if ( v.visit( this )) {
363 fieldValueTraverse( v );
364 if ( children != null ) {
365 int nChildren = numberChildren();
366 for ( int i = 0; i < nChildren; i++ ) {
367 VrmlElement child = getChildAt( i );
368 if ( !( v instanceof ComplexityVisitor ) && !child.isTraversable()) {
369 v.visit( child );
370 v.done();
371 continue;
372 }
373 if ( v.accepts( child )) {
374 child.traverse( v );
375 }
376 }
377 }
378 }
379 // used to keep track of how many levels down we are visiting
380 v.done();
381 }
382
383 /** Get the root of the scene graph */
384 public VrmlElement getRoot() {
385 if ( parent == null ) {
386 return( this );
387 } else {
388 return( parent.getRoot() );
389 }
390 }
391
392 /** Get the root scene containing this element.
393 * This is necessary in some cases because the Scene contains the
394 * name space for DEFs and PROTOs.
395 * This is done within VrmlElement, because we need this name space
396 * for a particular element.
397 * The "isScene" method is a simple template method, returns false
398 * for all VrmlElement instances except for Scene.
399 *
400 * @return the Scene object containing this element, or null if
401 * none found.
402 */
403 public VrmlElement getScene() {
404 VrmlElement scanner = this;
405 while ( !( scanner.isScene() )) {
406 scanner = scanner.parent;
407 if ( scanner == null ) {
408 break;
409 } else if ( scanner == this ) {
410 // cycle
411 scanner = null;
412 break;
413 }
414 }
415
416 if ( scanner == null ) {
417 return( null );
418 } else {
419 return( scanner );
420 }
421 }
422
423 /** Does this element or any of its children have any errors */
424 public boolean containsErrors() {
425 if ( errorString != null ) {
426 if (( errorString.indexOf( "Warning" ) != 0 ) && ( errorString.indexOf( "Nonconformance" ) != 0 )) {
427 return( true );
428 }
429 }
430 int nChildren = numberChildren();
431 for ( int i = 0; i < nChildren; i++ ) {
432 VrmlElement v = getChildAt( i );
433 if ( v.containsErrors() ) {
434 return( true );
435 }
436 }
437 return( false );
438 }
439
440 /** template method, Scene overrides this to return true */
441 public boolean isScene() {
442 return( false );
443 }
444
445 /** Get the TokenEnumerator for this element */
446 public TokenEnumerator getTokenEnumerator() {
447 Scene s = (Scene)getScene();
448 if ( s != null ) {
449 return( s.getTokenEnumerator() );
450 } else {
451 return( null );
452 }
453 }
454
455 /** Add a warning Value child. This exists for those cases where
456 * there is no VrmlElement for the tokenOffset associated with the
457 * warning. NOTE: this may be restricted (i.e. not called) by
458 * the ErrorSummary object contained in the Scene. The ErrorSummary
459 * restriction prevents OutOfMemory conditions when there are too
460 * many warnings in a file.
461 *
462 * @param tokenOffset token offset where warning is to be attached
463 * @param warning String message containing warning
464 */
465 public void addWarning( int tokenOffset, String warning ) {
466 if ( !nowarning ) {
467 Value v = new Value( tokenOffset );
468 v.setError( warning );
469 addChild( v );
470 }
471 }
472
473 /** debugging dump method */
474 public void dump( String header ) {
475 dump( header, false );
476 }
477
478 /** another debugging dump method */
479 public void dumpUserDefined( String header ) {
480 System.out.println( "BEFORE: " + header );
481 Scene s = (Scene)getScene();
482 DumpVisitor dv = new DumpVisitor( System.out, s.getTokenEnumerator() );
483 dv.dumpUserDefinedFields();
484 traverse( dv );
485 System.out.println( "AFTER: " + header );
486 }
487
488 /** and another debugging dump method */
489 public void dump( String header, boolean tokensToo ) {
490 System.out.println( "BEFORE: " + header );
491 Scene s = (Scene)getScene();
492 DumpVisitor dv = new DumpVisitor( System.out, s.getTokenEnumerator() );
493 traverse( dv );
494 System.out.println( "AFTER: " + header );
495 }
496
497 }