| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge.svg12; |
| |
| import javax.swing.event.EventListenerList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.Map; |
| |
| import org.apache.batik.dom.AbstractNode; |
| import org.apache.batik.dom.events.NodeEventTarget; |
| import org.apache.batik.dom.xbl.XBLManager; |
| import org.apache.batik.dom.svg12.XBLEventSupport; |
| import org.apache.batik.dom.svg12.XBLOMContentElement; |
| import org.apache.batik.dom.svg12.XBLOMShadowTreeElement; |
| import org.apache.batik.util.XBLConstants; |
| import org.apache.batik.util.XMLConstants; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.w3c.dom.events.Event; |
| import org.w3c.dom.events.EventListener; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.events.MutationEvent; |
| |
| /** |
| * A class to manage all XBL content elements in a shadow tree. |
| * |
| * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a> |
| * @version $Id$ |
| */ |
| public class ContentManager { |
| |
| /** |
| * The shadow tree whose content elements this object is managing. |
| */ |
| protected XBLOMShadowTreeElement shadowTree; |
| |
| /** |
| * The bound element that owns the shadow tree. |
| */ |
| protected Element boundElement; |
| |
| /** |
| * The XBL manager. |
| */ |
| protected DefaultXBLManager xblManager; |
| |
| /** |
| * Map of content elements to selectors. |
| * [XBLContentElement, AbstractContentSelector] |
| */ |
| protected HashMap selectors = new HashMap(); |
| |
| /** |
| * Map of content elements to a list of nodes that were selected |
| * by that content element. |
| * [XBLContentElement, NodeList] |
| */ |
| protected HashMap selectedNodes = new HashMap(); |
| |
| /** |
| * List of content elements. |
| * [XBLContentElement] |
| */ |
| protected LinkedList contentElementList = new LinkedList(); |
| |
| /** |
| * The recently removed node from the shadow tree. |
| */ |
| protected Node removedNode; |
| |
| /** |
| * Map of XBLContentElement objects to EventListenerList |
| * objects. |
| */ |
| protected HashMap listeners = new HashMap(); |
| |
| /** |
| * DOMAttrModified listener for content elements. |
| */ |
| protected ContentElementDOMAttrModifiedEventListener |
| contentElementDomAttrModifiedEventListener; |
| |
| /** |
| * DOMAttrModified listener for bound element children. |
| */ |
| protected DOMAttrModifiedEventListener domAttrModifiedEventListener; |
| |
| /** |
| * DOMNodeInserted listener for bound element children. |
| */ |
| protected DOMNodeInsertedEventListener domNodeInsertedEventListener; |
| |
| /** |
| * DOMNodeRemoved listener for bound element children. |
| */ |
| protected DOMNodeRemovedEventListener domNodeRemovedEventListener; |
| |
| /** |
| * DOMSubtreeModified listener for shadow tree nodes. |
| */ |
| protected DOMSubtreeModifiedEventListener domSubtreeModifiedEventListener; |
| |
| /** |
| * DOMNodeInserted listener for content elements in the shadow tree. |
| */ |
| protected ShadowTreeNodeInsertedListener shadowTreeNodeInsertedListener; |
| |
| /** |
| * DOMNodeRemoved listener for content elements in the shadow tree. |
| */ |
| protected ShadowTreeNodeRemovedListener shadowTreeNodeRemovedListener; |
| |
| /** |
| * DOMSubtreeModified listener for content elements in the shadow tree. |
| */ |
| protected ShadowTreeSubtreeModifiedListener |
| shadowTreeSubtreeModifiedListener; |
| |
| /** |
| * Creates a new ContentManager object. |
| * @param s the shadow tree element whose content elements this object |
| * will be managing |
| * @param xm the XBLManager for this document |
| */ |
| public ContentManager(XBLOMShadowTreeElement s, XBLManager xm) { |
| shadowTree = s; |
| xblManager = (DefaultXBLManager) xm; |
| |
| xblManager.setContentManager(s, this); |
| boundElement = xblManager.getXblBoundElement(s); |
| |
| contentElementDomAttrModifiedEventListener = |
| new ContentElementDOMAttrModifiedEventListener(); |
| |
| XBLEventSupport es = (XBLEventSupport) |
| shadowTree.initializeEventSupport(); |
| shadowTreeNodeInsertedListener = new ShadowTreeNodeInsertedListener(); |
| shadowTreeNodeRemovedListener = new ShadowTreeNodeRemovedListener(); |
| shadowTreeSubtreeModifiedListener |
| = new ShadowTreeSubtreeModifiedListener(); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeInserted", |
| shadowTreeNodeInsertedListener, true); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeRemoved", |
| shadowTreeNodeRemovedListener, true); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMSubtreeModified", |
| shadowTreeSubtreeModifiedListener, true); |
| |
| es = (XBLEventSupport) |
| ((AbstractNode) boundElement).initializeEventSupport(); |
| domAttrModifiedEventListener = new DOMAttrModifiedEventListener(); |
| domNodeInsertedEventListener = new DOMNodeInsertedEventListener(); |
| domNodeRemovedEventListener = new DOMNodeRemovedEventListener(); |
| domSubtreeModifiedEventListener = new DOMSubtreeModifiedEventListener(); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMAttrModified", |
| domAttrModifiedEventListener, true); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeInserted", |
| domNodeInsertedEventListener, true); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeRemoved", |
| domNodeRemovedEventListener, true); |
| es.addImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMSubtreeModified", |
| domSubtreeModifiedEventListener, false); |
| |
| update(true); |
| } |
| |
| /** |
| * Disposes this ContentManager. |
| */ |
| public void dispose() { |
| xblManager.setContentManager(shadowTree, null); |
| |
| Iterator i = selectedNodes.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry e = (Map.Entry) i.next(); |
| NodeList nl = (NodeList) e.getValue(); |
| for (int j = 0; j < nl.getLength(); j++) { |
| Node n = nl.item(j); |
| xblManager.getRecord(n).contentElement = null; |
| } |
| } |
| |
| i = contentElementList.iterator(); |
| while (i.hasNext()) { |
| NodeEventTarget n = (NodeEventTarget) i.next(); |
| n.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified", |
| contentElementDomAttrModifiedEventListener, false); |
| } |
| |
| contentElementList.clear(); |
| selectedNodes.clear(); |
| |
| XBLEventSupport es |
| = (XBLEventSupport) ((AbstractNode) boundElement).getEventSupport(); |
| es.removeImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMAttrModified", |
| domAttrModifiedEventListener, true); |
| es.removeImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeInserted", |
| domNodeInsertedEventListener, true); |
| es.removeImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMNodeRemoved", |
| domNodeRemovedEventListener, true); |
| es.removeImplementationEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, |
| "DOMSubtreeModified", |
| domSubtreeModifiedEventListener, false); |
| } |
| |
| /** |
| * Returns a NodeList of the content that was selected by the |
| * given content element. |
| */ |
| public NodeList getSelectedContent(XBLOMContentElement e) { |
| return (NodeList) selectedNodes.get(e); |
| } |
| |
| /** |
| * Returns the content element that selected a given node. |
| */ |
| protected XBLOMContentElement getContentElement(Node n) { |
| return xblManager.getXblContentElement(n); |
| } |
| |
| /** |
| * Adds the specified ContentSelectionChangedListener to the |
| * listener list. |
| */ |
| public void addContentSelectionChangedListener |
| (XBLOMContentElement e, ContentSelectionChangedListener l) { |
| EventListenerList ll = (EventListenerList) listeners.get(e); |
| if (ll == null) { |
| ll = new EventListenerList(); |
| listeners.put(e, ll); |
| } |
| ll.add(ContentSelectionChangedListener.class, l); |
| } |
| |
| /** |
| * Removes the specified ContentSelectionChangedListener from the |
| * listener list. |
| */ |
| public void removeContentSelectionChangedListener |
| (XBLOMContentElement e, ContentSelectionChangedListener l) { |
| EventListenerList ll = (EventListenerList) listeners.get(e); |
| if (ll != null) { |
| ll.remove(ContentSelectionChangedListener.class, l); |
| } |
| } |
| |
| /** |
| * Dispatches the ContentSelectionChangedEvent to the registered |
| * listeners. |
| */ |
| protected void dispatchContentSelectionChangedEvent(XBLOMContentElement e) { |
| xblManager.invalidateChildNodes(e.getXblParentNode()); |
| |
| ContentSelectionChangedEvent evt = |
| new ContentSelectionChangedEvent(e); |
| |
| EventListenerList ll = (EventListenerList) listeners.get(e); |
| if (ll != null) { |
| Object[] ls = ll.getListenerList(); |
| for (int i = ls.length - 2; i >= 0; i -= 2) { |
| ContentSelectionChangedListener l = |
| (ContentSelectionChangedListener) ls[i + 1]; |
| l.contentSelectionChanged(evt); |
| } |
| } |
| |
| Object[] ls = xblManager.getContentSelectionChangedListeners(); |
| for (int i = ls.length - 2; i >= 0; i -= 2) { |
| ContentSelectionChangedListener l = |
| (ContentSelectionChangedListener) ls[i + 1]; |
| l.contentSelectionChanged(evt); |
| } |
| } |
| |
| /** |
| * Updates all content elements. |
| * @param first Whether this is the first update for this ContentManager. |
| */ |
| protected void update(boolean first) { |
| HashSet previouslySelectedNodes = new HashSet(); |
| Iterator i = selectedNodes.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry e = (Map.Entry) i.next(); |
| NodeList nl = (NodeList) e.getValue(); |
| for (int j = 0; j < nl.getLength(); j++) { |
| Node n = nl.item(j); |
| xblManager.getRecord(n).contentElement = null; |
| previouslySelectedNodes.add(n); |
| } |
| } |
| |
| i = contentElementList.iterator(); |
| while (i.hasNext()) { |
| NodeEventTarget n = (NodeEventTarget) i.next(); |
| n.removeEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified", |
| contentElementDomAttrModifiedEventListener, false); |
| } |
| |
| contentElementList.clear(); |
| selectedNodes.clear(); |
| |
| boolean updated = false; |
| for (Node n = shadowTree.getFirstChild(); |
| n != null; |
| n = n.getNextSibling()) { |
| if (update(first, n)) { |
| updated = true; |
| } |
| } |
| |
| if (updated) { |
| HashSet newlySelectedNodes = new HashSet(); |
| i = selectedNodes.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry e = (Map.Entry) i.next(); |
| NodeList nl = (NodeList) e.getValue(); |
| for (int j = 0; j < nl.getLength(); j++) { |
| Node n = nl.item(j); |
| newlySelectedNodes.add(n); |
| } |
| } |
| |
| HashSet removed = new HashSet(); |
| removed.addAll(previouslySelectedNodes); |
| removed.removeAll(newlySelectedNodes); |
| |
| HashSet added = new HashSet(); |
| added.addAll(newlySelectedNodes); |
| added.removeAll(previouslySelectedNodes); |
| |
| if (!first) { |
| xblManager.shadowTreeSelectedContentChanged(removed, added); |
| } |
| } |
| } |
| |
| protected boolean update(boolean first, Node n) { |
| boolean updated = false; |
| for (Node m = n.getFirstChild(); m != null; m = m.getNextSibling()) { |
| if (update(first, m)) { |
| updated = true; |
| } |
| } |
| if (n instanceof XBLOMContentElement) { |
| contentElementList.add(n); |
| XBLOMContentElement e = (XBLOMContentElement) n; |
| e.addEventListenerNS |
| (XMLConstants.XML_EVENTS_NAMESPACE_URI, "DOMAttrModified", |
| contentElementDomAttrModifiedEventListener, false, null); |
| AbstractContentSelector s = |
| (AbstractContentSelector) selectors.get(n); |
| boolean changed; |
| if (s == null) { |
| if (e.hasAttributeNS(null, |
| XBLConstants.XBL_INCLUDES_ATTRIBUTE)) { |
| String lang = getContentSelectorLanguage(e); |
| String selector = e.getAttributeNS |
| (null, XBLConstants.XBL_INCLUDES_ATTRIBUTE); |
| s = AbstractContentSelector.createSelector |
| (lang, this, e, boundElement, selector); |
| } else { |
| s = new DefaultContentSelector(this, e, boundElement); |
| } |
| selectors.put(n, s); |
| changed = true; |
| } else { |
| changed = s.update(); |
| } |
| NodeList selectedContent = s.getSelectedContent(); |
| selectedNodes.put(n, selectedContent); |
| for (int i = 0; i < selectedContent.getLength(); i++) { |
| Node m = selectedContent.item(i); |
| xblManager.getRecord(m).contentElement = e; |
| } |
| if (changed) { |
| updated = true; |
| dispatchContentSelectionChangedEvent(e); |
| } |
| } |
| return updated; |
| } |
| |
| /** |
| * Returns the selector language to be used for the given |
| * xbl:content element. This will look at the xbl:content |
| * element and the document element for an attribute |
| * batik:selectorLanguage. |
| */ |
| protected String getContentSelectorLanguage(Element e) { |
| String lang = e.getAttributeNS("http://xml.apache.org/batik/ext", |
| "selectorLanguage"); |
| if (lang.length() != 0) { |
| return lang; |
| } |
| lang = e.getOwnerDocument().getDocumentElement().getAttributeNS |
| ("http://xml.apache.org/batik/ext", "selectorLanguage"); |
| if (lang.length() != 0) { |
| return lang; |
| } |
| return null; |
| } |
| |
| /** |
| * The DOM EventListener invoked when an attribute is modified, |
| * for content elements. |
| */ |
| protected class ContentElementDOMAttrModifiedEventListener |
| implements EventListener { |
| public void handleEvent(Event evt) { |
| MutationEvent me = (MutationEvent) evt; |
| Attr a = (Attr) me.getRelatedNode(); |
| Element e = (Element) evt.getTarget(); |
| if (e instanceof XBLOMContentElement) { |
| String ans = a.getNamespaceURI(); |
| String aln = a.getLocalName(); |
| if (aln == null) { |
| aln = a.getNodeName(); |
| } |
| if (ans == null && XBLConstants.XBL_INCLUDES_ATTRIBUTE.equals(aln) |
| || "http://xml.apache.org/batik/ext".equals(ans) |
| && "selectorLanguage".equals(aln)) { |
| selectors.remove(e); |
| update(false); |
| } |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when an attribute is modified. |
| */ |
| protected class DOMAttrModifiedEventListener implements EventListener { |
| public void handleEvent(Event evt) { |
| if (evt.getTarget() != boundElement) { |
| update(false); |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node is added. |
| */ |
| protected class DOMNodeInsertedEventListener implements EventListener { |
| public void handleEvent(Event evt) { |
| update(false); |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node is removed. |
| */ |
| protected class DOMNodeRemovedEventListener implements EventListener { |
| public void handleEvent(Event evt) { |
| removedNode = (Node) evt.getTarget(); |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a subtree has changed. |
| */ |
| protected class DOMSubtreeModifiedEventListener implements EventListener { |
| public void handleEvent(Event evt) { |
| if (removedNode != null) { |
| removedNode = null; |
| update(false); |
| } |
| } |
| |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node in the shadow tree has been |
| * inserted. |
| */ |
| protected class ShadowTreeNodeInsertedListener implements EventListener { |
| public void handleEvent(Event evt) { |
| if (evt.getTarget() instanceof XBLOMContentElement) { |
| update(false); |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a node in the shadow tree has been |
| * removed. |
| */ |
| protected class ShadowTreeNodeRemovedListener implements EventListener { |
| public void handleEvent(Event evt) { |
| EventTarget target = evt.getTarget(); |
| if (target instanceof XBLOMContentElement) { |
| removedNode = (Node) evt.getTarget(); |
| } |
| } |
| } |
| |
| /** |
| * The DOM EventListener invoked when a subtree of the shadow tree |
| * has changed. |
| */ |
| protected class ShadowTreeSubtreeModifiedListener implements EventListener { |
| public void handleEvent(Event evt) { |
| if (removedNode != null) { |
| removedNode = null; |
| update(false); |
| } |
| } |
| } |
| } |