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

Quick Search    Search Deep

Source code: org/apache/batik/bridge/BridgeContext.java


1   /*
2   
3      Copyright 2000-2004  The Apache Software Foundation 
4   
5      Licensed under the Apache License, Version 2.0 (the "License");
6      you may not use this file except in compliance with the License.
7      You may obtain a copy of the License at
8   
9          http://www.apache.org/licenses/LICENSE-2.0
10  
11     Unless required by applicable law or agreed to in writing, software
12     distributed under the License is distributed on an "AS IS" BASIS,
13     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14     See the License for the specific language governing permissions and
15     limitations under the License.
16  
17   */
18  package org.apache.batik.bridge;
19  
20  import java.awt.Cursor;
21  import java.awt.geom.Dimension2D;
22  import java.io.IOException;
23  import java.io.InterruptedIOException;
24  import java.lang.ref.SoftReference;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.ListIterator;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.WeakHashMap;
36  
37  import org.apache.batik.bridge.svg12.SVG12BridgeExtension;
38  import org.apache.batik.css.engine.CSSContext;
39  import org.apache.batik.css.engine.CSSEngine;
40  import org.apache.batik.css.engine.CSSEngineEvent;
41  import org.apache.batik.css.engine.CSSEngineListener;
42  import org.apache.batik.css.engine.CSSEngineUserAgent;
43  import org.apache.batik.css.engine.SVGCSSEngine;
44  import org.apache.batik.css.engine.SystemColorSupport;
45  import org.apache.batik.css.engine.value.Value;
46  import org.apache.batik.dom.svg.SVGContext;
47  import org.apache.batik.dom.svg.SVGDOMImplementation;
48  import org.apache.batik.dom.svg.SVGOMDocument;
49  import org.apache.batik.dom.svg.SVGOMElement;
50  import org.apache.batik.dom.svg.SVGStylableElement;
51  import org.apache.batik.gvt.CompositeGraphicsNode;
52  import org.apache.batik.gvt.GraphicsNode;
53  import org.apache.batik.gvt.TextPainter;
54  import org.apache.batik.script.Interpreter;
55  import org.apache.batik.script.InterpreterPool;
56  import org.apache.batik.util.CSSConstants;
57  import org.apache.batik.util.CleanerThread;
58  import org.apache.batik.util.ParsedURL;
59  import org.apache.batik.util.SVGConstants;
60  import org.apache.batik.util.Service;
61  
62  import org.w3c.dom.Document;
63  import org.w3c.dom.Element;
64  import org.w3c.dom.Node;
65  import org.w3c.dom.events.Event;
66  import org.w3c.dom.events.EventListener;
67  import org.w3c.dom.events.EventTarget;
68  import org.w3c.dom.events.MouseEvent;
69  import org.w3c.dom.events.MutationEvent;
70  import org.w3c.dom.svg.SVGDocument;
71  
72  /**
73   * This class represents a context used by the various bridges and the
74   * builder. A bridge context is associated to a particular document
75   * and cannot be reused.
76   *
77   * The context encapsulates the dynamic bindings between DOM elements
78   * and GVT nodes, graphic contexts such as a <tt>GraphicsNodeRenderContext</tt>,
79   * and the different objects required by the GVT builder to interpret
80   * a SVG DOM tree such as the current viewport or the user agent.
81   *
82   * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
83   * @version $Id: BridgeContext.java,v 1.83 2005/03/29 10:48:02 deweese Exp $
84   */
85  public class BridgeContext implements ErrorConstants, CSSContext {
86  
87      /**
88       * The document is bridge context is dedicated to.
89       */
90      protected Document document;
91  
92      /**
93       * The GVT builder that might be used to create a GVT subtree.
94       */
95      protected GVTBuilder gvtBuilder;
96  
97      /**
98       * The interpreter cache per document.
99       * key is the language -
100      * value is a Interpreter
101      */
102     protected Map interpreterMap = new HashMap(7);
103 
104     /**
105      * A Map of all the font families already matched. This is
106      * to reduce the number of instances of GVTFontFamilies and to
107      * hopefully reduce the time taken to search for a matching SVG font.
108      */
109     private Map fontFamilyMap;
110 
111     /**
112      * The viewports.
113      * key is an Element -
114      * value is a Viewport
115      */
116     protected Map viewportMap = new WeakHashMap();
117 
118     /**
119      * The viewport stack. Used in building time.
120      */
121     protected List viewportStack = new LinkedList();
122 
123     /**
124      * The user agent.
125      */
126     protected UserAgent userAgent;
127 
128     /**
129      * Binding Map:
130      * key is an SVG Element -
131      * value is a GraphicsNode
132      */
133     protected Map elementNodeMap;
134 
135     /**
136      * Binding Map:
137      * key is GraphicsNode -
138      * value is a SVG Element.
139      */
140     protected Map nodeElementMap;
141 
142     /**
143      * Bridge Map:
144      * Keys are namespace URI - values are HashMap (with keys are local
145      * name and values are a Bridge instance).
146      */
147     protected Map namespaceURIMap;
148 
149     /**
150      * Element Data Map:
151      * This is a general location for elements to 'cache'
152      * data.  Such as the graphics tree for a pattern or
153      * the Gradient arrays.
154      *
155      * This is a weak hash map and the data is referenced
156      * by SoftReference so both must be referenced elsewhere
157      * to stay live.
158      */
159     protected Map elementDataMap;
160 
161 
162     /**
163      * The interpreter pool used to handle scripts.
164      */
165     protected InterpreterPool interpreterPool;
166 
167     /**
168      * The document loader used to load/create Document.
169      */
170     protected DocumentLoader documentLoader;
171 
172     /**
173      * The size of the document.
174      */
175     protected Dimension2D documentSize;
176 
177     /**
178      * The text painter to use. Typically, you can specify the text painter that
179      * will be used be text nodes.
180      */
181     protected TextPainter textPainter;
182 
183     /**
184      * Indicates that no DOM listeners should be registered.  In this
185      * case the generated GVT tree should be totally independent of
186      * the DOM tree (in practice text holds references to the source
187      * text elements for font resolution).
188      */
189     public final static int STATIC      = 0;
190     /**
191      * Indicates that DOM listeners should be registered to support,
192      * 'interactivity' this includes anchors and cursors, but does not
193      * include support for DOM modifications.
194      */
195     public final static int INTERACTIVE = 1;
196 
197     /**
198      * Indicates that all DOM listeners should be registered. This supports
199      * 'interactivity' (anchors and cursors), as well as DOM modifications
200      * listeners to update the GVT rendering tree.
201      */
202     public final static int DYNAMIC     = 2;
203 
204     /**
205      * Whether the bridge should support dynamic, or interactive features.
206      */
207     protected int dynamicStatus = STATIC;
208 
209     /**
210      * The update manager.
211      */
212     protected UpdateManager updateManager;
213 
214     /**
215      * Constructs a new empty bridge context.
216      */
217     protected BridgeContext() {}
218 
219     /**
220      * By default we share a unique instance of InterpreterPool.
221      */
222     private static InterpreterPool sharedPool = new InterpreterPool();
223 
224     /**
225      * Constructs a new bridge context.
226      * @param userAgent the user agent
227      */
228     public BridgeContext(UserAgent userAgent) {
229         this(userAgent,
230              sharedPool,
231              new DocumentLoader(userAgent));
232     }
233 
234     /**
235      * Constructs a new bridge context.
236      * @param userAgent the user agent
237      * @param loader document loader
238      */
239     public BridgeContext(UserAgent userAgent,
240                          DocumentLoader loader) {
241         this(userAgent, sharedPool, loader);
242     }
243 
244     /**
245      * Constructs a new bridge context.
246      * @param userAgent the user agent
247      * @param interpreterPool the interpreter pool
248      * @param documentLoader document loader
249      */
250     public BridgeContext(UserAgent userAgent,
251                          InterpreterPool interpreterPool,
252                          DocumentLoader documentLoader) {
253         this.userAgent = userAgent;
254         this.viewportMap.put(userAgent, new UserAgentViewport(userAgent));
255         this.interpreterPool = interpreterPool;
256         this.documentLoader = documentLoader;
257     }
258 
259     /**
260      * Initializes the given document.
261      */
262     protected void initializeDocument(Document document) {
263         SVGOMDocument doc = (SVGOMDocument)document;
264         CSSEngine eng = doc.getCSSEngine();
265         if (eng == null) {
266             SVGDOMImplementation impl;
267             impl = (SVGDOMImplementation)doc.getImplementation();
268             eng = impl.createCSSEngine(doc, this);
269             eng.setCSSEngineUserAgent(new CSSEngineUserAgentWrapper(userAgent));
270             doc.setCSSEngine(eng);
271             eng.setMedia(userAgent.getMedia());
272             String uri = userAgent.getUserStyleSheetURI();
273             if (uri != null) {
274                 try {
275                     URL url = new URL(uri);
276                     eng.setUserAgentStyleSheet
277                         (eng.parseStyleSheet(url, "all"));
278                 } catch (MalformedURLException e) {
279                     userAgent.displayError(e);
280                 }
281             }
282             eng.setAlternateStyleSheet(userAgent.getAlternateStyleSheet());
283         }
284     }
285 
286     /**
287      * Returns the CSS engine associated with given element.
288      */
289     public CSSEngine getCSSEngineForElement(Element e) {
290         SVGOMDocument doc = (SVGOMDocument)e.getOwnerDocument();
291         return doc.getCSSEngine();
292     }
293 
294     // properties ////////////////////////////////////////////////////////////
295 
296     /**
297      * Sets the text painter that will be used by text nodes. This attributes
298      * might be used by bridges (especially SVGTextElementBridge) to set the
299      * text painter of each TextNode.
300      *
301      * @param textPainter the text painter for text nodes 
302      */
303     public void setTextPainter(TextPainter textPainter) {
304   this.textPainter = textPainter;
305     }
306 
307     /**
308      * Returns the text painter that will be used be text nodes.
309      */
310     public TextPainter getTextPainter() {
311   return textPainter;
312     }
313 
314     /**
315      * Returns the document this bridge context is dedicated to.
316      */
317     public Document getDocument() {
318         return document;
319     }
320 
321     /**
322      * Sets the document this bridge context is dedicated to, to the
323      * specified document.
324      * @param document the document
325      */
326     protected void setDocument(Document document) {
327         if (this.document != document){
328             fontFamilyMap = null;
329         }
330         this.document = document;
331         registerSVGBridges();
332     }
333 
334     /**
335      * Returns the map of font families
336      */
337     public Map getFontFamilyMap(){
338         if (fontFamilyMap == null){
339             fontFamilyMap = new HashMap();
340         }
341         return fontFamilyMap;
342     }
343 
344     /**
345      * Sets the map of font families to the specified value.
346      *
347      *@param fontFamilyMap the map of font families
348      */
349     protected void setFontFamilyMap(Map fontFamilyMap) {
350         this.fontFamilyMap = fontFamilyMap;
351     }
352 
353     /**
354      * Set Element Data.
355      * Associates data object with element so it can be
356      * retrieved later.
357      */
358     public void setElementData(Node n, Object data) {
359         if (elementDataMap == null)
360             elementDataMap = new WeakHashMap();
361         elementDataMap.put(n, new SoftReference(data));
362     }
363 
364     /**
365      * Set Element Data.
366      * Associates data object with element so it can be
367      * retrieved later.
368      */
369     public Object getElementData(Node n) {
370         if (elementDataMap == null)
371             return null;
372         Object o = elementDataMap.get(n);
373         if (o == null) return null;
374         SoftReference sr = (SoftReference)o;
375         o = sr.get();
376         if (o == null) {
377             elementDataMap.remove(n);
378         }
379         return o;
380     }
381 
382     /**
383      * Returns the user agent of this bridge context.
384      */
385     public UserAgent getUserAgent() {
386         return userAgent;
387     }
388 
389     /**
390      * Sets the user agent to the specified user agent.
391      * @param userAgent the user agent
392      */
393     protected void setUserAgent(UserAgent userAgent) {
394         this.userAgent = userAgent;
395     }
396 
397     /**
398      * Returns the GVT builder that is currently used to build the GVT tree.
399      */
400     public GVTBuilder getGVTBuilder() {
401         return gvtBuilder;
402     }
403 
404     /**
405      * Sets the GVT builder that uses this context.
406      */
407     protected void setGVTBuilder(GVTBuilder gvtBuilder) {
408         this.gvtBuilder = gvtBuilder;
409     }
410 
411     /**
412      * Returns the interpreter pool used to handle scripts.
413      */
414     public InterpreterPool getInterpreterPool() {
415         return interpreterPool;
416     }
417 
418     /**
419      * Returns the focus manager.
420      */
421     public FocusManager getFocusManager() {
422         return focusManager;
423     }
424 
425     /**
426      * Returns the cursor manager
427      */
428     public CursorManager getCursorManager() {
429         return cursorManager;
430     }
431 
432     /**
433      * Sets the interpreter pool used to handle scripts to the
434      * specified interpreter pool.
435      * @param interpreterPool the interpreter pool
436      */
437     protected void setInterpreterPool(InterpreterPool interpreterPool) {
438         this.interpreterPool = interpreterPool;
439     }
440 
441     /**
442      * Returns a Interpreter for the specified language.
443      *
444      * @param language the scripting language
445      */
446     public Interpreter getInterpreter(String language) {
447         if (document == null) {
448             throw new RuntimeException("Unknown document");
449         }
450         Interpreter interpreter = (Interpreter)interpreterMap.get(language);
451         if (interpreter == null) {
452             try {
453                 interpreter = interpreterPool.createInterpreter(document, language);
454                 interpreterMap.put(language, interpreter);
455             } catch (Exception e) {
456                 if (userAgent != null) {
457                     userAgent.displayError(e);
458                     return null;
459                 }
460             }
461         }
462 
463         if (interpreter == null) {
464             if (userAgent != null) {
465                 userAgent.displayError(new Exception("Unknown language: " + language));
466             }
467         }
468 
469         return interpreter;
470     }
471 
472     /**
473      * Returns the document loader used to load external documents.
474      */
475     public DocumentLoader getDocumentLoader() {
476         return documentLoader;
477     }
478 
479     /**
480      * Sets the document loader used to load external documents.
481      * @param newDocumentLoader the new document loader
482      */
483     protected void setDocumentLoader(DocumentLoader newDocumentLoader) {
484         this.documentLoader = newDocumentLoader;
485     }
486 
487     /**
488      * Returns the actual size of the document or null if the document
489      * has not been built yet.
490      */
491     public Dimension2D getDocumentSize() {
492         return documentSize;
493     }
494 
495     /**
496      * Sets the size of the document to the specified dimension.
497      *
498      * @param d the actual size of the SVG document
499      */
500     protected void setDocumentSize(Dimension2D d) {
501         this.documentSize = d;
502     }
503 
504     /**
505      * Returns true if the document is dynamic, false otherwise.
506      */
507     public boolean isDynamic() {
508         return (dynamicStatus == DYNAMIC);
509     }
510 
511     /**
512      * Returns true if the document is interactive, false otherwise.
513      */
514     public boolean isInteractive() {
515         return (dynamicStatus != STATIC);
516     }
517 
518     /**
519      * Sets the document as a STATIC, INTERACTIVE or DYNAMIC document. 
520      * Call this method before the build phase 
521      * (ie. before <tt>gvtBuilder.build(...)</tt>)
522      * otherwise, that will have no effect.
523      *
524      *@param status the document dynamicStatus
525      */
526     public void setDynamicState(int status) {
527         dynamicStatus = status;
528     }
529 
530     /**
531      * Sets the document as DYNAMIC if <tt>dynamic</tt> is true
532      * STATIC otherwise.
533      */
534     public void setDynamic(boolean dynamic) {
535         if (dynamic)
536             setDynamicState(DYNAMIC);
537         else
538             setDynamicState(STATIC);
539     }
540 
541     /**
542      * Sets the document as INTERACTIVE if <tt>interactive</tt> is
543      * true STATIC otherwise.
544      */
545     public void setInteractive(boolean interactive) {
546         if (interactive)
547             setDynamicState(INTERACTIVE);
548         else
549             setDynamicState(STATIC);
550     }
551 
552 
553     /**
554      * Returns the update manager, if the bridge supports dynamic features.
555      */
556     public UpdateManager getUpdateManager() {
557         return updateManager;
558     }
559 
560     /**
561      * Sets the update manager.
562      */
563     protected void setUpdateManager(UpdateManager um) {
564         updateManager = um;
565     }
566 
567     // reference management //////////////////////////////////////////////////
568 
569     /**
570      * Returns the element referenced by the specified element by the
571      * specified uri. The referenced element can not be a Document.
572      *
573      * @param e the element referencing
574      * @param uri the uri of the referenced element
575      */
576     public Element getReferencedElement(Element e, String uri) {
577         try {
578             SVGDocument document = (SVGDocument)e.getOwnerDocument();
579             URIResolver ur = new URIResolver(document, documentLoader);
580             Element ref = ur.getElement(uri, e);
581             if (ref == null) {
582                 throw new BridgeException(e, ERR_URI_BAD_TARGET,
583                                           new Object[] {uri});
584             } else {
585                 SVGOMDocument refDoc = (SVGOMDocument)ref.getOwnerDocument();
586                 // This is new rather than attaching this BridgeContext
587                 // with the new document we now create a whole new 
588                 // BridgeContext to go with the new document.
589                 // This means that the new document has it's own
590                 // world of stuff and it should avoid memory leaks
591                 // since the new document isn't 'tied into' this
592                 // bridge context.
593                 if (refDoc != document) {
594                     CSSEngine eng = refDoc.getCSSEngine();
595                     if (eng == null) {
596                         BridgeContext subCtx;
597                         subCtx = new BridgeContext(getUserAgent(),
598                                                    getDocumentLoader());
599                         subCtx.setGVTBuilder(getGVTBuilder());
600                         subCtx.setDocument(refDoc);
601                         subCtx.initializeDocument(refDoc);
602                     }
603                 }
604                 return ref;
605             }
606         } catch (MalformedURLException ex) {
607             throw new BridgeException(e, ERR_URI_MALFORMED,
608                                       new Object[] {uri});
609         } catch (InterruptedIOException ex) {
610             throw new InterruptedBridgeException();
611         } catch (IOException ex) {
612             throw new BridgeException(e, ERR_URI_IO,
613                                       new Object[] {uri});
614         } catch (IllegalArgumentException ex) {
615             throw new BridgeException(e, ERR_URI_REFERENCE_A_DOCUMENT,
616                                       new Object[] {uri});
617         } catch (SecurityException ex) {
618             throw new BridgeException(e, ERR_URI_UNSECURE,
619                                       new Object[] {uri});
620         }
621     }
622 
623     // Viewport //////////////////////////////////////////////////////////////
624 
625     /**
626      * Returns the viewport of the specified element.
627      *
628      * @param e the element interested in its viewport
629      */
630     public Viewport getViewport(Element e) {
631         if (viewportStack != null) {
632             // building time
633             if (viewportStack.size() == 0) {
634                 // outermost svg element
635                 return (Viewport)viewportMap.get(userAgent);
636             } else {
637                 // current viewport
638                 return (Viewport)viewportStack.get(0);
639             }
640         } else {
641             // search the first parent which has defined a viewport
642             e = SVGUtilities.getParentElement(e);
643             while (e != null) {
644                 Viewport viewport = (Viewport)viewportMap.get(e);
645                 if (viewport != null) {
646                     return viewport;
647                 }
648                 e = SVGUtilities.getParentElement(e);
649             }
650             return (Viewport)viewportMap.get(userAgent);
651         }
652     }
653 
654     /**
655      * Starts a new viewport from the specified element.
656      *
657      * @param e the element that defines a new viewport
658      * @param viewport the viewport of the element
659      */
660     public void openViewport(Element e, Viewport viewport) {
661         viewportMap.put(e, viewport);
662         if (viewportStack == null) {
663             viewportStack = new LinkedList();
664         }
665         viewportStack.add(0, viewport);
666     }
667 
668     public void removeViewport(Element e) {
669         viewportMap.remove(e);
670     }
671 
672     /**
673      * Closes the viewport associated to the specified element.
674      * @param e the element that closes its viewport
675      */
676     public void closeViewport(Element e) {
677         //viewportMap.remove(e); FIXME: potential memory leak
678         viewportStack.remove(0);
679         if (viewportStack.size() == 0) {
680             viewportStack = null;
681         }
682     }
683 
684     // Bindings //////////////////////////////////////////////////////////////
685 
686     /**
687      * Binds the specified GraphicsNode to the specified Element. This method
688      * automatically bind the graphics node to the element and the element to
689      * the graphics node.
690      *
691      * @param element the element to bind to the specified graphics node
692      * @param node the graphics node to bind to the specified element
693      */
694     public void bind(Element element, GraphicsNode node) {
695         if (elementNodeMap == null) {
696             elementNodeMap = new WeakHashMap();
697             nodeElementMap = new WeakHashMap();
698         }
699         elementNodeMap.put(element, new SoftReference(node));
700         nodeElementMap.put(node, new SoftReference(element));
701     }
702 
703     /**
704      * Removes the binding of the specified Element.
705      *
706      * @param element the element to unbind
707      */
708     public void unbind(Element element) {
709         if (elementNodeMap == null) {
710             return;
711         }
712         GraphicsNode node = null;
713         SoftReference sr = (SoftReference)elementNodeMap.get(element);
714         if (sr != null)
715             node = (GraphicsNode)sr.get();
716         elementNodeMap.remove(element);
717         if (node != null)
718             nodeElementMap.remove(node);
719     }
720 
721     /**
722      * Returns the GraphicsNode associated to the specified Element or
723      * null if any.
724      *
725      * @param element the element associated to the graphics node to return
726      */
727     public GraphicsNode getGraphicsNode(Element element) {
728         if (elementNodeMap != null) {
729             SoftReference sr = (SoftReference)elementNodeMap.get(element);
730             if (sr != null) 
731                 return (GraphicsNode)sr.get();
732         }
733         return null;
734     }
735 
736     /**
737      * Returns the Element associated to the specified GraphicsNode or
738      * null if any.
739      *
740      * @param node the graphics node associated to the element to return
741      */
742     public Element getElement(GraphicsNode node) {
743         if (nodeElementMap != null) {
744             SoftReference sr = (SoftReference)nodeElementMap.get(node);
745             if (sr != null) 
746                 return (Element)sr.get();
747         }
748         return null;
749     }
750 
751     // Bridge management /////////////////////////////////////////////////////
752  
753     /**
754      * Returns true if the specified element has a GraphicsNodeBridge
755      * associated to it, false otherwise.
756      *
757      * @param element the element
758      */
759     public boolean hasGraphicsNodeBridge(Element element) {
760         if (namespaceURIMap == null || element == null) {
761             return false;
762         }
763         String localName = element.getLocalName();
764         String namespaceURI = element.getNamespaceURI();
765         namespaceURI = ((namespaceURI == null)? "" : namespaceURI);
766         HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI);
767         if (localNameMap == null) {
768             return false;
769         }
770         return (localNameMap.get(localName) instanceof GraphicsNodeBridge);
771     }
772 
773     /**
774      * Returns the bridge associated with the specified element.
775      *
776      * @param element the element
777      */
778     public Bridge getBridge(Element element) {
779         if (namespaceURIMap == null || element == null) {
780             return null;
781         }
782         String localName = element.getLocalName();
783         String namespaceURI = element.getNamespaceURI();
784         namespaceURI = ((namespaceURI == null)? "" : namespaceURI);
785         return getBridge(namespaceURI, localName);
786     }
787 
788     /**
789      * Returns the bridge associated with the element type
790      *
791      * @param namespaceURI namespace of the requested element
792      * @param localName element's local name
793      *
794      */
795     public Bridge getBridge(String namespaceURI, String localName) {
796         HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI);
797         if (localNameMap == null) {
798             return null;
799         }
800         Bridge bridge = (Bridge)localNameMap.get(localName);
801         if (isDynamic()) {
802             return bridge == null ? null : bridge.getInstance();
803         } else {
804             return bridge;
805         }
806     }
807 
808     /**
809      * Associates the specified <tt>Bridge</tt> object with the specified
810      * namespace URI and local name.
811      * @param namespaceURI the namespace URI
812      * @param localName the local name
813      * @param bridge the bridge that manages the element
814      */
815     public void putBridge(String namespaceURI, String localName, Bridge bridge) {
816         // start assert
817         if (!(namespaceURI.equals(bridge.getNamespaceURI())
818               && localName.equals(bridge.getLocalName()))) {
819             throw new Error("Invalid Bridge: "+
820                             namespaceURI+"/"+bridge.getNamespaceURI()+" "+
821                             localName+"/"+bridge.getLocalName()+" "+
822                             bridge.getClass());
823         }
824         // end assert
825         if (namespaceURIMap == null) {
826             namespaceURIMap = new HashMap();
827         }
828         namespaceURI = ((namespaceURI == null)? "" : namespaceURI);
829         HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI);
830         if (localNameMap == null) {
831             localNameMap = new HashMap();
832             namespaceURIMap.put(namespaceURI, localNameMap);
833         }
834         localNameMap.put(localName, bridge);
835     }
836 
837     /**
838      * Associates the specified <tt>Bridge</tt> object with it's 
839      * namespace URI and local name.
840      *
841      * @param bridge the bridge that manages the element
842      */
843     public void putBridge(Bridge bridge) {
844         putBridge(bridge.getNamespaceURI(), bridge.getLocalName(), bridge);
845     }
846 
847     /**
848      * Removes the <tt>Bridge</tt> object associated to the specified
849      * namespace URI and local name.
850      *
851      * @param namespaceURI the namespace URI
852      * @param localName the local name
853      */
854     public void removeBridge(String namespaceURI, String localName) {
855         if (namespaceURIMap == null) {
856             return;
857         }
858         namespaceURI = ((namespaceURI == null)? "" : namespaceURI);
859         HashMap localNameMap = (HashMap) namespaceURIMap.get(namespaceURI);
860         if (localNameMap != null) {
861             localNameMap.remove(localName);
862             if (localNameMap.isEmpty()) {
863                 namespaceURIMap.remove(namespaceURI);
864                 if (namespaceURIMap.isEmpty()) {
865                     namespaceURIMap = null;
866                 }
867             }
868         }
869     }
870 
871     // dynamic support ////////////////////////////////////////////////////////
872 
873     /**
874      * The list of all EventListener attached by bridges that need to
875      * be removed on a dispose() call.
876      */
877     protected Set eventListenerSet = new HashSet();
878 
879     /**
880      * The DOM EventListener to receive 'DOMCharacterDataModified' event.
881      */
882     protected EventListener domCharacterDataModifiedListener;
883 
884     /**
885      * The DOM EventListener to receive 'DOMAttrModified' event.
886      */
887     protected EventListener domAttrModifiedEventListener;
888 
889     /**
890      * The DOM EventListener to receive 'DOMNodeInserted' event.
891      */
892     protected EventListener domNodeInsertedEventListener;
893 
894     /**
895      * The DOM EventListener to receive 'DOMNodeRemoved' event.
896      */
897     protected EventListener domNodeRemovedEventListener;
898 
899     /**
900      * The CSSEngine listener to receive CSSEngineEvent.
901      */
902     protected CSSEngineListener cssPropertiesChangedListener;
903 
904     /**
905      * The EventListener that is responsible of managing DOM focus event.
906      */
907     protected FocusManager focusManager;
908 
909     /**
910      * Manages cursors and performs caching when appropriate
911      */
912     protected CursorManager cursorManager = new CursorManager(this);
913 
914     /**
915      * Adds EventListeners to the input document to handle the cursor 
916      * property.
917      * This is not done in the addDOMListeners method because 
918      * addDOMListeners is only used for dynamic content whereas 
919      * cursor support is provided for all content.
920      * Also note that it is very important that the listeners be
921      * registered for the capture phase as the 'default' behavior
922      * for cursors is handled by the BridgeContext during the 
923      * capture phase and the 'custom' behavior (handling of 'auto'
924      * on anchors, for example), is handled during the bubling phase.
925      */
926     public void addUIEventListeners(Document doc) {
927         EventTarget evtTarget = (EventTarget)doc.getDocumentElement();
928 
929         DOMMouseOverEventListener domMouseOverListener =
930             new DOMMouseOverEventListener();
931         evtTarget.addEventListener(SVGConstants.SVG_EVENT_MOUSEOVER, 
932                                    domMouseOverListener,
933                                    true);
934         storeEventListener(evtTarget, SVGConstants.SVG_EVENT_MOUSEOVER, 
935                            domMouseOverListener, true);
936 
937         DOMMouseOutEventListener domMouseOutListener =
938             new DOMMouseOutEventListener();
939         evtTarget.addEventListener(SVGConstants.SVG_EVENT_MOUSEOUT,
940                                    domMouseOutListener,
941                                    true);
942         storeEventListener(evtTarget, SVGConstants.SVG_EVENT_MOUSEOUT, 
943                            domMouseOutListener, true);
944 
945     }
946 
947 
948     public void removeUIEventListeners(Document doc) {
949         EventTarget evtTarget = (EventTarget)doc.getDocumentElement();
950         synchronized (eventListenerSet) {
951             Iterator i = eventListenerSet.iterator();
952             while (i.hasNext()) {
953                 EventListenerMememto elm = (EventListenerMememto)i.next();
954                 EventTarget   et = elm.getTarget();
955                 if (et == evtTarget) {
956                     EventListener el = elm.getListener();
957                     boolean       uc = elm.getUseCapture();
958                     String        t  = elm.getEventType();
959                     if ((et == null) || (el == null) || (t == null))
960                         continue;
961                     et.removeEventListener(t, el, uc);
962                 }
963             }
964         }
965         
966     }
967 
968     /**
969      * Adds EventListeners to the DOM and CSSEngineListener to the
970      * CSSEngine to handle any modifications on the DOM tree or style
971      * properties and update the GVT tree in response.
972      */
973     public void addDOMListeners() {
974         EventTarget evtTarget = (EventTarget)document;
975 
976         domAttrModifiedEventListener = new DOMAttrModifiedEventListener();
977         evtTarget.addEventListener("DOMAttrModified",
978                                    domAttrModifiedEventListener,
979                                    true);
980 
981         domNodeInsertedEventListener = new DOMNodeInsertedEventListener();
982         evtTarget.addEventListener("DOMNodeInserted",
983                                    domNodeInsertedEventListener,
984                                    true);
985 
986         domNodeRemovedEventListener = new DOMNodeRemovedEventListener();
987         evtTarget.addEventListener("DOMNodeRemoved",
988                                    domNodeRemovedEventListener,
989                                    true);
990 
991         domCharacterDataModifiedListener = 
992             new DOMCharacterDataModifiedListener();
993         evtTarget.addEventListener("DOMCharacterDataModified",
994                                    domCharacterDataModifiedListener,
995                                    true);
996 
997 
998         focusManager = new FocusManager(document);
999 
1000        SVGOMDocument svgDocument = (SVGOMDocument)document;
1001        CSSEngine cssEngine = svgDocument.getCSSEngine();
1002        cssPropertiesChangedListener = new CSSPropertiesChangedListener();
1003        cssEngine.addCSSEngineListener(cssPropertiesChangedListener);
1004    }
1005
1006    /**
1007     * Adds to the eventListenerSet the specified event listener
1008     * registration.
1009     */
1010    protected void storeEventListener(EventTarget t,
1011                                      String s,
1012                                      EventListener l,
1013                                      boolean b) {
1014        synchronized (eventListenerSet) {
1015            eventListenerSet.add(new EventListenerMememto(t, s, l, b, this));
1016        }
1017    }
1018
1019    public static class SoftReferenceMememto 
1020        extends CleanerThread.SoftReferenceCleared {
1021        Object mememto;
1022        Set    set;
1023        // String refStr;
1024        SoftReferenceMememto(Object ref, Object mememto, Set set) {
1025            super(ref);
1026            // refStr = ref.toString();
1027            this.mememto = mememto;
1028            this.set     = set;
1029        }
1030
1031        public void cleared() {
1032            synchronized (set) {
1033                // System.err.println("SRClear: " + refStr);
1034                set.remove(mememto);
1035                mememto = null;
1036                set     = null;
1037            }
1038        }
1039    }
1040
1041    /**
1042     * A class used to store an EventListener added to the DOM.
1043     */
1044    protected static class EventListenerMememto {
1045
1046        public SoftReference target; // Soft ref to EventTarget
1047        public SoftReference listener; // Soft ref to EventListener 
1048        public boolean useCapture;
1049        public String eventType;
1050
1051        public EventListenerMememto(EventTarget t, 
1052                                    String s, 
1053                                    EventListener l, 
1054                                    boolean b,
1055                                    BridgeContext ctx) {
1056            Set set = ctx.eventListenerSet;
1057            target = new SoftReferenceMememto(t, this, set);
1058            listener = new SoftReferenceMememto(l, this, set);
1059            eventType = s;
1060            useCapture = b;
1061        }
1062
1063        public EventListener getListener() {
1064            return (EventListener)listener.get();
1065        }
1066        public EventTarget getTarget() {
1067            return (EventTarget)target.get();
1068        }
1069        public boolean getUseCapture() {
1070            return useCapture;
1071        }
1072        public String getEventType() {
1073            return eventType;
1074        }
1075    }
1076
1077
1078    /**
1079     * Disposes this BridgeContext.
1080     */
1081    public void dispose() {
1082
1083        synchronized (eventListenerSet) {
1084        // remove all listeners added by Bridges
1085            Iterator iter = eventListenerSet.iterator();
1086        while (iter.hasNext()) {
1087            EventListenerMememto m = (EventListenerMememto)iter.next();
1088            EventTarget   et = m.getTarget();
1089            EventListener el = m.getListener();
1090            boolean       uc = m.getUseCapture();
1091            String        t  = m.getEventType();
1092            if ((et == null) || (el == null) || (t == null))
1093                continue;
1094            et.removeEventListener(t, el, uc);
1095            }
1096        }
1097
1098        if (document != null) {
1099            EventTarget evtTarget = (EventTarget)document;
1100
1101            evtTarget.removeEventListener("DOMAttrModified",
1102                                          domAttrModifiedEventListener, 
1103                                          true);
1104            evtTarget.removeEventListener("DOMNodeInserted",
1105                                          domNodeInsertedEventListener, 
1106                                          true);
1107            evtTarget.removeEventListener("DOMNodeRemoved",
1108                                          domNodeRemovedEventListener, 
1109                                          true);
1110            evtTarget.removeEventListener("DOMCharacterDataModified",
1111                                          domCharacterDataModifiedListener, 
1112                                          true);
1113            
1114            SVGOMDocument svgDocument = (SVGOMDocument)document;
1115            CSSEngine cssEngine = svgDocument.getCSSEngine();
1116            if (cssEngine != null) {
1117                cssEngine.removeCSSEngineListener
1118                    (cssPropertiesChangedListener);
1119                cssEngine.dispose();
1120                svgDocument.setCSSEngine(null);
1121            }
1122        }
1123        Iterator iter = interpreterMap.values().iterator();
1124        while (iter.hasNext()) {
1125            Interpreter interpreter = (Interpreter)iter.next();
1126            if (interpreter != null)
1127                interpreter.dispose();
1128        }
1129        interpreterMap.clear();
1130
1131        if (focusManager != null) {
1132            focusManager.dispose();
1133        }
1134    }
1135
1136    /**
1137     * Returns the SVGContext associated to the specified Node or null if any.
1138     */
1139    protected static SVGContext getSVGContext(Node node) {
1140        if (node instanceof SVGOMElement) {
1141            return ((SVGOMElement)node).getSVGContext();
1142        } else {
1143            return null;
1144        }
1145    }
1146
1147    /**
1148     * Returns the SVGContext associated to the specified Node or null if any.
1149     */
1150    protected static BridgeUpdateHandler getBridgeUpdateHandler(Node node) {
1151        SVGContext ctx = getSVGContext(node);
1152        return (ctx == null) ? null : (BridgeUpdateHandler)ctx;
1153    }
1154
1155    /**
1156     * The DOM EventListener invoked when an attribute is modified.
1157     */
1158    protected class DOMAttrModifiedEventListener implements EventListener {
1159
1160        /**
1161         * Handles 'DOMAttrModified' event type.
1162         */
1163        public void handleEvent(Event evt) {
1164            Node node = (Node)evt.getTarget();
1165            BridgeUpdateHandler h = getBridgeUpdateHandler(node);
1166            if (h != null) {
1167                try {
1168                    h.handleDOMAttrModifiedEvent((MutationEvent)evt);
1169                } catch (Exception e) {
1170                    userAgent.displayError(e);
1171                }
1172            }
1173        }
1174    }
1175
1176    /**
1177     * The DOM EventListener invoked when the mouse exits an element
1178     */
1179    protected class DOMMouseOutEventListener implements EventListener {
1180        public void handleEvent(Event evt) {
1181            MouseEvent me = (MouseEvent)evt;
1182            Element newTarget = (Element)me.getRelatedTarget();
1183            Cursor cursor = CursorManager.DEFAULT_CURSOR;
1184            if (newTarget != null)
1185                cursor = CSSUtilities.convertCursor
1186                    (newTarget, BridgeContext.this);
1187            if (cursor == null) 
1188                cursor = CursorManager.DEFAULT_CURSOR;
1189
1190            userAgent.setSVGCursor(cursor);
1191        }
1192    }
1193
1194
1195    /**
1196     * The DOM EventListener invoked when the mouse mouves over a new 
1197     * element.
1198     * 
1199     * Here is how cursors are handled:
1200     * 
1201     */
1202    protected class DOMMouseOverEventListener implements EventListener {
1203        /**
1204         * Handles 'mouseover' MouseEvent event type.
1205         */
1206        public void handleEvent(Event evt) {
1207            Element target = (Element)evt.getTarget();
1208            Cursor cursor = CSSUtilities.convertCursor(target, BridgeContext.this);
1209            
1210            if (cursor != null) {
1211                userAgent.setSVGCursor(cursor);
1212            }
1213        }
1214    }
1215    
1216    /**
1217     * The DOM EventListener invoked when a node is added.
1218     */
1219    protected class DOMNodeInsertedEventListener implements EventListener {
1220
1221        /**
1222         * Handles 'DOMNodeInserted' event type.
1223         */
1224        public void handleEvent(Event evt) {
1225            MutationEvent me = (MutationEvent)evt;
1226            BridgeUpdateHandler h = 
1227                getBridgeUpdateHandler(me.getRelatedNode());
1228            if (h != null) {
1229                try {
1230                    h.handleDOMNodeInsertedEvent(me);
1231                } catch (InterruptedBridgeException ibe) {
1232                    /* do nothing */
1233                } catch (Exception e) {
1234                    userAgent.displayError(e);
1235                }
1236            }
1237        }
1238    }
1239
1240    /**
1241     * The DOM EventListener invoked when a node is removed.
1242     */
1243    protected class DOMNodeRemovedEventListener implements EventListener {
1244
1245        /**
1246         * Handles 'DOMNodeRemoved' event type.
1247         */
1248        public void handleEvent(Event evt) {
1249            Node node = (Node)evt.getTarget();
1250            BridgeUpdateHandler h = getBridgeUpdateHandler(node);
1251            if (h != null) {
1252                try {
1253                    h.handleDOMNodeRemovedEvent((MutationEvent)evt);
1254                } catch (Exception e) {
1255                    userAgent.displayError(e);
1256                }
1257            }
1258        }
1259    }
1260
1261    /**
1262     * The DOM EventListener invoked when a character data is changed.
1263     */
1264    protected class DOMCharacterDataModifiedListener implements EventListener {
1265
1266        /**
1267         * Handles 'DOMNodeRemoved' event type.
1268         */
1269        public void handleEvent(Event evt) {
1270            Node node = (Node)evt.getTarget();
1271            while (node != null && !(node instanceof SVGOMElement)) {
1272                node = node.getParentNode();
1273            }
1274            BridgeUpdateHandler h = getBridgeUpdateHandler(node);
1275            if (h != null) {
1276                try {
1277                    h.handleDOMCharacterDataModified((MutationEvent)evt);
1278                } catch (Exception e) {
1279                    userAgent.displayError(e);
1280                }
1281            }
1282        }
1283    }
1284
1285    /**
1286     * The CSSEngineListener invoked when CSS properties are modified
1287     * on a particular element.
1288     */
1289    protected class CSSPropertiesChangedListener implements CSSEngineListener {
1290
1291        /**
1292         * Handles CSSEngineEvent that describes the CSS properties
1293         * that have changed on a particular element.
1294         */
1295        public void propertiesChanged(CSSEngineEvent evt) {
1296            Element elem = evt.getElement();
1297            SVGContext ctx = getSVGContext(elem);
1298            if (ctx == null) {
1299                GraphicsNode pgn = getGraphicsNode
1300                    ((Element)elem.getParentNode());
1301                if ((pgn == null) || !(pgn instanceof CompositeGraphicsNode)) {
1302                    // Something changed in this element but we really don't
1303                    // care since it's parent isn't displayed either.
1304                    return;
1305                }
1306                CompositeGraphicsNode parent = (CompositeGraphicsNode)pgn;
1307                // Check if 'display' changed on this element.
1308
1309                int [] properties = evt.getProperties();
1310                for (int i=0; i < properties.length; ++i) {
1311                    if (properties[i] == SVGCSSEngine.DISPLAY_INDEX) {
1312                        if (!CSSUtilities.convertDisplay(elem)) {
1313                            // (Still) Not displayed
1314                            break;
1315                        }
1316                        // build the graphics node
1317                        GVTBuilder builder = getGVTBuilder();
1318                        GraphicsNode childNode = builder.build
1319                            (BridgeContext.this, elem);
1320                        if (childNode == null) {
1321                            // the added element is not a graphic element?
1322                            break; 
1323                        }
1324                        int idx = -1;
1325                        for(Node ps = elem.getPreviousSibling(); ps != null;
1326                            ps = ps.getPreviousSibling()) {
1327                            if