103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar/*
203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * Copyright (C) 2007 The Android Open Source Project
303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar *
403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * Licensed under the Apache License, Version 2.0 (the "License");
503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * you may not use this file except in compliance with the License.
603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * You may obtain a copy of the License at
703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar *
803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar *      http://www.apache.org/licenses/LICENSE-2.0
903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar *
1003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * Unless required by applicable law or agreed to in writing, software
1103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * distributed under the License is distributed on an "AS IS" BASIS,
1203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * See the License for the specific language governing permissions and
1403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * limitations under the License.
1503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar */
1603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
1703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarpackage org.apache.harmony.xml.dom;
1803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
1903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport java.util.Collections;
2003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport java.util.HashMap;
2103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport java.util.Map;
2203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport java.util.WeakHashMap;
2303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.CharacterData;
2403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.Comment;
2503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.DOMConfiguration;
2603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.DOMException;
2703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.DOMImplementation;
2803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.Document;
2903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.DocumentType;
3003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.Element;
3103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.NamedNodeMap;
3203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.Node;
3303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.NodeList;
3403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.ProcessingInstruction;
3503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.Text;
3603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarimport org.w3c.dom.UserDataHandler;
3703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
3803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar/**
3903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * Provides a straightforward implementation of the corresponding W3C DOM
4003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * interface. The class is used internally only, thus only notable members that
4103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * are not in the original interface are documented (the W3C docs are quite
4203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * extensive). Hope that's ok.
4303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * <p>
4403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * Some of the fields may have package visibility, so other classes belonging to
4503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * the DOM implementation can easily access them while maintaining the DOM tree
4603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar * structure.
4703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar */
4803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnarpublic final class DocumentImpl extends InnerNodeImpl implements Document {
4903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
5003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private DOMImplementation domImplementation;
5103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private DOMConfigurationImpl domConfiguration;
5203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
5303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    /*
5403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * The default values of these fields are specified by the Document
5503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * interface.
5603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     */
5703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private String documentUri;
5803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private String inputEncoding;
5903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private String xmlEncoding;
6003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private String xmlVersion = "1.0";
6103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private boolean xmlStandalone = false;
62295659002ae490481798a16e39b5f88e80a8151eBen Murdoch    private boolean strictErrorChecking = true;
6303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
6403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    /**
6503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * A lazily initialized map of user data values for this document's own
6603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * nodes. The map is weak because the document may live longer than its
6703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * nodes.
6803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     *
6903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * <p>Attaching user data directly to the corresponding node would cost a
7003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * field per node. Under the assumption that user data is rarely needed, we
7103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * attach user data to the document to save those fields. Xerces also takes
7203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     * this approach.
7303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar     */
7403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    private WeakHashMap<NodeImpl, Map<String, UserData>> nodeToUserData;
7503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
7603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    public DocumentImpl(DOMImplementationImpl impl, String namespaceURI,
7703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar            String qualifiedName, DocumentType doctype, String inputEncoding) {
7803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        super(null);
7903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        this.document = this;
8003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        this.domImplementation = impl;
8103c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        this.inputEncoding = inputEncoding;
8203c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
8303c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        if (doctype != null) {
8403c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar            appendChild(doctype);
8503c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        }
8603c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar
8703c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        if (qualifiedName != null) {
8803c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar            appendChild(createElementNS(namespaceURI, qualifiedName));
8903c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar        }
9003c25794b66b0d01e0e850042713f8009c787dc2Lajos Molnar    }
91
92    private static boolean isXMLIdentifierStart(char c) {
93        return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_');
94    }
95
96    private static boolean isXMLIdentifierPart(char c) {
97        return isXMLIdentifierStart(c) || (c >= '0' && c <= '9') || (c == '-') || (c == '.');
98    }
99
100    static boolean isXMLIdentifier(String s) {
101        if (s.length() == 0) {
102            return false;
103        }
104
105        if (!isXMLIdentifierStart(s.charAt(0))) {
106            return false;
107        }
108
109        for (int i = 1; i < s.length(); i++) {
110            if (!isXMLIdentifierPart(s.charAt(i))) {
111                return false;
112            }
113        }
114
115        return true;
116    }
117
118    /**
119     * Returns a shallow copy of the given node. If the node is an element node,
120     * its attributes are always copied.
121     *
122     * @param node a node belonging to any document or DOM implementation.
123     * @param operation the operation type to use when notifying user data
124     *     handlers of copied element attributes. It is the caller's
125     *     responsibility to notify user data handlers of the returned node.
126     * @return a new node whose document is this document and whose DOM
127     *     implementation is this DOM implementation.
128     */
129    private NodeImpl shallowCopy(short operation, Node node) {
130        switch (node.getNodeType()) {
131        case Node.ATTRIBUTE_NODE:
132            AttrImpl attr = (AttrImpl) node;
133            AttrImpl attrCopy;
134            if (attr.namespaceAware) {
135                attrCopy = createAttributeNS(attr.getNamespaceURI(), attr.getLocalName());
136                attrCopy.setPrefix(attr.getPrefix());
137            } else {
138                attrCopy = createAttribute(attr.getName());
139            }
140            attrCopy.setNodeValue(attr.getValue());
141            return attrCopy;
142
143        case Node.CDATA_SECTION_NODE:
144            return createCDATASection(((CharacterData) node).getData());
145
146        case Node.COMMENT_NODE:
147            return createComment(((Comment) node).getData());
148
149        case Node.DOCUMENT_FRAGMENT_NODE:
150            return createDocumentFragment();
151
152        case Node.DOCUMENT_NODE:
153        case Node.DOCUMENT_TYPE_NODE:
154            throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
155                    "Cannot copy node of type " + node.getNodeType());
156
157        case Node.ELEMENT_NODE:
158            ElementImpl element = (ElementImpl) node;
159            ElementImpl elementCopy;
160            if (element.namespaceAware) {
161                elementCopy = createElementNS(element.getNamespaceURI(), element.getLocalName());
162                elementCopy.setPrefix(element.getPrefix());
163            } else {
164                elementCopy = createElement(element.getTagName());
165            }
166
167            NamedNodeMap attributes = element.getAttributes();
168            for (int i = 0; i < attributes.getLength(); i++) {
169                AttrImpl elementAttr = (AttrImpl) attributes.item(i);
170                AttrImpl elementAttrCopy = (AttrImpl) shallowCopy(operation, elementAttr);
171                notifyUserDataHandlers(operation, elementAttr, elementAttrCopy);
172                if (elementAttr.namespaceAware) {
173                    elementCopy.setAttributeNodeNS(elementAttrCopy);
174                } else {
175                    elementCopy.setAttributeNode(elementAttrCopy);
176                }
177            }
178            return elementCopy;
179
180        case Node.ENTITY_NODE:
181        case Node.NOTATION_NODE:
182            // TODO: implement this when we support these node types
183            throw new UnsupportedOperationException();
184
185        case Node.ENTITY_REFERENCE_NODE:
186            /*
187             * When we support entities in the doctype, this will need to
188             * behave differently for clones vs. imports. Clones copy
189             * entities by value, copying the referenced subtree from the
190             * original document. Imports copy entities by reference,
191             * possibly referring to a different subtree in the new
192             * document.
193             */
194            return createEntityReference(node.getNodeName());
195
196        case Node.PROCESSING_INSTRUCTION_NODE:
197            ProcessingInstruction pi = (ProcessingInstruction) node;
198            return createProcessingInstruction(pi.getTarget(), pi.getData());
199
200        case Node.TEXT_NODE:
201            return createTextNode(((Text) node).getData());
202
203        default:
204            throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
205                    "Unsupported node type " + node.getNodeType());
206        }
207    }
208
209    /**
210     * Returns a copy of the given node or subtree with this document as its
211     * owner.
212     *
213     * @param operation either {@link UserDataHandler#NODE_CLONED} or
214     *      {@link UserDataHandler#NODE_IMPORTED}.
215     * @param node a node belonging to any document or DOM implementation.
216     * @param deep true to recursively copy any child nodes; false to do no such
217     *      copying and return a node with no children.
218     */
219    Node cloneOrImportNode(short operation, Node node, boolean deep) {
220        NodeImpl copy = shallowCopy(operation, node);
221
222        if (deep) {
223            NodeList list = node.getChildNodes();
224            for (int i = 0; i < list.getLength(); i++) {
225                copy.appendChild(cloneOrImportNode(operation, list.item(i), deep));
226            }
227        }
228
229        notifyUserDataHandlers(operation, node, copy);
230        return copy;
231    }
232
233    public Node importNode(Node importedNode, boolean deep) {
234        return cloneOrImportNode(UserDataHandler.NODE_IMPORTED, importedNode, deep);
235    }
236
237    /**
238     * Detaches the node from its parent (if any) and changes its document to
239     * this document. The node's subtree and attributes will remain attached,
240     * but their document will be changed to this document.
241     */
242    public Node adoptNode(Node node) {
243        if (!(node instanceof NodeImpl)) {
244            return null; // the API specifies this quiet failure
245        }
246        NodeImpl nodeImpl = (NodeImpl) node;
247        switch (nodeImpl.getNodeType()) {
248            case Node.ATTRIBUTE_NODE:
249                AttrImpl attr = (AttrImpl) node;
250                if (attr.ownerElement != null) {
251                    attr.ownerElement.removeAttributeNode(attr);
252                }
253                break;
254
255            case Node.DOCUMENT_FRAGMENT_NODE:
256            case Node.ENTITY_REFERENCE_NODE:
257            case Node.PROCESSING_INSTRUCTION_NODE:
258            case Node.TEXT_NODE:
259            case Node.CDATA_SECTION_NODE:
260            case Node.COMMENT_NODE:
261            case Node.ELEMENT_NODE:
262                break;
263
264            case Node.DOCUMENT_NODE:
265            case Node.DOCUMENT_TYPE_NODE:
266            case Node.ENTITY_NODE:
267            case Node.NOTATION_NODE:
268                throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
269                        "Cannot adopt nodes of type " + nodeImpl.getNodeType());
270
271            default:
272                throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
273                        "Unsupported node type " + node.getNodeType());
274        }
275
276        Node parent = nodeImpl.getParentNode();
277        if (parent != null) {
278            parent.removeChild(nodeImpl);
279        }
280
281        changeDocumentToThis(nodeImpl);
282        notifyUserDataHandlers(UserDataHandler.NODE_ADOPTED, node, null);
283        return nodeImpl;
284    }
285
286    /**
287     * Recursively change the document of {@code node} without also changing its
288     * parent node. Only adoptNode() should invoke this method, otherwise nodes
289     * will be left in an inconsistent state.
290     */
291    private void changeDocumentToThis(NodeImpl node) {
292        Map<String, UserData> userData = node.document.getUserDataMapForRead(node);
293        if (!userData.isEmpty()) {
294            getUserDataMap(node).putAll(userData);
295        }
296        node.document = this;
297
298        // change the document on all child nodes
299        NodeList list = node.getChildNodes();
300        for (int i = 0; i < list.getLength(); i++) {
301            changeDocumentToThis((NodeImpl) list.item(i));
302        }
303
304        // change the document on all attribute nodes
305        if (node.getNodeType() == Node.ELEMENT_NODE) {
306            NamedNodeMap attributes = node.getAttributes();
307            for (int i = 0; i < attributes.getLength(); i++) {
308                changeDocumentToThis((AttrImpl) attributes.item(i));
309            }
310        }
311    }
312
313    public Node renameNode(Node node, String namespaceURI, String qualifiedName) {
314        if (node.getOwnerDocument() != this) {
315            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
316        }
317
318        setNameNS((NodeImpl) node, namespaceURI, qualifiedName);
319        notifyUserDataHandlers(UserDataHandler.NODE_RENAMED, node, null);
320        return node;
321    }
322
323    public AttrImpl createAttribute(String name) {
324        return new AttrImpl(this, name);
325    }
326
327    public AttrImpl createAttributeNS(String namespaceURI, String qualifiedName) {
328        return new AttrImpl(this, namespaceURI, qualifiedName);
329    }
330
331    public CDATASectionImpl createCDATASection(String data) {
332        return new CDATASectionImpl(this, data);
333    }
334
335    public CommentImpl createComment(String data) {
336        return new CommentImpl(this, data);
337    }
338
339    public DocumentFragmentImpl createDocumentFragment() {
340        return new DocumentFragmentImpl(this);
341    }
342
343    public ElementImpl createElement(String tagName) {
344        return new ElementImpl(this, tagName);
345    }
346
347    public ElementImpl createElementNS(String namespaceURI, String qualifiedName) {
348        return new ElementImpl(this, namespaceURI, qualifiedName);
349    }
350
351    public EntityReferenceImpl createEntityReference(String name) {
352        return new EntityReferenceImpl(this, name);
353    }
354
355    public ProcessingInstructionImpl createProcessingInstruction(String target, String data) {
356        return new ProcessingInstructionImpl(this, target, data);
357    }
358
359    public TextImpl createTextNode(String data) {
360        return new TextImpl(this, data);
361    }
362
363    public DocumentType getDoctype() {
364        for (LeafNodeImpl child : children) {
365            if (child instanceof DocumentType) {
366                return (DocumentType) child;
367            }
368        }
369
370        return null;
371    }
372
373    public Element getDocumentElement() {
374        for (LeafNodeImpl child : children) {
375            if (child instanceof Element) {
376                return (Element) child;
377            }
378        }
379
380        return null;
381    }
382
383    public Element getElementById(String elementId) {
384        ElementImpl root = (ElementImpl) getDocumentElement();
385
386        return (root == null ? null : root.getElementById(elementId));
387    }
388
389    public NodeList getElementsByTagName(String name) {
390        NodeListImpl result = new NodeListImpl();
391        getElementsByTagName(result, name);
392        return result;
393    }
394
395    public NodeList getElementsByTagNameNS(String namespaceURI, String localName) {
396        NodeListImpl result = new NodeListImpl();
397        getElementsByTagNameNS(result, namespaceURI, localName);
398        return result;
399    }
400
401    public DOMImplementation getImplementation() {
402        return domImplementation;
403    }
404
405    @Override
406    public String getNodeName() {
407        return "#document";
408    }
409
410    @Override
411    public short getNodeType() {
412        return Node.DOCUMENT_NODE;
413    }
414
415    /**
416     * Document elements may have at most one root element and at most one DTD
417     * element.
418     */
419    @Override public Node insertChildAt(Node toInsert, int index) {
420        if (toInsert instanceof Element && getDocumentElement() != null) {
421            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
422                    "Only one root element allowed");
423        }
424        if (toInsert instanceof DocumentType && getDoctype() != null) {
425            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
426                    "Only one DOCTYPE element allowed");
427        }
428        return super.insertChildAt(toInsert, index);
429    }
430
431    @Override public String getTextContent() {
432        return null;
433    }
434
435    public String getInputEncoding() {
436        return inputEncoding;
437    }
438
439    public String getXmlEncoding() {
440        return xmlEncoding;
441    }
442
443    public boolean getXmlStandalone() {
444        return xmlStandalone;
445    }
446
447    public void setXmlStandalone(boolean xmlStandalone) {
448        this.xmlStandalone = xmlStandalone;
449    }
450
451    public String getXmlVersion() {
452        return xmlVersion;
453    }
454
455    public void setXmlVersion(String xmlVersion) {
456        this.xmlVersion = xmlVersion;
457    }
458
459    public boolean getStrictErrorChecking() {
460        return strictErrorChecking;
461    }
462
463    public void setStrictErrorChecking(boolean strictErrorChecking) {
464        this.strictErrorChecking = strictErrorChecking;
465    }
466
467    public String getDocumentURI() {
468        return documentUri;
469    }
470
471    public void setDocumentURI(String documentUri) {
472        this.documentUri = documentUri;
473    }
474
475    public DOMConfiguration getDomConfig() {
476        if (domConfiguration == null) {
477            domConfiguration = new DOMConfigurationImpl();
478        }
479        return domConfiguration;
480    }
481
482    public void normalizeDocument() {
483        Element root = getDocumentElement();
484        if (root == null) {
485            return;
486        }
487
488        ((DOMConfigurationImpl) getDomConfig()).normalize(root);
489    }
490
491    /**
492     * Returns a map with the user data objects attached to the specified node.
493     * This map is readable and writable.
494     */
495    Map<String, UserData> getUserDataMap(NodeImpl node) {
496        if (nodeToUserData == null) {
497            nodeToUserData = new WeakHashMap<NodeImpl, Map<String, UserData>>();
498        }
499        Map<String, UserData> userDataMap = nodeToUserData.get(node);
500        if (userDataMap == null) {
501            userDataMap = new HashMap<String, UserData>();
502            nodeToUserData.put(node, userDataMap);
503        }
504        return userDataMap;
505    }
506
507    /**
508     * Returns a map with the user data objects attached to the specified node.
509     * The returned map may be read-only.
510     */
511    Map<String, UserData> getUserDataMapForRead(NodeImpl node) {
512        if (nodeToUserData == null) {
513            return Collections.emptyMap();
514        }
515        Map<String, UserData> userDataMap = nodeToUserData.get(node);
516        return userDataMap == null
517                ? Collections.<String, UserData>emptyMap()
518                : userDataMap;
519    }
520
521    /**
522     * Calls {@link UserDataHandler#handle} on each of the source node's
523     * value/handler pairs.
524     *
525     * <p>If the source node comes from another DOM implementation, user data
526     * handlers will <strong>not</strong> be notified. The DOM API provides no
527     * mechanism to inspect a foreign node's user data.
528     */
529    private static void notifyUserDataHandlers(
530            short operation, Node source, NodeImpl destination) {
531        if (!(source instanceof NodeImpl)) {
532            return;
533        }
534
535        NodeImpl srcImpl = (NodeImpl) source;
536        if (srcImpl.document == null) {
537            return;
538        }
539
540        for (Map.Entry<String, UserData> entry
541                : srcImpl.document.getUserDataMapForRead(srcImpl).entrySet()) {
542            UserData userData = entry.getValue();
543            if (userData.handler != null) {
544                userData.handler.handle(
545                        operation, entry.getKey(), userData.value, source, destination);
546            }
547        }
548    }
549}
550