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