content: /*
   *  This file is part of AndroidIDE.
   *
   *  AndroidIDE is free software: you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation, either version 3 of the License, or
   *  (at your option) any later version.
   *
   *  AndroidIDE is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *   along with AndroidIDE.  If not, see <https://www.gnu.org/licenses/>.
   */
  
  /*
   * reserved comment block
   * DO NOT REMOVE OR ALTER!
   */
  /*
   * Copyright 2005 The Apache Software Foundation.
   *
   * Licensed 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 jaxp.sun.org.apache.xerces.internal.jaxp.validation;
  
  import java.io.IOException;
  import java.util.Enumeration;
  
  import jaxp.xml.parsers.DocumentBuilder;
  import jaxp.xml.parsers.DocumentBuilderFactory;
  import jaxp.xml.parsers.ParserConfigurationException;
  import jaxp.xml.transform.Result;
  import jaxp.xml.transform.Source;
  import jaxp.xml.transform.dom.DOMResult;
  import jaxp.xml.transform.dom.DOMSource;
  
  import jaxp.sun.org.apache.xerces.internal.impl.Constants;
  import jaxp.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
  import jaxp.sun.org.apache.xerces.internal.impl.validation.EntityState;
  import jaxp.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
  import jaxp.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator;
  import jaxp.sun.org.apache.xerces.internal.impl.xs.util.SimpleLocator;
  import jaxp.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl;
  import jaxp.sun.org.apache.xerces.internal.util.NamespaceSupport;
  import jaxp.sun.org.apache.xerces.internal.util.SymbolTable;
  import jaxp.sun.org.apache.xerces.internal.util.XMLAttributesImpl;
  import jaxp.sun.org.apache.xerces.internal.util.XMLSymbols;
  import jaxp.sun.org.apache.xerces.internal.xni.NamespaceContext;
  import jaxp.sun.org.apache.xerces.internal.xni.QName;
  import jaxp.sun.org.apache.xerces.internal.xni.XMLString;
  import jaxp.sun.org.apache.xerces.internal.xni.XNIException;
  import jaxp.sun.org.apache.xerces.internal.xni.parser.XMLParseException;
  import org.w3c.dom.Attr;
  import org.w3c.dom.CDATASection;
  import org.w3c.dom.Comment;
  import org.w3c.dom.Document;
  import org.w3c.dom.DocumentType;
  import org.w3c.dom.Entity;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  import org.w3c.dom.ProcessingInstruction;
  import org.w3c.dom.Text;
  import org.xml.sax.SAXException;
  
  /**
   * <p>A validator helper for <code>DOMSource</code>s.</p>
   *
   * @author Michael Glavassevich, IBM
   * @version $Id: DOMValidatorHelper.java,v 1.9 2010-11-01 04:40:08 joehw Exp $
   */
  final class DOMValidatorHelper implements ValidatorHelper, EntityState {
  
      //
      // Constants
      //
  
      /** Chunk size (1024). */
      private static final int CHUNK_SIZE = (1 << 10);
  
      /** Chunk mask (CHUNK_SIZE - 1). */
      private static final int CHUNK_MASK = CHUNK_SIZE - 1;
  
      // property identifiers
  
      /** Property identifier: error reporter. */
      private static final String ERROR_REPORTER =
          Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
  
      /** Property identifier: namespace context. */
      private static final String NAMESPACE_CONTEXT =
          Constants.XERCES_PROPERTY_PREFIX + Constants.NAMESPACE_CONTEXT_PROPERTY;
  
      /** Property identifier: XML Schema validator. */
      private static final String SCHEMA_VALIDATOR =
          Constants.XERCES_PROPERTY_PREFIX + Constants.SCHEMA_VALIDATOR_PROPERTY;
  
      /** Property identifier: symbol table. */
      private static final String SYMBOL_TABLE =
          Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
  
      /** Property identifier: validation manager. */
      private static final String VALIDATION_MANAGER =
          Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
  
      //
      // Data
      //
  
      /** Error reporter. */
      private XMLErrorReporter fErrorReporter;
  
      /** The namespace context of this document: stores namespaces in scope. **/
      private NamespaceSupport fNamespaceContext;
  
      /** The namespace context of the DOMSource, includes context from ancestor nodes. **/
      private DOMNamespaceContext fDOMNamespaceContext = new DOMNamespaceContext();
  
      /** Schema validator. **/
      private XMLSchemaValidator fSchemaValidator;
  
      /** Symbol table **/
      private SymbolTable fSymbolTable;
  
      /** Validation manager. **/
      private ValidationManager fValidationManager;
  
      /** Component manager. **/
      private XMLSchemaValidatorComponentManager fComponentManager;
  
      /** Simple Locator. **/
      private final SimpleLocator fXMLLocator = new SimpleLocator(null, null, -1, -1, -1);
  
      /** DOM document handler. **/
      private DOMDocumentHandler fDOMValidatorHandler;
  
      /** DOM result augmentor. **/
      private final DOMResultAugmentor fDOMResultAugmentor = new DOMResultAugmentor(this);
  
      /** DOM result builder. **/
      private final DOMResultBuilder fDOMResultBuilder = new DOMResultBuilder();
  
      /** Map for tracking unparsed entities. **/
      private NamedNodeMap fEntities = null;
  
      /** Array for holding character data. **/
      private char [] fCharBuffer = new char[CHUNK_SIZE];
  
      /** Root node. **/
      private Node fRoot;
  
      /** Current element. **/
      private Node fCurrentElement;
  
      /** Fields for start element, end element and characters. **/
      final QName fElementQName = new QName();
      final QName fAttributeQName = new QName();
      final XMLAttributesImpl fAttributes = new XMLAttributesImpl();
      final XMLString fTempString = new XMLString();
  
      public DOMValidatorHelper(XMLSchemaValidatorComponentManager componentManager) {
          fComponentManager = componentManager;
          fErrorReporter = (XMLErrorReporter) fComponentManager.getProperty(ERROR_REPORTER);
          fNamespaceContext = (NamespaceSupport) fComponentManager.getProperty(NAMESPACE_CONTEXT);
          fSchemaValidator = (XMLSchemaValidator) fComponentManager.getProperty(SCHEMA_VALIDATOR);
          fSymbolTable = (SymbolTable) fComponentManager.getProperty(SYMBOL_TABLE);
          fValidationManager = (ValidationManager) fComponentManager.getProperty(VALIDATION_MANAGER);
      }
  
      /*
       * ValidatorHelper methods
       */
  
      public void validate(Source source, Result result)
          throws SAXException, IOException {
          if (result instanceof DOMResult || result == null) {
              final DOMSource domSource = (DOMSource) source;
              final DOMResult domResult = (DOMResult) result;
              Node node = domSource.getNode();
              fRoot = node;
              if (node != null) {
                  fComponentManager.reset();
                  fValidationManager.setEntityState(this);
                  fDOMNamespaceContext.reset();
                  String systemId = domSource.getSystemId();
                  fXMLLocator.setLiteralSystemId(systemId);
                  fXMLLocator.setExpandedSystemId(systemId);
                  fErrorReporter.setDocumentLocator(fXMLLocator);
                  try {
                      // regardless of what type of node this is, fire start and end document events
                      setupEntityMap((node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument());
                      setupDOMResultHandler(domSource, domResult);
                      fSchemaValidator.startDocument(fXMLLocator, null, fDOMNamespaceContext, null);
                      validate(node);
                      fSchemaValidator.endDocument(null);
                  }
                  catch (XMLParseException e) {
                      throw Util.toSAXParseException(e);
                  }
                  catch (XNIException e) {
                      throw Util.toSAXException(e);
                  }
                  finally {
                      // Release references to application objects
                      fRoot = null;
                      //fCurrentElement = null; -- keep the reference to support current-element-node property
                      fEntities = null;
                      if (fDOMValidatorHandler != null) {
                          fDOMValidatorHandler.setDOMResult(null);
                      }
                  }
              }
              return;
          }
          throw new IllegalArgumentException(JAXPValidationMessageFormatter.formatMessage(fComponentManager.getLocale(),
                  "SourceResultMismatch",
                  new Object [] {source.getClass().getName(), result.getClass().getName()}));
      }
  
      /*
       * EntityState methods
       */
  
      public boolean isEntityDeclared(String name) {
          return false;
      }
  
      public boolean isEntityUnparsed(String name) {
          if (fEntities != null) {
              Entity entity = (Entity) fEntities.getNamedItem(name);
              if (entity != null) {
                  return (entity.getNotationName() != null);
              }
          }
          return false;
      }
  
      /*
       * Other methods
       */
  
      /** Traverse the DOM and fire events to the schema validator. */
      private void validate(Node node) {
          final Node top = node;
          // Performs a non-recursive traversal of the DOM. This
          // will avoid a stack overflow for DOMs with high depth.
          while (node != null) {
              beginNode(node);
              Node next = node.getFirstChild();
              while (next == null) {
                  finishNode(node);
                  if (top == node) {
                      break;
                  }
                  next = node.getNextSibling();
                  if (next == null) {
                      node = node.getParentNode();
                      if (node == null || top == node) {
                          if (node != null) {
                              finishNode(node);
                          }
                          next = null;
                          break;
                      }
                  }
              }
              node = next;
          }
      }
  
      /** Do processing for the start of a node. */
      private void beginNode(Node node) {
          switch (node.getNodeType()) {
              case Node.ELEMENT_NODE:
                  fCurrentElement = node;
                  // push namespace context
                  fNamespaceContext.pushContext();
                  // start element
                  fillQName(fElementQName, node);
                  processAttributes(node.getAttributes());
                  fSchemaValidator.startElement(fElementQName, fAttributes, null);
                  break;
              case Node.TEXT_NODE:
                  if (fDOMValidatorHandler != null) {
                      fDOMValidatorHandler.setIgnoringCharacters(true);
                      sendCharactersToValidator(node.getNodeValue());
                      fDOMValidatorHandler.setIgnoringCharacters(false);
                      fDOMValidatorHandler.characters((Text) node);
                  }
                  else {
                      sendCharactersToValidator(node.getNodeValue());
                  }
                  break;
              case Node.CDATA_SECTION_NODE:
                  if (fDOMValidatorHandler != null) {
                      fDOMValidatorHandler.setIgnoringCharacters(true);
                      fSchemaValidator.startCDATA(null);
                      sendCharactersToValidator(node.getNodeValue());
                      fSchemaValidator.endCDATA(null);
                      fDOMValidatorHandler.setIgnoringCharacters(false);
                      fDOMValidatorHandler.cdata((CDATASection) node);
                  }
                  else {
                      fSchemaValidator.startCDATA(null);
                      sendCharactersToValidator(node.getNodeValue());
                      fSchemaValidator.endCDATA(null);
                  }
                  break;
              case Node.PROCESSING_INSTRUCTION_NODE:
                  /**
                   * The validator does nothing with processing instructions so bypass it.
                   * Send the ProcessingInstruction node directly to the result builder.
                   */
                  if (fDOMValidatorHandler != null) {
                      fDOMValidatorHandler.processingInstruction((ProcessingInstruction) node);
                  }
                  break;
              case Node.COMMENT_NODE:
                  /**
                   * The validator does nothing with comments so bypass it.
                   * Send the Comment node directly to the result builder.
                   */
                  if (fDOMValidatorHandler != null) {
                      fDOMValidatorHandler.comment((Comment) node);
                  }
                  break;
              case Node.DOCUMENT_TYPE_NODE:
                  /**
                   * Send the DocumentType node directly to the result builder.
                   */
                  if (fDOMValidatorHandler != null) {
                      fDOMValidatorHandler.doctypeDecl((DocumentType) node);
                  }
                  break;
              default: // Ignore other node types.
                  break;
          }
      }
  
      /** Do processing for the end of a node. */
      private void finishNode(Node node) {
          if (node.getNodeType() == Node.ELEMENT_NODE) {
              fCurrentElement = node;
              // end element
              fillQName(fElementQName, node);
              fSchemaValidator.endElement(fElementQName, null);
              // pop namespace context
              fNamespaceContext.popContext();
          }
      }
  
      /**
       * Extracts NamedNodeMap of entities. We need this to validate
       * elements and attributes of type xs:ENTITY, xs:ENTITIES or
       * types dervied from them.
       */
      private void setupEntityMap(Document doc) {
          if (doc != null) {
              DocumentType docType = doc.getDoctype();
              if (docType != null) {
                  fEntities = docType.getEntities();
                  return;
              }
          }
          fEntities = null;
      }
  
      /**
       * Sets up handler for <code>DOMResult</code>.
       */
      private void setupDOMResultHandler(DOMSource source, DOMResult result) throws SAXException {
          // If there's no DOMResult, unset the validator handler
          if (result == null) {
              fDOMValidatorHandler = null;
              fSchemaValidator.setDocumentHandler(null);
              return;
          }
          final Node nodeResult = result.getNode();
          // If the source node and result node are the same use the DOMResultAugmentor.
          // Otherwise use the DOMResultBuilder.
          if (source.getNode() == nodeResult) {
              fDOMValidatorHandler = fDOMResultAugmentor;
              fDOMResultAugmentor.setDOMResult(result);
              fSchemaValidator.setDocumentHandler(fDOMResultAugmentor);
              return;
          }
          if (result.getNode() == null) {
              try {
                  DocumentBuilderFactory factory = fComponentManager.getFeature(Constants.ORACLE_FEATURE_SERVICE_MECHANISM) ?
                                      DocumentBuilderFactory.newInstance() : new DocumentBuilderFactoryImpl();
                  factory.setNamespaceAware(true);
                  DocumentBuilder builder = factory.newDocumentBuilder();
                  result.setNode(builder.newDocument());
              }
              catch (ParserConfigurationException e) {
                  throw new SAXException(e);
              }
          }
          fDOMValidatorHandler = fDOMResultBuilder;
          fDOMResultBuilder.setDOMResult(result);
          fSchemaValidator.setDocumentHandler(fDOMResultBuilder);
      }
  
      private void fillQName(QName toFill, Node node) {
          final String prefix = node.getPrefix();
          final String localName = node.getLocalName();
          final String rawName = node.getNodeName();
          final String namespace = node.getNamespaceURI();
  
          toFill.uri = (namespace != null && namespace.length() > 0) ? fSymbolTable.addSymbol(namespace) : null;
          toFill.rawname = (rawName != null) ? fSymbolTable.addSymbol(rawName) : XMLSymbols.EMPTY_STRING;
  
          // Is this a DOM level1 document?
          if (localName == null) {
              int k = rawName.indexOf(':');
              if (k > 0) {
                  toFill.prefix = fSymbolTable.addSymbol(rawName.substring(0, k));
                  toFill.localpart = fSymbolTable.addSymbol(rawName.substring(k + 1));
              }
              else {
                  toFill.prefix = XMLSymbols.EMPTY_STRING;
                  toFill.localpart = toFill.rawname;
              }
          }
          else {
              toFill.prefix = (prefix != null) ? fSymbolTable.addSymbol(prefix) : XMLSymbols.EMPTY_STRING;
              toFill.localpart = (localName != null) ? fSymbolTable.addSymbol(localName) : XMLSymbols.EMPTY_STRING;
          }
      }
  
      private void processAttributes(NamedNodeMap attrMap) {
          final int attrCount = attrMap.getLength();
          fAttributes.removeAllAttributes();
          for (int i = 0; i < attrCount; ++i) {
              Attr attr = (Attr) attrMap.item(i);
              String value = attr.getValue();
              if (value == null) {
                  value = XMLSymbols.EMPTY_STRING;
              }
              fillQName(fAttributeQName, attr);
              // REVISIT: Assuming all attributes are of type CDATA. The actual type may not matter. -- mrglavas
              fAttributes.addAttributeNS(fAttributeQName, XMLSymbols.fCDATASymbol, value);
              fAttributes.setSpecified(i, attr.getSpecified());
              // REVISIT: Should we be looking at non-namespace attributes
              // for additional mappings? Should we detect illegal namespace
              // declarations and exclude them from the context? -- mrglavas
              if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
                  // process namespace attribute
                  if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
                      fNamespaceContext.declarePrefix(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
                  }
                  else {
                      fNamespaceContext.declarePrefix(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
                  }
              }
          }
      }
  
      private void sendCharactersToValidator(String str) {
          if (str != null) {
              final int length = str.length();
              final int remainder = length & CHUNK_MASK;
              if (remainder > 0) {
                  str.getChars(0, remainder, fCharBuffer, 0);
                  fTempString.setValues(fCharBuffer, 0, remainder);
                  fSchemaValidator.characters(fTempString, null);
              }
              int i = remainder;
              while (i < length) {
                  str.getChars(i, i += CHUNK_SIZE, fCharBuffer, 0);
                  fTempString.setValues(fCharBuffer, 0, CHUNK_SIZE);
                  fSchemaValidator.characters(fTempString, null);
              }
          }
      }
  
      Node getCurrentElement() {
          return fCurrentElement;
      }
  
      /**
       * NamespaceContext for the DOMSource, includes context for ancestor nodes.
       */
      final class DOMNamespaceContext implements NamespaceContext {
  
          //
          // Data
          //
  
          /**
           * Namespace binding information. This array is composed of a
           * series of tuples containing the namespace binding information:
           * &lt;prefix, uri&gt;.
           */
          protected String[] fNamespace = new String[16 * 2];
  
          /** The size of the namespace information array. */
          protected int fNamespaceSize = 0;
  
          /**
           * Flag indicating whether the namespace context
           * has been from the root node's ancestors.
           */
          protected boolean fDOMContextBuilt = false;
  
          //
          // Methods
          //
  
          public void pushContext() {
              fNamespaceContext.pushContext();
          }
  
          public void popContext() {
              fNamespaceContext.popContext();
          }
  
          public boolean declarePrefix(String prefix, String uri) {
              return fNamespaceContext.declarePrefix(prefix, uri);
          }
  
          public String getURI(String prefix) {
              String uri = fNamespaceContext.getURI(prefix);
              if (uri == null) {
                  if (!fDOMContextBuilt) {
                      fillNamespaceContext();
                      fDOMContextBuilt = true;
                  }
                  if (fNamespaceSize > 0 &&
                      !fNamespaceContext.containsPrefix(prefix)) {
                      uri = getURI0(prefix);
                  }
              }
              return uri;
          }
  
          public String getPrefix(String uri) {
              return fNamespaceContext.getPrefix(uri);
          }
  
          public int getDeclaredPrefixCount() {
              return fNamespaceContext.getDeclaredPrefixCount();
          }
  
          public String getDeclaredPrefixAt(int index) {
              return fNamespaceContext.getDeclaredPrefixAt(index);
          }
  
          public Enumeration getAllPrefixes() {
              return fNamespaceContext.getAllPrefixes();
          }
  
          public void reset() {
              fDOMContextBuilt = false;
              fNamespaceSize = 0;
          }
  
          private void fillNamespaceContext() {
              if (fRoot != null) {
                  Node currentNode = fRoot.getParentNode();
                  while (currentNode != null) {
                      if (Node.ELEMENT_NODE == currentNode.getNodeType()) {
                          NamedNodeMap attributes = currentNode.getAttributes();
                          final int attrCount = attributes.getLength();
                          for (int i = 0; i < attrCount; ++i) {
                              Attr attr = (Attr) attributes.item(i);
                              String value = attr.getValue();
                              if (value == null) {
                                  value = XMLSymbols.EMPTY_STRING;
                              }
                              fillQName(fAttributeQName, attr);
                              // REVISIT: Should we be looking at non-namespace attributes
                              // for additional mappings? Should we detect illegal namespace
                              // declarations and exclude them from the context? -- mrglavas
                              if (fAttributeQName.uri == NamespaceContext.XMLNS_URI) {
                                  // process namespace attribute
                                  if (fAttributeQName.prefix == XMLSymbols.PREFIX_XMLNS) {
                                      declarePrefix0(fAttributeQName.localpart, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
                                  }
                                  else {
                                      declarePrefix0(XMLSymbols.EMPTY_STRING, value.length() != 0 ? fSymbolTable.addSymbol(value) : null);
                                  }
                              }
                          }
  
                      }
                      currentNode = currentNode.getParentNode();
                  }
              }
          }
  
          private void declarePrefix0(String prefix, String uri) {
              // resize array, if needed
              if (fNamespaceSize == fNamespace.length) {
                  String[] namespacearray = new String[fNamespaceSize * 2];
                  System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize);
                  fNamespace = namespacearray;
              }
  
              // bind prefix to uri in current context
              fNamespace[fNamespaceSize++] = prefix;
              fNamespace[fNamespaceSize++] = uri;
          }
  
          private String getURI0(String prefix) {
              // find prefix in the DOM context
              for (int i = 0; i < fNamespaceSize; i += 2) {
                  if (fNamespace[i] == prefix) {
                      return fNamespace[i + 1];
                  }
              }
              // prefix not found
              return null;
          }
      }
  
  } // DOMValidatorHelper
  