1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 */
4/*
5 * Licensed to the Apache Software Foundation (ASF) under one or more
6 * contributor license agreements.  See the NOTICE file distributed with
7 * this work for additional information regarding copyright ownership.
8 * The ASF licenses this file to You under the Apache License, Version 2.0
9 * (the "License"); you may not use this file except in compliance with
10 * the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package com.sun.org.apache.xerces.internal.dom;
22
23import java.io.IOException;
24import java.io.ObjectInputStream;
25import java.io.ObjectOutputStream;
26import java.io.ObjectStreamField;
27import java.util.HashMap;
28import java.util.Hashtable;
29import java.util.Map;
30import org.w3c.dom.DOMException;
31import org.w3c.dom.DocumentType;
32import org.w3c.dom.NamedNodeMap;
33import org.w3c.dom.Node;
34import org.w3c.dom.UserDataHandler;
35
36/**
37 * This class represents a Document Type <em>declaraction</em> in
38 * the document itself, <em>not</em> a Document Type Definition (DTD).
39 * An XML document may (or may not) have such a reference.
40 * <P>
41 * DocumentType is an Extended DOM feature, used in XML documents but
42 * not in HTML.
43 * <P>
44 * Note that Entities and Notations are no longer children of the
45 * DocumentType, but are parentless nodes hung only in their
46 * appropriate NamedNodeMaps.
47 * <P>
48 * This area is UNDERSPECIFIED IN REC-DOM-Level-1-19981001
49 * Most notably, absolutely no provision was made for storing
50 * and using Element and Attribute information. Nor was the linkage
51 * between Entities and Entity References nailed down solidly.
52 *
53 * @xerces.internal
54 *
55 * @author Arnaud  Le Hors, IBM
56 * @author Joe Kesselman, IBM
57 * @author Andy Clark, IBM
58 * @since  PR-DOM-Level-1-19980818.
59 */
60public class DocumentTypeImpl
61    extends ParentNode
62    implements DocumentType {
63
64    //
65    // Constants
66    //
67
68    /** Serialization version. */
69    static final long serialVersionUID = 7751299192316526485L;
70
71    //
72    // Data
73    //
74
75    /** Document type name. */
76    protected String name;
77
78    /** Entities. */
79    protected NamedNodeMapImpl entities;
80
81    /** Notations. */
82    protected NamedNodeMapImpl notations;
83
84    // NON-DOM
85
86    /** Elements. */
87    protected NamedNodeMapImpl elements;
88
89    // DOM2: support public ID.
90    protected String publicID;
91
92    // DOM2: support system ID.
93    protected String systemID;
94
95    // DOM2: support internal subset.
96    protected String internalSubset;
97
98    /** The following are required for compareDocumentPosition
99    */
100    // Doctype number.   Doc types which have no owner may be assigned
101    // a number, on demand, for ordering purposes for compareDocumentPosition
102    private int doctypeNumber=0;
103
104    private Map<String, UserDataRecord> userData =  null;
105
106
107    /**
108     * @serialField name String document type name
109     * @serialField entities NamedNodeMapImpl entities
110     * @serialField notations NamedNodeMapImpl notations
111     * @serialField elements NamedNodeMapImpl elements
112     * @serialField publicID String support public ID
113     * @serialField systemID String support system ID
114     * @serialField internalSubset String support internal subset
115     * @serialField doctypeNumber int Doctype number
116     * @serialField userData Hashtable user data
117     */
118    private static final ObjectStreamField[] serialPersistentFields =
119        new ObjectStreamField[] {
120            new ObjectStreamField("name", String.class),
121            new ObjectStreamField("entities", NamedNodeMapImpl.class),
122            new ObjectStreamField("notations", NamedNodeMapImpl.class),
123            new ObjectStreamField("elements", NamedNodeMapImpl.class),
124            new ObjectStreamField("publicID", String.class),
125            new ObjectStreamField("systemID", String.class),
126            new ObjectStreamField("internalSubset", String.class),
127            new ObjectStreamField("doctypeNumber", int.class),
128            new ObjectStreamField("userData", Hashtable.class),
129        };
130
131    //
132    // Constructors
133    //
134
135    /** Factory method for creating a document type node. */
136    public DocumentTypeImpl(CoreDocumentImpl ownerDocument, String name) {
137        super(ownerDocument);
138
139        this.name = name;
140        // DOM
141        entities  = new NamedNodeMapImpl(this);
142        notations = new NamedNodeMapImpl(this);
143
144        // NON-DOM
145        elements = new NamedNodeMapImpl(this);
146
147    } // <init>(CoreDocumentImpl,String)
148
149    /** Factory method for creating a document type node. */
150    public DocumentTypeImpl(CoreDocumentImpl ownerDocument,
151                            String qualifiedName,
152                            String publicID, String systemID) {
153        this(ownerDocument, qualifiedName);
154        this.publicID = publicID;
155        this.systemID = systemID;
156
157    } // <init>(CoreDocumentImpl,String)
158
159    //
160    // DOM2: methods.
161    //
162
163    /**
164     * Introduced in DOM Level 2. <p>
165     *
166     * Return the public identifier of this Document type.
167     * @since WD-DOM-Level-2-19990923
168     */
169    public String getPublicId() {
170        if (needsSyncData()) {
171            synchronizeData();
172        }
173        return publicID;
174    }
175    /**
176     * Introduced in DOM Level 2. <p>
177     *
178     * Return the system identifier of this Document type.
179     * @since WD-DOM-Level-2-19990923
180     */
181    public String getSystemId() {
182        if (needsSyncData()) {
183            synchronizeData();
184        }
185        return systemID;
186    }
187
188    /**
189     * NON-DOM. <p>
190     *
191     * Set the internalSubset given as a string.
192     */
193    public void setInternalSubset(String internalSubset) {
194        if (needsSyncData()) {
195            synchronizeData();
196        }
197        this.internalSubset = internalSubset;
198    }
199
200    /**
201     * Introduced in DOM Level 2. <p>
202     *
203     * Return the internalSubset given as a string.
204     * @since WD-DOM-Level-2-19990923
205     */
206    public String getInternalSubset() {
207        if (needsSyncData()) {
208            synchronizeData();
209        }
210        return internalSubset;
211    }
212
213    //
214    // Node methods
215    //
216
217    /**
218     * A short integer indicating what type of node this is. The named
219     * constants for this value are defined in the org.w3c.dom.Node interface.
220     */
221    public short getNodeType() {
222        return Node.DOCUMENT_TYPE_NODE;
223    }
224
225    /**
226     * Returns the document type name
227     */
228    public String getNodeName() {
229        if (needsSyncData()) {
230            synchronizeData();
231        }
232        return name;
233    }
234
235    /** Clones the node. */
236    public Node cloneNode(boolean deep) {
237
238        DocumentTypeImpl newnode = (DocumentTypeImpl)super.cloneNode(deep);
239        // NamedNodeMaps must be cloned explicitly, to avoid sharing them.
240        newnode.entities  = entities.cloneMap(newnode);
241        newnode.notations = notations.cloneMap(newnode);
242        newnode.elements  = elements.cloneMap(newnode);
243
244        return newnode;
245
246    } // cloneNode(boolean):Node
247
248    /*
249     * Get Node text content
250     * @since DOM Level 3
251     */
252    public String getTextContent() throws DOMException {
253        return null;
254    }
255
256    /*
257     * Set Node text content
258     * @since DOM Level 3
259     */
260    public void setTextContent(String textContent)
261        throws DOMException {
262        // no-op
263    }
264
265        /**
266          * DOM Level 3 WD- Experimental.
267          * Override inherited behavior from ParentNodeImpl to support deep equal.
268          */
269    public boolean isEqualNode(Node arg) {
270
271        if (!super.isEqualNode(arg)) {
272            return false;
273        }
274
275        if (needsSyncData()) {
276            synchronizeData();
277        }
278        DocumentTypeImpl argDocType = (DocumentTypeImpl) arg;
279
280        //test if the following string attributes are equal: publicId,
281        //systemId, internalSubset.
282        if ((getPublicId() == null && argDocType.getPublicId() != null)
283            || (getPublicId() != null && argDocType.getPublicId() == null)
284            || (getSystemId() == null && argDocType.getSystemId() != null)
285            || (getSystemId() != null && argDocType.getSystemId() == null)
286            || (getInternalSubset() == null
287                && argDocType.getInternalSubset() != null)
288            || (getInternalSubset() != null
289                && argDocType.getInternalSubset() == null)) {
290            return false;
291        }
292
293        if (getPublicId() != null) {
294            if (!getPublicId().equals(argDocType.getPublicId())) {
295                return false;
296            }
297        }
298
299        if (getSystemId() != null) {
300            if (!getSystemId().equals(argDocType.getSystemId())) {
301                return false;
302            }
303        }
304
305        if (getInternalSubset() != null) {
306            if (!getInternalSubset().equals(argDocType.getInternalSubset())) {
307                return false;
308            }
309        }
310
311        //test if NamedNodeMaps entities and notations are equal
312        NamedNodeMapImpl argEntities = argDocType.entities;
313
314        if ((entities == null && argEntities != null)
315            || (entities != null && argEntities == null))
316            return false;
317
318        if (entities != null && argEntities != null) {
319            if (entities.getLength() != argEntities.getLength())
320                return false;
321
322            for (int index = 0; entities.item(index) != null; index++) {
323                Node entNode1 = entities.item(index);
324                Node entNode2 =
325                    argEntities.getNamedItem(entNode1.getNodeName());
326
327                if (!((NodeImpl) entNode1).isEqualNode((NodeImpl) entNode2))
328                    return false;
329            }
330        }
331
332        NamedNodeMapImpl argNotations = argDocType.notations;
333
334        if ((notations == null && argNotations != null)
335            || (notations != null && argNotations == null))
336            return false;
337
338        if (notations != null && argNotations != null) {
339            if (notations.getLength() != argNotations.getLength())
340                return false;
341
342            for (int index = 0; notations.item(index) != null; index++) {
343                Node noteNode1 = notations.item(index);
344                Node noteNode2 =
345                    argNotations.getNamedItem(noteNode1.getNodeName());
346
347                if (!((NodeImpl) noteNode1).isEqualNode((NodeImpl) noteNode2))
348                    return false;
349            }
350        }
351
352        return true;
353    } //end isEqualNode
354
355
356    /**
357     * NON-DOM
358     * set the ownerDocument of this node and its children
359     */
360    void setOwnerDocument(CoreDocumentImpl doc) {
361        super.setOwnerDocument(doc);
362        entities.setOwnerDocument(doc);
363        notations.setOwnerDocument(doc);
364        elements.setOwnerDocument(doc);
365    }
366
367    /** NON-DOM
368        Get the number associated with this doctype.
369    */
370    protected int getNodeNumber() {
371         // If the doctype has a document owner, get the node number
372         // relative to the owner doc
373         if (getOwnerDocument()!=null)
374            return super.getNodeNumber();
375
376         // The doctype is disconnected and not associated with any document.
377         // Assign the doctype a number relative to the implementation.
378         if (doctypeNumber==0) {
379
380            CoreDOMImplementationImpl cd = (CoreDOMImplementationImpl)CoreDOMImplementationImpl.getDOMImplementation();
381            doctypeNumber = cd.assignDocTypeNumber();
382         }
383         return doctypeNumber;
384    }
385
386    //
387    // DocumentType methods
388    //
389
390    /**
391     * Name of this document type. If we loaded from a DTD, this should
392     * be the name immediately following the DOCTYPE keyword.
393     */
394    public String getName() {
395
396        if (needsSyncData()) {
397            synchronizeData();
398        }
399        return name;
400
401    } // getName():String
402
403    /**
404     * Access the collection of general Entities, both external and
405     * internal, defined in the DTD. For example, in:
406     * <p>
407     * <pre>
408     *   &lt;!doctype example SYSTEM "ex.dtd" [
409     *     &lt;!ENTITY foo "foo"&gt;
410     *     &lt;!ENTITY bar "bar"&gt;
411     *     &lt;!ENTITY % baz "baz"&gt;
412     *     ]&gt;
413     * </pre>
414     * <p>
415     * The Entities map includes foo and bar, but not baz. It is promised that
416     * only Nodes which are Entities will exist in this NamedNodeMap.
417     * <p>
418     * For HTML, this will always be null.
419     * <p>
420     * Note that "built in" entities such as &amp; and &lt; should be
421     * converted to their actual characters before being placed in the DOM's
422     * contained text, and should be converted back when the DOM is rendered
423     * as XML or HTML, and hence DO NOT appear here.
424     */
425    public NamedNodeMap getEntities() {
426        if (needsSyncChildren()) {
427            synchronizeChildren();
428            }
429        return entities;
430    }
431
432    /**
433     * Access the collection of Notations defined in the DTD.  A
434     * notation declares, by name, the format of an XML unparsed entity
435     * or is used to formally declare a Processing Instruction target.
436     */
437    public NamedNodeMap getNotations() {
438        if (needsSyncChildren()) {
439            synchronizeChildren();
440            }
441        return notations;
442    }
443
444    //
445    // Public methods
446    //
447
448    /**
449     * NON-DOM: Subclassed to flip the entities' and notations' readonly switch
450     * as well.
451     * @see NodeImpl#setReadOnly
452     */
453    public void setReadOnly(boolean readOnly, boolean deep) {
454
455        if (needsSyncChildren()) {
456            synchronizeChildren();
457        }
458        super.setReadOnly(readOnly, deep);
459
460        // set read-only property
461        elements.setReadOnly(readOnly, true);
462        entities.setReadOnly(readOnly, true);
463        notations.setReadOnly(readOnly, true);
464
465    } // setReadOnly(boolean,boolean)
466
467    /**
468     * NON-DOM: Access the collection of ElementDefinitions.
469     * @see ElementDefinitionImpl
470     */
471    public NamedNodeMap getElements() {
472        if (needsSyncChildren()) {
473            synchronizeChildren();
474        }
475        return elements;
476    }
477
478    public Object setUserData(String key,
479    Object data, UserDataHandler handler) {
480        if(userData == null)
481            userData = new HashMap<>();
482        if (data == null) {
483            if (userData != null) {
484                UserDataRecord udr = userData.remove(key);
485                if (udr != null) {
486                    return udr.fData;
487                }
488            }
489            return null;
490        }
491        else {
492            UserDataRecord udr = userData.put(key, new UserDataRecord(data, handler));
493            if (udr != null) {
494                return udr.fData;
495            }
496        }
497        return null;
498    }
499
500    public Object getUserData(String key) {
501        if (userData == null) {
502            return null;
503        }
504        UserDataRecord udr = userData.get(key);
505        if (udr != null) {
506            return udr.fData;
507        }
508        return null;
509    }
510
511    @Override
512    protected Map<String, UserDataRecord> getUserDataRecord(){
513        return userData;
514    }
515
516    /**
517     * @serialData Serialized fields. Convert Map to Hashtable for backward
518     * compatibility.
519     */
520    private void writeObject(ObjectOutputStream out) throws IOException {
521        // Convert the HashMap to Hashtable
522        Hashtable<String, UserDataRecord> ud = (userData == null)? null : new Hashtable<>(userData);
523
524        // Write serialized fields
525        ObjectOutputStream.PutField pf = out.putFields();
526        pf.put("name", name);
527        pf.put("entities", entities);
528        pf.put("notations", notations);
529        pf.put("elements", elements);
530        pf.put("publicID", publicID);
531        pf.put("systemID", systemID);
532        pf.put("internalSubset", internalSubset);
533        pf.put("doctypeNumber", doctypeNumber);
534        pf.put("userData", ud);
535        out.writeFields();
536    }
537
538    @SuppressWarnings("unchecked")
539    private void readObject(ObjectInputStream in)
540                        throws IOException, ClassNotFoundException {
541        // We have to read serialized fields first.
542        ObjectInputStream.GetField gf = in.readFields();
543        name = (String)gf.get("name", null);
544        entities = (NamedNodeMapImpl)gf.get("entities", null);
545        notations = (NamedNodeMapImpl)gf.get("notations", null);
546        elements = (NamedNodeMapImpl)gf.get("elements", null);
547        publicID = (String)gf.get("publicID", null);
548        systemID = (String)gf.get("systemID", null);
549        internalSubset = (String)gf.get("internalSubset", null);
550        doctypeNumber = gf.get("doctypeNumber", 0);
551
552        Hashtable<String, UserDataRecord> ud =
553                (Hashtable<String, UserDataRecord>)gf.get("userData", null);
554
555        //convert the Hashtable back to HashMap
556        if (ud != null) userData = new HashMap<>(ud);
557    }
558} // class DocumentTypeImpl
559