text: /*
   * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   */
  /*
   * 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 com.sun.org.apache.xerces.internal.impl.xs.opti;
  
  import com.sun.org.apache.xerces.internal.util.XMLSymbols;
  import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
  import com.sun.org.apache.xerces.internal.xni.QName;
  import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
  import com.sun.org.apache.xerces.internal.xni.XMLString;
  import java.util.ArrayList;
  import java.util.Enumeration;
  import java.util.List;
  import org.w3c.dom.Attr;
  import org.w3c.dom.DOMImplementation;
  import org.w3c.dom.Element;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  
  /**
   * @xerces.internal
   *
   * @author Rahul Srivastava, Sun Microsystems Inc.
   * @author Sandy Gao, IBM
   *
   * @LastModified: Oct 2017
   */
  public class SchemaDOM extends DefaultDocument {
  
      static final int relationsRowResizeFactor = 15;
      static final int relationsColResizeFactor = 10;
  
      NodeImpl[][] relations;
      // parent must be an element in this scheme
      ElementImpl parent;
      int currLoc;
      int nextFreeLoc;
      boolean hidden;
      boolean inCDATA;
  
      // for annotation support:
      private StringBuffer fAnnotationBuffer = null;
  
      public SchemaDOM() {
          reset();
      }
  
  
      public ElementImpl startElement(QName element, XMLAttributes attributes,
              int line, int column, int offset) {
          ElementImpl node = new ElementImpl(line, column, offset);
          processElement(element, attributes, node);
          // now the current node added, becomes the parent
          parent = node;
          return node;
      }
  
      public ElementImpl emptyElement(QName element, XMLAttributes attributes,
              int line, int column, int offset) {
          ElementImpl node = new ElementImpl(line, column, offset);
          processElement(element, attributes, node);
          return node;
      }
  
      public ElementImpl startElement(QName element, XMLAttributes attributes,
              int line, int column) {
          return startElement(element, attributes, line, column, -1);
      }
  
      public ElementImpl emptyElement(QName element, XMLAttributes attributes,
              int line, int column) {
          return emptyElement(element, attributes, line, column, -1);
      }
  
      private void processElement(QName element, XMLAttributes attributes, ElementImpl node) {
  
          // populate node
          node.prefix = element.prefix;
          node.localpart = element.localpart;
          node.rawname = element.rawname;
          node.uri = element.uri;
          node.schemaDOM = this;
  
          // set the attributes
          Attr[] attrs = new Attr[attributes.getLength()];
          for (int i=0; i<attributes.getLength(); i++) {
              attrs[i] = new AttrImpl(node,
                      attributes.getPrefix(i),
                      attributes.getLocalName(i),
                      attributes.getQName(i),
                      attributes.getURI(i),
                      attributes.getValue(i));
          }
          node.attrs = attrs;
  
          // check if array needs to be resized
          if (nextFreeLoc == relations.length) {
              resizeRelations();
          }
  
          // store the current parent
          //if (relations[currLoc][0] == null || relations[currLoc][0] != parent) {
          if (relations[currLoc][0] != parent) {
              relations[nextFreeLoc][0] = parent;
              currLoc = nextFreeLoc++;
          }
  
          // add the current node as child of parent
          boolean foundPlace = false;
          int i = 1;
          for (i = 1; i<relations[currLoc].length; i++) {
              if (relations[currLoc][i] == null) {
                  foundPlace = true;
                  break;
              }
          }
  
          if (!foundPlace) {
              resizeRelations(currLoc);
          }
          relations[currLoc][i] = node;
  
          parent.parentRow = currLoc;
          node.row = currLoc;
          node.col = i;
      }
  
  
      public void endElement()  {
          // the parent of current parent node becomes the parent
          // for the next node.
          currLoc = parent.row;
          parent = (ElementImpl)relations[currLoc][0];
      }
  
      // note that this will only be called within appinfo/documentation
      void comment(XMLString text) {
          fAnnotationBuffer.append("<!--");
          if (text.length > 0) {
              fAnnotationBuffer.append(text.ch, text.offset, text.length);
          }
          fAnnotationBuffer.append("-->");
      }
  
      // note that this will only be called within appinfo/documentation
      void processingInstruction(String target, XMLString data) {
          fAnnotationBuffer.append("<?").append(target);
          if (data.length > 0) {
              fAnnotationBuffer.append(' ').append(data.ch, data.offset, data.length);
          }
          fAnnotationBuffer.append("?>");
      }
  
      // note that this will only be called within appinfo/documentation
      void characters(XMLString text) {
  
          // escape characters if necessary
          if (!inCDATA) {
              final StringBuffer annotationBuffer = fAnnotationBuffer;
              for (int i = text.offset; i < text.offset+text.length; ++i) {
                  char ch = text.ch[i];
                  if (ch == '&') {
                      annotationBuffer.append("&amp;");
                  }
                  else if (ch == '<') {
                      annotationBuffer.append("&lt;");
                  }
                  // character sequence "]]>" cannot appear in content,
                  // therefore we should escape '>'.
                  else if (ch == '>') {
                      annotationBuffer.append("&gt;");
                  }
                  // If CR is part of the document's content, it
                  // must not be printed as a literal otherwise
                  // it would be normalized to LF when the document
                  // is reparsed.
                  else if (ch == '\r') {
                      annotationBuffer.append("&#xD;");
                  }
                  else {
                      annotationBuffer.append(ch);
                  }
              }
          }
          else {
              fAnnotationBuffer.append(text.ch, text.offset, text.length);
          }
      }
  
      // note that this will only be called within appinfo/documentation
      void charactersRaw(String text) {
          fAnnotationBuffer.append(text);
      }
  
      void endAnnotation(QName elemName, ElementImpl annotation) {
          fAnnotationBuffer.append("\n</").append(elemName.rawname).append(">");
          annotation.fAnnotation = fAnnotationBuffer.toString();
          // apparently, there is no sensible way of resetting these things
          fAnnotationBuffer = null;
      }
  
      void endAnnotationElement(QName elemName) {
          endAnnotationElement(elemName.rawname);
      }
  
      void endAnnotationElement(String elemRawName) {
          fAnnotationBuffer.append("</").append(elemRawName).append(">");
      }
  
      void endSyntheticAnnotationElement(QName elemName, boolean complete) {
          endSyntheticAnnotationElement(elemName.rawname, complete);
      }
  
      void endSyntheticAnnotationElement(String elemRawName, boolean complete) {
          if(complete) {
              fAnnotationBuffer.append("\n</").append(elemRawName).append(">");
              // note that this is always called after endElement on <annotation>'s
              // child and before endElement on annotation.
              // hence, we must make this the child of the current
              // parent's only child.
              parent.fSyntheticAnnotation = fAnnotationBuffer.toString();
  
              // apparently, there is no sensible way of resetting
              // these things
              fAnnotationBuffer = null;
          } else      //capturing character calls
              fAnnotationBuffer.append("</").append(elemRawName).append(">");
      }
  
      void startAnnotationCDATA() {
          inCDATA = true;
          fAnnotationBuffer.append("<![CDATA[");
      }
  
      void endAnnotationCDATA() {
          fAnnotationBuffer.append("]]>");
          inCDATA = false;
      }
  
      private void resizeRelations() {
          NodeImpl[][] temp = new NodeImpl[relations.length+relationsRowResizeFactor][];
          System.arraycopy(relations, 0, temp, 0, relations.length);
          for (int i = relations.length ; i < temp.length ; i++) {
              temp[i] = new NodeImpl[relationsColResizeFactor];
          }
          relations = temp;
      }
  
      private void resizeRelations(int i) {
          NodeImpl[] temp = new NodeImpl[relations[i].length+relationsColResizeFactor];
          System.arraycopy(relations[i], 0, temp, 0, relations[i].length);
          relations[i] = temp;
      }
  
  
      public void reset() {
  
          // help out the garbage collector
          if(relations != null)
              for(int i=0; i<relations.length; i++)
                  for(int j=0; j<relations[i].length; j++)
                      relations[i][j] = null;
          relations = new NodeImpl[relationsRowResizeFactor][];
          parent = new ElementImpl(0, 0, 0);
          parent.rawname = "DOCUMENT_NODE";
          currLoc = 0;
          nextFreeLoc = 1;
          inCDATA = false;
          for (int i=0; i<relationsRowResizeFactor; i++) {
              relations[i] = new NodeImpl[relationsColResizeFactor];
          }
          relations[currLoc][0] = parent;
      }
  
  
      public void printDOM() {
          /*
           for (int i=0; i<relations.length; i++) {
           if (relations[i][0] != null) {
           for (int j=0; j<relations[i].length; j++) {
           if (relations[i][j] != null) {
           System.out.print(relations[i][j].nodeType+"-"+relations[i][j].parentRow+"  ");
           }
           }
           System.out.println("");
           }
           }
           */
          //traverse(getDocumentElement(), 0);
      }
  
  
      // debug methods
  
      public static void traverse(Node node, int depth) {
          indent(depth);
          System.out.print("<"+node.getNodeName());
  
          if (node.hasAttributes()) {
              NamedNodeMap attrs = node.getAttributes();
              for (int i=0; i<attrs.getLength(); i++) {
                  System.out.print("  "+((Attr)attrs.item(i)).getName()+"=\""+((Attr)attrs.item(i)).getValue()+"\"");
              }
          }
  
          if (node.hasChildNodes()) {
              System.out.println(">");
              depth+=4;
              for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
                  traverse(child, depth);
              }
              depth-=4;
              indent(depth);
              System.out.println("</"+node.getNodeName()+">");
          }
          else {
              System.out.println("/>");
          }
      }
  
      public static void indent(int amount) {
          for (int i = 0; i < amount; i++) {
              System.out.print(' ');
          }
      }
  
      // org.w3c.dom methods
      public Element getDocumentElement() {
          // this returns a parent node, known to be an ElementImpl
          return (ElementImpl)relations[0][1];
      }
  
      public DOMImplementation getImplementation() {
          return SchemaDOMImplementation.getDOMImplementation();
      }
  
      // commence the serialization of an annotation
      void startAnnotation(QName elemName, XMLAttributes attributes,
              NamespaceContext namespaceContext) {
          startAnnotation(elemName.rawname, attributes, namespaceContext);
      }
      void startAnnotation(String elemRawName, XMLAttributes attributes,
              NamespaceContext namespaceContext) {
          if(fAnnotationBuffer == null) fAnnotationBuffer = new StringBuffer(256);
          fAnnotationBuffer.append("<").append(elemRawName).append(" ");
  
          // attributes are a bit of a pain.  To get this right, we have to keep track
          // of the namespaces we've seen declared, then examine the namespace context
          // for other namespaces so that we can also include them.
          // optimized for simplicity and the case that not many
          // namespaces are declared on this annotation...
          List<String> namespaces = new ArrayList<>();
          for (int i = 0; i < attributes.getLength(); ++i) {
              String aValue = attributes.getValue(i);
              String aPrefix = attributes.getPrefix(i);
              String aQName = attributes.getQName(i);
              // if it's xmlns:* or xmlns, must be a namespace decl
              if (aPrefix == XMLSymbols.PREFIX_XMLNS || aQName == XMLSymbols.PREFIX_XMLNS) {
                  namespaces.add(aPrefix == XMLSymbols.PREFIX_XMLNS ?
                          attributes.getLocalName(i) : XMLSymbols.EMPTY_STRING);
              }
              fAnnotationBuffer.append(aQName).append("=\"").append(processAttValue(aValue)).append("\" ");
          }
          // now we have to look through currently in-scope namespaces to see what
          // wasn't declared here
          Enumeration<String> currPrefixes = namespaceContext.getAllPrefixes();
          while(currPrefixes.hasMoreElements()) {
              String prefix = currPrefixes.nextElement();
              String uri = namespaceContext.getURI(prefix);
              if (uri == null) {
                  uri = XMLSymbols.EMPTY_STRING;
              }
              if (!namespaces.contains(prefix)) {
                  // have to declare this one
                  if(prefix == XMLSymbols.EMPTY_STRING) {
                      fAnnotationBuffer.append("xmlns").append("=\"").append(processAttValue(uri)).append("\" ");
                  }
                  else {
                      fAnnotationBuffer.append("xmlns:").append(prefix).append("=\"").append(processAttValue(uri)).append("\" ");
                  }
              }
          }
          fAnnotationBuffer.append(">\n");
      }
      void startAnnotationElement(QName elemName, XMLAttributes attributes) {
          startAnnotationElement(elemName.rawname, attributes);
      }
      void startAnnotationElement(String elemRawName, XMLAttributes attributes) {
          fAnnotationBuffer.append("<").append(elemRawName);
          for(int i=0; i<attributes.getLength(); i++) {
              String aValue = attributes.getValue(i);
              fAnnotationBuffer.append(" ").append(attributes.getQName(i)).append("=\"").append(processAttValue(aValue)).append("\"");
          }
          fAnnotationBuffer.append(">");
      }
  
      private static String processAttValue(String original) {
          final int length = original.length();
          // normally, nothing will happen
          for (int i = 0; i < length; ++i) {
              char currChar = original.charAt(i);
              if (currChar == '"' || currChar == '<' || currChar == '&' ||
                      currChar == 0x09 || currChar == 0x0A || currChar == 0x0D) {
                  return escapeAttValue(original, i);
              }
          }
          return original;
      }
  
      private static String escapeAttValue(String original, int from) {
          int i;
          final int length = original.length();
          StringBuffer newVal = new StringBuffer(length);
          newVal.append(original.substring(0, from));
          for (i = from; i < length; ++i) {
              char currChar = original.charAt(i);
              if (currChar == '"') {
                  newVal.append("&quot;");
              }
              else if (currChar == '<') {
                  newVal.append("&lt;");
              }
              else if (currChar == '&') {
                  newVal.append("&amp;");
              }
              // Must escape 0x09, 0x0A and 0x0D if they appear in attribute
              // value so that they may be round-tripped. They would otherwise
              // be transformed to a 0x20 during attribute value normalization.
              else if (currChar == 0x09) {
                  newVal.append("&#x9;");
              }
              else if (currChar == 0x0A) {
                  newVal.append("&#xA;");
              }
              else if (currChar == 0x0D) {
                  newVal.append("&#xD;");
              }
              else {
                  newVal.append(currChar);
              }
          }
          return newVal.toString();
      }
  }
  