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