1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
4  */
5 /*
6  * Licensed to the Apache Software Foundation (ASF) under one or more
7  * contributor license agreements.  See the NOTICE file distributed with
8  * this work for additional information regarding copyright ownership.
9  * The ASF licenses this file to You under the Apache License, Version 2.0
10  * (the "License"); you may not use this file except in compliance with
11  * the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 
22 package com.sun.org.apache.xerces.internal.dom;
23 
24 import org.w3c.dom.DOMException;
25 import org.w3c.dom.Node;
26 import org.w3c.dom.NodeList;
27 
28 /**
29  * CharacterData is an abstract Node that can carry character data as its
30  * Value.  It provides shared behavior for Text, CData, and
31  * possibly other node types. All offsets are 0-based.
32  * <p>
33  * Since ProcessingInstructionImpl inherits from this class to reuse the
34  * setNodeValue method, this class isn't declared as implementing the interface
35  * CharacterData. This is done by relevant subclasses (TexImpl, CommentImpl).
36  * <p>
37  * This class doesn't directly support mutation events, however, it notifies
38  * the document when mutations are performed so that the document class do so.
39  *
40  * @xerces.internal
41  *
42  * @since  PR-DOM-Level-1-19980818.
43  */
44 public abstract class CharacterDataImpl
45     extends ChildNode {
46 
47     //
48     // Constants
49     //
50 
51     /** Serialization version. */
52     static final long serialVersionUID = 7931170150428474230L;
53 
54     //
55     // Data
56     //
57 
58     protected String data;
59 
60     /** Empty child nodes. */
61     private static transient NodeList singletonNodeList = new NodeList() {
62         public Node item(int index) { return null; }
63         public int getLength() { return 0; }
64     };
65 
66     //
67     // Constructors
68     //
69 
CharacterDataImpl()70     public CharacterDataImpl(){}
71 
72     /** Factory constructor. */
CharacterDataImpl(CoreDocumentImpl ownerDocument, String data)73     protected CharacterDataImpl(CoreDocumentImpl ownerDocument, String data) {
74         super(ownerDocument);
75         this.data = data;
76     }
77 
78     //
79     // Node methods
80     //
81 
82     /** Returns an empty node list. */
getChildNodes()83     public NodeList getChildNodes() {
84         return singletonNodeList;
85     }
86 
87     /*
88      * returns the content of this node
89      */
getNodeValue()90     public String getNodeValue() {
91         if (needsSyncData()) {
92             synchronizeData();
93         }
94         return data;
95     }
96 
97    /** Convenience wrapper for calling setNodeValueInternal when
98      * we are not performing a replacement operation
99      */
setNodeValueInternal(String value)100     protected void setNodeValueInternal (String value) {
101         setNodeValueInternal(value, false);
102     }
103 
104     /** This function added so that we can distinguish whether
105      *  setNodeValue has been called from some other DOM functions.
106      *  or by the client.<p>
107      *  This is important, because we do one type of Range fix-up,
108      *  from the high-level functions in CharacterData, and another
109      *  type if the client simply calls setNodeValue(value).
110      */
setNodeValueInternal(String value, boolean replace)111     protected void setNodeValueInternal(String value, boolean replace) {
112 
113         CoreDocumentImpl ownerDocument = ownerDocument();
114 
115         if (ownerDocument.errorChecking && isReadOnly()) {
116             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
117             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
118         }
119 
120         // revisit: may want to set the value in ownerDocument.
121         // Default behavior, overridden in some subclasses
122         if (needsSyncData()) {
123             synchronizeData();
124         }
125 
126         // keep old value for document notification
127         String oldvalue = this.data;
128 
129         // notify document
130         ownerDocument.modifyingCharacterData(this, replace);
131 
132         this.data = value;
133 
134         // notify document
135         ownerDocument.modifiedCharacterData(this, oldvalue, value, replace);
136     }
137 
138     /**
139      * Sets the content, possibly firing related events,
140      * and updating ranges (via notification to the document)
141      */
setNodeValue(String value)142     public void setNodeValue(String value) {
143 
144         setNodeValueInternal(value);
145 
146         // notify document
147         ownerDocument().replacedText(this);
148     }
149 
150     //
151     // CharacterData methods
152     //
153 
154     /**
155      * Retrieve character data currently stored in this node.
156      *
157      * @throws DOMExcpetion(DOMSTRING_SIZE_ERR) In some implementations,
158      * the stored data may exceed the permitted length of strings. If so,
159      * getData() will throw this DOMException advising the user to
160      * instead retrieve the data in chunks via the substring() operation.
161      */
getData()162     public String getData() {
163         if (needsSyncData()) {
164             synchronizeData();
165         }
166         return data;
167     }
168 
169     /**
170      * Report number of characters currently stored in this node's
171      * data. It may be 0, meaning that the value is an empty string.
172      */
getLength()173     public int getLength() {
174         if (needsSyncData()) {
175             synchronizeData();
176         }
177         return data.length();
178     }
179 
180     /**
181      * Concatenate additional characters onto the end of the data
182      * stored in this node. Note that this, and insert(), are the paths
183      * by which a DOM could wind up accumulating more data than the
184      * language's strings can easily handle. (See above discussion.)
185      *
186      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
187      */
appendData(String data)188     public void appendData(String data) {
189 
190         if (isReadOnly()) {
191             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
192             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
193         }
194         if (data == null) {
195             return;
196         }
197         if (needsSyncData()) {
198             synchronizeData();
199         }
200 
201         setNodeValue(this.data + data);
202 
203     } // appendData(String)
204 
205     /**
206      * Remove a range of characters from the node's value. Throws a
207      * DOMException if the offset is beyond the end of the
208      * string. However, a deletion _count_ that exceeds the available
209      * data is accepted as a delete-to-end request.
210      *
211      * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
212      * greater than length, or if count is negative.
213      *
214      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
215      * readonly.
216      */
deleteData(int offset, int count)217     public void deleteData(int offset, int count)
218         throws DOMException {
219 
220         internalDeleteData(offset, count, false);
221     } // deleteData(int,int)
222 
223 
224     /** NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
225      * to control which mutation events are spawned. This version of the
226      * deleteData operation allows us to do so. It is not intended
227      * for use by application programs.
228      */
internalDeleteData(int offset, int count, boolean replace)229     void internalDeleteData (int offset, int count, boolean replace)
230     throws DOMException {
231 
232         CoreDocumentImpl ownerDocument = ownerDocument();
233         if (ownerDocument.errorChecking) {
234             if (isReadOnly()) {
235                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
236                 throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
237             }
238 
239             if (count < 0) {
240                 String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
241                 throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
242             }
243         }
244 
245         if (needsSyncData()) {
246             synchronizeData();
247         }
248         int tailLength = Math.max(data.length() - count - offset, 0);
249         try {
250             String value = data.substring(0, offset) +
251             (tailLength > 0 ? data.substring(offset + count, offset + count + tailLength) : "");
252 
253             setNodeValueInternal(value, replace);
254 
255             // notify document
256             ownerDocument.deletedText(this, offset, count);
257         }
258         catch (StringIndexOutOfBoundsException e) {
259             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
260             throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
261         }
262 
263     } // internalDeleteData(int,int,boolean)
264 
265     /**
266      * Insert additional characters into the data stored in this node,
267      * at the offset specified.
268      *
269      * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
270      * greater than length.
271      *
272      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
273      */
insertData(int offset, String data)274     public void insertData(int offset, String data)
275         throws DOMException {
276 
277         internalInsertData(offset, data, false);
278 
279     } // insertData(int,int)
280 
281 
282 
283     /** NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
284      * to control which mutation events are spawned. This version of the
285      * insertData operation allows us to do so. It is not intended
286      * for use by application programs.
287      */
internalInsertData(int offset, String data, boolean replace)288     void internalInsertData (int offset, String data, boolean replace)
289     throws DOMException {
290 
291         CoreDocumentImpl ownerDocument = ownerDocument();
292 
293         if (ownerDocument.errorChecking && isReadOnly()) {
294             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
295             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
296         }
297 
298         if (needsSyncData()) {
299             synchronizeData();
300         }
301         try {
302             String value =
303                 new StringBuffer(this.data).insert(offset, data).toString();
304 
305 
306             setNodeValueInternal(value, replace);
307 
308             // notify document
309             ownerDocument.insertedText(this, offset, data.length());
310         }
311         catch (StringIndexOutOfBoundsException e) {
312             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
313             throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
314         }
315 
316     } // internalInsertData(int,String,boolean)
317 
318 
319 
320     /**
321      * Replace a series of characters at the specified (zero-based)
322      * offset with a new string, NOT necessarily of the same
323      * length. Convenience method, equivalent to a delete followed by an
324      * insert. Throws a DOMException if the specified offset is beyond
325      * the end of the existing data.
326      *
327      * @param offset       The offset at which to begin replacing.
328      *
329      * @param count        The number of characters to remove,
330      * interpreted as in the delete() method.
331      *
332      * @param data         The new string to be inserted at offset in place of
333      * the removed data. Note that the entire string will
334      * be inserted -- the count parameter does not affect
335      * insertion, and the new data may be longer or shorter
336      * than the substring it replaces.
337      *
338      * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
339      * greater than length, or if count is negative.
340      *
341      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
342      * readonly.
343      */
replaceData(int offset, int count, String data)344     public void replaceData(int offset, int count, String data)
345     throws DOMException {
346 
347         CoreDocumentImpl ownerDocument = ownerDocument();
348 
349         // The read-only check is done by deleteData()
350         // ***** This could be more efficient w/r/t Mutation Events,
351         // specifically by aggregating DOMAttrModified and
352         // DOMSubtreeModified. But mutation events are
353         // underspecified; I don't feel compelled
354         // to deal with it right now.
355         if (ownerDocument.errorChecking && isReadOnly()) {
356             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
357             throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
358         }
359 
360         if (needsSyncData()) {
361             synchronizeData();
362         }
363 
364         //notify document
365         ownerDocument.replacingData(this);
366 
367         // keep old value for document notification
368         String oldvalue = this.data;
369 
370         internalDeleteData(offset, count, true);
371         internalInsertData(offset, data, true);
372 
373         ownerDocument.replacedCharacterData(this, oldvalue, this.data);
374 
375     } // replaceData(int,int,String)
376 
377     /**
378      * Store character data into this node.
379      *
380      * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
381      */
setData(String value)382     public void setData(String value)
383         throws DOMException {
384         setNodeValue(value);
385     }
386 
387     /**
388      * Substring is more than a convenience function. In some
389      * implementations of the DOM, where the stored data may exceed the
390      * length that can be returned in a single string, the only way to
391      * read it all is to extract it in chunks via this method.
392      *
393      * @param offset        Zero-based offset of first character to retrieve.
394      * @param count Number of characters to retrieve.
395      *
396      * If the sum of offset and count exceeds the length, all characters
397      * to end of data are returned.
398      *
399      * @throws DOMException(INDEX_SIZE_ERR) if offset is negative or
400      * greater than length, or if count is negative.
401      *
402      * @throws DOMException(WSTRING_SIZE_ERR) In some implementations,
403      * count may exceed the permitted length of strings. If so,
404      * substring() will throw this DOMException advising the user to
405      * instead retrieve the data in smaller chunks.
406      */
substringData(int offset, int count)407     public String substringData(int offset, int count)
408         throws DOMException {
409 
410         if (needsSyncData()) {
411             synchronizeData();
412         }
413 
414         int length = data.length();
415         if (count < 0 || offset < 0 || offset > length - 1) {
416             String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
417             throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
418         }
419 
420         int tailIndex = Math.min(offset + count, length);
421 
422         return data.substring(offset, tailIndex);
423 
424     } // substringData(int,int):String
425 
426 } // class CharacterDataImpl
427