Source code: com/trapezium/vrml/visitor/PROTOcollector.java
1 /*
2 * @(#)PROTOcollector.java
3 *
4 * Copyright (c) 1998-1999 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.visitor;
12
13 import com.trapezium.pattern.Visitor;
14 import com.trapezium.vrml.node.PROTObase;
15 import com.trapezium.vrml.node.PROTOInstance;
16 import com.trapezium.vrml.Scene;
17 import com.trapezium.vrml.ROUTE;
18 import com.trapezium.vrml.VrmlElement;
19 import com.trapezium.vrml.node.Node;
20 import com.trapezium.vrml.node.DEFUSENode;
21 import com.trapezium.vrml.grammar.DEFNameFactory;
22 import com.trapezium.parse.TokenEnumerator;
23
24 import java.util.Vector;
25 import java.util.BitSet;
26 import java.util.Hashtable;
27
28 /** The PROTOcollector resolves conflicts introduced when one scene graph
29 * is merged into another. It collects infomation about all conflicts,
30 * then provides information about those conflicts.
31 *
32 * The method "resolveConflicts" resolves those which can be handled in-place.
33 * Otherwise, it has methods for extracting the information token by token.
34 * In-place conflict resolution handles DEF/USE/PROTO naming conflicts, but
35 * does not handle moving PROTOs to new locations in the file.
36 *
37 * Conflict resolution is one of two ways: the DEFNameFactory in the destination
38 * scene is used if it exists, otherwise an algorithm of appending "_1", "_2", etc.
39 * is used until a name without a conflict is found.
40 */
41 public class PROTOcollector extends Visitor {
42 // the list of PROTOs in the Scene
43 Vector protoList;
44
45 // the visited Scene is merged with a destination Scene
46 // the visited Scene is the container for all objects visited
47 Scene visitedScene;
48
49 // the destination Scene is not visited, it is necessary only to
50 // resolve name space conflicts
51 Scene destinationScene;
52
53 // bitmap of PROTOInstance locations
54 BitSet protoInstanceLocations;
55 // bitmap of PROTO locations
56 BitSet protoLocations;
57 // bitmap of USE locations
58 BitSet useLocations;
59 // bitmap of ROUTE locations
60 BitSet routeLocations;
61 int numberTokens;
62
63 // PROTO name conflict resolution, key is old name, value is new name
64 Hashtable protoMapTable;
65
66 // DEF name conflict resolution, key is old name, value is new name
67 Hashtable defMapTable;
68
69 // PROTO lookup, key is PROTO token offset, value is PROTO
70 Hashtable protoTable;
71
72 // ROUTE lookup, key is ROUTE token offset, value is ROUTE
73 Hashtable routeTable;
74
75 // list of ROUTEs
76 Vector routes;
77
78 /** class constructor
79 *
80 * @param s the Scene to merge into the destination Scene
81 * @param destinationScene the Scene that is growing
82 */
83 public PROTOcollector( Scene s, Scene destinationScene ) {
84 super( s.getTokenEnumerator() );
85 this.visitedScene = s;
86 protoList = null;
87 this.destinationScene = destinationScene;
88 numberTokens = dataSource.getNumberTokens();
89 protoInstanceLocations = new BitSet( numberTokens );
90 protoLocations = new BitSet( numberTokens );
91 useLocations = new BitSet( numberTokens );
92 routeLocations = new BitSet( numberTokens );
93 protoMapTable = new Hashtable();
94 defMapTable = new Hashtable();
95 protoTable = new Hashtable();
96 routeTable = new Hashtable();
97 }
98
99 /** Get the scene that is going to be modified as a result of merging into
100 * the destinationScene.
101 */
102 public Scene getScene() {
103 return( visitedScene );
104 }
105
106 /** were any PROTOs found? */
107 public boolean hasPROTOs() {
108 return( protoList != null );
109 }
110
111 /** How many PROTOs were found */
112 public int getNumberPROTOs() {
113 if ( protoList != null ) {
114 return( protoList.size() );
115 } else {
116 return( 0 );
117 }
118 }
119
120 /** Get a particular PROTO */
121 public PROTObase getPROTO( int offset ) {
122 return( (PROTObase)protoList.elementAt( offset ));
123 }
124
125 /** Visit an object in the Scene, save PROTO, DEF/USE, and ROUTE info
126 * for possible conflict resolution.
127 */
128 public boolean visitObject( Object a ) {
129 if ( a instanceof PROTObase ) {
130 savePROTO( (PROTObase)a );
131 } else if ( a instanceof PROTOInstance ) {
132 savePROTOinstance( (PROTOInstance)a );
133 } else if ( a instanceof DEFUSENode ) {
134 saveDEFUSENode( (DEFUSENode)a );
135 } else if ( a instanceof ROUTE ) {
136 saveROUTE( (ROUTE)a );
137 }
138 return( true );
139 }
140
141 /** Save a ROUTE, if either its source or dest object name is remapped
142 * according to the defMapTable, redo the token.
143 */
144 void saveROUTE( ROUTE route ) {
145 int offset = route.getFirstTokenOffset();
146 routeLocations.set( offset );
147 routeTable.put( new Integer( offset ), route );
148 String sourceObject = route.getSourceDEFname();
149 String destObject = route.getDestDEFname();
150 String remapSource = (String)defMapTable.get( sourceObject );
151 String remapDest = (String)defMapTable.get( destObject );
152 if ( remapSource != null ) {
153 VrmlElement root = route.getRoot();
154 if ( root instanceof Scene ) {
155 Scene sroot = (Scene)root;
156 TokenEnumerator te = sroot.getTokenEnumerator();
157 VrmlElement v = route.getChildAt( 0 );
158 te.replace( v.getFirstTokenOffset(), remapSource + "." + route.getSourceFieldName() );
159 }
160 }
161 if ( remapDest != null ) {
162 VrmlElement root = route.getRoot();
163 if ( root instanceof Scene ) {
164 Scene sroot = (Scene)root;
165 TokenEnumerator te = sroot.getTokenEnumerator();
166 VrmlElement v = route.getChildAt( 2 );
167 te.replace( v.getFirstTokenOffset(), remapDest + "." + route.getDestFieldName() );
168 }
169 }
170 if ( routes == null ) {
171 routes = new Vector();
172 }
173 routes.addElement( route );
174 }
175
176 /** Check if any ROUTEs were saved */
177 public boolean hasROUTEs() {
178 return( routes != null );
179 }
180
181 /** Get the number of ROUTEs saved */
182 public int getNumberROUTEs() {
183 return( routes.size() );
184 }
185
186 /** Get a specific ROUTE that was saved */
187 public ROUTE getROUTE( int offset ) {
188 return( (ROUTE)routes.elementAt( offset ));
189 }
190
191 /** Save a DEFUSENode. If it is a DEF, it is renamed if its name
192 * conflicts with one in the destination scene. The DEF is also
193 * registered with the destination scene to allow detection of
194 * other conflicts. If it is a USE, it is renamed if the corresponding
195 * DEF was renamed.
196 */
197 void saveDEFUSENode( DEFUSENode dun ) {
198 if ( dun.isDEF() ) {
199 String defName = dun.getDEFName();
200 if ( destinationScene.getDEF( defName ) == null ) {
201 destinationScene.registerDEF( dun );
202 } else {
203 DEFNameFactory dnf = destinationScene.getDEFNameFactory();
204 if ( dnf == null ) {
205 for ( int i = 1; i < 100; i++ ) {
206 String s = defName + "_" + i;
207 if ( destinationScene.getDEF( s ) == null ) {
208 defMapTable.put( defName, s );
209 dun.setDEFName( s );
210 destinationScene.registerDEF( dun );
211 return;
212 }
213 }
214 } else {
215 String nodeName = "unknown";
216 Node n = dun.getNode();
217 if ( n != null ) {
218 nodeName = n.getNodeName();
219 }
220 String s = dnf.createDEFName( nodeName );
221 // NOTE: we assume user supplied DEF name factory generates
222 // a name without a conflict
223 defMapTable.put( defName, s );
224 dun.setDEFName( s );
225 destinationScene.registerDEF( dun );
226 return;
227 }
228 }
229 } else {
230 int offset = dun.getFirstTokenOffset();
231 offset = dataSource.getNextToken( offset );
232 if ( offset != -1 ) {
233 useLocations.set( offset );
234 }
235 }
236 }
237
238 /** Save a PROTO, renaming it if its name conflicts with one
239 * in the destination scene. The PROTO is also registered in
240 * the destination scene, since this is its ultimate destination,
241 * so that conflicts between this and others merging into the
242 * same destination are detected.
243 */
244 void savePROTO( PROTObase proto ) {
245 if ( protoList == null ) {
246 protoList = new Vector();
247 }
248 protoList.addElement( proto );
249 int offset = proto.getFirstTokenOffset();
250 protoTable.put( new Integer( offset ), proto );
251 if ( offset != -1 ) {
252 protoLocations.set( offset );
253 }
254 String protoId = proto.getId();
255 if ( destinationScene.getPROTO( protoId ) == null ) {
256 destinationScene.registerPROTO( proto );
257 } else {
258 for ( int i = 1; i < 100; i++ ) {
259 String s = protoId + "_" + i;
260 if ( destinationScene.getPROTO( s ) == null ) {
261 protoMapTable.put( protoId, s );
262 proto.setId( s );
263 destinationScene.registerPROTO( proto );
264 return;
265 }
266 }
267 }
268 }
269
270 /** Save a PROTOInstance, if the PROTO declaration has a naming conflict
271 * all PROTOInstances must be renamed. This method saves the location of
272 * a PROTOInstance in case this is necessary.
273 */
274 void savePROTOinstance( PROTOInstance a ) {
275 int offset = a.getFirstTokenOffset();
276 if ( offset != -1 ) {
277 protoInstanceLocations.set( offset );
278 }
279 }
280
281 int currentToken = -1;
282
283 /** Token scanning methods */
284
285 /** Start scanning tokens in the scene to merge */
286 public void scanTokens() {
287 currentToken = 0;
288 }
289
290 /** Check if there are more tokens to access in the scene to merge */
291 public boolean hasMoreTokens() {
292 return( currentToken < numberTokens );
293 }
294
295 /** Is there a PROTO instance at a particular token offset that is renamed due
296 * to a PROTO declaration conflict.
297 *
298 * @param tokenOffset the offset to check
299 *
300 * @return true if the token offset is the first token in a PROTO instance,
301 * and that PROTO instance has a corresponding PROTO declaration which
302 * has been renamed due to a conflict with another PROTO declartion in the
303 * destination Scene.
304 */
305 public boolean protoIsRemapped( int tokenOffset ) {
306 return( protoInstanceLocations.get( tokenOffset ));
307 }
308
309
310 /** Is there a USE node at a particular token offset that is renamed due to
311 * a DEF declaration conflict.
312 *
313 * @param tokenOFfset the offset to check
314 *
315 * @return true if the token offset is the first token in a USE node, and that
316 * USE node has a corresponding DEF that has been renamed due to a conflict
317 * with another DEF in the destination Scene.
318 */
319 public boolean useIsRemapped( int tokenOffset ) {
320 return( useLocations.get( tokenOffset ));
321 }
322
323
324 /** Get the name for a remapped PROTO.
325 *
326 * @param tokenOffset the offset of the PROTO
327 *
328 * @return the new name for the PROTO, or original name if the tokenOffset parameter
329 * does not indicate a remapped PROTO (this latter case should not occur
330 * normally)
331 */
332 public String remapProto( int tokenOffset ) {
333 return( remap( tokenOffset, protoMapTable ));
334 }
335
336
337 /** Get the name for a remapped USE node.
338 *
339 * @param tokenOffset the offset for the USE node
340 *
341 * @return the new name for the USE, or original name if the tokenOffset parameter
342 * does not indicate a remapped USE (this latter case should not occur
343 * normally)
344 */
345 public String remapUse( int tokenOffset ) {
346 return( remap( tokenOffset, defMapTable ));
347 }
348
349
350 /** Generic remapping of Strings based on token offset.
351 *
352 * @param tokenOffset the offset used as a key into the remapping table
353 * @param table the remapping table
354 *
355 * @return the remapped name, based on the contents of the remapping table.
356 */
357 String remap( int tokenOffset, Hashtable table ) {
358 String original = dataSource.toString( tokenOffset );
359 String remap = (String)table.get( original );
360 if ( remap == null ) {
361 return( original );
362 } else {
363 return( remap );
364 }
365 }
366
367 /** Get the next token offset */
368 public int getNextToken() {
369 currentToken++;
370 while ( protoLocations.get( currentToken ) || routeLocations.get( currentToken )) {
371 if ( protoLocations.get( currentToken )) {
372 PROTObase p = (PROTObase)protoTable.get( new Integer( currentToken ));
373 if ( p != null ) {
374 currentToken = p.getLastTokenOffset();
375 currentToken++;
376 if ( currentToken >= numberTokens ) {
377 return( -1 );
378 }
379 }
380 } else if ( routeLocations.get( currentToken )) {
381 ROUTE r = (ROUTE)routeTable.get( new Integer( currentToken ));
382 if ( r != null ) {
383 currentToken = r.getLastTokenOffset();
384 currentToken++;
385 if ( currentToken >= numberTokens ) {
386 return( -1 );
387 }
388 }
389 }
390 }
391 return( currentToken );
392 }
393 }