/*******************************************************************************
 * Copyright (c) 2002, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.contentmodel.modelqueryimpl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.eclipse.wst.xml.core.internal.contentmodel.CMAnyElement;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.modelqueryimpl.ModelQueryExtensionManagerImpl;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.CMDataTypeValueHelper;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.DOMValidator;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.CMDocumentManager;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAssociationProvider;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.extension.ModelQueryExtensionManager;
import org.eclipse.wst.xml.core.internal.contentmodel.util.CMVisitor;
import org.eclipse.wst.xml.core.internal.contentmodel.util.DOMNamespaceHelper;
import org.eclipse.wst.xml.core.internal.contentmodel.util.NamespaceInfo;
import org.eclipse.wst.xml.core.internal.contentmodel.util.NamespaceTable;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;


/**
 * This class implements a large portion of the ModelQuery interfaces.
 * Some work is delegated to the DOMHelper, CMDocumentManager, and DOMValidator.
 */
public class ModelQueryImpl implements ModelQuery
{
  protected ModelQueryAssociationProvider modelQueryAssociationProvider;
  protected ModelQueryActionHelper modelQueryActionHelper;
  protected DOMValidator validator;   
  protected ModelQueryExtensionManagerImpl extensionManager;   
  protected CMDataTypeValueHelper valueHelper;
  protected int editMode = EDIT_MODE_CONSTRAINED_STRICT;

  public ModelQueryImpl(ModelQueryAssociationProvider modelQueryAssociationProvider)
  {
    this.modelQueryAssociationProvider = modelQueryAssociationProvider;
    modelQueryActionHelper = createModelQueryActionHelper();
    validator = new DOMValidator();                         
    extensionManager = new ModelQueryExtensionManagerImpl();
    valueHelper = new CMDataTypeValueHelper();
  }
                         
  public int getEditMode()
  {
    return editMode;
  }
                    
  public void setEditMode(int editMode)
  {
    this.editMode =editMode;
  }


  // factory methods
  public ModelQueryActionHelper createModelQueryActionHelper()
  {
    return new ModelQueryActionHelper(this);
  } 

  public DOMValidator getValidator()
  {
    return validator;
  }

  public CMDocument getCorrespondingCMDocument(Node node)
  {
    return modelQueryAssociationProvider.getCorrespondingCMDocument(node);
  }

  public CMNode getCMNode(Node node)
  {
    return modelQueryAssociationProvider.getCMNode(node);
  }

  public CMDataType getCMDataType(Text text)
  {
    return modelQueryAssociationProvider.getCMDataType(text);
  }

  public CMAttributeDeclaration getCMAttributeDeclaration(Attr attr)
  {
    return modelQueryAssociationProvider.getCMAttributeDeclaration(attr);
  }

  public CMElementDeclaration getCMElementDeclaration(Element element)
  {
    return modelQueryAssociationProvider.getCMElementDeclaration(element);
  }
   
  public CMDocumentManager getCMDocumentManager()
  {
    CMDocumentManager result = null;
    if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
    {             
      XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
      result = xmlAssociationProvider.getCMDocumentManager();
    }    
    return result;
  }
       

  /**
   * @deprected - use 3 arg version below
   */
  public List getCMDocumentList(Element element, String uri)
  {        
    return Collections.EMPTY_LIST;
  }

  public List getCMDocumentList(Element element, CMElementDeclaration ed, String uri)
  {                
    List result = new ArrayList();
    if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
    {              
      XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
     
      // todo... revist... handle each ##thing explicitly
      //          
      if (uri == null)
      {
        uri = "##any"; //$NON-NLS-1$
      }               

      if (uri.equals("##targetNamespace")) //$NON-NLS-1$
      {                                                      
        CMDocument cmDocument = (CMDocument)ed.getProperty("CMDocument"); //$NON-NLS-1$
        if (cmDocument != null)
        {  
          result.add(cmDocument);
        }
      }
      else if (uri.equals("##any") || uri.equals("##other")) //$NON-NLS-1$ //$NON-NLS-2$
      {                                        
        String excludedURI = null;
        if (uri.equals("##other")) //$NON-NLS-1$
        {
          CMDocument cmDocument = (CMDocument)ed.getProperty("CMDocument");        //$NON-NLS-1$
          if (cmDocument != null)
          {
            excludedURI = (String)cmDocument.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI"); //$NON-NLS-1$
          }
        }
                               
        // in this case we should consider all of the schema related to this document
        //
        NamespaceTable namespaceTable = new NamespaceTable(element.getOwnerDocument());
        namespaceTable.addElementLineage(element);
        List list = namespaceTable.getNamespaceInfoList();
        for (Iterator i = list.iterator(); i.hasNext();)
        {
          NamespaceInfo info = (NamespaceInfo)i.next();
          if (info.uri != null && !info.uri.equals(excludedURI))
          {
            CMDocument document = xmlAssociationProvider.getCMDocument(info.uri, info.locationHint, "XSD"); //$NON-NLS-1$
            if (document != null)
            {
              result.add(document);
            }
          }
        }
      }   
      else
      {        
        CMDocument document = xmlAssociationProvider.getCMDocument(element, uri);
        if (document != null)
        {
          result.add(document);
        }
      }      
    }
    return result;
  }


  public CMDocument getCMDocument(Element element, String uri)
  {                

    CMDocument result = null;
    if (modelQueryAssociationProvider instanceof XMLAssociationProvider)
    {             
      XMLAssociationProvider xmlAssociationProvider = (XMLAssociationProvider)modelQueryAssociationProvider;
      result = xmlAssociationProvider.getCMDocument(element, uri);
    }
    //ContentModelManager.println("ModelQueryImpl.getCMDocument(" + element.getNodeName() + ", " + uri + ") = " + result);
    return result;
  }

  public boolean isContentValid(Element element)
  {               
    CMElementDeclaration ed = getCMElementDeclaration(element);
    return isContentValid(ed, element);
  }

  public boolean isContentValid(CMElementDeclaration ed, Element element)
  {                                               
    boolean result = true;    
    if (ed != null)
    { 
      // first check to see if all the required attributes are present
      //                                                      
      CMNamedNodeMap map = ed.getAttributes();
      int mapLength = map.getLength();
      for (int i = 0; i < mapLength; i++)
      {                                                           
        CMAttributeDeclaration ad = (CMAttributeDeclaration)map.item(i);
        String attributeName = DOMNamespaceHelper.computeName(ad, element, null);
        if (ad.getUsage() == CMAttributeDeclaration.REQUIRED)
        {               
           Attr attr = element.getAttributeNode(attributeName);
           if (attr == null)
           {
             result = false;
             break;
           }
        }
      }

      // now check to see of the children validate properly
      //
      if (result) 
      {
        CMNode[] originArray = getOriginArray(element);
        result = originArray != null && originArray.length == element.getChildNodes().getLength();
      }
    }
    return result;
  }


  public CMNode getOrigin(Node node)
  {
    CMNode result = null;
    // todo... make sure parent is right
    //
    Node parentNode = getParentOrOwnerNode(node);
    if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE)
    {
      Element parentElement = (Element)parentNode;
      CMNode[] array = getOriginArray(parentElement);
      if (array != null)
      {
        int index = getIndexOfNode(parentElement.getChildNodes(), node);
        if (index < array.length)
        {
          result = array[index];
        }
      }
    }
    return result;
  }

  public CMNode[] getOriginArray(Element element)
  {
    CMElementDeclaration ed = getCMElementDeclaration(element);
    return (ed != null) ? getValidator().getOriginArray(ed, element) : null;
  }

  public int getIndexOfNode(NodeList nodeList, Node node)
  {
    int result = -1;
    int size = nodeList.getLength();
    for (int i = 0; i < size; i++)
    {
       if (nodeList.item(i) == node)
       {
         result = i;
         break;
       }
    }
    return result;
  }


  /**
   * Returns a list of all CMNode 'meta data' that may be potentially added to the element.
   */
  public List getAvailableContent(Element element, CMElementDeclaration ed, int includeOptions)
  {
    AvailableContentCMVisitor visitor = new AvailableContentCMVisitor(element, ed);
    List list = visitor.computeAvailableContent(includeOptions);
    if (extensionManager != null)
    {                    
      extensionManager.filterAvailableElementContent(list, element, ed, includeOptions);
    }  
    return list;
  }  


  public boolean canInsert(Element parent, CMNode cmNode, int index, int validityChecking)
  {
    boolean result = true;
    CMElementDeclaration ed = getCMElementDeclaration(parent);
    if (ed != null)
    {
      result = canInsert(parent, ed, cmNode, index, validityChecking);
    }
    return result;
  }


  public boolean canInsert(Element parent, CMElementDeclaration ed, CMNode cmNode, int index, int validityChecking)
  {
    return canInsert(parent, ed, cmNode, index, validityChecking, null);
  }         

  protected boolean canInsert(Element parent, CMElementDeclaration ed, CMNode cmNode, int index, int validityChecking, Object reuseableData)
  {
    boolean result = true;
    switch (cmNode.getNodeType())
    {
      case CMNode.ATTRIBUTE_DECLARATION :
      {
        String attributeName = DOMNamespaceHelper.computeName(cmNode, parent, null);
        result = parent.getAttributeNode(attributeName) == null;
        break;
      }
      case CMNode.ELEMENT_DECLARATION :
      case CMNode.GROUP :
      {
        if (validityChecking == VALIDITY_STRICT)
        {                                  
          // create list                       
          List contentSpecificationList = null;
          if (reuseableData != null)
          {                            
            contentSpecificationList = (List)reuseableData;
          }    
          else
          {                                                                                  
            contentSpecificationList = getValidator().createContentSpecificationList(parent, ed);
          }
          result = getValidator().canInsert(ed, contentSpecificationList, index, cmNode);
        }
        break;
      }
      case CMNode.DATA_TYPE :
      {
        int contentType = ed.getContentType();
        result = (contentType == CMElementDeclaration.MIXED ||
                  contentType == CMElementDeclaration.PCDATA ||
                  contentType == CMElementDeclaration.ANY);
        break;
      }
      default :
      {
        result = false;
        break;
      }
    }
    return result;
  }

  public boolean canInsert(Element parent, List cmNodeList, int index, int validityChecking)
  {
    // todo
    return true;
  }


  public boolean canRemove(Node node, int validityChecking)
  {
    boolean result = true;      
    if (validityChecking == VALIDITY_STRICT)
    {
      int nodeType = node.getNodeType();
      switch (nodeType)
      {
        case Node.ATTRIBUTE_NODE:
        {
          CMAttributeDeclaration ad = getCMAttributeDeclaration((Attr)node);
          if (ad != null)
          {
            result = (ad.getUsage() == CMAttributeDeclaration.OPTIONAL);
          }
          break;
        }
        case Node.ELEMENT_NODE:
        {
          Node parentNode = node.getParentNode();
          if (parentNode.getNodeType() == Node.ELEMENT_NODE)
          {
            Element parentElement = (Element)parentNode;
            CMElementDeclaration ed = getCMElementDeclaration(parentElement);
            if (ed != null)
            {
              List contentSpecificationList = getValidator().createContentSpecificationList(parentElement, ed);
              int index = getIndexOfNode(parentElement.getChildNodes(), node);
              result = getValidator().canRemove(ed, contentSpecificationList, index);
            }
          }
          break;
        }
      }
    }
    return result;
  }


  public boolean canRemove(List nodeList, int validityChecking)
  {
    boolean result = true;

    if (validityChecking == VALIDITY_STRICT)
    {
      Element parentElement = null;
      List childList = null;

      for (Iterator i = nodeList.iterator(); i.hasNext(); )
      {
        Node node = (Node)i.next();

        if (parentElement == null)
        {
          parentElement = getParentOrOwnerElement(node);
        }
        else if (parentElement != getParentOrOwnerElement(node))
        {
          // make sure the parent are the same
          result = false;
          break;
        }

        if (parentElement == null)
        {
          result = true;
          break;
        }

        int nodeType = node.getNodeType();
        if (nodeType == Node.ATTRIBUTE_NODE)
        {
          if (!canRemove(node, validityChecking))
          {
            result = false;
            break;
          }
        }
        else
        {
          if (childList == null)
          {
            childList = nodeListToList(parentElement.getChildNodes());
          }
          childList.remove(node);
        }
      }

      if (result && childList != null)
      {
        CMElementDeclaration ed = getCMElementDeclaration(parentElement);
        if (ed != null)
        {                                
          List contentSpecificationList = getValidator().createContentSpecificationList(childList, ed);
          result = getValidator().isValid(ed, contentSpecificationList);
        }
      }
    }

    return result;
  }

  public boolean canReplace(Element parent, int startIndex, int endIndex, CMNode cmNode, int validityChecking)
  {
    return true;
  }

  public boolean canReplace(Element parent, int startIndex, int endIndex, List cmNodeList, int validityChecking)
  {
    return true;
  }     
   
  /**
   * This method is experimental... use at your own risk
   */
  public boolean canWrap(Element childElement, CMElementDeclaration wrapElement, int validityChecking)
  {                        
    boolean result = true;  
    Node parentNode = childElement.getParentNode();                      
    if (parentNode.getNodeType() == Node.ELEMENT_NODE)
    {           
      Element parentElement = (Element)parentNode;      
      CMElementDeclaration parentEd = getCMElementDeclaration(parentElement);
      if (parentEd != null)
      {                                                                                         
        if (validityChecking == VALIDITY_STRICT)
        {
          int index = getIndexOfNode(parentElement.getChildNodes(), childElement);

          List contentSpecificationList = getValidator().createContentSpecificationList(parentElement, parentEd);
          List subList = contentSpecificationList.subList(index, index + 1);
          result = getValidator().canReplace(parentEd, contentSpecificationList, index, index, wrapElement);
          if (result)
          {
            result = getValidator().isValid(wrapElement, subList);
          }
        }
      }
    }
    else
    {
      result = false;
    }                
    return result;
  }

  public void getInsertActions(Element parent, CMElementDeclaration ed, int index, int includeOptions, int validityChecking, List actionList)
  {
    modelQueryActionHelper.getInsertActions(parent, ed, index, includeOptions, validityChecking, actionList);
  }

  public void getInsertActions(Document parent, CMDocument cmDocument, int index, int includeOptions, int validityChecking, List actionList)
  {
    modelQueryActionHelper.getInsertActions(parent, cmDocument, index, includeOptions, validityChecking, actionList);
  }

  public void getReplaceActions(Element parent, CMElementDeclaration ed, int includeOptions, int validityChecking, List actionList)
  {
    modelQueryActionHelper.getReplaceActions(parent, ed, includeOptions, validityChecking, actionList);
  }                     

  public void getReplaceActions(Element parent, CMElementDeclaration ed, List selectedChildren, int includeOptions, int validityChecking, List actionList)
  {
    modelQueryActionHelper.getReplaceActions(parent, ed, selectedChildren, includeOptions, validityChecking, actionList);
  }

  public void getInsertChildNodeActionTable(Element parent, CMElementDeclaration ed, int validityChecking, Hashtable actionTable)
  {
    modelQueryActionHelper.getInsertChildNodeActionTable(parent, ed, validityChecking, actionTable);
  }

  public void getActionTable(Element parent, CMElementDeclaration ed, int index, int validityChecking, Hashtable actionTable)
  {
    //modelQueryActionHelper.getAllActions(parent, ed, validityChecking, actionList);
  }


  // some helper methods
  //
  protected Node getParentOrOwnerNode(Node node)
  {
    return (node.getNodeType() == Node.ATTRIBUTE_NODE) ?
           ((Attr)node).getOwnerElement() :
           node.getParentNode();
  }

  protected Element getParentOrOwnerElement(Node node)
  {
    Node parent = getParentOrOwnerNode(node);
    return (parent.getNodeType() == Node.ELEMENT_NODE) ? (Element)parent : null;
  }
               

  protected List nodeListToList(NodeList nodeList)
  {
    int size = nodeList.getLength();
    List v = new ArrayList(size);
    for (int i = 0; i < size; i++)
    {
      v.add(nodeList.item(i));
    }
    return v;
  }   
   
  /**
  protected List getCMNodeList(NodeList nodeList)
  {
    int size = nodeList.getLength();
    Vector v = new Vector(size);
    for (int i = 0; i < size; i++)
    {
      v.add(getCMNode(nodeList.item(i));
    }
    return v;
  }
  */  

  public class AvailableContentCMVisitor extends CMVisitor
  {
    public Hashtable childNodeTable = new Hashtable();
    public Hashtable attributeTable = new Hashtable();
    public Element rootElement;
    public CMElementDeclaration rootElementDeclaration; 
    public boolean isRootVisited;
    protected boolean includeSequenceGroups;

    public AvailableContentCMVisitor(Element rootElement, CMElementDeclaration rootElementDeclaration)
    {                                     
      this.rootElement = rootElement;
      this.rootElementDeclaration = rootElementDeclaration;
    }

    protected String getKey(CMNode cmNode)
    {
      String key = cmNode.getNodeName();
      CMDocument cmDocument = (CMDocument)cmNode.getProperty("CMDocument"); //$NON-NLS-1$
      if (cmDocument != null)
      {                         
        String namespaceURI = (String)cmDocument.getProperty("http://org.eclipse.wst/cm/properties/targetNamespaceURI");    //$NON-NLS-1$
        if (namespaceURI != null)
        {   
          key = "[" + namespaceURI + "]" + key; //$NON-NLS-1$ //$NON-NLS-2$
        }
      }
      return key;
    }
    
    protected void addToTable(Hashtable table, CMNode cmNode)
    {
      String nodeName = cmNode.getNodeName();
      if (nodeName != null && nodeName.length() > 0)
      {  
        table.put(getKey(cmNode), cmNode);
      }  
    }

    public List computeAvailableContent(int includeOptions)
    {                   
      List v = new ArrayList();  

      int contentType = rootElementDeclaration.getContentType();
      includeSequenceGroups = ((includeOptions & INCLUDE_SEQUENCE_GROUPS) != 0);
      visitCMNode(rootElementDeclaration);
      
      if ((includeOptions & INCLUDE_ATTRIBUTES) != 0)
      {
        v.addAll(attributeTable.values());
        CMAttributeDeclaration nillableAttribute = (CMAttributeDeclaration)rootElementDeclaration.getProperty("http://org.eclipse.wst/cm/properties/nillable"); //$NON-NLS-1$
        if (nillableAttribute != null)
        {
          v.add(nillableAttribute);
        }
      }  

      if ((includeOptions & INCLUDE_CHILD_NODES) != 0)
      {      
        if (contentType == CMElementDeclaration.MIXED ||
            contentType == CMElementDeclaration.ELEMENT)
        {
          v.addAll(childNodeTable.values());
        }
        else if (contentType == CMElementDeclaration.ANY)
        {      
          CMDocument cmDocument =  (CMDocument)rootElementDeclaration.getProperty("CMDocument"); //$NON-NLS-1$
          if (cmDocument != null)
          {
            CMNamedNodeMap elements = cmDocument.getElements();            
            for (Iterator i = elements.iterator(); i.hasNext(); )
            {
              v.add(i.next());
            } 
          }
        }
      }
      return v;
    }   

    public void visitCMAnyElement(CMAnyElement anyElement)
    {            
      String uri = anyElement.getNamespaceURI();                          
      List list = getCMDocumentList(rootElement, rootElementDeclaration, uri);
      for (Iterator iterator = list.iterator(); iterator.hasNext(); )
      {
        CMDocument cmdocument = (CMDocument)iterator.next();
        if (cmdocument != null)
        {                          
          CMNamedNodeMap map = cmdocument.getElements();
          int size = map.getLength();
          for (int i = 0; i < size; i++)
          {                       
            CMNode ed = map.item(i);                  
            addToTable(childNodeTable,ed);
          }        
        }                
      }
    }

    public void visitCMAttributeDeclaration(CMAttributeDeclaration ad)
    {
      super.visitCMAttributeDeclaration(ad);
      attributeTable.put(ad.getNodeName(), ad);
    }

    public void visitCMElementDeclaration(CMElementDeclaration ed)
    {
      if (ed == rootElementDeclaration && !isRootVisited)
      {
        isRootVisited = true;
        super.visitCMElementDeclaration(ed);
      }
      else
      {                                                                                  
        if (!Boolean.TRUE.equals(ed.getProperty("Abstract"))) //$NON-NLS-1$
        {
          addToTable(childNodeTable,ed);
        }

        CMNodeList substitutionGroup = (CMNodeList)ed.getProperty("SubstitutionGroup"); //$NON-NLS-1$
        if (substitutionGroup != null)
        {
          handleSubstitutionGroup(substitutionGroup);
        }
      }
    }                                              

    protected void handleSubstitutionGroup(CMNodeList substitutionGroup)
    {
      int substitutionGroupLength = substitutionGroup.getLength();
      for (int i = 0; i < substitutionGroupLength; i++)
      {
        CMNode ed = substitutionGroup.item(i);
        String substitutionGroupValue = (String)ed.getProperty("SubstitutionGroupValue");
        boolean hasSubstitutionGroupValue = (substitutionGroupValue != null && substitutionGroupValue.length() > 0);
        if (!Boolean.TRUE.equals(ed.getProperty("Abstract")) && hasSubstitutionGroupValue )
        {
          addToTable(childNodeTable,ed);
        }
      }
    }

    public void visitCMGroup(CMGroup group)
    {
      if (includeSequenceGroups)
      {
        if (group.getOperator() == CMGroup.SEQUENCE &&
            group.getChildNodes().getLength() > 1 &&
            includesRequiredContent(group))
        {                                        
          childNodeTable.put(group, group);
        }
      }  
      super.visitCMGroup(group);
    }   

    public boolean includesRequiredContent(CMGroup group)
    {
      List list = getValidator().createContentSpecificationList(group);
      return list.size() > 1;
    }
  }    

 
  /**
   * @deprected - use getPossibleDataTypeValues()
   */
  public List getDataTypeValues(Element element, CMNode cmNode)
  {                                                                             
    return Arrays.asList(getPossibleDataTypeValues(element, cmNode));
  }
  
  /**
   * This methods return an array of possible values corresponding to the datatype of the CMNode (either an CMAttributeDeclaration or a CMElementDeclaration)
   */
  public String[] getPossibleDataTypeValues(Element element, CMNode cmNode)
  {
    List list = new ArrayList();                            
                               
    if (cmNode != null)
    {       
      CMDataType dataType = null;
      if (cmNode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION)
      {
        dataType = ((CMAttributeDeclaration)cmNode).getAttrType();
      }
      else if (cmNode.getNodeType() == CMNode.ELEMENT_DECLARATION)
      {
        dataType = ((CMElementDeclaration)cmNode).getDataType();
      }         
     
      String[] enumeratedValues = dataType != null ? dataType.getEnumeratedValues() : null;      
      if (enumeratedValues != null)
      {
        for (int i = 0; i < enumeratedValues.length; i++)
        {
          list.add(enumeratedValues[i]);
        } 
      }                              
    }
                         
    addValuesForXSIType(element, cmNode, list);
    
    if (extensionManager != null)
    {                    
      list.addAll(extensionManager.getDataTypeValues(element, cmNode));
    }          
    
    // Remove duplicates
    List duplicateFreeList = new ArrayList();
    Iterator iterator = list.iterator();
    while(iterator.hasNext()) {
    	Object next = iterator.next();
    	if(duplicateFreeList.indexOf(next) == -1) {
    		duplicateFreeList.add(next);
    	}
    }
    
    return (String[]) duplicateFreeList.toArray(new String[duplicateFreeList.size()]);
  }    

           
  protected void addValuesForXSIType(Element element, CMNode cmNode, List list)
  {               
    if (cmNode != null && cmNode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) 
    {                         
      CMAttributeDeclaration ad = (CMAttributeDeclaration)cmNode;                              
      if (valueHelper.isXSIType(ad))
      {             
        NamespaceTable table = new NamespaceTable(element.getOwnerDocument());
        table.addElementLineage(element);
        list.addAll(valueHelper.getQualifiedXSITypes(ad, table));     
      }
    }
  }
    

  public ModelQueryExtensionManager getExtensionManager()
  {
    return extensionManager;
  }
}
