/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.core.refactor;
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.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.designer.core.util.ModelVisitor;
import org.teiid.designer.metamodels.diagram.Diagram;
import org.teiid.designer.metamodels.diagram.DiagramEntity;
/**
* The ExternalReferenceVisitor walks a model (or part of a model) and records the references to external resources.
*
* @since 8.0
*/
public class ExternalReferenceVisitor implements ModelVisitor {
public static final boolean DEFAULT_RESOLVE_REFERENCE = false;
public static final boolean DEFAULT_INCLUDE_RESOLVED_REFERENCES = true;
public static final boolean DEFAULT_INCLUDE_DIAGRAM_REFERENCES = true;
private final Map referencedObjectsByResourceUri;
private final Resource resource;
private boolean includeResolvedReferences = DEFAULT_INCLUDE_RESOLVED_REFERENCES;
private boolean includeDiagramReferences = DEFAULT_INCLUDE_DIAGRAM_REFERENCES;
/**
* Construct an instance of ExternalReferenceVisitor.
*
* @param resource the resource that this visitor is visiting; may not be null
*/
public ExternalReferenceVisitor( final Resource resource ) {
super();
CoreArgCheck.isNotNull(resource);
this.resource = resource;
this.referencedObjectsByResourceUri = new HashMap();
}
/**
* Return the resource that this object is visiting over.
*
* @return the resource; never null
*/
public Resource getResource() {
return this.resource;
}
/**
* @return
*/
public boolean isIncludeResolvedReferences() {
return this.includeResolvedReferences;
}
/**
* @param b
*/
public void setIncludeResolvedReferences( boolean b ) {
this.includeResolvedReferences = b;
}
/**
* @return
*/
public boolean isIncludeDiagramReferences() {
return this.includeDiagramReferences;
}
/**
* @param b
*/
public void setIncludeDiagramReferences( boolean b ) {
this.includeDiagramReferences = b;
}
/**
* @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource)
*/
@Override
public boolean visit( final Resource resource ) {
// Nothing to do for a resource, but we want to visit the children
return true;
}
/**
* This method should be called only on objects by following ownership references.
*
* @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject)
*/
@Override
public boolean visit( final EObject object ) {
// If we are ignoring external references by a DiagramEntry then return ...
if (!this.includeDiagramReferences && (object instanceof DiagramEntity || object instanceof Diagram)) {
return false;
}
// Iterate over all the features that are non-containment ...
final Iterator iter = object.eClass().getEAllReferences().iterator();
while (iter.hasNext()) {
final EReference eReference = (EReference)iter.next();
if (!eReference.isContainment() && !eReference.isContainer() && !eReference.isVolatile()) {
// The reference is NOT the container NOR a containment feature ...
final Object value = object.eGet(eReference, DEFAULT_RESOLVE_REFERENCE);
if (eReference.isMany()) {
// There may be many values ...
final Iterator valueIter = ((List)value).iterator();
while (valueIter.hasNext()) {
final Object valueInList = valueIter.next();
if (valueInList instanceof EObject) {
processReference((EObject)valueInList);
}
}
} else {
// There may be 0..1 value ...
if (value != null && value instanceof EObject) {
processReference((EObject)value);
}
}
}
}
return true; // always visit children ...
}
/**
* @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject)
*/
public boolean processReference( final EObject object ) {
if (object == null) {
return false;
}
// Check if the object is an EMF proxy ...
if (object.eIsProxy()) {
if (object instanceof InternalEObject) {
final InternalEObject iObject = (InternalEObject)object;
final URI proxyUri = iObject.eProxyURI();
CoreArgCheck.isNotNull(proxyUri);
recordResourceUsage(proxyUri, object);
}
return false;
}
// It might be resolved but still be an object in another resource
final Resource resourceForObject = object.eResource();
if (resourceForObject == this.resource) {
// The object is in the resource being visited
return false;
}
// The object is NOT in the resource ...
if (resourceForObject != null) { // may be null if ref'ed object is transient
// Record it's usage
recordResourceUsage(resourceForObject, object);
}
return false;
}
/**
* Return the collection of {@link ExternalReferenceVisitor.ExternalReferences ExternalReferences}.
*
* @return the collection of external references; never null
*/
public Collection getExternalReferences() {
return this.referencedObjectsByResourceUri.values();
}
/**
* Record that the supplied resource was referenced by an object in this resource. This method is called when the EObject is
* resolved.
*
* @param externalResource the external resource referenced by this resource; never null
* @param referencedObject the referenced object that is in the external resource; never null
*/
protected void recordResourceUsage( final Resource externalResource,
final EObject referencedObject ) {
// Get the URI of the resource ...
final URI resourceUri = externalResource.getURI();
// And find the list of external references for that resource ...
final ExternalReferences refedObjs = getReferencedObjects(resourceUri, true);
refedObjs.setResource(externalResource);
if (this.includeResolvedReferences) {
refedObjs.addReferencedObject(referencedObject);
}
}
/**
* Record that the supplied object URI to an external object was used in this resource. This method is called when the
* referenced object has not yet been resolved.
*
* @param uriToExternalObject the URI to the external object; never null
* @param referencedObject the referenced object that is in the external resource; never null
*/
protected void recordResourceUsage( final URI uriToExternalObject,
final EObject referencedObject ) {
// Get the URI of the resource ...
URI resourceUri = uriToExternalObject.trimFragment();
// Make the relative URI absolute if necessary
if (resourceUri != null && resourceUri.isRelative()) {
resourceUri = resourceUri.resolve(resource.getURI());
}
// And find the list of external references for that resource ...
final ExternalReferences refedObjs = getReferencedObjects(resourceUri, true);
refedObjs.addReferencedObject(referencedObject);
}
/**
* Helper to find the referenced object container for the resource with the supplied URI.
*
* @param resourceUri the URI of the resource; may not be null
* @param createIfNeeded true if a ExternalReferences object should be created for the supplied resource URI if none already
* exists, or false otherwise.
* @return the ExternalReferences for the resource URI; null only if there was no collection for the reosurce and
* <code>createIfNeeded</code> was false
*/
protected ExternalReferences getReferencedObjects( final URI resourceUri,
final boolean createIfNeeded ) {
ExternalReferences refedObjs = (ExternalReferences)this.referencedObjectsByResourceUri.get(resourceUri);
if (refedObjs == null && createIfNeeded) {
// First time we've seen the resource, so create the container
refedObjs = new ExternalReferences(resourceUri);
this.referencedObjectsByResourceUri.put(resourceUri, refedObjs);
}
return refedObjs;
}
public class ExternalReferences {
private final URI resourceUri;
private final Set objects;
private Resource resource;
protected ExternalReferences( final URI resourceUri ) {
CoreArgCheck.isNotNull(resourceUri);
this.resourceUri = resourceUri;
this.objects = new HashSet();
}
/**
* @param referencedObject
*/
protected void addReferencedObject( EObject referencedObject ) {
this.objects.add(referencedObject);
}
/**
* @return
*/
public Collection getReferencedObjects() {
return objects;
}
/**
* @return
*/
public URI getResourceUri() {
return resourceUri;
}
/**
* @return
*/
public Resource getResource() {
return resource;
}
/**
* @param resource
*/
protected void setResource( final Resource resource ) {
if (this.resource != null) {
CoreArgCheck.isTrue(resource == this.resource, "Resource cannot be reset to itself"); //$NON-NLS-1$
return;
}
this.resource = resource;
}
}
}