0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.bridge.svg12;
0020:
0021: import java.util.ArrayList;
0022: import java.util.HashMap;
0023: import java.util.Iterator;
0024: import java.util.LinkedList;
0025: import java.util.Set;
0026: import java.util.TreeSet;
0027: import java.util.List;
0028: import java.util.Map;
0029:
0030: import javax.swing.event.EventListenerList;
0031:
0032: import org.apache.batik.bridge.BridgeContext;
0033: import org.apache.batik.bridge.BridgeException;
0034: import org.apache.batik.bridge.ErrorConstants;
0035: import org.apache.batik.dom.AbstractAttrNS;
0036: import org.apache.batik.dom.AbstractDocument;
0037: import org.apache.batik.dom.AbstractNode;
0038: import org.apache.batik.dom.events.NodeEventTarget;
0039: import org.apache.batik.dom.svg12.BindableElement;
0040: import org.apache.batik.dom.svg12.XBLEventSupport;
0041: import org.apache.batik.dom.svg12.XBLOMContentElement;
0042: import org.apache.batik.dom.svg12.XBLOMDefinitionElement;
0043: import org.apache.batik.dom.svg12.XBLOMImportElement;
0044: import org.apache.batik.dom.svg12.XBLOMShadowTreeElement;
0045: import org.apache.batik.dom.svg12.XBLOMTemplateElement;
0046: import org.apache.batik.dom.util.DoublyIndexedTable;
0047: import org.apache.batik.dom.xbl.NodeXBL;
0048: import org.apache.batik.dom.xbl.ShadowTreeEvent;
0049: import org.apache.batik.dom.xbl.XBLManager;
0050: import org.apache.batik.dom.xbl.XBLManagerData;
0051: import org.apache.batik.dom.xbl.XBLShadowTreeElement;
0052: import org.apache.batik.util.XBLConstants;
0053: import org.apache.batik.util.XMLConstants;
0054:
0055: import org.w3c.dom.Attr;
0056: import org.w3c.dom.Document;
0057: import org.w3c.dom.Element;
0058: import org.w3c.dom.NamedNodeMap;
0059: import org.w3c.dom.Node;
0060: import org.w3c.dom.NodeList;
0061: import org.w3c.dom.events.DocumentEvent;
0062: import org.w3c.dom.events.Event;
0063: import org.w3c.dom.events.EventListener;
0064: import org.w3c.dom.events.EventTarget;
0065: import org.w3c.dom.events.MutationEvent;
0066:
0067: /**
0068: * A full featured sXBL manager.
0069: *
0070: * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a>
0071: * @version $Id: DefaultXBLManager.java 479559 2006-11-27 09:46:16Z dvholten $
0072: */
0073: public class DefaultXBLManager implements XBLManager, XBLConstants {
0074:
0075: /**
0076: * Whether XBL processing is currently taking place.
0077: */
0078: protected boolean isProcessing;
0079:
0080: /**
0081: * The document.
0082: */
0083: protected Document document;
0084:
0085: /**
0086: * The BridgeContext.
0087: */
0088: protected BridgeContext ctx;
0089:
0090: /**
0091: * Map of namespace URI/local name pairs to ordered sets of
0092: * definition records.
0093: */
0094: protected DoublyIndexedTable definitionLists = new DoublyIndexedTable();
0095:
0096: /**
0097: * Map of definition element/import element pairs to definition records.
0098: */
0099: protected DoublyIndexedTable definitions = new DoublyIndexedTable();
0100:
0101: /**
0102: * Map of shadow trees to content managers.
0103: */
0104: protected Map contentManagers = new HashMap();
0105:
0106: /**
0107: * Map of import elements to import records.
0108: */
0109: protected Map imports = new HashMap();
0110:
0111: /**
0112: * DOM node inserted listener for the document.
0113: */
0114: protected DocInsertedListener docInsertedListener = new DocInsertedListener();
0115:
0116: /**
0117: * DOM node removed listener for the document.
0118: */
0119: protected DocRemovedListener docRemovedListener = new DocRemovedListener();
0120:
0121: /**
0122: * DOM subtree mutation listener for the document.
0123: */
0124: protected DocSubtreeListener docSubtreeListener = new DocSubtreeListener();
0125:
0126: /**
0127: * DOM attribute listener for import elements.
0128: */
0129: protected ImportAttrListener importAttrListener = new ImportAttrListener();
0130:
0131: /**
0132: * DOM attribute listener for referencing definition elements.
0133: */
0134: protected RefAttrListener refAttrListener = new RefAttrListener();
0135:
0136: /**
0137: * Global event listener list for XBL binding related events.
0138: */
0139: protected EventListenerList bindingListenerList = new EventListenerList();
0140:
0141: /**
0142: * Global event listener list for ContentSelectionChanged events.
0143: */
0144: protected EventListenerList contentSelectionChangedListenerList = new EventListenerList();
0145:
0146: /**
0147: * Creates a new DefaultXBLManager for the given document.
0148: */
0149: public DefaultXBLManager(Document doc, BridgeContext ctx) {
0150: document = doc;
0151: this .ctx = ctx;
0152: ImportRecord ir = new ImportRecord(null, null);
0153: imports.put(null, ir);
0154: }
0155:
0156: /**
0157: * Starts XBL processing on the document.
0158: */
0159: public void startProcessing() {
0160: if (isProcessing) {
0161: return;
0162: }
0163:
0164: // Get list of all current definitions in the document.
0165: NodeList nl = document.getElementsByTagNameNS(
0166: XBL_NAMESPACE_URI, XBL_DEFINITION_TAG);
0167: XBLOMDefinitionElement[] defs = new XBLOMDefinitionElement[nl
0168: .getLength()];
0169: for (int i = 0; i < defs.length; i++) {
0170: defs[i] = (XBLOMDefinitionElement) nl.item(i);
0171: }
0172:
0173: // Get list of all imports in the document.
0174: nl = document.getElementsByTagNameNS(XBL_NAMESPACE_URI,
0175: XBL_IMPORT_TAG);
0176: Element[] imports = new Element[nl.getLength()];
0177: for (int i = 0; i < imports.length; i++) {
0178: imports[i] = (Element) nl.item(i);
0179: }
0180:
0181: // Add document listeners.
0182: AbstractDocument doc = (AbstractDocument) document;
0183: XBLEventSupport es = (XBLEventSupport) doc
0184: .initializeEventSupport();
0185: es.addImplementationEventListenerNS(
0186: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0187: "DOMNodeRemoved", docRemovedListener, true);
0188: es.addImplementationEventListenerNS(
0189: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0190: "DOMNodeInserted", docInsertedListener, true);
0191: es.addImplementationEventListenerNS(
0192: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0193: "DOMSubtreeModified", docSubtreeListener, true);
0194:
0195: // Add definitions.
0196: for (int i = 0; i < defs.length; i++) {
0197: if (defs[i].getAttributeNS(null, XBL_REF_ATTRIBUTE)
0198: .length() != 0) {
0199: addDefinitionRef(defs[i]);
0200: } else {
0201: String ns = defs[i].getElementNamespaceURI();
0202: String ln = defs[i].getElementLocalName();
0203: addDefinition(ns, ln, defs[i], null);
0204: }
0205: }
0206:
0207: // Add imports.
0208: for (int i = 0; i < imports.length; i++) {
0209: addImport(imports[i]);
0210: }
0211:
0212: // Bind all of the bindable elements in the document that have a
0213: // matching definition.
0214: isProcessing = true;
0215: bind(document.getDocumentElement());
0216: }
0217:
0218: /**
0219: * Stops XBL processing on the document.
0220: */
0221: public void stopProcessing() {
0222: if (!isProcessing) {
0223: return;
0224: }
0225: isProcessing = false;
0226:
0227: // Remove document listeners.
0228: AbstractDocument doc = (AbstractDocument) document;
0229: XBLEventSupport es = (XBLEventSupport) doc
0230: .initializeEventSupport();
0231: es.removeImplementationEventListenerNS(
0232: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0233: "DOMNodeRemoved", docRemovedListener, true);
0234: es.removeImplementationEventListenerNS(
0235: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0236: "DOMNodeInserted", docInsertedListener, true);
0237: es.removeImplementationEventListenerNS(
0238: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0239: "DOMSubtreeModified", docSubtreeListener, true);
0240:
0241: // Remove all imports.
0242: int nSlots = imports.values().size();
0243: ImportRecord[] irs = new ImportRecord[nSlots];
0244: imports.values().toArray(irs);
0245: for (int i = 0; i < irs.length; i++) {
0246: ImportRecord ir = irs[i];
0247: if (ir.importElement.getLocalName().equals(
0248: XBL_DEFINITION_TAG)) {
0249: removeDefinitionRef(ir.importElement);
0250: } else {
0251: removeImport(ir.importElement);
0252: }
0253: }
0254:
0255: // Remove all bindings.
0256: Object[] defRecs = definitions.getValuesArray();
0257: definitions.clear();
0258: for (int i = 0; i < defRecs.length; i++) {
0259: DefinitionRecord defRec = (DefinitionRecord) defRecs[i];
0260: TreeSet defs = (TreeSet) definitionLists.get(
0261: defRec.namespaceURI, defRec.localName);
0262: if (defs != null) {
0263: while (!defs.isEmpty()) {
0264: defRec = (DefinitionRecord) defs.first();
0265: defs.remove(defRec);
0266: removeDefinition(defRec);
0267: }
0268: definitionLists.put(defRec.namespaceURI,
0269: defRec.localName, null);
0270: }
0271: }
0272: definitionLists = new DoublyIndexedTable();
0273: contentManagers.clear();
0274: }
0275:
0276: /**
0277: * Returns whether XBL processing is currently enabled.
0278: */
0279: public boolean isProcessing() {
0280: return isProcessing;
0281: }
0282:
0283: /**
0284: * Adds a definition through its referring definition element (one
0285: * with a 'ref' attribute).
0286: */
0287: protected void addDefinitionRef(Element defRef) {
0288: String ref = defRef.getAttributeNS(null, XBL_REF_ATTRIBUTE);
0289: Element e = ctx.getReferencedElement(defRef, ref);
0290: if (!XBL_NAMESPACE_URI.equals(e.getNamespaceURI())
0291: || !XBL_DEFINITION_TAG.equals(e.getLocalName())) {
0292: throw new BridgeException(ctx, defRef,
0293: ErrorConstants.ERR_URI_BAD_TARGET,
0294: new Object[] { ref });
0295: }
0296: ImportRecord ir = new ImportRecord(defRef, e);
0297: imports.put(defRef, ir);
0298:
0299: NodeEventTarget et = (NodeEventTarget) defRef;
0300: et.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0301: "DOMAttrModified", refAttrListener, false, null);
0302:
0303: XBLOMDefinitionElement d = (XBLOMDefinitionElement) defRef;
0304: String ns = d.getElementNamespaceURI();
0305: String ln = d.getElementLocalName();
0306: addDefinition(ns, ln, (XBLOMDefinitionElement) e, defRef);
0307: }
0308:
0309: /**
0310: * Removes a definition through its referring definition element (one
0311: * with a 'ref' attribute).
0312: */
0313: protected void removeDefinitionRef(Element defRef) {
0314: ImportRecord ir = (ImportRecord) imports.get(defRef);
0315: NodeEventTarget et = (NodeEventTarget) defRef;
0316: et.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0317: "DOMAttrModified", refAttrListener, false);
0318: DefinitionRecord defRec = (DefinitionRecord) definitions.get(
0319: ir.node, defRef);
0320: removeDefinition(defRec);
0321: imports.remove(defRef);
0322: }
0323:
0324: /**
0325: * Imports bindings from another document.
0326: */
0327: protected void addImport(Element imp) {
0328: String bindings = imp.getAttributeNS(null,
0329: XBL_BINDINGS_ATTRIBUTE);
0330: Node n = ctx.getReferencedNode(imp, bindings);
0331: if (n.getNodeType() == Node.ELEMENT_NODE
0332: && !(XBL_NAMESPACE_URI.equals(n.getNamespaceURI()) && XBL_XBL_TAG
0333: .equals(n.getLocalName()))) {
0334: throw new BridgeException(ctx, imp,
0335: ErrorConstants.ERR_URI_BAD_TARGET,
0336: new Object[] { n });
0337: }
0338: ImportRecord ir = new ImportRecord(imp, n);
0339: imports.put(imp, ir);
0340:
0341: NodeEventTarget et = (NodeEventTarget) imp;
0342: et.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0343: "DOMAttrModified", importAttrListener, false, null);
0344:
0345: et = (NodeEventTarget) n;
0346: et.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0347: "DOMNodeInserted", ir.importInsertedListener, false,
0348: null);
0349: et
0350: .addEventListenerNS(
0351: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0352: "DOMNodeRemoved", ir.importRemovedListener,
0353: false, null);
0354: et.addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0355: "DOMSubtreeModified", ir.importSubtreeListener, false,
0356: null);
0357: addImportedDefinitions(imp, n);
0358: }
0359:
0360: /**
0361: * Adds the definitions in the given imported subtree.
0362: */
0363: protected void addImportedDefinitions(Element imp, Node n) {
0364: if (n instanceof XBLOMDefinitionElement) {
0365: XBLOMDefinitionElement def = (XBLOMDefinitionElement) n;
0366: String ns = def.getElementNamespaceURI();
0367: String ln = def.getElementLocalName();
0368: addDefinition(ns, ln, def, imp);
0369: } else {
0370: n = n.getFirstChild();
0371: while (n != null) {
0372: addImportedDefinitions(imp, n);
0373: n = n.getNextSibling();
0374: }
0375: }
0376: }
0377:
0378: /**
0379: * Removes an import.
0380: */
0381: protected void removeImport(Element imp) {
0382: ImportRecord ir = (ImportRecord) imports.get(imp);
0383: NodeEventTarget et = (NodeEventTarget) ir.node;
0384: et.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0385: "DOMNodeInserted", ir.importInsertedListener, false);
0386: et.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0387: "DOMNodeRemoved", ir.importRemovedListener, false);
0388: et.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0389: "DOMSubtreeModified", ir.importSubtreeListener, false);
0390:
0391: et = (NodeEventTarget) imp;
0392: et.removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
0393: "DOMAttrModified", importAttrListener, false);
0394:
0395: Object[] defRecs = definitions.getValuesArray();
0396: for (int i = 0; i < defRecs.length; i++) {
0397: DefinitionRecord defRec = (DefinitionRecord) defRecs[i];
0398: if (defRec.importElement == imp) {
0399: removeDefinition(defRec);
0400: }
0401: }
0402: imports.remove(imp);
0403: }
0404:
0405: /**
0406: * Adds an xbl:definition element to the list of definitions that
0407: * could possibly affect elements with the specified QName. This
0408: * may or may not actually cause a new binding to come in to effect,
0409: * as this new definition element may be added earlier in the
0410: * document than another already in effect.
0411: *
0412: * @param namespaceURI the namespace URI of the bound elements
0413: * @param localName the local name of the bound elements
0414: * @param def the xbl:definition element
0415: * @param imp the xbl:import or xbl;definition element through which
0416: * this definition is being added, or null if the binding
0417: * is in the original document
0418: */
0419: protected void addDefinition(String namespaceURI, String localName,
0420: XBLOMDefinitionElement def, Element imp) {
0421: ImportRecord ir = (ImportRecord) imports.get(imp);
0422: DefinitionRecord oldDefRec = null;
0423: DefinitionRecord defRec;
0424: TreeSet defs = (TreeSet) definitionLists.get(namespaceURI,
0425: localName);
0426: if (defs == null) {
0427: defs = new TreeSet();
0428: definitionLists.put(namespaceURI, localName, defs);
0429: } else if (defs.size() > 0) {
0430: oldDefRec = (DefinitionRecord) defs.first();
0431: }
0432: XBLOMTemplateElement template = null;
0433: for (Node n = def.getFirstChild(); n != null; n = n
0434: .getNextSibling()) {
0435: if (n instanceof XBLOMTemplateElement) {
0436: template = (XBLOMTemplateElement) n;
0437: break;
0438: }
0439: }
0440: defRec = new DefinitionRecord(namespaceURI, localName, def,
0441: template, imp);
0442: defs.add(defRec);
0443: definitions.put(def, imp, defRec);
0444: addDefinitionElementListeners(def, ir);
0445: if (defs.first() != defRec) {
0446: return;
0447: }
0448: if (oldDefRec != null) {
0449: XBLOMDefinitionElement oldDef = oldDefRec.definition;
0450: XBLOMTemplateElement oldTemplate = oldDefRec.template;
0451: if (oldTemplate != null) {
0452: removeTemplateElementListeners(oldTemplate, ir);
0453: }
0454: removeDefinitionElementListeners(oldDef, ir);
0455: }
0456: if (template != null) {
0457: addTemplateElementListeners(template, ir);
0458: }
0459: if (isProcessing) {
0460: rebind(namespaceURI, localName, document
0461: .getDocumentElement());
0462: }
0463: }
0464:
0465: /**
0466: * Adds DOM mutation listeners to the given definition element.
0467: */
0468: protected void addDefinitionElementListeners(
0469: XBLOMDefinitionElement def, ImportRecord ir) {
0470: XBLEventSupport es = (XBLEventSupport) def
0471: .initializeEventSupport();
0472: es.addImplementationEventListenerNS(
0473: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0474: "DOMAttrModified", ir.defAttrListener, false);
0475: es.addImplementationEventListenerNS(
0476: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0477: "DOMNodeInserted", ir.defNodeInsertedListener, false);
0478: es.addImplementationEventListenerNS(
0479: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0480: "DOMNodeRemoved", ir.defNodeRemovedListener, false);
0481: }
0482:
0483: /**
0484: * Adds DOM mutation listeners to the given template element.
0485: */
0486: protected void addTemplateElementListeners(
0487: XBLOMTemplateElement template, ImportRecord ir) {
0488: XBLEventSupport es = (XBLEventSupport) template
0489: .initializeEventSupport();
0490: es.addImplementationEventListenerNS(
0491: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0492: "DOMAttrModified", ir.templateMutationListener, false);
0493: es.addImplementationEventListenerNS(
0494: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0495: "DOMNodeInserted", ir.templateMutationListener, false);
0496: es.addImplementationEventListenerNS(
0497: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0498: "DOMNodeRemoved", ir.templateMutationListener, false);
0499: es.addImplementationEventListenerNS(
0500: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0501: "DOMCharacterDataModified",
0502: ir.templateMutationListener, false);
0503: }
0504:
0505: /**
0506: * Removes an xbl:definition element from the list of definitions that
0507: * could possibly affect elements with the specified QName. This
0508: * will only cause a new binding to come in to effect if it is currently
0509: * active.
0510: */
0511: protected void removeDefinition(DefinitionRecord defRec) {
0512: TreeSet defs = (TreeSet) definitionLists.get(
0513: defRec.namespaceURI, defRec.localName);
0514: if (defs == null) {
0515: return;
0516: }
0517: Element imp = defRec.importElement;
0518: ImportRecord ir = (ImportRecord) imports.get(imp);
0519: DefinitionRecord activeDefRec = (DefinitionRecord) defs.first();
0520: defs.remove(defRec);
0521: definitions.remove(defRec.definition, imp);
0522: removeDefinitionElementListeners(defRec.definition, ir);
0523: if (defRec != activeDefRec) {
0524: return;
0525: }
0526: if (defRec.template != null) {
0527: removeTemplateElementListeners(defRec.template, ir);
0528: }
0529: rebind(defRec.namespaceURI, defRec.localName, document
0530: .getDocumentElement());
0531: }
0532:
0533: /**
0534: * Removes DOM mutation listeners from the given definition element.
0535: */
0536: protected void removeDefinitionElementListeners(
0537: XBLOMDefinitionElement def, ImportRecord ir) {
0538: XBLEventSupport es = (XBLEventSupport) def
0539: .initializeEventSupport();
0540: es.removeImplementationEventListenerNS(
0541: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0542: "DOMAttrModified", ir.defAttrListener, false);
0543: es.removeImplementationEventListenerNS(
0544: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0545: "DOMNodeInserted", ir.defNodeInsertedListener, false);
0546: es.removeImplementationEventListenerNS(
0547: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0548: "DOMNodeRemoved", ir.defNodeRemovedListener, false);
0549: }
0550:
0551: /**
0552: * Removes DOM mutation listeners from the given template element.
0553: */
0554: protected void removeTemplateElementListeners(
0555: XBLOMTemplateElement template, ImportRecord ir) {
0556: XBLEventSupport es = (XBLEventSupport) template
0557: .initializeEventSupport();
0558: es.removeImplementationEventListenerNS(
0559: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0560: "DOMAttrModified", ir.templateMutationListener, false);
0561: es.removeImplementationEventListenerNS(
0562: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0563: "DOMNodeInserted", ir.templateMutationListener, false);
0564: es.removeImplementationEventListenerNS(
0565: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0566: "DOMNodeRemoved", ir.templateMutationListener, false);
0567: es.removeImplementationEventListenerNS(
0568: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0569: "DOMCharacterDataModified",
0570: ir.templateMutationListener, false);
0571: }
0572:
0573: /**
0574: * Returns the definition record of the active definition for namespace
0575: * URI/local name pair.
0576: */
0577: protected DefinitionRecord getActiveDefinition(String namespaceURI,
0578: String localName) {
0579: TreeSet defs = (TreeSet) definitionLists.get(namespaceURI,
0580: localName);
0581: if (defs == null || defs.size() == 0) {
0582: return null;
0583: }
0584: return (DefinitionRecord) defs.first();
0585: }
0586:
0587: /**
0588: * Unbinds each bindable element in the given element's subtree.
0589: */
0590: protected void unbind(Element e) {
0591: if (e instanceof BindableElement) {
0592: setActiveDefinition((BindableElement) e, null);
0593: } else {
0594: NodeList nl = getXblScopedChildNodes(e);
0595: for (int i = 0; i < nl.getLength(); i++) {
0596: Node n = nl.item(i);
0597: if (n.getNodeType() == Node.ELEMENT_NODE) {
0598: unbind((Element) n);
0599: }
0600: }
0601: }
0602: }
0603:
0604: /**
0605: * Binds each bindable element in the given element's subtree.
0606: */
0607: protected void bind(Element e) {
0608: AbstractDocument doc = (AbstractDocument) e.getOwnerDocument();
0609: if (doc != document) {
0610: XBLManager xm = doc.getXBLManager();
0611: if (xm instanceof DefaultXBLManager) {
0612: ((DefaultXBLManager) xm).bind(e);
0613: return;
0614: }
0615: }
0616:
0617: if (e instanceof BindableElement) {
0618: DefinitionRecord defRec = getActiveDefinition(e
0619: .getNamespaceURI(), e.getLocalName());
0620: setActiveDefinition((BindableElement) e, defRec);
0621: } else {
0622: NodeList nl = getXblScopedChildNodes(e);
0623: for (int i = 0; i < nl.getLength(); i++) {
0624: Node n = nl.item(i);
0625: if (n.getNodeType() == Node.ELEMENT_NODE) {
0626: bind((Element) n);
0627: }
0628: }
0629: }
0630: }
0631:
0632: /**
0633: * Rebinds each bindable element of the given name in the given element's
0634: * subtree.
0635: */
0636: protected void rebind(String namespaceURI, String localName,
0637: Element e) {
0638: AbstractDocument doc = (AbstractDocument) e.getOwnerDocument();
0639: if (doc != document) {
0640: XBLManager xm = doc.getXBLManager();
0641: if (xm instanceof DefaultXBLManager) {
0642: ((DefaultXBLManager) xm).rebind(namespaceURI,
0643: localName, e);
0644: return;
0645: }
0646: }
0647:
0648: if (e instanceof BindableElement
0649: && namespaceURI.equals(e.getNamespaceURI())
0650: && localName.equals(e.getLocalName())) {
0651: DefinitionRecord defRec = getActiveDefinition(e
0652: .getNamespaceURI(), e.getLocalName());
0653: setActiveDefinition((BindableElement) e, defRec);
0654: } else {
0655: NodeList nl = getXblScopedChildNodes(e);
0656: for (int i = 0; i < nl.getLength(); i++) {
0657: Node n = nl.item(i);
0658: if (n.getNodeType() == Node.ELEMENT_NODE) {
0659: rebind(namespaceURI, localName, (Element) n);
0660: }
0661: }
0662: }
0663: }
0664:
0665: /**
0666: * Sets the given definition as the active one for a particular
0667: * bindable element.
0668: */
0669: protected void setActiveDefinition(BindableElement elt,
0670: DefinitionRecord defRec) {
0671: XBLRecord rec = getRecord(elt);
0672: rec.definitionElement = defRec == null ? null
0673: : defRec.definition;
0674: if (defRec != null && defRec.definition != null
0675: && defRec.template != null) {
0676: setXblShadowTree(elt, cloneTemplate(defRec.template));
0677: } else {
0678: setXblShadowTree(elt, null);
0679: }
0680: }
0681:
0682: /**
0683: * Sets the shadow tree for the given bindable element.
0684: */
0685: protected void setXblShadowTree(BindableElement elt,
0686: XBLOMShadowTreeElement newShadow) {
0687: XBLOMShadowTreeElement oldShadow = (XBLOMShadowTreeElement) getXblShadowTree(elt);
0688: if (oldShadow != null) {
0689: fireShadowTreeEvent(elt, XBL_UNBINDING_EVENT_TYPE,
0690: oldShadow);
0691: ContentManager cm = getContentManager(oldShadow);
0692: if (cm != null) {
0693: cm.dispose();
0694: }
0695: elt.setShadowTree(null);
0696: XBLRecord rec = getRecord(oldShadow);
0697: rec.boundElement = null;
0698: oldShadow.removeEventListenerNS(
0699: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0700: "DOMSubtreeModified", docSubtreeListener, false);
0701: }
0702: if (newShadow != null) {
0703: newShadow.addEventListenerNS(
0704: XMLConstants.XML_EVENTS_NAMESPACE_URI,
0705: "DOMSubtreeModified", docSubtreeListener, false,
0706: null);
0707: fireShadowTreeEvent(elt, XBL_PREBIND_EVENT_TYPE, newShadow);
0708: elt.setShadowTree(newShadow);
0709: XBLRecord rec = getRecord(newShadow);
0710: rec.boundElement = elt;
0711: AbstractDocument doc = (AbstractDocument) elt
0712: .getOwnerDocument();
0713: XBLManager xm = doc.getXBLManager();
0714: ContentManager cm = new ContentManager(newShadow, xm);
0715: setContentManager(newShadow, cm);
0716: }
0717: invalidateChildNodes(elt);
0718: if (newShadow != null) {
0719: NodeList nl = getXblScopedChildNodes(elt);
0720: for (int i = 0; i < nl.getLength(); i++) {
0721: Node n = nl.item(i);
0722: if (n.getNodeType() == Node.ELEMENT_NODE) {
0723: bind((Element) n);
0724: }
0725: }
0726: dispatchBindingChangedEvent(elt, newShadow);
0727: fireShadowTreeEvent(elt, XBL_BOUND_EVENT_TYPE, newShadow);
0728: } else {
0729: dispatchBindingChangedEvent(elt, newShadow);
0730: }
0731: }
0732:
0733: /**
0734: * Fires a ShadowTreeEvent of the given type on this element.
0735: */
0736: protected void fireShadowTreeEvent(BindableElement elt,
0737: String type, XBLShadowTreeElement e) {
0738: DocumentEvent de = (DocumentEvent) elt.getOwnerDocument();
0739: ShadowTreeEvent evt = (ShadowTreeEvent) de
0740: .createEvent("ShadowTreeEvent");
0741: evt.initShadowTreeEventNS(XBL_NAMESPACE_URI, type, true, false,
0742: e);
0743: elt.dispatchEvent(evt);
0744: }
0745:
0746: /**
0747: * Clones a template element for use as a shadow tree.
0748: */
0749: protected XBLOMShadowTreeElement cloneTemplate(
0750: XBLOMTemplateElement template) {
0751: XBLOMShadowTreeElement clone = (XBLOMShadowTreeElement) template
0752: .getOwnerDocument().createElementNS(XBL_NAMESPACE_URI,
0753: XBL_SHADOW_TREE_TAG);
0754: NamedNodeMap attrs = template.getAttributes();
0755: for (int i = 0; i < attrs.getLength(); i++) {
0756: Attr attr = (Attr) attrs.item(i);
0757: if (attr instanceof AbstractAttrNS) {
0758: clone.setAttributeNodeNS(attr);
0759: } else {
0760: clone.setAttributeNode(attr);
0761: }
0762: }
0763: for (Node n = template.getFirstChild(); n != null; n = n
0764: .getNextSibling()) {
0765: clone.appendChild(n.cloneNode(true));
0766: }
0767: return clone;
0768: }
0769:
0770: /**
0771: * Get the parent of a node in the fully flattened tree.
0772: */
0773: public Node getXblParentNode(Node n) {
0774: Node contentElement = getXblContentElement(n);
0775: Node parent = contentElement == null ? n.getParentNode()
0776: : contentElement.getParentNode();
0777: if (parent instanceof XBLOMContentElement) {
0778: parent = parent.getParentNode();
0779: }
0780: if (parent instanceof XBLOMShadowTreeElement) {
0781: parent = getXblBoundElement(parent);
0782: }
0783: return parent;
0784: }
0785:
0786: /**
0787: * Get the list of child nodes of a node in the fully flattened tree.
0788: */
0789: public NodeList getXblChildNodes(Node n) {
0790: XBLRecord rec = getRecord(n);
0791: if (rec.childNodes == null) {
0792: rec.childNodes = new XblChildNodes(rec);
0793: }
0794: return rec.childNodes;
0795: }
0796:
0797: /**
0798: * Get the list of child nodes of a node in the fully flattened tree
0799: * that are within the same shadow scope.
0800: */
0801: public NodeList getXblScopedChildNodes(Node n) {
0802: XBLRecord rec = getRecord(n);
0803: if (rec.scopedChildNodes == null) {
0804: rec.scopedChildNodes = new XblScopedChildNodes(rec);
0805: }
0806: return rec.scopedChildNodes;
0807: }
0808:
0809: /**
0810: * Get the first child node of a node in the fully flattened tree.
0811: */
0812: public Node getXblFirstChild(Node n) {
0813: NodeList nl = getXblChildNodes(n);
0814: return nl.item(0);
0815: }
0816:
0817: /**
0818: * Get the last child node of a node in the fully flattened tree.
0819: */
0820: public Node getXblLastChild(Node n) {
0821: NodeList nl = getXblChildNodes(n);
0822: return nl.item(nl.getLength() - 1);
0823: }
0824:
0825: /**
0826: * Get the node which directly precedes a node in the xblParentNode's
0827: * xblChildNodes list.
0828: */
0829: public Node getXblPreviousSibling(Node n) {
0830: Node p = getXblParentNode(n);
0831: if (p == null || getRecord(p).childNodes == null) {
0832: return n.getPreviousSibling();
0833: }
0834: XBLRecord rec = getRecord(n);
0835: if (!rec.linksValid) {
0836: updateLinks(n);
0837: }
0838: return rec.previousSibling;
0839: }
0840:
0841: /**
0842: * Get the node which directly follows a node in the xblParentNode's
0843: * xblChildNodes list.
0844: */
0845: public Node getXblNextSibling(Node n) {
0846: Node p = getXblParentNode(n);
0847: if (p == null || getRecord(p).childNodes == null) {
0848: return n.getNextSibling();
0849: }
0850: XBLRecord rec = getRecord(n);
0851: if (!rec.linksValid) {
0852: updateLinks(n);
0853: }
0854: return rec.nextSibling;
0855: }
0856:
0857: /**
0858: * Get the first element child of a node in the fully flattened tree.
0859: */
0860: public Element getXblFirstElementChild(Node n) {
0861: n = getXblFirstChild(n);
0862: while (n != null && n.getNodeType() != Node.ELEMENT_NODE) {
0863: n = getXblNextSibling(n);
0864: }
0865: return (Element) n;
0866: }
0867:
0868: /**
0869: * Get the last element child of a node in the fully flattened tree.
0870: */
0871: public Element getXblLastElementChild(Node n) {
0872: n = getXblLastChild(n);
0873: while (n != null && n.getNodeType() != Node.ELEMENT_NODE) {
0874: n = getXblPreviousSibling(n);
0875: }
0876: return (Element) n;
0877: }
0878:
0879: /**
0880: * Get the first element that precedes the a node in the
0881: * xblParentNode's xblChildNodes list.
0882: */
0883: public Element getXblPreviousElementSibling(Node n) {
0884: do {
0885: n = getXblPreviousSibling(n);
0886: } while (n != null && n.getNodeType() != Node.ELEMENT_NODE);
0887: return (Element) n;
0888: }
0889:
0890: /**
0891: * Get the first element that follows a node in the
0892: * xblParentNode's xblChildNodes list.
0893: */
0894: public Element getXblNextElementSibling(Node n) {
0895: do {
0896: n = getXblNextSibling(n);
0897: } while (n != null && n.getNodeType() != Node.ELEMENT_NODE);
0898: return (Element) n;
0899: }
0900:
0901: /**
0902: * Get the bound element whose shadow tree a node resides in.
0903: */
0904: public Element getXblBoundElement(Node n) {
0905: while (n != null && !(n instanceof XBLShadowTreeElement)) {
0906: XBLOMContentElement content = getXblContentElement(n);
0907: if (content != null) {
0908: n = content;
0909: }
0910: n = n.getParentNode();
0911: }
0912: if (n == null) {
0913: return null;
0914: }
0915: return getRecord(n).boundElement;
0916: }
0917:
0918: /**
0919: * Get the shadow tree of a node.
0920: */
0921: public Element getXblShadowTree(Node n) {
0922: if (n instanceof BindableElement) {
0923: BindableElement elt = (BindableElement) n;
0924: return elt.getShadowTree();
0925: }
0926: return null;
0927: }
0928:
0929: /**
0930: * Get the xbl:definition elements currently binding an element.
0931: */
0932: public NodeList getXblDefinitions(Node n) {
0933: final String namespaceURI = n.getNamespaceURI();
0934: final String localName = n.getLocalName();
0935: return new NodeList() {
0936: public Node item(int i) {
0937: TreeSet defs = (TreeSet) definitionLists.get(
0938: namespaceURI, localName);
0939: if (defs != null && defs.size() != 0 && i == 0) {
0940: DefinitionRecord defRec = (DefinitionRecord) defs
0941: .first();
0942: return defRec.definition;
0943: }
0944: return null;
0945: }
0946:
0947: public int getLength() {
0948: Set defs = (TreeSet) definitionLists.get(namespaceURI,
0949: localName);
0950: return defs != null && defs.size() != 0 ? 1 : 0;
0951: }
0952: };
0953: }
0954:
0955: /**
0956: * Returns the XBL record for the given node.
0957: */
0958: protected XBLRecord getRecord(Node n) {
0959: XBLManagerData xmd = (XBLManagerData) n;
0960: XBLRecord rec = (XBLRecord) xmd.getManagerData();
0961: if (rec == null) {
0962: rec = new XBLRecord();
0963: rec.node = n;
0964: xmd.setManagerData(rec);
0965: }
0966: return rec;
0967: }
0968:
0969: /**
0970: * Updates the xblPreviousSibling and xblNextSibling properties of the
0971: * given XBL node.
0972: */
0973: protected void updateLinks(Node n) {
0974: XBLRecord rec = getRecord(n);
0975: rec.previousSibling = null;
0976: rec.nextSibling = null;
0977: rec.linksValid = true;
0978: Node p = getXblParentNode(n);
0979: if (p != null) {
0980: NodeList xcn = getXblChildNodes(p);
0981: if (xcn instanceof XblChildNodes) {
0982: ((XblChildNodes) xcn).update();
0983: }
0984: }
0985: }
0986:
0987: /**
0988: * Returns the content element that caused the given node to be
0989: * present in the flattened tree.
0990: */
0991: public XBLOMContentElement getXblContentElement(Node n) {
0992: return getRecord(n).contentElement;
0993: }
0994:
0995: /**
0996: * Determines the number of nodes events should bubble if the
0997: * mouse pointer has moved from one element to another.
0998: * @param from the element from which the mouse pointer moved
0999: * @param to the element to which the mouse pointer moved
1000: */
1001: public static int computeBubbleLimit(Node from, Node to) {
1002: ArrayList fromList = new ArrayList(10);
1003: ArrayList toList = new ArrayList(10);
1004: while (from != null) {
1005: fromList.add(from);
1006: from = ((NodeXBL) from).getXblParentNode();
1007: }
1008: while (to != null) {
1009: toList.add(to);
1010: to = ((NodeXBL) to).getXblParentNode();
1011: }
1012: int fromSize = fromList.size();
1013: int toSize = toList.size();
1014: for (int i = 0; i < fromSize && i < toSize; i++) {
1015: Node n1 = (Node) fromList.get(fromSize - i - 1);
1016: Node n2 = (Node) toList.get(toSize - i - 1);
1017: if (n1 != n2) {
1018: Node prevBoundElement = ((NodeXBL) n1)
1019: .getXblBoundElement();
1020: while (i > 0
1021: && prevBoundElement != fromList.get(fromSize
1022: - i - 1)) {
1023: i--;
1024: }
1025: return fromSize - i - 1;
1026: }
1027: }
1028: return 1;
1029: }
1030:
1031: /**
1032: * Returns the ContentManager that handles the shadow tree the given
1033: * node resides in.
1034: */
1035: public ContentManager getContentManager(Node n) {
1036: Node b = getXblBoundElement(n);
1037: if (b != null) {
1038: Element s = getXblShadowTree(b);
1039: if (s != null) {
1040: ContentManager cm;
1041: Document doc = b.getOwnerDocument();
1042: if (doc != document) {
1043: DefaultXBLManager xm = (DefaultXBLManager) ((AbstractDocument) doc)
1044: .getXBLManager();
1045: cm = (ContentManager) xm.contentManagers.get(s);
1046: } else {
1047: cm = (ContentManager) contentManagers.get(s);
1048: }
1049: return cm;
1050: }
1051: }
1052: return null;
1053: }
1054:
1055: /**
1056: * Records the ContentManager that handles the given shadow tree.
1057: */
1058: void setContentManager(Element shadow, ContentManager cm) {
1059: if (cm == null) {
1060: contentManagers.remove(shadow);
1061: } else {
1062: contentManagers.put(shadow, cm);
1063: }
1064: }
1065:
1066: /**
1067: * Mark the xblChildNodes and xblScopedChildNodes variables
1068: * as invalid.
1069: */
1070: public void invalidateChildNodes(Node n) {
1071: XBLRecord rec = getRecord(n);
1072: if (rec.childNodes != null) {
1073: rec.childNodes.invalidate();
1074: }
1075: if (rec.scopedChildNodes != null) {
1076: rec.scopedChildNodes.invalidate();
1077: }
1078: }
1079:
1080: /**
1081: * Adds the specified ContentSelectionChangedListener to the
1082: * global listener list.
1083: */
1084: public void addContentSelectionChangedListener(
1085: ContentSelectionChangedListener l) {
1086: contentSelectionChangedListenerList.add(
1087: ContentSelectionChangedListener.class, l);
1088: }
1089:
1090: /**
1091: * Removes the specified ContentSelectionChangedListener from the
1092: * global listener list.
1093: */
1094: public void removeContentSelectionChangedListener(
1095: ContentSelectionChangedListener l) {
1096: contentSelectionChangedListenerList.remove(
1097: ContentSelectionChangedListener.class, l);
1098: }
1099:
1100: /**
1101: * Returns an array of the gloabl ContentSelectionChangedListeners.
1102: */
1103: protected Object[] getContentSelectionChangedListeners() {
1104: return contentSelectionChangedListenerList.getListenerList();
1105: }
1106:
1107: /**
1108: * Called by the ContentManager of a shadow tree to indicate some
1109: * selected nodes have changed.
1110: */
1111: void shadowTreeSelectedContentChanged(Set deselected, Set selected) {
1112: Iterator i = deselected.iterator();
1113: while (i.hasNext()) {
1114: Node n = (Node) i.next();
1115: if (n.getNodeType() == Node.ELEMENT_NODE) {
1116: unbind((Element) n);
1117: }
1118: }
1119: i = selected.iterator();
1120: while (i.hasNext()) {
1121: Node n = (Node) i.next();
1122: if (n.getNodeType() == Node.ELEMENT_NODE) {
1123: bind((Element) n);
1124: }
1125: }
1126: }
1127:
1128: /**
1129: * Adds the specified BindingListener to the global listener list.
1130: */
1131: public void addBindingListener(BindingListener l) {
1132: bindingListenerList.add(BindingListener.class, l);
1133: }
1134:
1135: /**
1136: * Removes the specified BindingListener from the global listener list.
1137: */
1138: public void removeBindingListener(BindingListener l) {
1139: bindingListenerList.remove(BindingListener.class, l);
1140: }
1141:
1142: /**
1143: * Dispatches a BindingEvent the registered listeners.
1144: * @param bindableElement the bindable element whose binding has changed
1145: * @param shadowTree the new shadow tree of the bindable element
1146: */
1147: protected void dispatchBindingChangedEvent(Element bindableElement,
1148: Element shadowTree) {
1149: Object[] ls = bindingListenerList.getListenerList();
1150: for (int i = ls.length - 2; i >= 0; i -= 2) {
1151: BindingListener l = (BindingListener) ls[i + 1];
1152: l.bindingChanged(bindableElement, shadowTree);
1153: }
1154: }
1155:
1156: /**
1157: * Returns whether the given definition element is the active one
1158: * for its element name.
1159: */
1160: protected boolean isActiveDefinition(XBLOMDefinitionElement def,
1161: Element imp) {
1162: DefinitionRecord defRec = (DefinitionRecord) definitions.get(
1163: def, imp);
1164: if (defRec == null) {
1165: return false;
1166: }
1167: return defRec == getActiveDefinition(defRec.namespaceURI,
1168: defRec.localName);
1169: }
1170:
1171: /**
1172: * Record class for storing information about an XBL definition.
1173: */
1174: protected class DefinitionRecord implements Comparable {
1175:
1176: /**
1177: * The namespace URI.
1178: */
1179: public String namespaceURI;
1180:
1181: /**
1182: * The local name.
1183: */
1184: public String localName;
1185:
1186: /**
1187: * The definition element.
1188: */
1189: public XBLOMDefinitionElement definition;
1190:
1191: /**
1192: * The template element for this definition.
1193: */
1194: public XBLOMTemplateElement template;
1195:
1196: /**
1197: * The import element that imported this definition.
1198: */
1199: public Element importElement;
1200:
1201: /**
1202: * Creates a new DefinitionRecord.
1203: */
1204: public DefinitionRecord(String ns, String ln,
1205: XBLOMDefinitionElement def, XBLOMTemplateElement t,
1206: Element imp) {
1207: namespaceURI = ns;
1208: localName = ln;
1209: definition = def;
1210: template = t;
1211: importElement = imp;
1212: }
1213:
1214: /**
1215: * Returns whether two definition records are the same.
1216: */
1217: public boolean equals(Object other) {
1218: return compareTo(other) == 0;
1219: }
1220:
1221: /**
1222: * Compares two definition records.
1223: */
1224: public int compareTo(Object other) {
1225: DefinitionRecord rec = (DefinitionRecord) other;
1226: AbstractNode n1, n2;
1227: if (importElement == null) {
1228: n1 = definition;
1229: if (rec.importElement == null) {
1230: n2 = rec.definition;
1231: } else {
1232: n2 = (AbstractNode) rec.importElement;
1233: }
1234: } else if (rec.importElement == null) {
1235: n1 = (AbstractNode) importElement;
1236: n2 = rec.definition;
1237: } else if (definition.getOwnerDocument() == rec.definition
1238: .getOwnerDocument()) {
1239: n1 = definition;
1240: n2 = rec.definition;
1241: } else {
1242: n1 = (AbstractNode) importElement;
1243: n2 = (AbstractNode) rec.importElement;
1244: }
1245: short comp = n1.compareDocumentPosition(n2);
1246: if ((comp & AbstractNode.DOCUMENT_POSITION_PRECEDING) != 0) {
1247: return -1;
1248: }
1249: if ((comp & AbstractNode.DOCUMENT_POSITION_FOLLOWING) != 0) {
1250: return 1;
1251: }
1252: return 0;
1253: }
1254: }
1255:
1256: /**
1257: * Record class for storing information about an XBL import.
1258: */
1259: protected class ImportRecord {
1260:
1261: /**
1262: * The import element.
1263: */
1264: public Element importElement;
1265:
1266: /**
1267: * The imported tree.
1268: */
1269: public Node node;
1270:
1271: /**
1272: * The DOM node inserted listener for definitions accessed through
1273: * this import.
1274: */
1275: public DefNodeInsertedListener defNodeInsertedListener;
1276:
1277: /**
1278: * The DOM node removed listener for definitions accessed through
1279: * this import.
1280: */
1281: public DefNodeRemovedListener defNodeRemovedListener;
1282:
1283: /**
1284: * The DOM attribute mutation listener for definitions accessed through
1285: * this import.
1286: */
1287: public DefAttrListener defAttrListener;
1288:
1289: /**
1290: * The DOM node inserted listener for the imported tree.
1291: */
1292: public ImportInsertedListener importInsertedListener;
1293:
1294: /**
1295: * The DOM node removed listener for the imported tree.
1296: */
1297: public ImportRemovedListener importRemovedListener;
1298:
1299: /**
1300: * The DOM subtree modified listener for the imported tree.
1301: */
1302: public ImportSubtreeListener importSubtreeListener;
1303:
1304: /**
1305: * The DOM subtree modified listener for templates of definitions
1306: * accessed through this import.
1307: */
1308: public TemplateMutationListener templateMutationListener;
1309:
1310: /**
1311: * Creates a new ImportRecord.
1312: */
1313: public ImportRecord(Element imp, Node n) {
1314: importElement = imp;
1315: node = n;
1316: defNodeInsertedListener = new DefNodeInsertedListener(imp);
1317: defNodeRemovedListener = new DefNodeRemovedListener(imp);
1318: defAttrListener = new DefAttrListener(imp);
1319: importInsertedListener = new ImportInsertedListener(imp);
1320: importRemovedListener = new ImportRemovedListener();
1321: importSubtreeListener = new ImportSubtreeListener(imp,
1322: importRemovedListener);
1323: templateMutationListener = new TemplateMutationListener(imp);
1324: }
1325: }
1326:
1327: /**
1328: * DOM node inserted listener for imported XBL trees.
1329: */
1330: protected class ImportInsertedListener implements EventListener {
1331:
1332: /**
1333: * The import element.
1334: */
1335: protected Element importElement;
1336:
1337: /**
1338: * Creates a new ImportInsertedListener.
1339: */
1340: public ImportInsertedListener(Element importElement) {
1341: this .importElement = importElement;
1342: }
1343:
1344: /**
1345: * Handles the event.
1346: */
1347: public void handleEvent(Event evt) {
1348: EventTarget target = evt.getTarget();
1349: if (target instanceof XBLOMDefinitionElement) {
1350: XBLOMDefinitionElement def = (XBLOMDefinitionElement) target;
1351: addDefinition(def.getElementNamespaceURI(), def
1352: .getElementLocalName(), def, importElement);
1353: }
1354: }
1355: }
1356:
1357: /**
1358: * DOM node removed listener for imported XBL trees.
1359: */
1360: protected class ImportRemovedListener implements EventListener {
1361:
1362: /**
1363: * List of definition elements to be removed from the document.
1364: */
1365: protected LinkedList toBeRemoved = new LinkedList();
1366:
1367: /**
1368: * Handles the event.
1369: */
1370: public void handleEvent(Event evt) {
1371: toBeRemoved.add(evt.getTarget());
1372: }
1373: }
1374:
1375: /**
1376: * DOM subtree listener for imported XBL trees.
1377: */
1378: protected class ImportSubtreeListener implements EventListener {
1379:
1380: /**
1381: * The import element.
1382: */
1383: protected Element importElement;
1384:
1385: /**
1386: * The ImportedRemovedListener to check for to-be-removed definitions.
1387: */
1388: protected ImportRemovedListener importRemovedListener;
1389:
1390: /**
1391: * Creates a new ImportSubtreeListener.
1392: */
1393: public ImportSubtreeListener(Element imp,
1394: ImportRemovedListener irl) {
1395: importElement = imp;
1396: importRemovedListener = irl;
1397: }
1398:
1399: /**
1400: * Handles the event.
1401: */
1402: public void handleEvent(Event evt) {
1403: Object[] defs = importRemovedListener.toBeRemoved.toArray();
1404: importRemovedListener.toBeRemoved.clear();
1405: for (int i = 0; i < defs.length; i++) {
1406: XBLOMDefinitionElement def = (XBLOMDefinitionElement) defs[i];
1407: DefinitionRecord defRec = (DefinitionRecord) definitions
1408: .get(def, importElement);
1409: removeDefinition(defRec);
1410: }
1411: }
1412: }
1413:
1414: /**
1415: * DOM node inserted listener for the document.
1416: */
1417: protected class DocInsertedListener implements EventListener {
1418:
1419: /**
1420: * Handles the event.
1421: */
1422: public void handleEvent(Event evt) {
1423: EventTarget target = evt.getTarget();
1424: if (target instanceof XBLOMDefinitionElement) {
1425: // only handle definition elements in document-level scope
1426: if (getXblBoundElement((Node) target) == null) { // ??? suspect cast ???
1427: XBLOMDefinitionElement def = (XBLOMDefinitionElement) target;
1428: if (def.getAttributeNS(null, XBL_REF_ATTRIBUTE)
1429: .length() == 0) {
1430: addDefinition(def.getElementNamespaceURI(), def
1431: .getElementLocalName(), def, null);
1432: } else {
1433: addDefinitionRef(def);
1434: }
1435: }
1436: } else if (target instanceof XBLOMImportElement) {
1437: // only handle import elements in document-level scope
1438: if (getXblBoundElement((Node) target) == null) { // ??? suspect cast ???
1439: addImport((Element) target);
1440: }
1441: } else {
1442: evt = XBLEventSupport.getUltimateOriginalEvent(evt);
1443: target = evt.getTarget();
1444: Node parent = getXblParentNode((Node) target);
1445: if (parent != null) {
1446: invalidateChildNodes(parent);
1447: }
1448: if (target instanceof BindableElement) {
1449: // Only bind it if it's not the descendent of a bound
1450: // element. If it is, and this new element will be
1451: // selected by an xbl:content element in the shadow tree,
1452: // the ContentManager will bind it.
1453: for (Node n = ((Node) target).getParentNode(); n != null; n = n
1454: .getParentNode()) {
1455: if (n instanceof BindableElement
1456: && getRecord(n).definitionElement != null) {
1457: return;
1458: }
1459: }
1460: bind((Element) target);
1461: }
1462: }
1463: }
1464: }
1465:
1466: /**
1467: * DOM node removed listener for the document.
1468: */
1469: protected class DocRemovedListener implements EventListener {
1470:
1471: /**
1472: * List of definition elements to be removed from the document.
1473: */
1474: protected LinkedList defsToBeRemoved = new LinkedList();
1475:
1476: /**
1477: * List of import elements to be removed from the document.
1478: */
1479: protected LinkedList importsToBeRemoved = new LinkedList();
1480:
1481: /**
1482: * List of nodes to have their XBL child lists invalidated.
1483: */
1484: protected LinkedList nodesToBeInvalidated = new LinkedList();
1485:
1486: /**
1487: * Handles the event.
1488: */
1489: public void handleEvent(Event evt) {
1490: EventTarget target = evt.getTarget();
1491: if (target instanceof XBLOMDefinitionElement) {
1492: // only handle definition elements in document-level scope
1493: if (getXblBoundElement((Node) target) == null) {
1494: defsToBeRemoved.add(target);
1495: }
1496: } else if (target instanceof XBLOMImportElement) {
1497: // only handle import elements in document-level scope
1498: if (getXblBoundElement((Node) target) == null) {
1499: importsToBeRemoved.add(target);
1500: }
1501: }
1502:
1503: Node parent = getXblParentNode((Node) target);
1504: if (parent != null) {
1505: nodesToBeInvalidated.add(parent);
1506: }
1507: }
1508: }
1509:
1510: /**
1511: * DOM subtree mutation listener for the document.
1512: */
1513: protected class DocSubtreeListener implements EventListener {
1514:
1515: /**
1516: * Handles the event.
1517: */
1518: public void handleEvent(Event evt) {
1519: Object[] defs = docRemovedListener.defsToBeRemoved
1520: .toArray();
1521: docRemovedListener.defsToBeRemoved.clear();
1522: for (int i = 0; i < defs.length; i++) {
1523: XBLOMDefinitionElement def = (XBLOMDefinitionElement) defs[i];
1524: if (def.getAttributeNS(null, XBL_REF_ATTRIBUTE)
1525: .length() == 0) {
1526: DefinitionRecord defRec = (DefinitionRecord) definitions
1527: .get(def, null);
1528: removeDefinition(defRec);
1529: } else {
1530: removeDefinitionRef(def);
1531: }
1532: }
1533:
1534: Object[] imps = docRemovedListener.importsToBeRemoved
1535: .toArray();
1536: docRemovedListener.importsToBeRemoved.clear();
1537: for (int i = 0; i < imps.length; i++) {
1538: removeImport((Element) imps[i]);
1539: }
1540:
1541: Object[] nodes = docRemovedListener.nodesToBeInvalidated
1542: .toArray();
1543: docRemovedListener.nodesToBeInvalidated.clear();
1544: for (int i = 0; i < nodes.length; i++) {
1545: invalidateChildNodes((Node) nodes[i]);
1546: }
1547: }
1548: }
1549:
1550: /**
1551: * DOM mutation listener for template elements.
1552: */
1553: protected class TemplateMutationListener implements EventListener {
1554:
1555: /**
1556: * The import element.
1557: */
1558: protected Element importElement;
1559:
1560: /**
1561: * Creates a new TemplateMutationListener.
1562: */
1563: public TemplateMutationListener(Element imp) {
1564: importElement = imp;
1565: }
1566:
1567: /**
1568: * Handles the event.
1569: */
1570: public void handleEvent(Event evt) {
1571: Node n = (Node) evt.getTarget();
1572: while (n != null && !(n instanceof XBLOMDefinitionElement)) {
1573: n = n.getParentNode();
1574: }
1575:
1576: DefinitionRecord defRec = (DefinitionRecord) definitions
1577: .get(n, importElement);
1578: if (defRec == null) {
1579: return;
1580: }
1581:
1582: rebind(defRec.namespaceURI, defRec.localName, document
1583: .getDocumentElement());
1584: }
1585: }
1586:
1587: /**
1588: * DOM attribute mutation listener for definition elements.
1589: */
1590: protected class DefAttrListener implements EventListener {
1591:
1592: /**
1593: * The import element.
1594: */
1595: protected Element importElement;
1596:
1597: /**
1598: * Creates a new DefAttrListener.
1599: */
1600: public DefAttrListener(Element imp) {
1601: importElement = imp;
1602: }
1603:
1604: /**
1605: * Handles the event.
1606: */
1607: public void handleEvent(Event evt) {
1608: EventTarget target = evt.getTarget();
1609: if (!(target instanceof XBLOMDefinitionElement)) {
1610: return;
1611: }
1612:
1613: XBLOMDefinitionElement def = (XBLOMDefinitionElement) target;
1614: if (!isActiveDefinition(def, importElement)) {
1615: return;
1616: }
1617:
1618: MutationEvent mevt = (MutationEvent) evt;
1619: String attrName = mevt.getAttrName();
1620: if (attrName.equals(XBL_ELEMENT_ATTRIBUTE)) {
1621: DefinitionRecord defRec = (DefinitionRecord) definitions
1622: .get(def, importElement);
1623: removeDefinition(defRec);
1624:
1625: addDefinition(def.getElementNamespaceURI(), def
1626: .getElementLocalName(), def, importElement);
1627: } else if (attrName.equals(XBL_REF_ATTRIBUTE)) {
1628: if (mevt.getNewValue().length() != 0) {
1629: DefinitionRecord defRec = (DefinitionRecord) definitions
1630: .get(def, importElement);
1631: removeDefinition(defRec);
1632: addDefinitionRef(def);
1633: }
1634: }
1635: }
1636: }
1637:
1638: /**
1639: * DOM node inserted listener for definition elements.
1640: */
1641: protected class DefNodeInsertedListener implements EventListener {
1642:
1643: /**
1644: * The import element.
1645: */
1646: protected Element importElement;
1647:
1648: /**
1649: * Creates a new DefNodeInsertedListener.
1650: */
1651: public DefNodeInsertedListener(Element imp) {
1652: importElement = imp;
1653: }
1654:
1655: /**
1656: * Handles the event.
1657: */
1658: public void handleEvent(Event evt) {
1659: MutationEvent mevt = (MutationEvent) evt;
1660: Node parent = mevt.getRelatedNode();
1661: if (!(parent instanceof XBLOMDefinitionElement)) {
1662: return;
1663: }
1664:
1665: EventTarget target = evt.getTarget();
1666: if (!(target instanceof XBLOMTemplateElement)) {
1667: return;
1668: }
1669: XBLOMTemplateElement template = (XBLOMTemplateElement) target;
1670:
1671: DefinitionRecord defRec = (DefinitionRecord) definitions
1672: .get(parent, importElement);
1673: if (defRec == null) {
1674: return;
1675: }
1676:
1677: ImportRecord ir = (ImportRecord) imports.get(importElement);
1678:
1679: if (defRec.template != null) {
1680: for (Node n = parent.getFirstChild(); n != null; n = n
1681: .getNextSibling()) {
1682: if (n == template) {
1683: removeTemplateElementListeners(defRec.template,
1684: ir);
1685: defRec.template = template;
1686: break;
1687: } else if (n == defRec.template) {
1688: return;
1689: }
1690: }
1691: } else {
1692: defRec.template = template;
1693: }
1694: addTemplateElementListeners(template, ir);
1695: rebind(defRec.namespaceURI, defRec.localName, document
1696: .getDocumentElement());
1697: }
1698: }
1699:
1700: /**
1701: * DOM node removed listener for definition elements.
1702: */
1703: protected class DefNodeRemovedListener implements EventListener {
1704:
1705: /**
1706: * The import element.
1707: */
1708: protected Element importElement;
1709:
1710: /**
1711: * Creates a new DefNodeRemovedListener.
1712: */
1713: public DefNodeRemovedListener(Element imp) {
1714: importElement = imp;
1715: }
1716:
1717: /**
1718: * Handles the event.
1719: */
1720: public void handleEvent(Event evt) {
1721: MutationEvent mevt = (MutationEvent) evt;
1722: Node parent = mevt.getRelatedNode();
1723: if (!(parent instanceof XBLOMDefinitionElement)) {
1724: return;
1725: }
1726:
1727: EventTarget target = evt.getTarget();
1728: if (!(target instanceof XBLOMTemplateElement)) {
1729: return;
1730: }
1731: XBLOMTemplateElement template = (XBLOMTemplateElement) target;
1732:
1733: DefinitionRecord defRec = (DefinitionRecord) definitions
1734: .get(parent, importElement);
1735: if (defRec == null || defRec.template != template) {
1736: return;
1737: }
1738:
1739: ImportRecord ir = (ImportRecord) imports.get(importElement);
1740:
1741: removeTemplateElementListeners(template, ir);
1742: defRec.template = null;
1743:
1744: for (Node n = template.getNextSibling(); n != null; n = n
1745: .getNextSibling()) {
1746: if (n instanceof XBLOMTemplateElement) {
1747: defRec.template = (XBLOMTemplateElement) n;
1748: break;
1749: }
1750: }
1751:
1752: addTemplateElementListeners(defRec.template, ir);
1753: rebind(defRec.namespaceURI, defRec.localName, document
1754: .getDocumentElement());
1755: }
1756: }
1757:
1758: /**
1759: * DOM attribute mutation listener for import elements.
1760: */
1761: protected class ImportAttrListener implements EventListener {
1762:
1763: /**
1764: * Handles the event.
1765: */
1766: public void handleEvent(Event evt) {
1767: EventTarget target = evt.getTarget();
1768: if (target != evt.getCurrentTarget()) {
1769: return;
1770: }
1771:
1772: MutationEvent mevt = (MutationEvent) evt;
1773: if (mevt.getAttrName().equals(XBL_BINDINGS_ATTRIBUTE)) {
1774: Element imp = (Element) target;
1775: removeImport(imp);
1776: addImport(imp);
1777: }
1778: }
1779: }
1780:
1781: /**
1782: * DOM attribute mutation listener for referencing definition elements.
1783: */
1784: protected class RefAttrListener implements EventListener {
1785:
1786: /**
1787: * Handles the event.
1788: */
1789: public void handleEvent(Event evt) {
1790: EventTarget target = evt.getTarget();
1791: if (target != evt.getCurrentTarget()) {
1792: return;
1793: }
1794:
1795: MutationEvent mevt = (MutationEvent) evt;
1796: if (mevt.getAttrName().equals(XBL_REF_ATTRIBUTE)) {
1797: Element defRef = (Element) target;
1798: removeDefinitionRef(defRef);
1799: if (mevt.getNewValue().length() == 0) {
1800: XBLOMDefinitionElement def = (XBLOMDefinitionElement) defRef;
1801: String ns = def.getElementNamespaceURI();
1802: String ln = def.getElementLocalName();
1803: addDefinition(ns, ln,
1804: (XBLOMDefinitionElement) defRef, null);
1805: } else {
1806: addDefinitionRef(defRef);
1807: }
1808: }
1809: }
1810: }
1811:
1812: /**
1813: * XBL record.
1814: */
1815: protected class XBLRecord {
1816:
1817: /**
1818: * The node.
1819: */
1820: public Node node;
1821:
1822: /**
1823: * The xblChildNodes NodeList for this node.
1824: */
1825: public XblChildNodes childNodes;
1826:
1827: /**
1828: * The xblScopedChildNodes NodeList for this node.
1829: */
1830: public XblScopedChildNodes scopedChildNodes;
1831:
1832: /**
1833: * The content element which caused this node to appear in the
1834: * flattened tree.
1835: */
1836: public XBLOMContentElement contentElement;
1837:
1838: /**
1839: * The definition element that applies to this element.
1840: */
1841: public XBLOMDefinitionElement definitionElement;
1842:
1843: /**
1844: * The bound element that owns this shadow tree, if this node
1845: * is an XBLOMShadowTreeElement.
1846: */
1847: public BindableElement boundElement;
1848:
1849: /**
1850: * Whether the next/previous links are valid.
1851: */
1852: public boolean linksValid;
1853:
1854: /**
1855: * The following sibling in the flattened tree.
1856: */
1857: public Node nextSibling;
1858:
1859: /**
1860: * The previous sibling in the flattened tree.
1861: */
1862: public Node previousSibling;
1863: }
1864:
1865: /**
1866: * To iterate over the XBL child nodes.
1867: */
1868: protected class XblChildNodes implements NodeList {
1869:
1870: /**
1871: * The XBLRecord.
1872: */
1873: protected XBLRecord record;
1874:
1875: /**
1876: * The nodes.
1877: */
1878: protected List nodes;
1879:
1880: /**
1881: * The number of nodes.
1882: */
1883: protected int size;
1884:
1885: /**
1886: * Creates a new XblChildNodes.
1887: */
1888: public XblChildNodes(XBLRecord rec) {
1889: record = rec;
1890: nodes = new ArrayList();
1891: size = -1;
1892: }
1893:
1894: /**
1895: * Update the NodeList.
1896: */
1897: protected void update() {
1898: size = 0;
1899: Node shadowTree = getXblShadowTree(record.node);
1900: Node last = null;
1901: Node m = shadowTree == null ? record.node.getFirstChild()
1902: : shadowTree.getFirstChild();
1903: while (m != null) {
1904: last = collectXblChildNodes(m, last);
1905: m = m.getNextSibling();
1906: }
1907: if (last != null) {
1908: XBLRecord rec = getRecord(last);
1909: rec.nextSibling = null;
1910: rec.linksValid = true;
1911: }
1912: }
1913:
1914: /**
1915: * Find the XBL child nodes of this element.
1916: */
1917: protected Node collectXblChildNodes(Node n, Node prev) {
1918: boolean isChild = false;
1919: if (n.getNodeType() == Node.ELEMENT_NODE) {
1920: if (!XBL_NAMESPACE_URI.equals(n.getNamespaceURI())) {
1921: isChild = true;
1922: } else if (n instanceof XBLOMContentElement) {
1923: ContentManager cm = getContentManager(n);
1924: if (cm != null) {
1925: NodeList selected = cm
1926: .getSelectedContent((XBLOMContentElement) n);
1927: for (int i = 0; i < selected.getLength(); i++) {
1928: prev = collectXblChildNodes(selected
1929: .item(i), prev);
1930: }
1931: }
1932: }
1933: } else {
1934: isChild = true;
1935: }
1936: if (isChild) {
1937: nodes.add(n);
1938: size++;
1939: if (prev != null) {
1940: XBLRecord rec = getRecord(prev);
1941: rec.nextSibling = n;
1942: rec.linksValid = true;
1943: }
1944: XBLRecord rec = getRecord(n);
1945: rec.previousSibling = prev;
1946: rec.linksValid = true;
1947: prev = n;
1948: }
1949: return prev;
1950: }
1951:
1952: /**
1953: * Mark the xblNextSibling and xblPreviousSibling variables
1954: * on each node in the list as invalid, then invalidate the
1955: * NodeList.
1956: */
1957: public void invalidate() {
1958: for (int i = 0; i < size; i++) {
1959: XBLRecord rec = getRecord((Node) nodes.get(i));
1960: rec.previousSibling = null;
1961: rec.nextSibling = null;
1962: rec.linksValid = false;
1963: }
1964: nodes.clear();
1965: size = -1;
1966: }
1967:
1968: /**
1969: * Returns the first node in the list.
1970: */
1971: public Node getFirstNode() {
1972: if (size == -1) {
1973: update();
1974: }
1975: return size == 0 ? null : (Node) nodes.get(0);
1976: }
1977:
1978: /**
1979: * Returns the last node in the list.
1980: */
1981: public Node getLastNode() {
1982: if (size == -1) {
1983: update();
1984: }
1985: return size == 0 ? null : (Node) nodes
1986: .get(nodes.size() - 1);
1987: }
1988:
1989: /**
1990: * <b>DOM</b>: Implements {@link org.w3c.dom.NodeList#item(int)}.
1991: */
1992: public Node item(int index) {
1993: if (size == -1) {
1994: update();
1995: }
1996: if (index < 0 || index >= size) {
1997: return null;
1998: }
1999: return (Node) nodes.get(index);
2000: }
2001:
2002: /**
2003: * <b>DOM</b>: Implements {@link org.w3c.dom.NodeList#getLength()}.
2004: */
2005: public int getLength() {
2006: if (size == -1) {
2007: update();
2008: }
2009: return size;
2010: }
2011: }
2012:
2013: /**
2014: * To iterate over the scoped XBL child nodes.
2015: */
2016: protected class XblScopedChildNodes extends XblChildNodes {
2017:
2018: /**
2019: * Creates a new XblScopedChildNodes object.
2020: */
2021: public XblScopedChildNodes(XBLRecord rec) {
2022: super (rec);
2023: }
2024:
2025: /**
2026: * Update the NodeList.
2027: */
2028: protected void update() {
2029: size = 0;
2030: Node shadowTree = getXblShadowTree(record.node);
2031: Node n = shadowTree == null ? record.node.getFirstChild()
2032: : shadowTree.getFirstChild();
2033: while (n != null) {
2034: collectXblScopedChildNodes(n);
2035: n = n.getNextSibling();
2036: }
2037: }
2038:
2039: /**
2040: * Find the XBL child nodes of this element.
2041: */
2042: protected void collectXblScopedChildNodes(Node n) {
2043: boolean isChild = false;
2044: if (n.getNodeType() == Node.ELEMENT_NODE) {
2045: if (!n.getNamespaceURI().equals(XBL_NAMESPACE_URI)) {
2046: isChild = true;
2047: } else if (n instanceof XBLOMContentElement) {
2048: ContentManager cm = getContentManager(n);
2049: if (cm != null) {
2050: NodeList selected = cm
2051: .getSelectedContent((XBLOMContentElement) n);
2052: for (int i = 0; i < selected.getLength(); i++) {
2053: collectXblScopedChildNodes(selected.item(i));
2054: }
2055: }
2056: }
2057: } else {
2058: isChild = true;
2059: }
2060: if (isChild) {
2061: nodes.add(n);
2062: size++;
2063: }
2064: }
2065: }
2066: }
|