Source code: com/trapezium/vrml/Scene.java
1 /*
2 * @(#)Scene.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.parse.TokenFactory;
15 import com.trapezium.vrml.node.PROTO;
16 import com.trapezium.vrml.node.PROTObase;
17 import com.trapezium.vrml.node.PROTOInstance;
18 import com.trapezium.vrml.node.DEFUSENode;
19 import com.trapezium.vrml.node.Node;
20 import com.trapezium.vrml.node.NodeType;
21 import com.trapezium.vrml.node.TokenData;
22 import com.trapezium.util.ReturnInteger;
23 import com.trapezium.vrml.grammar.VRML97;
24 import com.trapezium.vrml.grammar.DEFNameFactory;
25 import com.trapezium.vrml.visitor.DEFVisitor;
26 import com.trapezium.vrml.visitor.NodeSelectionVisitor;
27 import com.trapezium.vrml.grammar.VRML97parser;
28 import com.trapezium.vorlon.ErrorSummary;
29 import com.trapezium.parse.InputStreamFactory;
30
31 import java.util.Hashtable;
32 import java.util.Vector;
33 import java.util.StringTokenizer;
34 import java.io.InputStream;
35
36 /**
37 * Scene graph component representing an entire VRML file, or the
38 * body of a PROTO.
39 *
40 * The only difference between the PROTO body and a Scene is that
41 * the PROTO body is required to have at least one node. This
42 * restriction is not part of the Scene, and is a check performed
43 * by the grammar package during parsing.
44 *
45 * The Scene has a single Declaration for each child element.
46 *
47 * The Scene gives the name scope for PROTO and DEF nodes. As a file is processed, any
48 * PROTO and DEF nodes are registered with the Scene. Name uniqueness is forced during
49 * this registration by appending "_1", "_2", etc.
50 *
51 * @author Johannes N. Johannsen
52 * @version 1.1, 14 Jan 1998
53 *
54 * @since 1.0
55 */
56
57 public class Scene extends MultipleTokenElement implements SelectNode {
58 /** The Scene tokens exist only in relation to a specific TokenEnumerator kept by all Scenes */
59 TokenEnumerator dataSource = null;
60
61 /** Set the data source for this Scene's text */
62 public void setTokenEnumerator( TokenEnumerator t ) {
63 dataSource = t;
64 }
65
66 /** Get the data source for this Scene's text */
67 public TokenEnumerator getTokenEnumerator() {
68 return( dataSource );
69 }
70
71 /** Can get count from ComplexityVisitor and set here */
72 int vrmlElementCount = 0;
73 public void setVrmlElementCount( int n ) {
74 vrmlElementCount = n;
75 }
76 public int getVrmlElementCount() {
77 return( vrmlElementCount );
78 }
79
80 /** ErrorSummary info, possibly null */
81 ErrorSummary errorSummary;
82
83 public ErrorSummary getErrorSummary() {
84 return( errorSummary );
85 }
86
87 public void setErrorSummary( ErrorSummary errorSummary ) {
88 this.errorSummary = errorSummary;
89 }
90
91 /** List of nodes verified, created if necessary */
92 Hashtable verifyList;
93 public Hashtable getVerifyList() {
94 if ( verifyList == null ) {
95 verifyList = new Hashtable();
96 }
97 return( verifyList );
98 }
99
100 /** PROTO nodes by type */
101 public Hashtable protoTable = null;
102
103 /** PROTO node declarations available to Scene, identified by PROTO name */
104 public Hashtable PROTONodes = null;
105
106 /** DEF/USE nodes scope is limited to file */
107 public Hashtable DEFNodes = null;
108
109 /** list of all ROUTEs, used to check for duplicates */
110 public Hashtable routeTable = null;
111
112 /** add a ROUTE to the list kept by this Scene */
113 public void addRoute( String sourceDEF, String sourceField, String destDEF, String destField ) {
114 if (( sourceDEF != null ) && ( sourceField != null ) && ( destDEF != null ) && ( destField != null )) {
115 if ( routeTable == null ) {
116 routeTable = new Hashtable();
117 }
118 String routeText = sourceDEF + '.' + sourceField + '_' + destDEF + '.' + destField;
119 routeTable.put( routeText, routeText );
120 }
121 }
122
123 /** Check if a ROUTE is already known to this Scene */
124 public boolean hasRoute( String sourceDEF, String sourceField, String destDEF, String destField ) {
125 if ( routeTable == null ) {
126 return( false );
127 }
128 if (( sourceDEF != null ) && ( sourceField != null ) && ( destDEF != null ) && ( destField != null )) {
129 String routeText = sourceDEF + '.' + sourceField + '_' + destDEF + '.' + destField;
130 return( routeTable.get( routeText ) != null );
131 }
132 return( false );
133 }
134
135
136 //
137 // IFS specific tables that have a scene context, since we can only check usage in a
138 // global context. The key to the hash table is the "coord", "texCoord", "color",
139 // and "normal" node. The value associated with the key is a UsageInfo object
140 // which contains the following:
141 //
142 // - a BitSet indicating the usage of those values.
143 // - a count of the actual number of values
144 // - the node owning those values
145 //
146 Hashtable usageTable = null;
147 public Hashtable getUsageTable() {
148 if ( usageTable == null ) {
149 usageTable = new Hashtable();
150 }
151 return( usageTable );
152 }
153 //
154 // The key is an actual Coordinate, TextureCoordinate, Color, or Normal node,
155 // the associated value is an IndexInfo (see node/NodeType.java for IndexInfo class)
156 //
157 Hashtable coordTable = null;
158 public Hashtable getCoordTable() {
159 if ( coordTable == null ) {
160 coordTable = new Hashtable();
161 }
162 return( coordTable );
163 }
164 Hashtable texCoordTable = null;
165 public Hashtable getTexCoordTable() {
166 if ( texCoordTable == null ) {
167 texCoordTable = new Hashtable();
168 }
169 return( texCoordTable );
170 }
171 Hashtable colorTable = null;
172 public Hashtable getColorTable() {
173 if ( colorTable == null ) {
174 colorTable = new Hashtable();
175 }
176 return( colorTable );
177 }
178 Hashtable normalTable = null;
179 public Hashtable getNormalTable() {
180 if ( normalTable == null ) {
181 normalTable = new Hashtable();
182 }
183 return( normalTable );
184 }
185
186 /** A Scene has a PROTO parent if it is embedded within a PROTO node */
187 PROTO protoParent = null;
188 public PROTO getPROTOparent() {
189 return( protoParent );
190 }
191 public void setPROTOparent( PROTO rent ) {
192 protoParent = rent;
193 }
194
195 /** for the autoDEF feature */
196 DEFNameFactory defNameFactory;
197
198 /** main url */
199 String url;
200
201 /** Construct a scene entirely from a url.
202 *
203 * @param urlName url to use in constructing scene
204 */
205 public Scene( String urlName ) {
206 super( -1 );
207 try {
208 InputStream is = InputStreamFactory.getInputStream( urlName );
209
210 // null return is possible
211 if ( is != null ) {
212 TokenEnumerator vrmlTokens = new TokenEnumerator( is, urlName );
213 init( urlName, vrmlTokens, null );
214 VRML97parser parser = new VRML97parser();
215 parser.Build( vrmlTokens, this );
216 }
217 } catch ( Exception e ) {
218 // could not create InputStream or TokenEnumerator
219 }
220 }
221
222 public Scene() {
223 this( null, null, null );
224 }
225
226 public Scene( String urlName, TokenEnumerator te ) {
227 this( urlName, te, null );
228 }
229
230 /** Main constructor, all other constructors go through this one
231 *
232 * @param urlName url used to identify the scene
233 * @param te TokenEnumerator containing text used to create the Scene
234 * @param defNameFactory optional DEFNameFactory for autoDEF feature
235 */
236 static int staticSceneId = 1;
237 int sceneId;
238 public Scene( String urlName, TokenEnumerator te, DEFNameFactory defNameFactory ) {
239 super( -1 );
240 init( urlName, te, defNameFactory );
241 }
242
243 void init( String urlName, TokenEnumerator te, DEFNameFactory defNameFactory ) {
244 sceneId = staticSceneId++;
245 this.url = urlName;
246 this.dataSource = te;
247 this.defNameFactory = defNameFactory;
248 }
249
250 /** Each scene assigned an id number as it is created */
251 public int getSceneId() {
252 return( sceneId );
253 }
254
255 /** Get the url used to identify the scene */
256 public String getUrl() {
257 return( url );
258 }
259
260 /** Get the DEFNameFactory associated with the Scene */
261 public DEFNameFactory getDEFNameFactory() {
262 return( defNameFactory );
263 }
264
265 /** Set the DEFNameFactory for the Scene */
266 public void setDEFNameFactory( DEFNameFactory defNameFactory ) {
267 this.defNameFactory = defNameFactory;
268 }
269
270 /** untitled scenes */
271 static public int untitledCount = 0;
272 static public String getUntitle() {
273 untitledCount++;
274 return( "Untitled" + untitledCount + ".wrl" );
275 }
276
277 /** optional text header used by display */
278 String text;
279 public void setText( String t ) {
280 text = t;
281 }
282 public String getText() {
283 return( text );
284 }
285
286 /** How many node classes, including PROTOs, exist for a particular actual type */
287 public int getClassCount( String typeString ) {
288 String[] typeList = (String[])NodeType.typeTable.get( typeString );
289 if ( typeList != null ) {
290 System.out.println( "Count for nodeType '" + typeString + "' is " + typeList.length );
291 return( typeList.length );
292 } else {
293 System.out.println( "Count for actualType '" + typeString + "' is 0" );
294 return( 0 );
295 }
296 }
297
298 /** Get a string listing current classes, including protos */
299 public String getClassList( String typeString ) {
300 StringBuffer result = new StringBuffer( "NULL" );
301 String[] typeList = (String[])NodeType.typeTable.get( typeString );
302 if ( typeList != null ) {
303 for ( int i = 0; i < typeList.length; i++ ) {
304 result.append( "," );
305 result.append( typeList[i] );
306 }
307 }
308 return( new String( result ));
309 }
310
311 /** create DEF node hashtable if it doesn't already exist */
312 void createDEFtable() {
313 if ( DEFNodes == null ) {
314 DEFNodes = new Hashtable();
315 }
316 }
317
318 /** register DEF node in Scene. This is done as file is processed so at this
319 * point names are changed to enforce uniqueness.
320 */
321 public void registerDEF( DEFUSENode def ) {
322 deregisterDEF( def );
323 DEFNodes.put( def.getId(), def );
324 }
325
326 /** deregister DEF node in Scene, used before a rename */
327 public void deregisterDEF( DEFUSENode def ) {
328 createDEFtable();
329 if ( DEFNodes.get( def.getId() ) != null ) {
330 DEFNodes.remove( def.getId() );
331 }
332 }
333
334 /** just reserve the String of a DEF */
335 public void registerDEF( String defName ) {
336 createDEFtable();
337 DEFNodes.put( defName, "" );
338 }
339
340 /** get a def node given a "USE" */
341 public DEFUSENode getDEF( DEFUSENode use ) {
342 createDEFtable();
343 DEFUSENode d = (DEFUSENode)DEFNodes.get( use.getId() );
344 return( d );
345 }
346
347 /** lookup a DEF node given an id */
348 public DEFUSENode getDEF( String id ) {
349 if ( DEFNodes != null ) {
350 return( (DEFUSENode)DEFNodes.get( id ));
351 } else {
352 return( null );
353 }
354 }
355
356 /** is there a DEF node with a given name */
357 public boolean DEFexists( String id ) {
358 if ( DEFNodes != null ) {
359 return( DEFNodes.get( id ) != null );
360 } else {
361 return( false );
362 }
363 }
364
365
366 public Hashtable getDEFtable() {
367 return( DEFNodes );
368 }
369
370 /**
371 * Register PROTO node in Scene.
372 */
373 public void registerPROTO( PROTObase proto ) {
374 // register the PROTO with the actual type vector if necessary
375 if ( proto.getBuiltInNodeType() == null ) {
376 // PROTOs without a type are allowed, but reported with errors
377 } else if ( NodeType.typeTable.get( proto.getBuiltInNodeType() ) != null ) {
378 // create the Scene's protoTable if necessary.
379 // The "protoTable" keeps track of PROTOs that can be substituted
380 // for nodes of a particular type.
381 if ( protoTable == null ) {
382 protoTable = new Hashtable();
383 }
384
385 // see if there is already a vector for this particular type, if not create
386 // the vector and add it to the proto table
387 Vector vec = (Vector)protoTable.get( proto.getBuiltInNodeType() );
388 if ( vec == null ) {
389 vec = new Vector();
390 protoTable.put( proto.getBuiltInNodeType(), vec );
391 }
392
393 // add the PROTO to its built in type vector
394 vec.addElement( proto );
395 }
396
397 // force a unique name in the proto
398 if ( PROTONodes == null ) {
399 PROTONodes = new Hashtable();
400 } else if ( PROTONodes.get( proto.getId() ) != null ) {
401 PROTONodes.remove( proto.getId() );
402 }
403 PROTONodes.put( proto.getId(), proto );
404 }
405
406 /**
407 * Get the built in node name. First check for the name in the PROTO nodes, and if
408 * found, use the proto to determine the built in node name. Otherwise, assume it is
409 * a built in node, and use that name.
410 */
411 public String getBuiltInNodeName( String name ) {
412 if ( PROTONodes != null ) {
413 PROTObase p = (PROTObase)PROTONodes.get( name );
414 if ( p != null ) {
415 return( p.getBuiltInNodeType() );
416 }
417 }
418 if ( parent != null ) {
419 Scene s = (Scene)parent.getScene();
420 if ( s != null ) {
421 return( s.getBuiltInNodeName( name ));
422 }
423 }
424
425 return( name );
426 }
427
428 /** get the PROTO declaration associated with a PROTO name.
429 * If not found in this scene, and this scene is contained within another
430 * scene (i.e. this scene itself is contained in a PROTO), check its parent
431 * scene for the PROTO also.
432 */
433 public PROTObase getPROTO( String name ) {
434 PROTObase result = null;
435 if ( PROTONodes != null ) {
436 result = (PROTObase)PROTONodes.get( name );
437 }
438 if ( result == null ) {
439 if ( parent != null ) {
440 Scene s = (Scene)parent.getScene();
441 if ( s != null ) {
442 return( s.getPROTO( name ));
443 }
444 }
445 }
446 return( result );
447 }
448
449
450 /** Create an instance of a particular PROTO */
451 public PROTOInstance PROTOFactory( String PROTOName ) {
452 PROTObase pb = getPROTO( PROTOName );
453 if ( pb == null ) {
454 return( null );
455 } else {
456 return( new PROTOInstance( pb ));
457 }
458 }
459
460 /** Is there a PROTO with the given name */
461 public boolean isPROTO( String name ) {
462 return( getPROTO( name ) != null );
463 }
464
465 /** template method, overrides VrmlElement.isScene(), used for finding
466 * Scene that contains a particular VrmlElement.
467 */
468 public boolean isScene() {
469 return( true );
470 }
471
472
473 /**
474 * Get the first node type in the Scene. When Scene is contained in a PROTO,
475 * this first type is the the actual type for PROTO nodes.
476 */
477 public String getFirstNodeType() {
478 if ( children != null ) {
479 int nChildren = numberChildren();
480 for ( int i = 0; i < nChildren; i++ ) {
481 VrmlElement e = getChildAt( i );
482 if ( e instanceof Node ) {
483 Node n = (Node)e;
484 if ( n instanceof DEFUSENode ) {
485 DEFUSENode dun = (DEFUSENode)n;
486 n = n.getNode();
487 // handle case where USE has bad id
488 if ( n == null ) {
489 return( null );
490 }
491 }
492 return( n.getBaseName() );
493 }
494 }
495 }
496 return( null );
497 }
498
499 /** Get the name of an existing PROTO that is the closest match with
500 * the unknown PROTO name provided.
501 */
502 public String getClosestMatch( String protoString, ReturnInteger result ) {
503 return( VRML97.getClosestMatch( protoString, PROTONodes, result ));
504 }
505
506 /** Add a ROUTE to the scene. If the ROUTE has DEFs that conflict with
507 * previously existing DEFs in this scene, then the Scene may have
508 * re DEFfed those Nodes to eliminate the conflict (if the Scene was
509 * constructed with a DEFNameFactory). In this case, the ROUTE DEF
510 * names are renamed in exactly the same way. Otherwise, the ROUTE
511 * names are preserved, which may result in a parsing error if the
512 * DEF names they refer to do not exist.
513 *
514 * @param route ROUTE from another scene graph
515 * @return newly added ROUTE
516 */
517 public ROUTE addROUTE( ROUTE route ) {
518 StringBuffer routeString = new StringBuffer();
519 Scene s = (Scene)route.getScene();
520 TokenEnumerator dataSource = s.getTokenEnumerator();
521 Hashtable mapper = getNameMapper( s );
522 for ( int i = route.getFirstTokenOffset(); i <= route.getLastTokenOffset(); i++ ) {
523 String sourceString = dataSource.toString( i );
524 if ( mapper != null ) {
525 // if String has the "." in it, break with StringTokenizer,
526 // and if first token is in mapper, rebuild string
527 if ( sourceString.indexOf( "." ) > 0 ) {
528 StringTokenizer st = new StringTokenizer( sourceString, "." );
529 String oldName = st.nextToken();
530 String newName = (String)mapper.get( oldName );
531 if ( newName != null ) {
532 sourceString = new String( newName + "." + st.nextToken() );
533 }
534 }
535 }
536 routeString.append( sourceString );
537 if ( i < route.getLastTokenOffset() ) {
538 routeString.append( " " );
539 }
540 }
541 return( addROUTE( new String( routeString )));
542 }
543
544 /** Add a ROUTE from a String
545 *
546 * @param route String form of ROUTE
547 * @return newly added ROUTE
548 */
549 public ROUTE addROUTE( String routeString ) {
550 TokenEnumerator dataDestination = getTokenEnumerator();
551 int newTokenOffset = dataDestination.getNumberTokens();
552 dataDestination.addLine( routeString, new TokenFactory() );
553 dataDestination.setState( newTokenOffset );
554 ROUTE newROUTE = new ROUTE( newTokenOffset, dataDestination );
555 RouteSource rs = new RouteSource( dataDestination.getNextToken(), dataDestination, this );
556 newROUTE.addChild( rs );
557 newROUTE.addChild( new TO( dataDestination.getNextToken(), dataDestination ));
558 RouteDestination rd = new RouteDestination( dataDestination.getNextToken(), dataDestination, this );
559 newROUTE.addChild( rd );
560 newROUTE.setLastTokenOffset( rd.getLastTokenOffset() );
561 addChild( newROUTE );
562 setLastTokenOffset( newROUTE.getLastTokenOffset() );
563 return( newROUTE );
564 }
565
566 /** Add a Node to the scene.
567 *
568 * @param node Node, possibly from another scene graph, to add to this scene
569 * @return newly added Node
570 */
571 public Node addNode( Node node ) {
572 DEFResolver defResolver = createDEFNames( node );
573 TokenData sceneTokenData = new TokenData( this );
574 TokenData nodeTokenData = new TokenData( node, TokenData.ReCreate );
575 sceneTokenData.insert( nodeTokenData );
576 Node newNode = nodeTokenData.getNode();
577 addChild( newNode );
578 setLastTokenOffset( newNode.getLastTokenOffset() );
579 if ( defResolver != null ) {
580 defResolver.resolve( newNode );
581 }
582 return( newNode );
583 }
584
585 /** Add a Node to the Scene.
586 *
587 * @param sourceNode node in String form
588 * @return newly added Node
589 */
590 public Node addNode( String sourceNode ) {
591 TokenData newTokenData = new TokenData( sourceNode, defNameFactory );
592 return( addNode( newTokenData.getNode() ));
593 }
594
595 /** table of source Scenes for node copies */
596 Hashtable sourceScenes = null;
597
598 /** Rename conflicting DEFs with new names using DEFNameFactory.
599 *
600 * @param node Node with possibly conflicting DEFs being added to Scene
601 *
602 * @return DEFResolver object that has all info necessary to resolve
603 * conflicting DEFs after new node gets created
604 */
605 public DEFResolver createDEFNames( Node node ) {
606 if ( defNameFactory != null ) {
607 DEFVisitor defVisitor = new DEFVisitor();
608 node.traverse( defVisitor );
609 int defCount = defVisitor.getNumberDEFs();
610 Scene s = (Scene)node.getScene();
611 if ( s != null ) {
612 for ( int i = 0; i < defCount; i++ ) {
613 if ( DEFexists( defVisitor.getDEF( i ))) {
614 DEFUSENode dun = getDEF( defVisitor.getDEF( i ));
615 if (( dun != null ) && ( dun.getNode() != null )) {
616 resolveDEF( s, defVisitor.getDEF( i ), defNameFactory.createDEFName( dun.getNode().getBaseName() ));
617 }
618 }
619 }
620 if ( defCount > 0 ) {
621 return( new DEFResolver( this, s ));
622 }
623 }
624 }
625 return( null );
626 }
627
628 /** Resolve a DEF name conflict, generate a new name for the DEF.
629 *
630 * @param scene source Scene for original Node
631 * @param originalName original DEF name
632 * @param newName new name as generated by DEFNameFactory
633 */
634 void resolveDEF( Scene scene, String originalName, String newName ) {
635 if ( sourceScenes == null ) {
636 sourceScenes = new Hashtable();
637 }
638 Hashtable mapper = (Hashtable)sourceScenes.get( scene );
639 if ( mapper == null ) {
640 mapper = new Hashtable();
641 sourceScenes.put( scene, mapper );
642 }
643 mapper.put( originalName, newName );
644 }
645
646 /** Get the name mapper for a particular source Scene. */
647 public Hashtable getNameMapper( Scene scene ) {
648 if ( sourceScenes == null ) {
649 return( null );
650 } else {
651 return( (Hashtable)sourceScenes.get( scene ));
652 }
653 }
654
655 /** Select a node, SelectNode interface */
656 public boolean selectNode( NodeSelection nodeSelection ) {
657 if (( nodeSelection.startLine == -1 ) || ( nodeSelection.endLine == -1 )) {
658 return( false );
659 }
660 if (( nodeSelection.startColumn == -1 ) || ( nodeSelection.endColumn == -1 )) {
661 return( false );
662 }
663 NodeSelectionVisitor nsv = new NodeSelectionVisitor( dataSource, nodeSelection );
664 traverse( nsv );
665 return( nsv.updateNodeSelection() );
666 }
667 }