/* * 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.util; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.edit.provider.ITreeItemContentProvider; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.workspace.ModelFolder; import org.teiid.designer.core.workspace.ModelProject; import org.teiid.designer.core.workspace.ModelResource; import org.teiid.designer.core.workspace.ModelWorkspace; import org.teiid.designer.core.workspace.ModelWorkspaceItem; import org.teiid.designer.core.workspace.ModelWorkspaceManager; /** * The ModelVisitorProcessor is used to walk a {@link ModelVisitor} implementation through one or more * models. * <p> * To use, simply create a {@link ModelVisitor visitor} and a ModelProcessor, and use the processor * to walk one or more objects by supply the starting point(s): * <pre> * final ModelVisitor visitor = new MyVisitor(); * final ModelVisitorProcessor processor = new ModelVisitorProcessor(visitor); * try { * processor.walk(startingPoint,ModelVisitorProcessor.DEPTH_INFINITE); * } catch (ModelerCoreException e) { * // handle the exception * } * </pre> * where <code>startingPoint</code> can be one of the following: * <ul> * <li>An {@link EObject}</li> * <li>An {@link Resource EMF Resource}</li> * <li>A {@link org.teiid.designer.core.workspace.ModelResource ModelResource}</li> * <li>A {@link org.teiid.designer.core.workspace.ModelFolder ModelFolder}</li> * <li>A {@link org.teiid.designer.core.workspace.ModelProject ModelProject}</li> * <li>A {@link org.teiid.designer.core.workspace.ModelWorkspace ModelWorkspace}</li> * <li>A {@link List list} of any of the above objects</li> * </ul> * and where <code>depth</code> is one of the following: * <ul> * <li>{@link org.teiid.designer.core.util.ModelVisitorProcessor#DEPTH_INFINITE ModelVisitorProcessor.DEPTH_INFINITE}</li> * <li>{@link org.teiid.designer.core.util.ModelVisitorProcessor#DEPTH_ONE ModelVisitorProcessor.DEPTH_ONE}</li> * <li>{@link org.teiid.designer.core.util.ModelVisitorProcessor#DEPTH_ZERO ModelVisitorProcessor.DEPTH_ZERO}</li> * </ul> * </p> * @see ModelVisitor. * * @since 8.0 */ public class ModelVisitorProcessor { /*======================================================================== * Constants defining the depth of resource tree traversal *========================================================================*/ /** * Depth constant (value 0) indicating a walk of the supplied object, but not any of its children. */ public static final int DEPTH_ZERO = 0; /** * Depth constant (value 1) indicating a walk of the supplied object and its direct children. */ public static final int DEPTH_ONE = 1; /** * Depth constant (value 2) indicating a walk of the supplied object and its direct and * indirect children at any depth. */ public static final int DEPTH_INFINITE = 2; /*======================================================================== * Constants defining the mode of obtaining the children of model objects *========================================================================*/ /** * Mode constant (value 0) indicating that all containment references should be followed. * For some metamodels, this exposes more objects than are visible to the user (e.g., in * a tree view via the item providers), but does navigate to <i>all</i> objects. */ public static final int MODE_ALL_CONTAINMENTS = 0; /** * Mode constant (value 1) indicating that visible objects should be processed. * For some metamodels, this exposes fewer objects than there actually are. */ public static final int MODE_VISIBLE_CONTAINMENTS = 1; /** * The default mode constant, currently set to {@link #MODE_ALL_CONTAINMENTS}. */ public static final int MODE_DEFAULT = MODE_ALL_CONTAINMENTS; protected ModelVisitor visitor; protected Navigator navigator; /** * Construct an instance of ModelWalker. * */ public ModelVisitorProcessor( final ModelVisitor visitor ) { this(visitor,MODE_DEFAULT); } /** * Construct an instance of ModelWalker. * */ public ModelVisitorProcessor( final ModelVisitor visitor, final int mode ) { super(); CoreArgCheck.isNotNull(visitor); assertValidMode(mode); this.visitor = visitor; switch(mode) { case MODE_VISIBLE_CONTAINMENTS: this.navigator = new ItemProviderNavigator(); break; case MODE_ALL_CONTAINMENTS: this.navigator = new ContainmentNavigator(); break; } } /** * Return the ModelVisitor that this processor is using. * @return the ModelVisitor; never null; */ public ModelVisitor getModelVisitor() { return this.visitor; } /** * Helper method to check that the mode is valid. * @param mode */ protected void assertValidMode( final int mode ) { if ( mode == MODE_ALL_CONTAINMENTS || mode == MODE_VISIBLE_CONTAINMENTS ) { return; } final String msg = ModelerCore.Util.getString("ModelVisitorProcessor.Invalid_mode"); //$NON-NLS-1$ throw new IllegalArgumentException(msg); } /** * Helper method to check that the depth is valid. * @param depth */ protected void assertValidDepth( final int depth ) { if ( depth == DEPTH_INFINITE || depth == DEPTH_ONE || depth == DEPTH_ZERO ) { return; } final String msg = ModelerCore.Util.getString("ModelVisitorProcessor.Invalid_depth"); //$NON-NLS-1$ throw new IllegalArgumentException(msg); } /** * Walk the {@link ModelWorkspace} to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param workspace the workspace to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the workspace */ public void walk( final ModelWorkspace workspace, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(workspace); assertValidDepth(depth); // visit this resource if (depth == DEPTH_ZERO) return; // visit the children final int nextDepth = (depth == DEPTH_INFINITE ? DEPTH_INFINITE : DEPTH_ZERO ); final ModelWorkspaceItem[] children = workspace.getChildren(); for (int i = 0; i < children.length; ++i) { final ModelWorkspaceItem child = children[i]; if ( child instanceof ModelProject ) { walk((ModelProject)child,nextDepth); } else if ( child instanceof ModelFolder ) { walk((ModelFolder)child,nextDepth); } else if ( child instanceof ModelResource ) { walk((ModelResource)child,nextDepth); } } } /** * Walk the supplied {@link ModelProject} to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param project the project to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the project */ public void walk( final ModelProject project, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(project); assertValidDepth(depth); // visit this resource if (depth == DEPTH_ZERO) return; // visit the children final int nextDepth = (depth == DEPTH_INFINITE ? DEPTH_INFINITE : DEPTH_ZERO ); final ModelWorkspaceItem[] children = project.getChildren(); for (int i = 0; i < children.length; ++i) { final ModelWorkspaceItem child = children[i]; if ( child instanceof ModelFolder ) { walk((ModelFolder)child,nextDepth); } else if ( child instanceof ModelResource ) { walk((ModelResource)child,nextDepth); } } } /** * Walk the supplied {@link ModelFolder} to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param folder the folder to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the folder */ public void walk( final ModelFolder folder, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(folder); assertValidDepth(depth); // visit this resource if (depth == DEPTH_ZERO) return; // visit the children final int nextDepth = (depth == DEPTH_INFINITE ? DEPTH_INFINITE : DEPTH_ZERO ); final ModelWorkspaceItem[] children = folder.getChildren(); for (int i = 0; i < children.length; ++i) { final ModelWorkspaceItem child = children[i]; if ( child instanceof ModelFolder ) { walk((ModelFolder)child,nextDepth); } else if ( child instanceof ModelResource ) { walk((ModelResource)child,nextDepth); } } } /** * Walk the supplied {@link ModelResource} to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param startingResource the model to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the model */ public void walk( final ModelResource startingResource, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(startingResource); assertValidDepth(depth); // visit this resource final Resource resource = startingResource.getEmfResource(); walk(resource,depth); } /** * Walk the supplied {@link Resource} to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param startingResource the EMF resource to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the resource */ public void walk( final Resource startingResource, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(startingResource); assertValidDepth(depth); // visit this resource if (!getModelVisitor().visit(startingResource) || depth == DEPTH_ZERO) return; // visit the children final int nextDepth = (depth == DEPTH_INFINITE ? DEPTH_INFINITE : DEPTH_ZERO ); final List children = startingResource.getContents(); final Iterator iter = children.iterator(); while (iter.hasNext()) { final EObject object = (EObject)iter.next(); walk(object,nextDepth); } } /** * Walk to the supplied depth the tree of model objects below the supplied {@link EObject}. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param startingResource the root of the tree of model objects to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the model objects */ public void walk( final EObject startingObject, final int depth ) throws ModelerCoreException { CoreArgCheck.isNotNull(startingObject); assertValidDepth(depth); // visit this resource if (!this.visitor.visit(startingObject) || depth == DEPTH_ZERO) return; // final URI uri = EcoreUtil.getURI(startingObject); // System.out.println("Visiting " + uri.fragment() + " (" + startingObject.eClass().getName() + ")"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ // visit the children final int nextDepth = (depth == DEPTH_INFINITE ? DEPTH_INFINITE : DEPTH_ZERO ); final Object children = this.navigator.getChildren(startingObject); if ( children instanceof List ) { final Iterator valueIter = ((List)children).iterator(); while (valueIter.hasNext()) { final Object obj = valueIter.next(); if ( obj instanceof EObject ) { walk((EObject) obj, nextDepth); } } } else if ( children instanceof EObject ) { walk((EObject)children,nextDepth); } if ( this.visitor instanceof ModelVisitorWithFinish ) { ((ModelVisitorWithFinish)this.visitor).postVisit(startingObject); } } /** * Walk each of the supplied objects to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param startingObjects the objects that are all to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @throws ModelerCoreException if there is an exception walking the model objects */ public void walk( final Collection startingObjects, final int depth ) throws ModelerCoreException { walk(startingObjects,depth,true); } /** * Walk each of the supplied objects to the supplied depth. * The {@link #getModelVisitor() model visitor} is only called when an {@link EObject} or * {@link Resource EMF Resource} is found. Of course, because the {@link #getModelVisitor() model visitor} * can decide whether or not children are to be visited, the supplied depth is really considered * to be the maximum depth allowed. * @param startingObjects the objects that are all to be walked; may not be null * @param depth the depth to walk; must be one of {@link #DEPTH_INFINITE DEPTH_INFINITE}, * {@link #DEPTH_ONE DEPTH_ONE} or {@link #DEPTH_ZERO DEPTH_ZERO} * @param skipDuplicateBranches true if any duplicate branches in the <code>startingObjects</code> * should be skipped. An object is considered to be a duplicate branch if it is a child of another * object in the <code>startingObjects</code> list * @throws ModelerCoreException if there is an exception walking the model objects */ public void walk( final Collection startingObjects, final int depth, final boolean skipDuplicateBranches ) throws ModelerCoreException { CoreArgCheck.isNotNull(startingObjects); assertValidDepth(depth); final Collection roots = skipDuplicateBranches ? removeDuplicates(startingObjects) : startingObjects; final Iterator iter = roots.iterator(); while (iter.hasNext()) { final Object object = iter.next(); if ( object != null ) { if ( object instanceof Resource ) { walk((Resource)object,depth); } else if ( object instanceof EObject ) { walk((EObject)object,depth); } else if ( object instanceof ModelResource ) { walk((ModelResource)object,depth); } else if ( object instanceof ModelProject ) { walk((ModelProject)object,depth); } else if ( object instanceof ModelFolder ) { walk((ModelFolder)object,depth); } else if ( object instanceof ModelWorkspace ) { walk((ModelWorkspace)object,depth); } } } } protected Collection removeDuplicates( final Collection startingObjects ) { if ( startingObjects.size() < 2 ) { return startingObjects; // definitely no duplicates! } if ( startingObjects instanceof Set ) { return startingObjects; // a Set definitely has no duplicates! } final Collection copy = new LinkedList(startingObjects); // See if there are any ModelWorkspaceItems ... final Set setOfUnique = new HashSet(); boolean includesModelWorkspaceItems = false; final Iterator iter = copy.iterator(); while (iter.hasNext() && !includesModelWorkspaceItems) { final Object obj = iter.next(); if ( obj instanceof ModelWorkspaceItem ) { includesModelWorkspaceItems = true; } if ( setOfUnique.contains(obj) ) { iter.remove(); } setOfUnique.add(obj); } // There should be no duplicates ... final Iterator iter2 = copy.iterator(); while (iter2.hasNext()) { final Object obj = iter2.next(); final boolean foundDuplicateBranch = isAncestorInList(obj,copy,includesModelWorkspaceItems); if ( foundDuplicateBranch ) { iter2.remove(); } } return copy; } protected boolean isAncestorInList( final Object child, final Collection inputs, final boolean useModelWorkspace ) { Object parent = null; ModelResource modelResource = null; if ( child instanceof EObject ) { final EObject childObject = (EObject)child; parent = childObject.eContainer(); if ( parent == null ) { parent = childObject.eResource(); if ( useModelWorkspace && parent instanceof Resource ) { // Find the ModelResource for the resource, since the inputs might contain // the parent Resource or the ModelResource ... modelResource = ModelWorkspaceManager.getModelWorkspaceManager().findModelResource((Resource)parent); } } } // Need to find the ancestor for other types of children only if using the ModelWorkspace else if ( useModelWorkspace ) { if ( child instanceof Resource ) { // Find the ModelResource for the resource ... final ModelResource mr = ModelWorkspaceManager.getModelWorkspaceManager().findModelResource((Resource)child); if ( mr != null ) { parent = mr.getParent(); } } else if ( child instanceof ModelWorkspace ) { return false; } else if ( child instanceof ModelWorkspaceItem ) { // Find the ModelResource for the resource ... parent = ((ModelWorkspaceItem)child).getParent(); } } // See if the parent is in the list ... if ( parent == null ) { return false; // there is no parent, so don't continue ... } if ( inputs.contains(parent) ) { return true; } if ( modelResource != null ) { if ( inputs.contains(modelResource) ) { return true; } } return isAncestorInList(parent,inputs,useModelWorkspace); } protected abstract class Navigator { /** * Return the children for the supplied parent object. * @param parent * @return the List of children, the sole child, or null if there are no children. */ public abstract Object getChildren(final EObject parent ); } /** * This navigator examines the containment features, and obtains their values * using the reflective methods. */ protected class ContainmentNavigator extends Navigator { @Override public Object getChildren( final EObject parent ) { return parent.eContents(); } } /** * This navigator uses the ItemProvider to obtain * ItemProviderNavigator */ protected class ItemProviderNavigator extends Navigator { private AdapterFactory adapterFactory; public ItemProviderNavigator() { this.adapterFactory = ModelerCore.getMetamodelRegistry().getAdapterFactory(); } @Override public Object getChildren(final EObject parent ) { final ITreeItemContentProvider provider = (ITreeItemContentProvider)this.adapterFactory.adapt(parent,ITreeItemContentProvider.class); if ( provider != null ) { return provider.getChildren(parent); } return null; } } }