123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223 |
- /* DomNode.java --
- Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package gnu.xml.dom;
- import gnu.java.lang.CPStringBuilder;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Map;
- import org.w3c.dom.Document;
- import org.w3c.dom.DOMException;
- import org.w3c.dom.DOMImplementation;
- import org.w3c.dom.NamedNodeMap;
- import org.w3c.dom.Node;
- import org.w3c.dom.NodeList;
- import org.w3c.dom.Text;
- import org.w3c.dom.UserDataHandler;
- import org.w3c.dom.events.DocumentEvent;
- import org.w3c.dom.events.Event;
- import org.w3c.dom.events.EventException;
- import org.w3c.dom.events.EventListener;
- import org.w3c.dom.events.EventTarget;
- import org.w3c.dom.events.MutationEvent;
- import org.w3c.dom.traversal.NodeFilter;
- import org.w3c.dom.traversal.NodeIterator;
- /**
- * <p> "Node", "EventTarget", and "DocumentEvent" implementation.
- * This provides most of the core DOM functionality; only more
- * specialized features are provided by subclasses. Those subclasses may
- * have some particular constraints they must implement, by overriding
- * methods defined here. Such constraints are noted here in the method
- * documentation. </p>
- *
- * <p> Note that you can create events with type names prefixed with "USER-",
- * and pass them through this DOM. This lets you use the DOM event scheme
- * for application specific purposes, although you must use a predefined event
- * structure (such as MutationEvent) to pass data along with those events.
- * Test for existence of this feature with the "USER-Events" DOM feature
- * name.</p>
- *
- * <p> Other kinds of events you can send include the "html" events,
- * like "load", "unload", "abort", "error", and "blur"; and the mutation
- * events. If this DOM has been compiled with mutation event support
- * enabled, it will send mutation events when you change parts of the
- * tree; otherwise you may create and send such events yourself, but
- * they won't be generated by the DOM itself. </p>
- *
- * <p> Note that there is a namespace-aware name comparison method,
- * <em>nameAndTypeEquals</em>, which compares the names (and types) of
- * two nodes in conformance with the "Namespaces in XML" specification.
- * While mostly intended for use with elements and attributes, this should
- * also be helpful for ProcessingInstruction nodes and some others which
- * do not have namespace URIs.
- *
- * @author David Brownell
- * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
- */
- public abstract class DomNode
- implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable
- {
- // package private
- //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace";
- //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/";
- // tunable
- // NKIDS_* affects arrays of children (which grow)
- // (currently) fixed size:
- // ANCESTORS_* is for event capture/bubbling, # ancestors
- // NOTIFICATIONS_* is for per-node event delivery, # events
- private static final int NKIDS_DELTA = 8;
- private static final int ANCESTORS_INIT = 20;
- private static final int NOTIFICATIONS_INIT = 10;
- // tunable: enable mutation events or not? Enabling it costs about
- // 10-15% in DOM construction time, last time it was measured.
- // package private !!!
- static final boolean reportMutations = true;
- // locking protocol changeable only within this class
- private static final Object lockNode = new Object();
- // NON-FINAL class data
- // Optimize event dispatch by not allocating memory each time
- private static boolean dispatchDataLock;
- private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT];
- private static ListenerRecord[] notificationSet
- = new ListenerRecord[NOTIFICATIONS_INIT];
- // Ditto for the (most common) event object itself!
- private static boolean eventDataLock;
- private static DomEvent.DomMutationEvent mutationEvent
- = new DomEvent.DomMutationEvent(null);
- //
- // PER-INSTANCE DATA
- //
- DomDocument owner;
- DomNode parent; // parent node;
- DomNode previous; // previous sibling node
- DomNode next; // next sibling node
- DomNode first; // first child node
- DomNode last; // last child node
- int index; // index of this node in its parent's children
- int depth; // depth of the node in the document
- int length; // number of children
- final short nodeType;
- // Bleech ... "package private" so a builder can populate entity refs.
- // writable during construction. DOM spec is nasty.
- boolean readonly;
- // event registrations
- private HashSet listeners;
- private int nListeners;
- // DOM Level 3 userData dictionary.
- private HashMap userData;
- private HashMap userDataHandlers;
- //
- // Some of the methods here are declared 'final' because
- // knowledge about their implementation is built into this
- // class -- for both integrity and performance.
- //
- /**
- * Reduces space utilization for this node.
- */
- public void compact()
- {
- }
- /**
- * Constructs a node and associates it with its owner. Only
- * Document and DocumentType nodes may be created with no owner,
- * and DocumentType nodes get an owner as soon as they are
- * associated with a document.
- */
- protected DomNode(short nodeType, DomDocument owner)
- {
- this.nodeType = nodeType;
- if (owner == null)
- {
- // DOM calls never go down this path
- if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE)
- {
- throw new IllegalArgumentException ("no owner!");
- }
- }
- this.owner = owner;
- this.listeners = new HashSet();
- }
- /**
- * <b>DOM L1</b>
- * Returns null; Element subclasses must override this method.
- */
- public NamedNodeMap getAttributes()
- {
- return null;
- }
- /**
- * <b>DOM L2></b>
- * Returns true iff this is an element node with attributes.
- */
- public boolean hasAttributes()
- {
- return false;
- }
- /**
- * <b>DOM L1</b>
- * Returns a list, possibly empty, of the children of this node.
- * In this implementation, to conserve memory, nodes are the same
- * as their list of children. This can have ramifications for
- * subclasses, which may need to provide their own getLength method
- * for reasons unrelated to the NodeList method of the same name.
- */
- public NodeList getChildNodes()
- {
- return this;
- }
- /**
- * <b>DOM L1</b>
- * Returns the first child of this node, or null if there are none.
- */
- public Node getFirstChild()
- {
- return first;
- }
- /**
- * <b>DOM L1</b>
- * Returns the last child of this node, or null if there are none.
- */
- public Node getLastChild()
- {
- return last;
- }
- /**
- * <b>DOM L1</b>
- * Returns true if this node has children.
- */
- public boolean hasChildNodes()
- {
- return length != 0;
- }
- /**
- * Exposes the internal "readonly" flag. In DOM, children of
- * entities and entity references are readonly, as are the
- * objects associated with DocumentType objets.
- */
- public final boolean isReadonly()
- {
- return readonly;
- }
- /**
- * Sets the internal "readonly" flag so this subtree can't be changed.
- * Subclasses need to override this method for any associated content
- * that's not a child node, such as an element's attributes or the
- * (few) declarations associated with a DocumentType.
- */
- public void makeReadonly()
- {
- readonly = true;
- for (DomNode child = first; child != null; child = child.next)
- {
- child.makeReadonly();
- }
- }
- /**
- * Used to adopt a node to a new document.
- */
- void setOwner(DomDocument doc)
- {
- this.owner = doc;
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- ctx.setOwner(doc);
- }
- }
- // just checks the node for inclusion -- may be called many
- // times (docfrag) before anything is allowed to change
- private void checkMisc(DomNode child)
- {
- if (readonly && !owner.building)
- {
- throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
- null, this, 0);
- }
- for (DomNode ctx = this; ctx != null; ctx = ctx.parent)
- {
- if (child == ctx)
- {
- throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
- "can't make ancestor into a child",
- this, 0);
- }
- }
- DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this :
- this.owner;
- DomDocument childOwner = child.owner;
- short childNodeType = child.nodeType;
- if (childOwner != owner)
- {
- // new in DOM L2, this case -- patch it up later, in reparent()
- if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null))
- {
- throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
- null, child, 0);
- }
- }
- // enforce various structural constraints
- switch (nodeType)
- {
- case DOCUMENT_NODE:
- switch (childNodeType)
- {
- case ELEMENT_NODE:
- case PROCESSING_INSTRUCTION_NODE:
- case COMMENT_NODE:
- case DOCUMENT_TYPE_NODE:
- return;
- }
- break;
- case ATTRIBUTE_NODE:
- switch (childNodeType)
- {
- case TEXT_NODE:
- case ENTITY_REFERENCE_NODE:
- return;
- }
- break;
- case DOCUMENT_FRAGMENT_NODE:
- case ENTITY_REFERENCE_NODE:
- case ELEMENT_NODE:
- case ENTITY_NODE:
- switch (childNodeType)
- {
- case ELEMENT_NODE:
- case TEXT_NODE:
- case COMMENT_NODE:
- case PROCESSING_INSTRUCTION_NODE:
- case CDATA_SECTION_NODE:
- case ENTITY_REFERENCE_NODE:
- return;
- }
- break;
- case DOCUMENT_TYPE_NODE:
- if (!owner.building)
- break;
- switch (childNodeType)
- {
- case COMMENT_NODE:
- case PROCESSING_INSTRUCTION_NODE:
- return;
- }
- break;
- }
- if (owner.checkingWellformedness)
- {
- throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
- "can't append " +
- nodeTypeToString(childNodeType) +
- " to node of type " +
- nodeTypeToString(nodeType),
- this, 0);
- }
- }
- // Here's hoping a good optimizer will detect the case when the
- // next several methods are never called, and won't allocate
- // object code space of any kind. (Case: not reporting any
- // mutation events. We can also remove some static variables
- // listed above.)
- private void insertionEvent(DomEvent.DomMutationEvent event,
- DomNode target)
- {
- if (owner == null || owner.building)
- {
- return;
- }
- boolean doFree = false;
- if (event == null)
- {
- event = getMutationEvent();
- }
- if (event != null)
- {
- doFree = true;
- }
- else
- {
- event = new DomEvent.DomMutationEvent(null);
- }
- event.initMutationEvent("DOMNodeInserted",
- true /* bubbles */, false /* nocancel */,
- this /* related */, null, null, null, (short) 0);
- target.dispatchEvent(event);
- // XXX should really visit every descendant of 'target'
- // and sent a DOMNodeInsertedIntoDocument event to it...
- // bleech, there's no way to keep that acceptably fast.
- if (doFree)
- {
- event.target = null;
- event.relatedNode = null;
- event.currentNode = null;
- eventDataLock = false;
- } // else we created work for the GC
- }
- private void removalEvent(DomEvent.DomMutationEvent event,
- DomNode target)
- {
- if (owner == null || owner.building)
- {
- return;
- }
- boolean doFree = false;
- if (event == null)
- {
- event = getMutationEvent();
- }
- if (event != null)
- {
- doFree = true;
- }
- else
- {
- event = new DomEvent.DomMutationEvent(null);
- }
- event.initMutationEvent("DOMNodeRemoved",
- true /* bubbles */, false /* nocancel */,
- this /* related */, null, null, null, (short) 0);
- target.dispatchEvent(event);
- // XXX should really visit every descendant of 'target'
- // and sent a DOMNodeRemovedFromDocument event to it...
- // bleech, there's no way to keep that acceptably fast.
- event.target = null;
- event.relatedNode = null;
- event.currentNode = null;
- if (doFree)
- {
- eventDataLock = false;
- }
- // else we created more work for the GC
- }
- //
- // Avoid creating lots of memory management work, by using a simple
- // allocation strategy for the mutation event objects that get used
- // at least once per tree modification. We can't use stack allocation,
- // so we do the next simplest thing -- more or less, static allocation.
- // Concurrent notifications should be rare, anyway.
- //
- // Returns the preallocated object, which needs to be carefully freed,
- // or null to indicate the caller needs to allocate their own.
- //
- static private DomEvent.DomMutationEvent getMutationEvent()
- {
- synchronized (lockNode)
- {
- if (eventDataLock)
- {
- return null;
- }
- eventDataLock = true;
- return mutationEvent;
- }
- }
- // NOTE: this is manually inlined in the insertion
- // and removal event methods above; change in sync.
- static private void freeMutationEvent()
- {
- // clear fields to enable GC
- mutationEvent.clear();
- eventDataLock = false;
- }
- void setDepth(int depth)
- {
- this.depth = depth;
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- ctx.setDepth(depth + 1);
- }
- }
- /**
- * <b>DOM L1</b>
- * Appends the specified node to this node's list of children.
- * Document subclasses must override this to enforce the restrictions
- * that there be only one element and document type child.
- *
- * <p> Causes a DOMNodeInserted mutation event to be reported.
- * Will first cause a DOMNodeRemoved event to be reported if the
- * parameter already has a parent. If the new child is a document
- * fragment node, both events will be reported for each child of
- * the fragment; the order in which children are removed and
- * inserted is implementation-specific.
- *
- * <p> If this DOM has been compiled without mutation event support,
- * these events will not be reported.
- */
- public Node appendChild(Node newChild)
- {
- try
- {
- DomNode child = (DomNode) newChild;
- if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
- {
- // Append all nodes in the fragment to this node
- for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
- {
- checkMisc(ctx);
- }
- for (DomNode ctx = child.first; ctx != null; )
- {
- DomNode ctxNext = ctx.next;
- appendChild(ctx);
- ctx = ctxNext;
- }
- }
- else
- {
- checkMisc(child);
- if (child.parent != null)
- {
- child.parent.removeChild(child);
- }
- child.parent = this;
- child.index = length++;
- child.setDepth(depth + 1);
- child.next = null;
- if (last == null)
- {
- first = child;
- child.previous = null;
- }
- else
- {
- last.next = child;
- child.previous = last;
- }
- last = child;
- if (reportMutations)
- {
- insertionEvent(null, child);
- }
- }
- return child;
- }
- catch (ClassCastException e)
- {
- throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
- null, newChild, 0);
- }
- }
- /**
- * <b>DOM L1</b>
- * Inserts the specified node in this node's list of children.
- * Document subclasses must override this to enforce the restrictions
- * that there be only one element and document type child.
- *
- * <p> Causes a DOMNodeInserted mutation event to be reported. Will
- * first cause a DOMNodeRemoved event to be reported if the newChild
- * parameter already has a parent. If the new child is a document
- * fragment node, both events will be reported for each child of
- * the fragment; the order in which children are removed and inserted
- * is implementation-specific.
- *
- * <p> If this DOM has been compiled without mutation event support,
- * these events will not be reported.
- */
- public Node insertBefore(Node newChild, Node refChild)
- {
- if (refChild == null)
- {
- return appendChild(newChild);
- }
- try
- {
- DomNode child = (DomNode) newChild;
- DomNode ref = (DomNode) refChild;
- if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
- {
- // Append all nodes in the fragment to this node
- for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
- {
- checkMisc(ctx);
- }
- for (DomNode ctx = child.first; ctx != null; )
- {
- DomNode ctxNext = ctx.next;
- insertBefore(ctx, ref);
- ctx = ctxNext;
- }
- }
- else
- {
- checkMisc(child);
- if (ref == null || ref.parent != this)
- {
- throw new DomDOMException(DOMException.NOT_FOUND_ERR,
- null, ref, 0);
- }
- if (ref == child)
- {
- throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR,
- "can't insert node before itself",
- ref, 0);
- }
- if (child.parent != null)
- {
- child.parent.removeChild(child);
- }
- child.parent = this;
- int i = ref.index;
- child.setDepth(depth + 1);
- child.next = ref;
- if (ref.previous != null)
- {
- ref.previous.next = child;
- }
- child.previous = ref.previous;
- ref.previous = child;
- if (first == ref)
- {
- first = child;
- }
- // index renumbering
- for (DomNode ctx = child; ctx != null; ctx = ctx.next)
- {
- ctx.index = i++;
- }
- if (reportMutations)
- {
- insertionEvent(null, child);
- }
- length++;
- }
- return child;
- }
- catch (ClassCastException e)
- {
- throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
- null, newChild, 0);
- }
- }
- /**
- * <b>DOM L1</b>
- * Replaces the specified node in this node's list of children.
- * Document subclasses must override this to test the restrictions
- * that there be only one element and document type child.
- *
- * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be
- * reported. Will cause another DOMNodeRemoved event to be reported if
- * the newChild parameter already has a parent. These events may be
- * delivered in any order, except that the event reporting removal
- * from such an existing parent will always be delivered before the
- * event reporting its re-insertion as a child of some other node.
- * The order in which children are removed and inserted is implementation
- * specific.
- *
- * <p> If your application needs to depend on the in which those removal
- * and insertion events are delivered, don't use this API. Instead,
- * invoke the removeChild and insertBefore methods directly, to guarantee
- * a specific delivery order. Similarly, don't use document fragments,
- * Otherwise your application code may not work on a DOM which implements
- * this method differently.
- *
- * <p> If this DOM has been compiled without mutation event support,
- * these events will not be reported.
- */
- public Node replaceChild(Node newChild, Node refChild)
- {
- try
- {
- DomNode child = (DomNode) newChild;
- DomNode ref = (DomNode) refChild;
- DomEvent.DomMutationEvent event = getMutationEvent();
- boolean doFree = (event != null);
- if (child.nodeType == DOCUMENT_FRAGMENT_NODE)
- {
- // Append all nodes in the fragment to this node
- for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
- {
- checkMisc(ctx);
- }
- if (ref == null || ref.parent != this)
- {
- throw new DomDOMException(DOMException.NOT_FOUND_ERR,
- null, ref, 0);
- }
- if (reportMutations)
- {
- removalEvent(event, ref);
- }
- length--;
- length += child.length;
- if (child.length == 0)
- {
- // Removal
- if (ref.previous != null)
- {
- ref.previous.next = ref.next;
- }
- if (ref.next != null)
- {
- ref.next.previous = ref.previous;
- }
- if (first == ref)
- {
- first = ref.next;
- }
- if (last == ref)
- {
- last = ref.previous;
- }
- }
- else
- {
- int i = ref.index;
- for (DomNode ctx = child.first; ctx != null; ctx = ctx.next)
- {
- // Insertion
- ctx.parent = this;
- ctx.index = i++;
- ctx.setDepth(ref.depth);
- if (ctx == child.first)
- {
- ctx.previous = ref.previous;
- }
- if (ctx == child.last)
- {
- ctx.next = ref.next;
- }
- }
- if (first == ref)
- {
- first = child.first;
- }
- if (last == ref)
- {
- last = child.last;
- }
- }
- }
- else
- {
- checkMisc(child);
- if (ref == null || ref.parent != this)
- {
- throw new DomDOMException(DOMException.NOT_FOUND_ERR,
- null, ref, 0);
- }
- if (reportMutations)
- {
- removalEvent(event, ref);
- }
- if (child.parent != null)
- {
- child.parent.removeChild(child);
- }
- child.parent = this;
- child.index = ref.index;
- child.setDepth(ref.depth);
- if (ref.previous != null)
- {
- ref.previous.next = child;
- }
- child.previous = ref.previous;
- if (ref.next != null)
- {
- ref.next.previous = child;
- }
- child.next = ref.next;
- if (first == ref)
- {
- first = child;
- }
- if (last == ref)
- {
- last = child;
- }
- if (reportMutations)
- {
- insertionEvent(event, child);
- }
- if (doFree)
- {
- freeMutationEvent();
- }
- }
- ref.parent = null;
- ref.index = 0;
- ref.setDepth(0);
- ref.previous = null;
- ref.next = null;
- return ref;
- }
- catch (ClassCastException e)
- {
- throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
- null, newChild, 0);
- }
- }
- /**
- * <b>DOM L1</b>
- * Removes the specified child from this node's list of children,
- * or else reports an exception.
- *
- * <p> Causes a DOMNodeRemoved mutation event to be reported.
- *
- * <p> If this DOM has been compiled without mutation event support,
- * these events will not be reported.
- */
- public Node removeChild(Node refChild)
- {
- try
- {
- DomNode ref = (DomNode) refChild;
- if (ref == null || ref.parent != this)
- {
- throw new DomDOMException(DOMException.NOT_FOUND_ERR,
- null, ref, 0);
- }
- if (readonly && !owner.building)
- {
- throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
- null, this, 0);
- }
- for (DomNode child = first; child != null; child = child.next)
- {
- if (child == ref)
- {
- if (reportMutations)
- {
- removalEvent(null, child);
- }
- length--;
- if (ref.previous != null)
- {
- ref.previous.next = ref.next;
- }
- if (ref.next != null)
- {
- ref.next.previous = ref.previous;
- }
- if (first == ref)
- {
- first = ref.next;
- }
- if (last == ref)
- {
- last = ref.previous;
- }
- // renumber indices
- int i = 0;
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- ctx.index = i++;
- }
- ref.parent = null;
- ref.setDepth(0);
- ref.index = 0;
- ref.previous = null;
- ref.next = null;
- return ref;
- }
- }
- throw new DomDOMException(DOMException.NOT_FOUND_ERR,
- "that's no child of mine", refChild, 0);
- }
- catch (ClassCastException e)
- {
- throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR,
- null, refChild, 0);
- }
- }
- /**
- * <b>DOM L1 (NodeList)</b>
- * Returns the item with the specified index in this NodeList,
- * else null.
- */
- public Node item(int index)
- {
- DomNode child = first;
- int count = 0;
- while (child != null && count < index)
- {
- child = child.next;
- count++;
- }
- return child;
- }
- /**
- * <b>DOM L1 (NodeList)</b>
- * Returns the number of elements in this NodeList.
- * (Note that many interfaces have a "Length" property, not just
- * NodeList, and if a node subtype must implement one of those,
- * it will also need to override getChildNodes.)
- */
- public int getLength()
- {
- return length;
- }
- /**
- * Minimize extra space consumed by this node to hold children and event
- * listeners.
- */
- public void trimToSize()
- {
- }
- /**
- * <b>DOM L1</b>
- * Returns the previous sibling, if one is known.
- */
- public Node getNextSibling()
- {
- return next;
- }
- /**
- * <b>DOM L1</b>
- * Returns the previous sibling, if one is known.
- */
- public Node getPreviousSibling()
- {
- return previous;
- }
- /**
- * <b>DOM L1</b>
- * Returns the parent node, if one is known.
- */
- public Node getParentNode()
- {
- return parent;
- }
- /**
- * <b>DOM L2</b>
- * Consults the DOM implementation to determine if the requested
- * feature is supported. DocumentType subclasses must override
- * this method, and associate themselves directly with the
- * DOMImplementation node used. (This method relies on being able
- * to access the DOMImplementation from the owner document, but
- * DocumentType nodes can be created without an owner.)
- */
- public boolean isSupported(String feature, String version)
- {
- Document doc = owner;
- DOMImplementation impl = null;
- if (doc == null && nodeType == DOCUMENT_NODE)
- {
- doc = (Document) this;
- }
- if (doc == null)
- {
- // possible for DocumentType
- throw new IllegalStateException ("unbound ownerDocument");
- }
- impl = doc.getImplementation();
- return impl.hasFeature(feature, version);
- }
- /**
- * <b>DOM L1 (modified in L2)</b>
- * Returns the owner document. This is only null for Document nodes,
- * and (new in L2) for DocumentType nodes which have not yet been
- * associated with the rest of their document.
- */
- final public Document getOwnerDocument()
- {
- return owner;
- }
- /**
- * <b>DOM L1</b>
- * Does nothing; this must be overridden (along with the
- * getNodeValue method) for nodes with a non-null defined value.
- */
- public void setNodeValue(String value)
- {
- }
- /**
- * <b>DOM L1</b>
- * Returns null; this must be overridden for nodes types with
- * a defined value, along with the setNodeValue method.
- */
- public String getNodeValue()
- {
- return null;
- }
- /** This forces GCJ compatibility.
- * Without this method GCJ is unable to compile to byte code.
- */
- public final short getNodeType()
- {
- return nodeType;
- }
- /** This forces GCJ compatibility.
- * Without this method GCJ seems unable to natively compile GNUJAXP.
- */
- public abstract String getNodeName();
- /**
- * <b>DOM L2</b>
- * Does nothing; this must be overridden (along with the
- * getPrefix method) for element and attribute nodes.
- */
- public void setPrefix(String prefix)
- {
- }
- /**
- * <b>DOM L2</b>
- * Returns null; this must be overridden for element and
- * attribute nodes.
- */
- public String getPrefix()
- {
- return null;
- }
- /**
- * <b>DOM L2</b>
- * Returns null; this must be overridden for element and
- * attribute nodes.
- */
- public String getNamespaceURI()
- {
- return null;
- }
- /**
- * <b>DOM L2</b>
- * Returns the node name; this must be overridden for element and
- * attribute nodes.
- */
- public String getLocalName()
- {
- return null;
- }
- /**
- * <b>DOM L1</b>
- * Returns a clone of this node which optionally includes cloned
- * versions of child nodes. Clones are always mutable, except for
- * entity reference nodes.
- */
- public Node cloneNode(boolean deep)
- {
- if (deep)
- {
- return cloneNodeDeepInternal(true, null);
- }
- DomNode node = (DomNode) clone();
- if (nodeType == ENTITY_REFERENCE_NODE)
- {
- node.makeReadonly();
- }
- notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
- return node;
- }
- /**
- * Returns a deep clone of this node.
- */
- private DomNode cloneNodeDeepInternal(boolean root, DomDocument doc)
- {
- DomNode node = (DomNode) clone();
- boolean building = false; // Never used unless root is true
- if (root)
- {
- doc = (nodeType == DOCUMENT_NODE) ? (DomDocument) node : node.owner;
- building = doc.building;
- doc.building = true; // Permit certain structural rules
- }
- node.owner = doc;
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- DomNode newChild = ctx.cloneNodeDeepInternal(false, doc);
- node.appendChild(newChild);
- }
- if (nodeType == ENTITY_REFERENCE_NODE)
- {
- node.makeReadonly();
- }
- if (root)
- {
- doc.building = building;
- }
- notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node);
- return node;
- }
- void notifyUserDataHandlers(short op, Node src, Node dst)
- {
- if (userDataHandlers != null)
- {
- for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); )
- {
- Map.Entry entry = (Map.Entry) i.next();
- String key = (String) entry.getKey();
- UserDataHandler handler = (UserDataHandler) entry.getValue();
- Object data = userData.get(key);
- handler.handle(op, key, data, src, dst);
- }
- }
- }
- /**
- * Clones this node; roughly equivalent to cloneNode(false).
- * Element subclasses must provide a new implementation which
- * invokes this method to handle the basics, and then arranges
- * to clone any element attributes directly. Attribute subclasses
- * must make similar arrangements, ensuring that existing ties to
- * elements are broken by cloning.
- */
- public Object clone()
- {
- try
- {
- DomNode node = (DomNode) super.clone();
- node.parent = null;
- node.depth = 0;
- node.index = 0;
- node.length = 0;
- node.first = null;
- node.last = null;
- node.previous = null;
- node.next = null;
- node.readonly = false;
- node.listeners = new HashSet();
- node.nListeners = 0;
- return node;
- }
- catch (CloneNotSupportedException x)
- {
- throw new Error("clone didn't work");
- }
- }
- // the elements-by-tagname stuff is needed for both
- // elements and documents ... this is in lieu of a
- // common base class between Node and NodeNS.
- /**
- * <b>DOM L1</b>
- * Creates a NodeList giving array-style access to elements with
- * the specified name. Access is fastest if indices change by
- * small values, and the DOM is not modified.
- */
- public NodeList getElementsByTagName(String tag)
- {
- return new ShadowList(null, tag);
- }
- /**
- * <b>DOM L2</b>
- * Creates a NodeList giving array-style access to elements with
- * the specified namespace and local name. Access is fastest if
- * indices change by small values, and the DOM is not modified.
- */
- public NodeList getElementsByTagNameNS(String namespace, String local)
- {
- return new ShadowList(namespace, local);
- }
- //
- // This shadow class is GC-able even when the live list it shadows
- // can't be, because of event registration hookups. Its finalizer
- // makes that live list become GC-able.
- //
- final class ShadowList
- implements NodeList
- {
- private LiveNodeList liveList;
- ShadowList(String ns, String local)
- {
- liveList = new LiveNodeList(ns, local);
- }
- public void finalize()
- {
- liveList.detach();
- liveList = null;
- }
- public Node item(int index)
- {
- return liveList.item(index);
- }
- public int getLength()
- {
- return liveList.getLength();
- }
- }
- final class LiveNodeList
- implements NodeList, EventListener, NodeFilter
- {
- private final boolean matchAnyURI;
- private final boolean matchAnyName;
- private final String elementURI;
- private final String elementName;
- private DomIterator current;
- private int lastIndex;
- LiveNodeList(String uri, String name)
- {
- elementURI = uri;
- elementName = name;
- matchAnyURI = "*".equals(uri);
- matchAnyName = "*".equals(name);
- DomNode.this.addEventListener("DOMNodeInserted", this, true);
- DomNode.this.addEventListener("DOMNodeRemoved", this, true);
- }
- void detach()
- {
- if (current != null)
- current.detach();
- current = null;
- DomNode.this.removeEventListener("DOMNodeInserted", this, true);
- DomNode.this.removeEventListener("DOMNodeRemoved", this, true);
- }
- public short acceptNode(Node element)
- {
- if (element == DomNode.this)
- {
- return FILTER_SKIP;
- }
- // use namespace-aware matching ...
- if (elementURI != null)
- {
- if (!(matchAnyURI
- || elementURI.equals(element.getNamespaceURI())))
- {
- return FILTER_SKIP;
- }
- if (!(matchAnyName
- || elementName.equals(element.getLocalName())))
- {
- return FILTER_SKIP;
- }
- // ... or qName-based kind.
- }
- else
- {
- if (!(matchAnyName
- || elementName.equals(element.getNodeName())))
- {
- return FILTER_SKIP;
- }
- }
- return FILTER_ACCEPT;
- }
- private DomIterator createIterator()
- {
- return new DomIterator(DomNode.this,
- NodeFilter.SHOW_ELEMENT,
- this, /* filter */
- true /* expand entity refs */
- );
- }
- public void handleEvent(Event e)
- {
- MutationEvent mutation = (MutationEvent) e;
- Node related = mutation.getRelatedNode();
- // XXX if it's got children ... check all kids too, they
- // will invalidate our saved index
- if (related.getNodeType() != Node.ELEMENT_NODE ||
- related.getNodeName() != elementName ||
- related.getNamespaceURI() != elementURI)
- {
- return;
- }
- if (current != null)
- current.detach();
- current = null;
- }
- public Node item(int index)
- {
- if (current == null)
- {
- current = createIterator();
- lastIndex = -1;
- }
- // last node or before? go backwards
- if (index <= lastIndex) {
- while (index != lastIndex) {
- current.previousNode ();
- lastIndex--;
- }
- Node ret = current.previousNode ();
- current.detach();
- current = null;
- return ret;
- }
- // somewhere after last node
- while (++lastIndex != index)
- current.nextNode ();
- Node ret = current.nextNode ();
- current.detach();
- current = null;
- return ret;
- }
- public int getLength()
- {
- int retval = 0;
- NodeIterator iter = createIterator();
- while (iter.nextNode() != null)
- {
- retval++;
- }
- iter.detach();
- return retval;
- }
- }
- //
- // EventTarget support
- //
- static final class ListenerRecord
- {
- String type;
- EventListener listener;
- boolean useCapture;
- // XXX use JDK 1.2 java.lang.ref.WeakReference to listener,
- // and we can both get rid of "shadow" classes and remove
- // the need for applications to apply similar trix ... but
- // JDK 1.2 support isn't generally available yet
- ListenerRecord(String type, EventListener listener, boolean useCapture)
- {
- this.type = type.intern();
- this.listener = listener;
- this.useCapture = useCapture;
- }
- public boolean equals(Object o)
- {
- ListenerRecord rec = (ListenerRecord)o;
- return listener == rec.listener
- && useCapture == rec.useCapture
- && type == rec.type;
- }
- public int hashCode()
- {
- return listener.hashCode() ^ type.hashCode();
- }
- }
- /**
- * <b>DOM L2 (Events)</b>
- * Returns an instance of the specified type of event object.
- * Understands about DOM Mutation, HTML, and UI events.
- *
- * <p>If the name of the event type begins with "USER-", then an object
- * implementing the "Event" class will be returned; this provides a
- * limited facility for application-defined events to use the DOM event
- * infrastructure. Alternatively, use one of the standard DOM event
- * classes and initialize it using use such a "USER-" event type name;
- * or defin, instantiate, and initialize an application-specific subclass
- * of DomEvent and pass that to dispatchEvent().
- *
- * @param eventType Identifies the particular DOM feature module
- * defining the type of event, such as "MutationEvents".
- * <em>The event "name" is a different kind of "type".</em>
- */
- public Event createEvent(String eventType)
- {
- eventType = eventType.toLowerCase();
- if ("mutationevents".equals(eventType))
- {
- return new DomEvent.DomMutationEvent(null);
- }
- if ("htmlevents".equals(eventType)
- || "events".equals(eventType)
- || "user-events".equals(eventType))
- {
- return new DomEvent(null);
- }
- if ("uievents".equals(eventType))
- {
- return new DomEvent.DomUIEvent(null);
- }
- // mouse events
- throw new DomDOMException(DOMException.NOT_SUPPORTED_ERR,
- eventType, null, 0);
- }
- /**
- * <b>DOM L2 (Events)</b>
- * Registers an event listener's interest in a class of events.
- */
- public final void addEventListener(String type,
- EventListener listener,
- boolean useCapture)
- {
- // prune duplicates
- ListenerRecord record;
- record = new ListenerRecord(type, listener, useCapture);
- listeners.add(record);
- nListeners = listeners.size();
- }
- // XXX this exception should be discarded from DOM
- // this class can be instantiated, unlike the one in the spec
- static final class DomEventException
- extends EventException
- {
- DomEventException()
- {
- super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type");
- }
- }
- /**
- * <b>DOM L2 (Events)</b>
- * Delivers an event to all relevant listeners, returning true if the
- * caller should perform their default action. Note that the event
- * must have been provided by the createEvent() method on this
- * class, else it can't be dispatched.
- *
- * @see #createEvent
- *
- * @exception NullPointerException When a null event is passed.
- * @exception ClassCastException When the event wasn't provided by
- * the createEvent method, or otherwise isn't a DomEvent.
- * @exception EventException If the event type wasn't specified
- */
- public final boolean dispatchEvent(Event event)
- throws EventException
- {
- DomEvent e = (DomEvent) event;
- DomNode[] ancestors = null;
- int ancestorMax = 0;
- boolean haveDispatchDataLock = false;
- if (e.type == null)
- {
- throw new DomEventException();
- }
- e.doDefault = true;
- e.target = this;
- //
- // Typical case: one nonrecursive dispatchEvent call at a time
- // for this class. If that's our case, we can avoid allocating
- // garbage, which is overall a big win. Even with advanced GCs
- // that deal well with short-lived garbage, and wayfast allocators,
- // it still helps.
- //
- // Remember -- EVERY mutation goes though here at least once.
- //
- // When populating a DOM tree, trying to send mutation events is
- // the primary cost; this dominates the critical path.
- //
- try
- {
- DomNode current;
- int index;
- boolean haveAncestorRegistrations = false;
- ListenerRecord[] notificationSet;
- int ancestorLen;
- synchronized (lockNode)
- {
- if (!dispatchDataLock)
- {
- haveDispatchDataLock = dispatchDataLock = true;
- notificationSet = DomNode.notificationSet;
- ancestors = DomNode.ancestors;
- }
- else
- {
- notificationSet = new ListenerRecord[NOTIFICATIONS_INIT];
- ancestors = new DomNode[ANCESTORS_INIT];
- }
- ancestorLen = ancestors.length;
- }
- // Climb to the top of this subtree and handle capture, letting
- // each node (from the top down) capture until one stops it or
- // until we get to this one.
- current = (parent == null) ? this : parent;
- if (current.depth >= ANCESTORS_INIT)
- {
- DomNode[] newants = new DomNode[current.depth + 1];
- System.arraycopy(ancestors, 0, newants, 0, ancestors.length);
- ancestors = newants;
- ancestorLen = ancestors.length;
- }
- for (index = 0; index < ancestorLen; index++)
- {
- if (current == null || current.depth == 0)
- break;
- if (current.nListeners != 0)
- {
- haveAncestorRegistrations = true;
- }
- ancestors [index] = current;
- current = current.parent;
- }
- if (current.depth > 0)
- {
- throw new RuntimeException("dispatchEvent capture stack size");
- }
- ancestorMax = index;
- e.stop = false;
- if (haveAncestorRegistrations)
- {
- e.eventPhase = Event.CAPTURING_PHASE;
- while (!e.stop && index-- > 0)
- {
- current = ancestors [index];
- if (current.nListeners != 0)
- {
- notifyNode(e, current, true, notificationSet);
- }
- }
- }
- // Always deliver events to the target node (this)
- // unless stopPropagation was called. If we saw
- // no registrations yet (typical!), we never will.
- if (!e.stop && nListeners != 0)
- {
- e.eventPhase = Event.AT_TARGET;
- notifyNode (e, this, false, notificationSet);
- }
- else if (!haveAncestorRegistrations)
- {
- e.stop = true;
- }
- // If the event bubbles and propagation wasn't halted,
- // walk back up the ancestor list. Stop bubbling when
- // any bubbled event handler stops it.
- if (!e.stop && e.bubbles)
- {
- e.eventPhase = Event.BUBBLING_PHASE;
- for (index = 0;
- !e.stop
- && index < ancestorMax
- && (current = ancestors[index]) != null;
- index++)
- {
- if (current.nListeners != 0)
- {
- notifyNode(e, current, false, notificationSet);
- }
- }
- }
- e.eventPhase = 0;
- // Caller chooses whether to perform the default
- // action based on return from this method.
- return e.doDefault;
- }
- finally
- {
- if (haveDispatchDataLock)
- {
- // synchronize to force write ordering
- synchronized (lockNode)
- {
- // null out refs to ensure they'll be GC'd
- for (int i = 0; i < ancestorMax; i++)
- {
- ancestors [i] = null;
- }
- // notificationSet handled by notifyNode
- dispatchDataLock = false;
- }
- }
- }
- }
- private void notifyNode(DomEvent e,
- DomNode current,
- boolean capture,
- ListenerRecord[] notificationSet)
- {
- int count = 0;
- Iterator iter;
- iter = current.listeners.iterator();
- // do any of this set of listeners get notified?
- while (iter.hasNext())
- {
- ListenerRecord rec = (ListenerRecord)iter.next();
- if (rec.useCapture != capture)
- {
- continue;
- }
- if (!e.type.equals (rec.type))
- {
- continue;
- }
- if (count >= notificationSet.length)
- {
- // very simple growth algorithm
- int len = Math.max(notificationSet.length, 1);
- ListenerRecord[] tmp = new ListenerRecord[len * 2];
- System.arraycopy(notificationSet, 0, tmp, 0,
- notificationSet.length);
- notificationSet = tmp;
- }
- notificationSet[count++] = rec;
- }
- iter = null;
- // Notify just those listeners
- e.currentNode = current;
- for (int i = 0; i < count; i++)
- {
- try
- {
- iter = current.listeners.iterator();
- // Late in the DOM CR process (3rd or 4th CR?) the
- // removeEventListener spec became asymmetric with respect
- // to addEventListener ... effect is now immediate.
- while (iter.hasNext())
- {
- ListenerRecord rec = (ListenerRecord)iter.next();
- if (rec.equals(notificationSet[i]))
- {
- notificationSet[i].listener.handleEvent(e);
- break;
- }
- }
- iter = null;
- }
- catch (Exception x)
- {
- // ignore all exceptions
- }
- notificationSet[i] = null; // free for GC
- }
- }
- /**
- * <b>DOM L2 (Events)</b>
- * Unregisters an event listener.
- */
- public final void removeEventListener(String type,
- EventListener listener,
- boolean useCapture)
- {
- listeners.remove(new ListenerRecord(type, listener, useCapture));
- nListeners = listeners.size();
- // no exceptions reported
- }
- /**
- * <b>DOM L1 (relocated in DOM L2)</b>
- * In this node and all contained nodes (including attributes if
- * relevant) merge adjacent text nodes. This is done while ignoring
- * text which happens to use CDATA delimiters).
- */
- public final void normalize()
- {
- // Suspend readonly status
- boolean saved = readonly;
- readonly = false;
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- boolean saved2 = ctx.readonly;
- ctx.readonly = false;
- switch (ctx.nodeType)
- {
- case TEXT_NODE:
- case CDATA_SECTION_NODE:
- while (ctx.next != null &&
- (ctx.next.nodeType == TEXT_NODE ||
- ctx.next.nodeType == CDATA_SECTION_NODE))
- {
- Text text = (Text) ctx;
- text.appendData(ctx.next.getNodeValue());
- removeChild(ctx.next);
- }
- break;
- case ELEMENT_NODE:
- NamedNodeMap attrs = ctx.getAttributes();
- int len = attrs.getLength();
- for (int i = 0; i < len; i++)
- {
- DomNode attr = (DomNode) attrs.item(i);
- boolean saved3 = attr.readonly;
- attr.readonly = false;
- attr.normalize();
- attr.readonly = saved3;
- }
- // Fall through
- case DOCUMENT_NODE:
- case DOCUMENT_FRAGMENT_NODE:
- case ATTRIBUTE_NODE:
- case ENTITY_REFERENCE_NODE:
- ctx.normalize();
- break;
- }
- ctx.readonly = saved2;
- }
- readonly = saved;
- }
- /**
- * Returns true iff node types match, and either (a) both nodes have no
- * namespace and their getNodeName() values are the same, or (b) both
- * nodes have the same getNamespaceURI() and same getLocalName() values.
- *
- * <p>Note that notion of a "Per-Element-Type" attribute name scope, as
- * found in a non-normative appendix of the XML Namespaces specification,
- * is not supported here. Your application must implement that notion,
- * typically by not bothering to check nameAndTypeEquals for attributes
- * without namespace URIs unless you already know their elements are
- * nameAndTypeEquals.
- */
- public boolean nameAndTypeEquals(Node other)
- {
- if (other == this)
- {
- return true;
- }
- // node types must match
- if (nodeType != other.getNodeType())
- {
- return false;
- }
- // if both have namespaces, do a "full" comparision
- // this is a "global" partition
- String ns1 = this.getNamespaceURI();
- String ns2 = other.getNamespaceURI();
- if (ns1 != null && ns2 != null)
- {
- return ns1.equals(ns2) &&
- equal(getLocalName(), other.getLocalName());
- }
- // if neither has a namespace, this is a "no-namespace" name.
- if (ns1 == null && ns2 == null)
- {
- if (!getNodeName().equals(other.getNodeName()))
- {
- return false;
- }
- // can test the non-normative "per-element-type" scope here.
- // if this is an attribute node and both nodes have been bound
- // to elements (!!), then return the nameAndTypeEquals()
- // comparison of those elements.
- return true;
- }
- // otherwise they're unequal: one scoped, one not.
- return false;
- }
- // DOM Level 3 methods
- public String getBaseURI()
- {
- return (parent != null) ? parent.getBaseURI() : null;
- }
- public short compareDocumentPosition(Node other)
- throws DOMException
- {
- return (short) compareTo(other);
- }
- /**
- * DOM nodes have a natural ordering: document order.
- */
- public final int compareTo(Object other)
- {
- if (other instanceof DomNode)
- {
- DomNode n1 = this;
- DomNode n2 = (DomNode) other;
- if (n1.owner != n2.owner)
- {
- return 0;
- }
- int d1 = n1.depth, d2 = n2.depth;
- int delta = d1 - d2;
- while (d1 > d2)
- {
- n1 = n1.parent;
- d1--;
- }
- while (d2 > d1)
- {
- n2 = n2.parent;
- d2--;
- }
- int c = compareTo2(n1, n2);
- return (c != 0) ? c : delta;
- }
- return 0;
- }
- /**
- * Compare two nodes at the same depth.
- */
- final int compareTo2(DomNode n1, DomNode n2)
- {
- if (n1 == n2 || n1.depth == 0 || n2.depth == 0)
- {
- return 0;
- }
- int c = compareTo2(n1.parent, n2.parent);
- return (c != 0) ? c : n1.index - n2.index;
- }
- public final String getTextContent()
- throws DOMException
- {
- return getTextContent(true);
- }
- final String getTextContent(boolean topLevel)
- throws DOMException
- {
- switch (nodeType)
- {
- case ELEMENT_NODE:
- case ENTITY_NODE:
- case ENTITY_REFERENCE_NODE:
- case DOCUMENT_FRAGMENT_NODE:
- CPStringBuilder buffer = new CPStringBuilder();
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- {
- String textContent = ctx.getTextContent(false);
- if (textContent != null)
- {
- buffer.append(textContent);
- }
- }
- return buffer.toString();
- case TEXT_NODE:
- case CDATA_SECTION_NODE:
- if (((Text) this).isElementContentWhitespace())
- {
- return "";
- }
- return getNodeValue();
- case ATTRIBUTE_NODE:
- return getNodeValue();
- case COMMENT_NODE:
- case PROCESSING_INSTRUCTION_NODE:
- return topLevel ? getNodeValue() : "";
- default:
- return null;
- }
- }
- public void setTextContent(String textContent)
- throws DOMException
- {
- switch (nodeType)
- {
- case ELEMENT_NODE:
- case ATTRIBUTE_NODE:
- case ENTITY_NODE:
- case ENTITY_REFERENCE_NODE:
- case DOCUMENT_FRAGMENT_NODE:
- for (DomNode ctx = first; ctx != null; )
- {
- DomNode n = ctx.next;
- removeChild(ctx);
- ctx = n;
- }
- if (textContent != null)
- {
- Text text = owner.createTextNode(textContent);
- appendChild(text);
- }
- break;
- case TEXT_NODE:
- case CDATA_SECTION_NODE:
- case COMMENT_NODE:
- case PROCESSING_INSTRUCTION_NODE:
- setNodeValue(textContent);
- break;
- }
- }
- public boolean isSameNode(Node other)
- {
- return this == other;
- }
- public String lookupPrefix(String namespaceURI)
- {
- return (parent == null || parent == owner) ? null :
- parent.lookupPrefix(namespaceURI);
- }
- public boolean isDefaultNamespace(String namespaceURI)
- {
- return (parent == null || parent == owner) ? false :
- parent.isDefaultNamespace(namespaceURI);
- }
- public String lookupNamespaceURI(String prefix)
- {
- return (parent == null || parent == owner) ? null :
- parent.lookupNamespaceURI(prefix);
- }
- public boolean isEqualNode(Node arg)
- {
- if (this == arg)
- return true;
- if (arg == null)
- return false;
- if (nodeType != arg.getNodeType())
- return false;
- switch (nodeType)
- {
- case ELEMENT_NODE:
- case ATTRIBUTE_NODE:
- if (!equal(getLocalName(), arg.getLocalName()) ||
- !equal(getNamespaceURI(), arg.getNamespaceURI()))
- return false;
- break;
- case PROCESSING_INSTRUCTION_NODE:
- if (!equal(getNodeName(), arg.getNodeName()) ||
- !equal(getNodeValue(), arg.getNodeValue()))
- return false;
- break;
- case COMMENT_NODE:
- case TEXT_NODE:
- case CDATA_SECTION_NODE:
- if (!equal(getNodeValue(), arg.getNodeValue()))
- return false;
- break;
- }
- // Children
- Node argCtx = arg.getFirstChild();
- getFirstChild(); // because of DomAttr lazy children
- DomNode ctx = first;
- for (; ctx != null && argCtx != null; ctx = ctx.next)
- {
- if (nodeType == DOCUMENT_NODE)
- {
- // Ignore whitespace outside document element
- while (ctx != null && ctx.nodeType == TEXT_NODE)
- ctx = ctx.next;
- while (argCtx != null && ctx.getNodeType() == TEXT_NODE)
- argCtx = argCtx.getNextSibling();
- if (ctx == null && argCtx != null)
- return false;
- else if (argCtx == null && ctx != null)
- return false;
- }
- if (!ctx.isEqualNode(argCtx))
- return false;
- argCtx = argCtx.getNextSibling();
- }
- if (ctx != null || argCtx != null)
- return false;
- // TODO DocumentType
- return true;
- }
- boolean equal(String arg1, String arg2)
- {
- return ((arg1 == null && arg2 == null) ||
- (arg1 != null && arg1.equals(arg2)));
- }
- public Object getFeature(String feature, String version)
- {
- DOMImplementation impl = (nodeType == DOCUMENT_NODE) ?
- ((Document) this).getImplementation() : owner.getImplementation();
- if (impl.hasFeature(feature, version))
- {
- return this;
- }
- return null;
- }
- public Object setUserData(String key, Object data, UserDataHandler handler)
- {
- if (userData == null)
- {
- userData = new HashMap();
- }
- if (handler != null)
- {
- if (userDataHandlers == null)
- {
- userDataHandlers = new HashMap();
- }
- userDataHandlers.put(key, handler);
- }
- return userData.put(key, data);
- }
- public Object getUserData(String key)
- {
- if (userData == null)
- {
- return null;
- }
- return userData.get(key);
- }
- public String toString()
- {
- String nodeName = getNodeName();
- String nodeValue = getNodeValue();
- CPStringBuilder buf = new CPStringBuilder(getClass().getName());
- buf.append('[');
- if (nodeName != null)
- {
- buf.append(nodeName);
- }
- if (nodeValue != null)
- {
- if (nodeName != null)
- {
- buf.append('=');
- }
- buf.append('\'');
- buf.append(encode(nodeValue));
- buf.append('\'');
- }
- buf.append(']');
- return buf.toString();
- }
- String encode(String value)
- {
- CPStringBuilder buf = null;
- int len = value.length();
- for (int i = 0; i < len; i++)
- {
- char c = value.charAt(i);
- if (c == '\n')
- {
- if (buf == null)
- {
- buf = new CPStringBuilder(value.substring(0, i));
- }
- buf.append("\\n");
- }
- else if (c == '\r')
- {
- if (buf == null)
- {
- buf = new CPStringBuilder(value.substring(0, i));
- }
- buf.append("\\r");
- }
- else if (buf != null)
- {
- buf.append(c);
- }
- }
- return (buf != null) ? buf.toString() : value;
- }
- String nodeTypeToString(short nodeType)
- {
- switch (nodeType)
- {
- case ELEMENT_NODE:
- return "ELEMENT_NODE";
- case ATTRIBUTE_NODE:
- return "ATTRIBUTE_NODE";
- case TEXT_NODE:
- return "TEXT_NODE";
- case CDATA_SECTION_NODE:
- return "CDATA_SECTION_NODE";
- case DOCUMENT_NODE:
- return "DOCUMENT_NODE";
- case DOCUMENT_TYPE_NODE:
- return "DOCUMENT_TYPE_NODE";
- case COMMENT_NODE:
- return "COMMENT_NODE";
- case PROCESSING_INSTRUCTION_NODE:
- return "PROCESSING_INSTRUCTION_NODE";
- case DOCUMENT_FRAGMENT_NODE:
- return "DOCUMENT_FRAGMENT_NODE";
- case ENTITY_NODE:
- return "ENTITY_NODE";
- case ENTITY_REFERENCE_NODE:
- return "ENTITY_REFERENCE_NODE";
- case NOTATION_NODE:
- return "NOTATION_NODE";
- default:
- return "UNKNOWN";
- }
- }
- public void list(java.io.PrintStream out, int indent)
- {
- for (int i = 0; i < indent; i++)
- out.print(" ");
- out.println(toString());
- for (DomNode ctx = first; ctx != null; ctx = ctx.next)
- ctx.list(out, indent + 1);
- }
- }
|