content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.dom;
  
  import mf.org.w3c.dom.DOMException;
  import mf.org.w3c.dom.Node;
  import mf.org.w3c.dom.NodeList;
  
  /**
   * CharacterData is an abstract Node that can carry character data as its
   * Value.  It provides shared behavior for Text, CData, and
   * possibly other node types. All offsets are 0-based.
   * <p/>
   * Since ProcessingInstructionImpl inherits from this class to reuse the
   * setNodeValue method, this class isn't declared as implementing the interface
   * CharacterData. This is done by relevant subclasses (TexImpl, CommentImpl).
   * <p/>
   * This class doesn't directly support mutation events, however, it notifies
   * the document when mutations are performed so that the document class do so.
   *
   * @version $Id: CharacterDataImpl.java 447266 2006-09-18 05:57:49Z mrglavas $
   * @xerces.internal
   * @since PR-DOM-Level-1-19980818.
   */
  public abstract class CharacterDataImpl
          extends ChildNode {
  
      //
      // Constants
      //
  
      /**
       * Serialization version.
       */
      static final long serialVersionUID = 7931170150428474230L;
  
      //
      // Data
      //
      /**
       * Empty child nodes.
       */
      private static transient NodeList singletonNodeList = new NodeList() {
          @Override
          public Node item(int index) {
              return null;
          }
  
          @Override
          public int getLength() {
              return 0;
          }
      };
      protected String data;
  
      //
      // Constructors
      //
  
      public CharacterDataImpl() {
      }
  
      /**
       * Factory constructor.
       */
      protected CharacterDataImpl(CoreDocumentImpl ownerDocument, String data) {
          super(ownerDocument);
          this.data = data;
      }
  
      //
      // Node methods
      //
  
      /**
       * Returns an empty node list.
       */
      @Override
      public NodeList getChildNodes() {
          return singletonNodeList;
      }
  
      /*
       * returns the content of this node
       */
      @Override
      public String getNodeValue() {
          if (needsSyncData()) {
              synchronizeData();
          }
          return data;
      }
  
      /**
       * Sets the content, possibly firing related events,
       * and updating ranges (via notification to the document)
       */
      @Override
      public void setNodeValue(String value) {
  
          setNodeValueInternal(value);
  
          // notify document
          ownerDocument().replacedText(this);
      }
  
      /**
       * Convenience wrapper for calling setNodeValueInternal when
       * we are not performing a replacement operation
       */
      protected void setNodeValueInternal(String value) {
          setNodeValueInternal(value, false);
      }
  
      /**
       * This function added so that we can distinguish whether
       * setNodeValue has been called from some other DOM functions.
       * or by the client.<p>
       * This is important, because we do one type of Range fix-up,
       * from the high-level functions in CharacterData, and another
       * type if the client simply calls setNodeValue(value).
       */
      protected void setNodeValueInternal(String value, boolean replace) {
  
          CoreDocumentImpl ownerDocument = ownerDocument();
  
          if (ownerDocument.errorChecking && isReadOnly()) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
              throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
          }
  
          // revisit: may want to set the value in ownerDocument.
          // Default behavior, overridden in some subclasses
          if (needsSyncData()) {
              synchronizeData();
          }
  
          // keep old value for document notification
          String oldvalue = this.data;
  
          // notify document
          ownerDocument.modifyingCharacterData(this, replace);
  
          this.data = value;
  
          // notify document
          ownerDocument.modifiedCharacterData(this, oldvalue, value, replace);
      }
  
      //
      // CharacterData methods
      //
  
      /**
       * Retrieve character data currently stored in this node.
       *
       * @throws DOMExcpetion(DOMSTRING_SIZE_ERR) In some implementations,
       *                                          the stored data may exceed the permitted length of strings. If so,
       *                                          getData() will throw this DOMException advising the user to
       *                                          instead retrieve the data in chunks via the substring() operation.
       */
      public String getData() {
          if (needsSyncData()) {
              synchronizeData();
          }
          return data;
      }
  
      /**
       * Store character data into this node.
       *
       * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
       */
      public void setData(String value)
              throws DOMException {
          setNodeValue(value);
      }
  
      /**
       * Report number of characters currently stored in this node's
       * data. It may be 0, meaning that the value is an empty string.
       */
      @Override
      public int getLength() {
          if (needsSyncData()) {
              synchronizeData();
          }
          return data.length();
      }
  
      /**
       * Concatenate additional characters onto the end of the data
       * stored in this node. Note that this, and insert(), are the paths
       * by which a DOM could wind up accumulating more data than the
       * language's strings can easily handle. (See above discussion.)
       *
       * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
       */
      public void appendData(String data) {
  
          if (isReadOnly()) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
              throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
          }
          if (data == null) {
              return;
          }
          if (needsSyncData()) {
              synchronizeData();
          }
  
          setNodeValue(this.data + data);
  
      } // appendData(String)
  
      /**
       * Remove a range of characters from the node's value. Throws a
       * DOMException if the offset is beyond the end of the
       * string. However, a deletion _count_ that exceeds the available
       * data is accepted as a delete-to-end request.
       *
       * @throws DOMException(INDEX_SIZE_ERR)              if offset is negative or
       *                                                   greater than length, or if count is negative.
       * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
       *                                                   readonly.
       */
      public void deleteData(int offset, int count)
              throws DOMException {
  
          internalDeleteData(offset, count, false);
      } // deleteData(int,int)
  
      /**
       * NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
       * to control which mutation events are spawned. This version of the
       * deleteData operation allows us to do so. It is not intended
       * for use by application programs.
       */
      void internalDeleteData(int offset, int count, boolean replace)
              throws DOMException {
  
          CoreDocumentImpl ownerDocument = ownerDocument();
          if (ownerDocument.errorChecking) {
              if (isReadOnly()) {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
                  throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
              }
  
              if (count < 0) {
                  String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
                  throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
              }
          }
  
          if (needsSyncData()) {
              synchronizeData();
          }
          int tailLength = Math.max(data.length() - count - offset, 0);
          try {
              String value = data.substring(0, offset) +
                      (tailLength > 0 ? data.substring(offset + count, offset + count + tailLength) : "");
  
              setNodeValueInternal(value, replace);
  
              // notify document
              ownerDocument.deletedText(this, offset, count);
          } catch (StringIndexOutOfBoundsException e) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
              throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
          }
  
      } // internalDeleteData(int,int,boolean)
  
      /**
       * Insert additional characters into the data stored in this node,
       * at the offset specified.
       *
       * @throws DOMException(INDEX_SIZE_ERR)              if offset is negative or
       *                                                   greater than length.
       * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is readonly.
       */
      public void insertData(int offset, String data)
              throws DOMException {
  
          internalInsertData(offset, data, false);
  
      } // insertData(int,int)
  
      /**
       * NON-DOM INTERNAL: Within DOM actions, we sometimes need to be able
       * to control which mutation events are spawned. This version of the
       * insertData operation allows us to do so. It is not intended
       * for use by application programs.
       */
      void internalInsertData(int offset, String data, boolean replace)
              throws DOMException {
  
          CoreDocumentImpl ownerDocument = ownerDocument();
  
          if (ownerDocument.errorChecking && isReadOnly()) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
              throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
          }
  
          if (needsSyncData()) {
              synchronizeData();
          }
          try {
              String value =
                      new StringBuffer(this.data).insert(offset, data).toString();
  
  
              setNodeValueInternal(value, replace);
  
              // notify document
              ownerDocument.insertedText(this, offset, data.length());
          } catch (StringIndexOutOfBoundsException e) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
              throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
          }
  
      } // internalInsertData(int,String,boolean)
  
      /**
       * Replace a series of characters at the specified (zero-based)
       * offset with a new string, NOT necessarily of the same
       * length. Convenience method, equivalent to a delete followed by an
       * insert. Throws a DOMException if the specified offset is beyond
       * the end of the existing data.
       *
       * @param offset The offset at which to begin replacing.
       * @param count  The number of characters to remove,
       *               interpreted as in the delete() method.
       * @param data   The new string to be inserted at offset in place of
       *               the removed data. Note that the entire string will
       *               be inserted -- the count parameter does not affect
       *               insertion, and the new data may be longer or shorter
       *               than the substring it replaces.
       * @throws DOMException(INDEX_SIZE_ERR)              if offset is negative or
       *                                                   greater than length, or if count is negative.
       * @throws DOMException(NO_MODIFICATION_ALLOWED_ERR) if node is
       *                                                   readonly.
       */
      public void replaceData(int offset, int count, String data)
              throws DOMException {
  
          CoreDocumentImpl ownerDocument = ownerDocument();
  
          // The read-only check is done by deleteData()
          // ***** This could be more efficient w/r/t Mutation Events,
          // specifically by aggregating DOMAttrModified and
          // DOMSubtreeModified. But mutation events are
          // underspecified; I don't feel compelled
          // to deal with it right now.
          if (ownerDocument.errorChecking && isReadOnly()) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null);
              throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, msg);
          }
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          //notify document
          ownerDocument.replacingData(this);
  
          // keep old value for document notification
          String oldvalue = this.data;
  
          internalDeleteData(offset, count, true);
          internalInsertData(offset, data, true);
  
          ownerDocument.replacedCharacterData(this, oldvalue, this.data);
  
      } // replaceData(int,int,String)
  
      /**
       * Substring is more than a convenience function. In some
       * implementations of the DOM, where the stored data may exceed the
       * length that can be returned in a single string, the only way to
       * read it all is to extract it in chunks via this method.
       *
       * @param offset Zero-based offset of first character to retrieve.
       * @param count  Number of characters to retrieve.
       *               <p/>
       *               If the sum of offset and count exceeds the length, all characters
       *               to end of data are returned.
       * @throws DOMException(INDEX_SIZE_ERR)   if offset is negative or
       *                                        greater than length, or if count is negative.
       * @throws DOMException(WSTRING_SIZE_ERR) In some implementations,
       *                                        count may exceed the permitted length of strings. If so,
       *                                        substring() will throw this DOMException advising the user to
       *                                        instead retrieve the data in smaller chunks.
       */
      public String substringData(int offset, int count)
              throws DOMException {
  
          if (needsSyncData()) {
              synchronizeData();
          }
  
          int length = data.length();
          if (count < 0 || offset < 0 || offset > length - 1) {
              String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null);
              throw new DOMException(DOMException.INDEX_SIZE_ERR, msg);
          }
  
          int tailIndex = Math.min(offset + count, length);
  
          return data.substring(offset, tailIndex);
  
      } // substringData(int,int):String
  
  } // class CharacterDataImpl
  