/*****************************************************************************
* This file is part of Rinzo
*
* Author: Claudio Cancinos
* WWW: https://sourceforge.net/projects/editorxml
* Copyright (C): 2008, Claudio Cancinos
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public
* License along with this program; If not, see <http://www.gnu.org/licenses/>
****************************************************************************/
package ar.com.tadp.xml.rinzo.core.model.tags.xsd;
import java.io.File;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDContentTypeCategory;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDTerm;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDResourceFactoryImpl;
import org.eclipse.xsd.util.XSDResourceImpl;
import ar.com.tadp.xml.rinzo.XMLEditorPlugin;
import ar.com.tadp.xml.rinzo.core.model.XMLNode;
import ar.com.tadp.xml.rinzo.core.model.tags.OnlyNameTypeTagDefinition;
import ar.com.tadp.xml.rinzo.core.model.tags.TagTypeDefinition;
import ar.com.tadp.xml.rinzo.core.model.tags.XMLTagDefinitionProvider;
import ar.com.tadp.xml.rinzo.core.resources.cache.DocumentCache;
import ar.com.tadp.xml.rinzo.core.resources.cache.DocumentStructureDeclaration;
import ar.com.tadp.xml.rinzo.core.utils.FileUtils;
/**
* Retrieves the relationship between tags and attributes in a tag defined in an
* XSD.
*
* @author ccancinos
*/
public class XSDTagDefinitionProvider implements XMLTagDefinitionProvider {
private static final XSDPossibleRootsTagTypeDefinition EMPTY_POSSIBLE_ROOTS = new XSDPossibleRootsTagTypeDefinition(Collections.EMPTY_LIST);
private String schemaPath;
private String fileName;
private Map<String, TagTypeDefinition> tags = new HashMap<String, TagTypeDefinition>();
private Collection<TagTypeDefinition> possibleRoots = new HashSet<TagTypeDefinition>();
private DocumentStructureDeclaration documentStructureDeclaration;
private long lastModified;
private List<String> namespaceContainers = new ArrayList<String>();
private Collection<DocumentStructureDeclaration> otherSchemas;
public XSDTagDefinitionProvider(String fileName, DocumentStructureDeclaration structureDeclaration, Collection<DocumentStructureDeclaration> otherSchemas) {
this.otherSchemas = otherSchemas;
try {
this.fileName = fileName;
this.documentStructureDeclaration = structureDeclaration;
this.schemaPath = FileUtils.resolveURI(fileName, structureDeclaration.getSystemId()).toString();
this.updateDefinition();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public TagTypeDefinition getTagDefinition(XMLNode node) {
String tagName = node.getFullTagName();
TagTypeDefinition tagTypeDefinition = this.tags.get(tagName);
if (node == null || tagTypeDefinition == null) {
String nodeSchemaId = this.getSchemaURI(node);
if (this.namespaceContainers.contains(nodeSchemaId)) {
return new XSDPossibleRootsTagTypeDefinition(this.possibleRoots);
} else {
return EMPTY_POSSIBLE_ROOTS;
}
}
return tagTypeDefinition != null ? tagTypeDefinition : new OnlyNameTypeTagDefinition(tagName);
}
private String getSchemaURI(XMLNode node) {
for (DocumentStructureDeclaration declaration : this.otherSchemas) {
if(declaration.getNamespace().equals(node.getNamespace())) {
return declaration.getPublicId();
}
}
return "";
}
/**
* Se encarga de mapear las definiciones que se encuentran en el schema
*/
private void updateDefinition() {
this.tags.clear();
this.possibleRoots.clear();
this.parseElementsFrom(this.getSchema(this.documentStructureDeclaration.getPublicId(), this.schemaPath));
}
// TODO THIS THREE METHODS ARE REPEATED IN XSDTAG!!!!
private void parseElementsFrom(XSDSchema schema) {
this.parseNamespaceContainers(schema);
for (Iterator it = schema.getElementDeclarations().iterator(); it.hasNext();) {
XSDElementDeclaration elementDeclaration = (XSDElementDeclaration) it.next();
if (this.documentStructureDeclaration.getPublicId() == null || elementDeclaration.getURI().startsWith(this.documentStructureDeclaration.getPublicId())) {
if (!elementDeclaration.isAbstract()) {
this.possibleRoots.add(new XSDTagTypeDefinition(elementDeclaration,
this.documentStructureDeclaration.getNamespace(), this.tags));
}
this.handleLeaf(elementDeclaration);
this.handleElementDeclaration(elementDeclaration);
}
}
}
private void parseNamespaceContainers(XSDSchema schema) {
Iterator iterator = schema.getContents().iterator();
while (iterator.hasNext()) {
Object element = (Object) iterator.next();
if (element instanceof XSDImport) {
XSDImport importElement = (XSDImport) element;
this.namespaceContainers.add(importElement.getNamespace());
}
}
}
private void handleElementDeclaration(XSDElementDeclaration tagDeclaration) {
XSDTypeDefinition type = tagDeclaration.getTypeDefinition();
if (type instanceof XSDComplexTypeDefinition) {
XSDComplexTypeDefinition xsdComplexTypeDefinition = (XSDComplexTypeDefinition) type;
int contentType = xsdComplexTypeDefinition.getContentTypeCategory().getValue();
if (contentType == XSDContentTypeCategory.ELEMENT_ONLY || contentType == XSDContentTypeCategory.MIXED) {
XSDParticle xsdParticle = (XSDParticle) xsdComplexTypeDefinition.getContentType();
if (xsdParticle != null) {
XSDTerm xsdTerm = xsdParticle.getTerm();
if (xsdTerm instanceof XSDModelGroup) {
XSDModelGroup xsdModelGroup = (XSDModelGroup) xsdTerm;
this.handleContainer(xsdModelGroup);
} else if (xsdTerm instanceof XSDElementDeclaration) {
XSDElementDeclaration eldeclaration = (XSDElementDeclaration) xsdTerm;
this.handleLeaf(eldeclaration);
}
}
}
}
}
private void handleLeaf(XSDElementDeclaration tagDeclaration) {
this.tags.put(getFullDeclarationName(tagDeclaration), new XSDTagTypeDefinition(tagDeclaration,
this.documentStructureDeclaration.getNamespace(), this.tags));
}
private void handleContainer(XSDModelGroup xsdModelGroup) {
for (Iterator i = xsdModelGroup.getParticles().iterator(); i.hasNext();) {
XSDParticle childXSDParticle = (XSDParticle) i.next();
XSDTerm childXSDTerm = childXSDParticle.getTerm();
if (childXSDTerm instanceof XSDElementDeclaration) {
XSDElementDeclaration eldeclaration = (XSDElementDeclaration) childXSDTerm;
if (!this.tags.containsKey(getFullDeclarationName(eldeclaration))) {
this.handleLeaf(eldeclaration);
this.handleElementDeclaration(eldeclaration);
}
} else if (childXSDTerm instanceof XSDModelGroup) {
this.handleContainer((XSDModelGroup) childXSDTerm);
}
}
}
private String getFullDeclarationName(XSDElementDeclaration eldeclaration) {
if (this.documentStructureDeclaration.getNamespace().isEmpty()) {
return eldeclaration.getName();
} else {
return this.documentStructureDeclaration.getNamespace() + ":" + eldeclaration.getName();
}
}
public void setDefinition(String fileName, java.net.URI schema) {
if (!fileName.equals(this.fileName) || !schema.toString().equals(this.schemaPath) || this.isDefinitionUpdated(schema.toString())) {
this.updateDefinition();
if(fileName.equals(this.fileName)) {
this.setLastDefinitionUpdate(schema.toString());
}
}
this.fileName = fileName;
this.schemaPath = schema.toString();
}
/**
* @deprecated use setDefinition
*/
public void setFileName(String fileName) {
if (!fileName.equals(this.fileName)) {
this.updateDefinition();
}
this.fileName = fileName;
}
/**
* @deprecated use setDefinition
*/
public void setSchema(java.net.URI schema) {
if (!schema.toString().equals(this.schemaPath) || this.isDefinitionUpdated(schema.toString())) {
this.updateDefinition();
this.schemaPath = schema.toString();
this.setLastDefinitionUpdate(schema.toString());
}
}
protected boolean isDefinitionUpdated(String definitionPath) {
try {
return this.lastModified < new File(definitionPath).lastModified();
} catch (Exception e) {
return false;
}
}
protected void setLastDefinitionUpdate(String definitionPath) {
try {
this.lastModified = new File(definitionPath).lastModified();
} catch (Exception e) {
}
}
private XSDSchema getSchema(String publicName, String schemaURIString) {
XSDSchema xsdSchema = null;
try {
String schemaLocation = DocumentCache.getInstance().getLocation(publicName, schemaURIString);
schemaLocation = FileUtils.addProtocol(schemaLocation);
ResourceSet resourceSet = new ResourceSetImpl();
XSDResourceFactoryImpl resourceFactoryImpl = new XSDResourceFactoryImpl();
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xsd", resourceFactoryImpl);
Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(null, resourceFactoryImpl);
resourceSet.getAdapterFactories().add(new XSDSchemaLocatorAdapterFactory());
URI uri = createURI(schemaURIString);
InputStream inputStream = resourceSet.getURIConverter().createInputStream(URI.createURI(schemaLocation));
XSDResourceImpl resource = (XSDResourceImpl) resourceSet.createResource(URI.createURI("*.xsd"));
resource.setURI(uri);
resource.load(inputStream, null);
xsdSchema = resource.getSchema();
} catch (Exception e) {
XMLEditorPlugin.logErrorMessage("Error retrieving Schema for publicName: " + publicName + " schemaURIString: " + schemaURIString, e);
}
return xsdSchema;
}
public static URI createURI(String uriString) {
return hasProtocol(uriString) ? URI.createURI(uriString) : URI.createFileURI(uriString);
}
private static boolean hasProtocol(String uri) {
boolean result = false;
if (uri != null) {
int index = uri.indexOf(":");
// assume protocol with be length 3 so that the'C' in 'C:/' is not
// interpreted as a protocol
if (index != -1 && index > 2) {
result = true;
}
}
return result;
}
}