blob: 4d7076d9f14836987961eeb61061def2bd322d17 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.wsdl.ui.internal.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.wst.wsdl.Binding;
import org.eclipse.wst.wsdl.Definition;
import org.eclipse.wst.wsdl.Import;
import org.eclipse.wst.wsdl.Message;
import org.eclipse.wst.wsdl.Part;
import org.eclipse.wst.wsdl.Port;
import org.eclipse.wst.wsdl.PortType;
import org.eclipse.wst.wsdl.Types;
import org.eclipse.wst.wsdl.WSDLElement;
import org.eclipse.wst.wsdl.XSDSchemaExtensibilityElement;
import org.eclipse.wst.wsdl.ui.internal.WSDLEditorPlugin;
import org.eclipse.wst.wsdl.util.WSDLConstants;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xsd.ui.internal.common.util.XSDDirectivesManager;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This class performs cleanup/removal of unused WSDL imports, unused XSD imports and includes from
* inline XML Schemas, and xmlns entries.
*
*/
public class WSDLImportManager extends XSDDirectivesManager
{
// The definition to modify
protected Definition definition;
// Map of inline schemas and their used prefixes
protected Map schemaToUsedPrefixesMap;
// Map of inline schemas and their unused prefixes
protected Map schemaToUnusedPrefixesMap;
// Set of used prefixes in the WSDL document's XML namespace table
protected Set usedWSDLPrefixes;
// Set of unused prefixes in the WSDL document's XML namespace table
protected Set unusedWSDLPrefixes;
// List of used definitions, computed from the cross referencer
protected List usedDefinitions;
// List of unused WSDL Imports to be removed
protected List unusedWSDLImports;
public WSDLImportManager()
{
super();
}
public static void removeUnusedImports(Definition definition)
{
// Only do the removal if the preference is turned on
if (WSDLEditorPlugin.getInstance().getRemoveImportSetting())
{
WSDLImportManager mgr = new WSDLImportManager();
mgr.performRemoval(definition);
mgr.cleanup();
}
}
/**
* Main method to do the clean up.
* @param definition
*/
public void performRemoval(Definition definition)
{
this.definition = definition;
// Compute unused WSDL Imports, unused imports from inline schemas, and unused prefixes
computeUnusedImports(definition);
// Remove the imports
removeUnusedImports();
// Remove the prefixes
removeUnusedPrefixes();
}
/**
* Returns the list of all the unused directives from inline schemas to remove
*
* @return the list of all the unused directives from inline schemas to remove
*/
public List getUnusedImports()
{
return unusedDirectives;
}
/**
* Returns the list of the unused WSDL imports to remove
*
* @return list of unused WSDL Imports
*/
public List getWSDLUnusedImports()
{
return unusedWSDLImports;
}
/**
* Returns the list of unused prefixes in the XML Namespace table of the WSDL file
* @return the list of unused prefixes in the XML Namespace table of the WSDL file
*/
public Set getUnusedWSDLPrefixes()
{
return unusedWSDLPrefixes;
}
/**
* This computes the list of unused imports for a definition and its inline schemas
* @param definition
*/
protected void computeUnusedImports(Definition definition)
{
List usedSchemas = new ArrayList();
unusedDirectives = new ArrayList();
usedPrefixes = new HashSet();
schemaToPrefixMap = new HashMap();
schemaToUsedPrefixesMap = new HashMap();
schemaToUnusedPrefixesMap = new HashMap();
usedDefinitions = new ArrayList();
unusedWSDLImports = new ArrayList();
usedWSDLPrefixes = new HashSet();
unusedWSDLPrefixes = new HashSet();
Types types = definition.getETypes();
if (types != null)
{
try
{
// Compute cross references
Map xsdNamedComponentUsage = TopLevelComponentCrossReferencer.find(definition);
// Now find unused imports from each inline schema
for (Iterator schemaIterator = types.getSchemas().iterator(); schemaIterator.hasNext();)
{
XSDSchema schema = (XSDSchema) schemaIterator.next();
// need to reset this for each inline schema
usedPrefixes.clear();
// Step One. Find additional unused imports using cross referencer
doCrossReferencer(schema, usedSchemas, xsdNamedComponentUsage);
// Step Two. Update the unusedImport list given the list of used schemas obtained from cross referencing
addToUnusedImports(schema, usedSchemas);
// Step Three. Compute unused xmlns entries
computeUnusedPrefixes(schema);
}
}
catch (Exception e)
{
unusedDirectives.clear();
schemaToPrefixMap.clear();
}
}
// Next, compute unused WSDL imports
computeUsedDefinitions(definition);
// Analyze the current xml ns table for the definition and determine list of used prefixes
analyzeDefinitionXMLNSTable(definition);
// Now compute the list of unused imports
computeUnusedWSDLImports(definition);
// Finally, compute unused prefixes for the definition
unusedWSDLPrefixes = computeUnusedPrefixes(definition);
}
/*
* This computes the list of unused WSDL Imports and updates list of used prefixes
*/
private void computeUsedDefinitions(Definition definition)
{
// Find cross references
Map wsdlNamedComponentUsage = WSDLNamedComponentCrossReferencer.find(definition);
Iterator iterator = wsdlNamedComponentUsage.keySet().iterator();
while (iterator.hasNext())
{
WSDLElement wsdlElement = (WSDLElement)iterator.next();
Definition wsdlElementDef = wsdlElement.getEnclosingDefinition();
if (wsdlElementDef == definition)
{
continue;
}
Collection collection = (Collection)wsdlNamedComponentUsage.get(wsdlElement);
for (Iterator iter = collection.iterator(); iter.hasNext(); )
{
Setting setting = (Setting) iter.next();
Object obj = setting.getEObject();
// If component is used, then mark its definition as used which we will
// use later to compare with the import's definition
if (isComponentUsed(obj, definition, wsdlElementDef))
{
if (!usedDefinitions.contains(wsdlElementDef))
usedDefinitions.add(wsdlElementDef);
}
}
}
}
/**
* @param definition
*/
private void computeUnusedWSDLImports(Definition definition)
{
for (Iterator iter = definition.getEImports().iterator(); iter.hasNext(); )
{
Object obj = iter.next();
if (obj instanceof Import)
{
Import imp = (Import)obj;
Definition def = imp.getEDefinition();
XSDSchema schema = imp.getESchema();
boolean isUsed = false;
if (def != null)
{
for (Iterator usedDef = usedDefinitions.iterator(); usedDef.hasNext();)
{
Definition used = (Definition) usedDef.next();
if (used == def)
{
isUsed = true;
break;
}
}
}
if (schema != null)
{
String ns = imp.getNamespaceURI();
Map defPrefixMap = definition.getNamespaces();
Set wsdlKeys = defPrefixMap.keySet();
boolean prefixFound = false;
String pref = null;
for (Iterator iterator = wsdlKeys.iterator(); iterator.hasNext();)
{
pref = (String)iterator.next();
String aNS = (String)defPrefixMap.get(pref);
if ((aNS != null && aNS.equals(ns)) || (aNS == ns))
{
prefixFound = true;
break;
}
}
if (prefixFound && (usedPrefixes.contains(pref) || usedWSDLPrefixes.contains(pref)))
isUsed = true;
}
if (!isUsed)
{
unusedWSDLImports.add(imp);
}
}
}
}
/**
* Removes the unused directives and unused WSDL imports
*/
protected void removeUnusedImports()
{
super.removeUnusedImports();
Iterator iter = unusedWSDLImports.iterator();
while (iter.hasNext())
{
Import wsdlImport = (Import) iter.next();
Element element = wsdlImport.getElement();
Document doc = element.getOwnerDocument();
if (doc instanceof IDOMNode)
((IDOMNode)doc).getModel().aboutToChangeModel();
try
{
if (!removeTextNodesBetweenNextElement(element))
{
removeTextNodeBetweenPreviousElement(element);
}
element.getParentNode().removeChild(element);
}
finally
{
if (doc instanceof IDOMNode)
((IDOMNode)doc).getModel().changedModel();
}
}
}
/**
* Mark definition's target namespace prefix, and WSDL namespace prefix as used.
* Also find prefixes used in the document
* @param definition
*/
private void analyzeDefinitionXMLNSTable(Definition definition)
{
String prefix = definition.getPrefix(definition.getTargetNamespace());
usedWSDLPrefixes.add(prefix);
String wsdlPrefix = definition.getPrefix(WSDLConstants.WSDL_NAMESPACE_URI);
usedWSDLPrefixes.add(wsdlPrefix);
Element element = definition.getElement();
NodeList childElements = element.getChildNodes();
int length = childElements.getLength();
for (int index = 0; index < length; index++)
{
Node node = childElements.item(index);
if (node instanceof Element)
{
traverseDOMElement((Element)node, definition);
}
}
}
/**
* Removes the unused prefixes from the inline schema's XML namespace tables as well as the
* unused prefixes in the WSDL's namespace table
*/
protected void removeUnusedPrefixes()
{
super.removeUnusedPrefixes();
Map defPrefixMap = definition.getNamespaces();
for (Iterator iter = unusedWSDLPrefixes.iterator(); iter.hasNext(); )
{
String string = (String)iter.next();
// bug, key is "" not null
defPrefixMap.remove(string != null ? string : ""); //$NON-NLS-1$
Element element = definition.getElement();
if (element != null)
{
if (string != null)
element.removeAttribute(XMLNS + ":" + string); //$NON-NLS-1$
else
element.removeAttribute(XMLNS);
}
}
}
/**
* Compute the unused prefixes
* @param definition
* @return set of unused prefixes
*/
private Set computeUnusedPrefixes(Definition definition)
{
Map defPrefixMap = definition.getNamespaces();
Set wsdlKeys = defPrefixMap.keySet();
Set netSet = new HashSet();
Types eTypes = definition.getETypes();
List extElements = new ArrayList();
if (eTypes != null)
{
extElements = definition.getETypes().getEExtensibilityElements();
for (Iterator eeIter = extElements.iterator(); eeIter.hasNext(); )
{
Object ee = eeIter.next();
if (ee instanceof XSDSchemaExtensibilityElement)
{
XSDSchemaExtensibilityElement xsdEx = (XSDSchemaExtensibilityElement)ee;
XSDSchema inlineXSD = xsdEx.getSchema();
usedWSDLPrefixes.add(inlineXSD.getSchemaForSchemaQNamePrefix());
}
}
}
for (Iterator wsdlIter = wsdlKeys.iterator(); wsdlIter.hasNext(); )
{
String aWsdlPrefix = (String)wsdlIter.next();
if (aWsdlPrefix != null && aWsdlPrefix.equals("")) aWsdlPrefix = null;
String nsForWSDLPrefix = (String)defPrefixMap.get(aWsdlPrefix);
// If the wsdl prefix is not referenced by any wsdl component other than
// the import then check if the inline schemas reference that prefix/ns.
if (!usedWSDLPrefixes.contains(aWsdlPrefix))
{
boolean canRemoveWSDLPrefix = true;
for (Iterator eeIter = extElements.iterator(); eeIter.hasNext(); )
{
Object ee = eeIter.next();
if (ee instanceof XSDSchemaExtensibilityElement)
{
XSDSchemaExtensibilityElement xsdEx = (XSDSchemaExtensibilityElement)ee;
XSDSchema inlineXSD = xsdEx.getSchema();
Map inlineXSDMap = inlineXSD.getQNamePrefixToNamespaceMap();
Set usedInlineXSDPrefixes = (Set)schemaToUsedPrefixesMap.get(inlineXSD);
if (usedInlineXSDPrefixes.contains(aWsdlPrefix))
{
String nsForXSDPrefix = (String)inlineXSDMap.get(aWsdlPrefix);
if ((nsForXSDPrefix != null && nsForXSDPrefix.equals(nsForWSDLPrefix)) ||
(nsForXSDPrefix == null && nsForWSDLPrefix == null))
{
canRemoveWSDLPrefix = false;
}
}
}
}
if (canRemoveWSDLPrefix)
netSet.add(aWsdlPrefix);
}
}
return netSet;
}
/**
* Need to keep track of the inline schemas and their prefixes
*/
protected void doAdditionalProcessing(XSDSchema schema)
{
schemaToUsedPrefixesMap.put(schema, new HashSet(usedPrefixes));
schemaToUnusedPrefixesMap.put(schema, new HashSet(unusedPrefixes));
}
/**
* Find prefixes that are in the document.
* @param element
* @param definition
*/
private void traverseDOMElement(Element element, Definition definition)
{
String prefix = element.getPrefix();
usedWSDLPrefixes.add(prefix);
NamedNodeMap attrs = element.getAttributes();
int numOfAttrs = attrs.getLength();
for (int index = 0; index < numOfAttrs; index++)
{
Node node = attrs.item(index);
String attrPrefix = node.getPrefix();
if (attrPrefix != null && !attrPrefix.equals(XMLNS)) // This is to avoid adding the xmlns for the schema attributes
{
usedWSDLPrefixes.add(attrPrefix);
}
String attr = node.getLocalName();
if (attr != null)
{
String value = node.getNodeValue();
if (value == null) continue;
if (attr.equals(WSDLConstants.BINDING_ATTRIBUTE) ||
attr.equals(WSDLConstants.TYPE_ATTRIBUTE) ||
attr.equals(WSDLConstants.ELEMENT_ATTRIBUTE) ||
attr.equals(WSDLConstants.MESSAGE_ATTRIBUTE))
{
usedWSDLPrefixes.add(extractPrefix(value));
}
}
}
NodeList childElements = element.getChildNodes();
int length = childElements.getLength();
for (int index = 0; index < length; index++)
{
Node node = childElements.item(index);
if (node instanceof Element)
{
if (XSDConstants.SCHEMA_ELEMENT_TAG.equals(((Element)node).getLocalName()))
{
usedWSDLPrefixes.add(node.getPrefix());
}
else
{
traverseDOMElement((Element)node, definition);
}
}
}
}
/**
* Determines if the object to be analyzed is referenced by the inline schema
*/
protected boolean isComponentUsed(Object obj, XSDSchema schema, XSDSchema targetSchema)
{
if (obj instanceof XSDConcreteComponent)
{
return super.isComponentUsed(obj, schema, targetSchema);
}
else if (obj instanceof Part)
{
Part part = (Part) obj;
XSDElementDeclaration elem = part.getElementDeclaration();
XSDTypeDefinition type = part.getTypeDefinition();
if (elem != null)
{
// This reference belongs to the imported schema, so it is used.
if (elem.getSchema() == targetSchema)
{
String value = part.getElement().getAttribute(WSDLConstants.ELEMENT_ATTRIBUTE);
String pref = extractPrefix(value);
usedPrefixes.add(pref);
usedWSDLPrefixes.add(pref);
return true;
}
}
if (type != null)
{
// This reference belongs to the imported schema, so it is used.
if (type.getSchema() == targetSchema)
{
String value = part.getElement().getAttribute(WSDLConstants.TYPE_ATTRIBUTE);
String pref = extractPrefix(value);
usedPrefixes.add(pref);
usedWSDLPrefixes.add(pref);
return true;
}
}
}
return false;
}
/**
* clear out maps
*/
protected void clearMaps()
{
super.clearMaps();
if (schemaToUsedPrefixesMap != null)
schemaToUsedPrefixesMap.clear();
if (schemaToUnusedPrefixesMap != null)
schemaToUnusedPrefixesMap.clear();
if (usedDefinitions != null)
usedDefinitions.clear();
}
/**
* Determines if the object to be analyzed is referenced by the definition
* @param obj
* @param definition
* @param targetDefinition
* @return
*/
protected boolean isComponentUsed(Object obj, Definition definition, Definition targetDefinition)
{
if (obj instanceof WSDLElement)
{
WSDLElement component = (WSDLElement) obj;
if (component == definition || component instanceof Definition)
{
return false;
}
return true;
}
return false;
}
/**
* See cross referencer for more details.
*/
protected static class WSDLNamedComponentCrossReferencer extends EcoreUtil.CrossReferencer
{
private static final long serialVersionUID = 1L;
protected WSDLNamedComponentCrossReferencer(EObject eObject)
{
super(eObject);
}
protected WSDLNamedComponentCrossReferencer(Resource resource)
{
super(resource);
}
protected WSDLNamedComponentCrossReferencer(ResourceSet resourceSet)
{
super(resourceSet);
}
protected WSDLNamedComponentCrossReferencer(Collection emfObjects)
{
super(emfObjects);
}
protected boolean containment(EObject eObject)
{
// Create an empty setting collection for any named component.
//
if (isWSDLNamedComponent(eObject))
{
getCollection(eObject);
}
return true;
}
protected static boolean isWSDLNamedComponent(EObject eObject)
{
return eObject instanceof Message || eObject instanceof PortType || eObject instanceof Binding || eObject instanceof Port;
}
protected boolean crossReference(EObject eObject, EReference eReference, EObject crossReferencedEObject)
{
// Add a setting for any named component in an interesting reference.
//
return !eReference.isVolatile() &&
eReference.isChangeable() &&
isWSDLNamedComponent(crossReferencedEObject);
}
/**
* Returns a map of all XSDNamedComponent cross references in the content
* tree.
*/
public static Map find(EObject eObject)
{
WSDLNamedComponentCrossReferencer result = new WSDLNamedComponentCrossReferencer(eObject);
result.crossReference();
result.done();
return result;
}
/**
* Returns a map of all XSDNamedComponent cross references in the content
* tree.
*/
public static Map find(Resource resource)
{
WSDLNamedComponentCrossReferencer result = new WSDLNamedComponentCrossReferencer(resource);
result.crossReference();
result.done();
return result;
}
/**
* Returns a map of all XSDNamedComponent cross references in the content
* tree.
*/
public static Map find(ResourceSet resourceSet)
{
WSDLNamedComponentCrossReferencer result = new WSDLNamedComponentCrossReferencer(resourceSet);
result.crossReference();
result.done();
return result;
}
}
}