/* * 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.compare.ui.tree; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IPath; import org.eclipse.emf.common.util.BasicEMap; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingHelper; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; import org.teiid.core.designer.util.CoreStringUtil; import org.teiid.designer.compare.CompareFactory; import org.teiid.designer.compare.DifferenceDescriptor; import org.teiid.designer.compare.DifferenceReport; import org.teiid.designer.compare.DifferenceType; import org.teiid.designer.compare.PropertyDifference; import org.teiid.designer.compare.impl.CompareFactoryImpl; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.metamodels.core.Annotation; import org.teiid.designer.metamodels.core.AnnotationContainer; import org.teiid.designer.metamodels.core.CorePackage; import org.teiid.designer.metamodels.diagram.Diagram; import org.teiid.designer.metamodels.diagram.DiagramContainer; import org.teiid.designer.metamodels.diagram.DiagramEntity; import org.teiid.designer.metamodels.transformation.MappingClassSet; import org.teiid.designer.metamodels.transformation.MappingClassSetContainer; import org.teiid.designer.metamodels.transformation.TransformationContainer; import org.teiid.designer.metamodels.transformation.TransformationMappingRoot; /** * MappingTreeContentProvider * * @since 8.0 */ public class MappingTreeContentProvider implements ITreeContentProvider { private List /* of DifferenceReport*/diffReports; private HashMap hmTargetToRelatedNodes; private ArrayList arylContainerNodes; private boolean bInsertRelatedNodes = true; /** Factory needed to create {@link PropertyDifference}s. */ private CompareFactory compareFactory = new CompareFactoryImpl(); /** * A <code>Map</code> keyed by target <code>EObject</code> model relative path. Values are the {@link PropertyDifference} * associated with the object's additional property (like description). */ private Map propDiffMap = null; public MappingTreeContentProvider() { super(); } /** * Obtains (creating if necessary) the EObject property difference map used to store additional property differences (like * descriptions). Keyed by EObject model relative path. Value is {@link PropertyDifference}. * * @return the map */ private Map getPropertyDifferenceMap() { if (this.propDiffMap == null) { this.propDiffMap = new PropertyDifferenceMap(); } return this.propDiffMap; } /** * Indicates if the {@link EObject} of the given <code>Mapping</code> has additional property differences. * * @param theMapping the mapping being checked * @return <code>true</code>if has additional property differences; <code>false</code> otherwise. */ public boolean hasAdditionalPropertyDifferences( Mapping theMapping ) { EObject eobj = MappingTreeContentProvider.getEObjectForMapping(theMapping); return ((eobj != null) && (this.propDiffMap != null) && this.propDiffMap.containsKey(eobj)); } public boolean hasChildAdditionalPropertyDifferences( Mapping theMapping ) { if (this.propDiffMap == null) { return false; } EObject eobj = MappingTreeContentProvider.getEObjectForMapping(theMapping); if (eobj == null) { return false; } return hasChildAdditionalPropertyDifferences(eobj); } public boolean hasChildAdditionalPropertyDifferences( final EObject eobj ) { final Iterator children = eobj.eContents().iterator(); boolean result = false; while (children.hasNext() && !result) { final EObject nextChild = (EObject)children.next(); if (this.propDiffMap.containsKey(nextChild)) { result = true; } else { result = hasChildAdditionalPropertyDifferences(nextChild); } } return result; } public void setDifferenceReports( List diffReports ) { hmTargetToRelatedNodes = null; arylContainerNodes = null; this.propDiffMap = null; this.diffReports = diffReports; if (this.diffReports != null) { Iterator it = this.diffReports.iterator(); // this implies that a series of reports can be parsed into // a common set of collections. verify that. while (it.hasNext()) { DifferenceReport drTemp = (DifferenceReport)it.next(); parseReport(drTemp); } } } private void parseReport( DifferenceReport drReport ) { /* * Analyze the report and parse out certain content into Maps for later use. */ if (drReport == null) { return; } // this is the root of all mappings Mapping mapping = drReport.getMapping(); if (mapping != null) { MappingHelper helper = mapping.getHelper(); if (helper instanceof DifferenceDescriptor) { parseChildren(drReport); } } } private HashMap getTargetToRelatedMap() { if (hmTargetToRelatedNodes == null) { hmTargetToRelatedNodes = new HashMap(); } return hmTargetToRelatedNodes; } private ArrayList getcontainerNodesArray() { if (arylContainerNodes == null) { arylContainerNodes = new ArrayList(); } return arylContainerNodes; } public static EObject getEObjectForMapping( Mapping mapping ) { EObject eoResult = null; MappingHelper helper = mapping.getHelper(); if (helper instanceof DifferenceDescriptor) { final DifferenceDescriptor desc = (DifferenceDescriptor)helper; final DifferenceType type = desc.getType(); if (type.getValue() == DifferenceType.DELETION) { final List inputs = mapping.getInputs(); final EObject input = inputs.isEmpty() ? null : (EObject)inputs.get(0); if (input != null) { eoResult = input; } } else { final List outputs = mapping.getOutputs(); final EObject output = outputs.isEmpty() ? null : (EObject)outputs.get(0); final List inputs = mapping.getInputs(); final EObject input = inputs.isEmpty() ? null : (EObject)inputs.get(0); if (output != null) { eoResult = output; } else if (input != null) { eoResult = input; } } } return eoResult; } private void parseChildren( Object mapping ) { /* For each container... * 1. get children. These are always of type Mapping. * 2. for each child, * - get the EObject for that mapping * - get the target of that EObject * - using the target as a key, add the child (the Mapping!) to the Map (see addEntry) * */ EObject eo = null; // the 'mapping' is either the root (DiffReport) or a mapping under it: if (mapping instanceof Mapping) { eo = getEObjectForMapping((Mapping)mapping); } if (eo != null && eo instanceof DiagramContainer) { // save the container node so we can ignore it later getcontainerNodesArray().add(mapping); final Object[] children = getTheChildren(mapping); for (int i = 0, j = children.length; i < j; i++) { // special processing for this type: EObject eoChild = getEObjectForMapping((Mapping)children[i]); if (eoChild instanceof Diagram) { EObject eoTarget = ((Diagram)eoChild).getTarget(); addEntry(eoTarget, (Mapping)children[i]); } else if (eoChild instanceof DiagramEntity) { EObject eoTarget = ((DiagramEntity)eoChild).getDiagram().getTarget(); addEntry(eoTarget, (Mapping)children[i]); } } } else if (eo != null && eo instanceof TransformationContainer) { // save the container node so we can ignore it later getcontainerNodesArray().add(mapping); final Object[] children = getTheChildren(mapping); for (int i = 0, j = children.length; i < j; i++) { EObject eoChild = getEObjectForMapping((Mapping)children[i]); if (eoChild instanceof TransformationMappingRoot) { // special processing for this type: EObject eoTarget = ((TransformationMappingRoot)eoChild).getTarget(); addEntry(eoTarget, (Mapping)children[i]); } } } else if (eo != null && eo instanceof MappingClassSetContainer) { // save the container node so we can ignore it later getcontainerNodesArray().add(mapping); final Object[] children = getTheChildren(mapping); for (int i = 0, j = children.length; i < j; i++) { EObject eoChild = getEObjectForMapping((Mapping)children[i]); if (eoChild instanceof MappingClassSet) { // special processing for this type: EObject eoTarget = ((MappingClassSet)eoChild).getTarget(); addEntry(eoTarget, (Mapping)children[i]); } } } else if (eo != null && eo instanceof AnnotationContainer) { /* DESIGN NOTE: * The Annotations contained in the AnnotationContainer will be made to look as a feature of the annotated EObject. * The AnnotationContainer itself will not be shown as content. */ // save the container node so we can ignore it later getcontainerNodesArray().add(mapping); // add annotation descriptions List annotations = ((AnnotationContainer)eo).getAnnotations(); if ((annotations != null) && !annotations.isEmpty()) { List mappings = ((Mapping)mapping).getNested(); if (mappings.isEmpty()) { // Since there are no nested mappings this is the first time AnnotationContainer existed. // So one or more descriptions were added. List list = null; AnnotationContainer ac = null; MappingHelper helper = ((Mapping)mapping).getHelper(); if (helper instanceof DifferenceDescriptor) { boolean isAdd = true; if (((DifferenceDescriptor)helper).isAddition()) { list = ((Mapping)mapping).getOutputs(); } else if (((DifferenceDescriptor)helper).isDeletion()) { list = ((Mapping)mapping).getInputs(); isAdd = false; } if ((list != null) && !list.isEmpty()) { ac = (AnnotationContainer)list.get(0); List newAnnotations = ac.getAnnotations(); // create a PropertyDifference for each Annotation. These will show up as a property of the // annotated object. for (int size = newAnnotations.size(), i = 0; i < size; ++i) { Annotation a = (Annotation)newAnnotations.get(i); String desc = a.getDescription(); EMap tags = a.getTags(); if (isAdd) { if (!CoreStringUtil.isEmpty(desc)) { createNewDescriptionPropertyDifference(a, true); } if (tags != null && !tags.isEmpty()) { createNewAnnotationTagPropertyDifference(a, true); } } else { if (!CoreStringUtil.isEmpty(desc)) { createNewDescriptionPropertyDifference(a, false); } if (tags != null && !tags.isEmpty()) { createNewAnnotationTagPropertyDifference(a, false); } } } } } } else { // description was changed for (int size = mappings.size(), i = 0; i < size; ++i) { Mapping nestedMapping = (Mapping)mappings.get(i); MappingHelper nestedHelper = nestedMapping.getHelper(); if ((nestedHelper instanceof DifferenceDescriptor) && !((DifferenceDescriptor)nestedHelper).isNoChange()) { List inputs = nestedMapping.getInputs(); Annotation oldValue = inputs.isEmpty() ? null : (Annotation)inputs.get(0); List outputs = nestedMapping.getOutputs(); Annotation newValue = outputs.isEmpty() ? null : (Annotation)outputs.get(0); // create property difference for description feature and attach to annotated object's mapping if // available createChangedDescriptionPropertyDifference(oldValue, newValue); } } } } } else { // this branch always/only handles the mapping arg, never a translated EObject // recurse over all children final Object[] children = getTheChildren(mapping); for (int i = 0, j = children.length; i < j; i++) { if (children[i] instanceof Mapping) { parseChildren(children[i]); } } } } /** * Creates a {@link PropertyDifference} for the specified changed description. * * @param theOldAnnotation the annotation containing the old description * @param theNewAnnotation the annotation containing the new description * @since 4.2 */ private void createChangedDescriptionPropertyDifference( Annotation theOldAnnotation, Annotation theNewAnnotation ) { EClass eclass = (theNewAnnotation == null) ? theOldAnnotation.eClass() : theNewAnnotation.eClass(); PropertyDifference propDiff = this.compareFactory.createPropertyDifference(); propDiff.setAffectedFeature(eclass.getEAttributes().get(CorePackage.ANNOTATION__DESCRIPTION)); propDiff.setOldValue((theOldAnnotation == null) ? null : theOldAnnotation.getDescription()); propDiff.setNewValue((theNewAnnotation == null) ? null : theNewAnnotation.getDescription()); EObject target = (theNewAnnotation == null) ? theOldAnnotation.getAnnotatedObject() : theNewAnnotation.getAnnotatedObject(); registerDescriptionPropertyDifference(target, propDiff); } /** * Associates the specified <code>PropertyDifference</code> with specified <code>Mapping</code>. * * @param thePropDifference the property difference being associated to the mapping * @param theMapping the mapping that the property difference is being associated with * @return <code>true</code>if successfully associated; <code>false</code> otherwise. * @since 4.2 */ private boolean associate( PropertyDifference thePropDifference, Mapping theMapping ) { boolean result = false; Object helper = theMapping.getHelper(); if (helper instanceof DifferenceDescriptor) { thePropDifference.setDescriptor((DifferenceDescriptor)helper); // defect 17172 - make sure we do not change the original mapping helper type, so that dependent behavior is not // affected if (((DifferenceDescriptor)helper).isNoChange()) { ((DifferenceDescriptor)helper).setType(DifferenceType.get(DifferenceType.CHANGE)); } result = true; } return result; } /** * Registers the specified <code>PropertyDifference</code> of the specified object. * * @param theObject the target of the property difference * @param thePropDifference the property difference * @since 4.2 * @see #hasAdditionalPropertyDifferences(Mapping) */ private void registerDescriptionPropertyDifference( EObject theObject, PropertyDifference thePropDifference ) { boolean cacheDiff = true; Mapping mapping = getPropertyDifferenceMapping(theObject); if (mapping != null) { cacheDiff = !associate(thePropDifference, mapping); } if (cacheDiff) { // save diff since it can't be associated with a mapping right now. // it will be associated either later until mapping of target is processed getPropertyDifferenceMap().put(theObject, thePropDifference); } } /** * Creates a {@link PropertyDifference} for the specified <code>Annotation</code>. This <code>PropertyDifference</code> will * be associated with the annotated object of the <code>Annotation</code>. * * @param theAnnotation the Annotation whose description has changed * @param theAddedFlag the flag indicating if the description was added or deleted * @since 4.2 */ private void createNewDescriptionPropertyDifference( Annotation theAnnotation, boolean theAddedFlag ) { EClass eclass = theAnnotation.eClass(); PropertyDifference propDiff = this.compareFactory.createPropertyDifference(); propDiff.setAffectedFeature(eclass.getEStructuralFeature(CorePackage.ANNOTATION__DESCRIPTION)); if (theAddedFlag) { propDiff.setNewValue(theAnnotation.getDescription()); } else { propDiff.setOldValue(theAnnotation.getDescription()); } EObject target = theAnnotation.getAnnotatedObject(); registerDescriptionPropertyDifference(target, propDiff); } /** * Creates a {@link PropertyDifference} for the specified <code>Annotation</code>. This <code>PropertyDifference</code> will * be associated with the annotated object of the <code>Annotation</code>. * * @param theAnnotation the Annotation whose description has changed * @param theAddedFlag the flag indicating if the description was added or deleted * @since 4.2 */ private void createNewAnnotationTagPropertyDifference( Annotation theAnnotation, boolean theAddedFlag ) { EClass eclass = theAnnotation.eClass(); PropertyDifference propDiff = this.compareFactory.createPropertyDifference(); propDiff.setAffectedFeature(eclass.getEStructuralFeature(CorePackage.ANNOTATION__TAGS)); if (theAddedFlag) { propDiff.setOldValue(new BasicEMap()); propDiff.setNewValue(theAnnotation.getTags()); } else { propDiff.setOldValue(theAnnotation.getTags()); propDiff.setNewValue(new BasicEMap()); } EObject target = theAnnotation.getAnnotatedObject(); registerDescriptionPropertyDifference(target, propDiff); } /* * getTheChildren( node ) is a private version for use by the parsing preprocess. * It is necessary because the public getChildren( o ) has requirements (like the supression * of Containers) that make it unsuitable for use in the parsing step. */ private Object[] getTheChildren( Object node ) { Object[] result; if (node instanceof DifferenceReport) { Mapping mapping = ((DifferenceReport)node).getMapping(); result = getTheChildren(mapping); } else if (node instanceof List) { result = ((List)node).toArray(); } else if (node instanceof Mapping) { EList nestedMappings = ((Mapping)node).getNested(); result = nestedMappings.toArray(); } else { EObject parent = (EObject)node; EList children = parent.eContents(); result = children.toArray(); } return result; } private void addEntry( Object oKey, Mapping mpgValue ) { if (getTargetToRelatedMap().containsKey(oKey)) { // add to existing list List lstNodes = (List)getTargetToRelatedMap().get(oKey); lstNodes.add(mpgValue); } else { // create new list and add to it ArrayList aryl = new ArrayList(); aryl.add(mpgValue); getTargetToRelatedMap().put(oKey, aryl); } } /** * Obtains the <code>Mapping</code> that a {@link PropertyDifference} can be associated with for the specified * <code>EObject</code>. * * @param theObject the object whose mapping is being requested * @return the mapping or <code>null</code> if one not found * @since 4.2 */ private Mapping getPropertyDifferenceMapping( EObject theObject ) { Mapping result = null; List list = (List)getTargetToRelatedMap().get(theObject); if (list != null) { IPath path = ModelerCore.getModelEditor().getModelRelativePath(theObject); // loop through mappings here. use first mapping whose EObject is the same. for (int size = list.size(), i = 0; i < size; ++i) { Mapping m = (Mapping)list.get(i); EObject eobj = getEObjectForMapping(m); // need to use path since EObject.equals fails if ((eobj != null) && path.equals(ModelerCore.getModelEditor().getModelRelativePath(eobj))) { result = m; break; } } } return result; } @Override public Object[] getChildren( Object node ) { Object[] result; if (node instanceof DifferenceReport) { /* * special case: the EObject for the mapping we get from DifferenceReport.getMapping() * is actually the Model Annotation. The getChildren call we do on that mapping * (see next line) will look up the Model Annotation in the Target Hashmap and * get the Diagram stuff that we normally insert into the tree under the Annotation. * The result is that the Diagram stuff appears twice in the tree, once at the very top * at later, correctly, under the Model Annotation. We must suppress the one at the * top. To do this we will implement a flag that will control whether or not * we do the inserts in getChildren. When we are processing the DifferenceReport * node that flag will be turned off. */ Mapping mapping = ((DifferenceReport)node).getMapping(); // get the children of the root. prevent inserting related nodes this time. bInsertRelatedNodes = false; result = getChildren(mapping); bInsertRelatedNodes = true; // remove the Container nodes from this list before returning result = filterContainers(result); } else if (node instanceof List) { result = ((List)node).toArray(); } else if (node instanceof Mapping) { // create an array ArrayList aryl = new ArrayList(); // if this is a container, do no further processing if (getcontainerNodesArray().contains(node)) { return aryl.toArray(); } if (bInsertRelatedNodes) { // 1. get the EObject for this mapping node EObject eo = getEObjectForMapping((Mapping)node); // 2. Try to get a list of mappings from the 'target' hashmap for this EObject List lst = (List)getTargetToRelatedMap().get(eo); // check to see if a PropertyDifference has been saved for the target EObject of this Mapping. // if one is available associate it with that Mapping if (hasAdditionalPropertyDifferences((Mapping)node)) { PropertyDifference propDiff = (PropertyDifference)this.getPropertyDifferenceMap().get(eo); associate(propDiff, (Mapping)node); } // 3. If such a list exists, add it to the front of the result if (lst != null) { aryl.addAll(lst); } } // Now add the 'real' children EList nestedMappings = ((Mapping)node).getNested(); aryl.addAll(nestedMappings); result = aryl.toArray(); } else { EObject parent = (EObject)node; EList children = parent.eContents(); result = children.toArray(); } return result; } private Object[] filterContainers( Object[] children ) { ArrayList aryl = new ArrayList(); for (int i = 0, j = children.length; i < j; i++) { if (!getcontainerNodesArray().contains(children[i])) { aryl.add(children[i]); } } return aryl.toArray(); } @Override public boolean hasChildren( Object node ) { boolean hasChildren = false; if (node instanceof DifferenceReport) { Mapping mapping = ((DifferenceReport)node).getMapping(); hasChildren = hasChildren(mapping); } else if (node instanceof List) { hasChildren = ((List)node).size() > 0; } else if (node instanceof Mapping) { Mapping parent = (Mapping)node; EList children = parent.getNested(); hasChildren = children.size() > 0; } return hasChildren; } @Override public Object[] getElements( Object node ) { Object[] array = null; if (node instanceof DifferenceReport) { Mapping mapping = ((DifferenceReport)node).getMapping(); ArrayList aryl = new ArrayList(1); aryl.add(mapping); array = aryl.toArray(); } else if (node instanceof List) { array = ((List)node).toArray(); } else if (node instanceof Mapping) { Mapping parent = (Mapping)node; java.util.List elements = parent.getNested(); if (elements == null) { elements = new ArrayList(0); } array = elements.toArray(); } return array; } @Override public Object getParent( Object node ) { Object result = null; if (node instanceof Mapping) { Mapping child = (Mapping)node; result = child.eContainer(); } return result; } @Override public void dispose() { } @Override public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) { } /** * The <code>PropertyDifferenceMap</code> class is a helper <code>Map</code> which internally generates the <code>Map</code> * key based on a given {@link EObject}. Care should be used when using methods other than the overridden ones (i.e., did not * ensure all appropriate methods have been overridden to handle the internal key generation). * * @since 4.2 */ class PropertyDifferenceMap extends HashMap { /** */ private static final long serialVersionUID = 1L; /** * @see java.util.HashMap#containsKey(java.lang.Object) * @since 4.2 */ @Override public boolean containsKey( Object theKey ) { Object key = getKey(theKey); return (key == null) ? false : super.containsKey(key); } /** * @see java.util.HashMap#get(java.lang.Object) * @since 4.2 */ @Override public Object get( Object theKey ) { Object key = getKey(theKey); return (key == null) ? null : super.get(key); } /** * Obtains the key for the specified object. * * @param theObject the object whose key is being requested * @return the key or <code>null</code> if object is not an {@link EObject} * @since 4.2 */ private Object getKey( Object theObject ) { return (theObject instanceof EObject) ? ModelerCore.getModelEditor().getModelRelativePath((EObject)theObject) : null; } /** * @see java.util.HashMap#put(java.lang.Object, java.lang.Object) * @since 4.2 */ @Override public Object put( Object theKey, Object theValue ) { Object key = getKey(theKey); return (key == null) ? null : super.put(key, theValue); } } }