/* * 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.util; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingHelper; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.compare.DifferenceDescriptor; import org.teiid.designer.compare.DifferenceReport; import org.teiid.designer.compare.DifferenceType; import org.teiid.designer.compare.ModelerComparePlugin; import org.teiid.designer.compare.PropertyDifference; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.metamodels.core.AnnotationContainer; import org.teiid.designer.metamodels.core.ModelAnnotation; import org.teiid.designer.metamodels.core.ModelImport; import org.teiid.designer.metamodels.diagram.DiagramContainer; import org.teiid.designer.metamodels.transformation.TransformationContainer; /** * Utility class for the {@link ModelerComparePlugin}. * * @since 8.0 */ public class CompareUtil { /** * Prevent instantiation. */ private CompareUtil() { super(); } /** * Process all of the changes that are specified in the supplied {@link DifferenceReport}, and * {@link org.teiid.designer.compare.DifferenceDescriptor#setSkip(boolean) skip} all changes <i>from</i> a default value * <i>to</i> a non-default value. * * @param report */ public static void skipChangesFromDefault( final DifferenceReport report ) { final Mapping rootMapping = report.getMapping(); if (rootMapping != null) { skipChangesFromDefault(rootMapping, true); } } /** * Process all of the changes that are specified in the supplied {@link Mapping}, and * {@link org.teiid.designer.compare.DifferenceDescriptor#setSkip(boolean) skip} all changes <i>from</i> a default value * <i>to</i> a non-default value. * * @param differenceNode the node in a difference report; may not be null * @param recursive true if the same logic should also be applied to {@link Mapping#getNested() nested} mappings, or false if * only the logic should only be applied to the supplied node. * @return the number of {@link PropertyDifference} instances that were changed to skip */ public static int skipChangesFromDefault( final Mapping differenceNode, final boolean recursive ) { int count = 0; // Get the helper ... final DifferenceDescriptor desc = getDifferenceDescriptor(differenceNode); if (desc != null) { count += skipChangesFromDefault(desc); } if (recursive) { final Iterator iter = differenceNode.getNested().iterator(); while (iter.hasNext()) { final Mapping nestedNode = (Mapping)iter.next(); count += skipChangesFromDefault(nestedNode, recursive); } } return count; } /** * Process all of the changes that are specified in the supplied {@link DifferenceDescriptor}, and * {@link org.teiid.designer.compare.DifferenceDescriptor#setSkip(boolean) skip} all changes <i>from</i> a default value * <i>to</i> a non-default value, or if there is no default value but the new value is null and the old value is not null. * * @param differenceDescriptor the descriptor for a node in a difference report; may not be null * @return the number of {@link PropertyDifference} instances that were changed to skip */ public static int skipChangesFromDefault( final DifferenceDescriptor differenceDescriptor ) { // If the difference is an add or a delete, then skip ... final DifferenceType diffType = differenceDescriptor.getType(); if (diffType.getValue() == DifferenceType.ADDITION) { return 0; } if (diffType.getValue() == DifferenceType.DELETION) { return 0; } // Go through the changed features ... int count = 0; final List propertyDifferences = differenceDescriptor.getPropertyDifferences(); final Iterator iter = propertyDifferences.iterator(); while (iter.hasNext()) { final PropertyDifference propDiff = (PropertyDifference)iter.next(); // Only need to process property diffs that are not already skipped ... if (!propDiff.isSkip()) { final EStructuralFeature feature = propDiff.getAffectedFeature(); // If there is a default value on this feature ... final Object featureDefaultValue = feature.getDefaultValue(); if (featureDefaultValue != null) { // Then look at the new value to see if the value is going to be the default ... final Object newValue = propDiff.getNewValue(); if (featureDefaultValue.equals(newValue)) { // The new value is the default, so mark this as skipped ... propDiff.setSkip(true); ++count; } } else { // There is no default value, so skip if the new value is null but the old value is not if (propDiff.getNewValue() == null && propDiff.getOldValue() != null) { propDiff.setSkip(true); ++count; } } } } return count; } /** * Obtain the {@link DifferenceDescriptor} at the supplied node, if there is such a descriptor. * * @param mappingNode the mapping node; may not be null * @return the descriptor, or null if there is no descriptor at the supplied node. */ public static DifferenceDescriptor getDifferenceDescriptor( final Mapping mappingNode ) { final MappingHelper helper = mappingNode.getHelper(); if (helper instanceof DifferenceDescriptor) { return (DifferenceDescriptor)helper; } return null; } /** * Determine whether the supplied mapping node has any {@link Mapping#getInputs() inputs} or {@link Mapping#getOutputs() * outputs} that are instances of the supplied Class. * * @param mappingNode the mapping node. * @param c the class; may not be null * @return true if there is at least one input or output that is an instance of <code>c</code> */ public static boolean hasInstanceof( final Mapping mappingNode, final Class c ) { // Check the inputs ... final Iterator inputIter = mappingNode.getInputs().iterator(); while (inputIter.hasNext()) { final Object obj = inputIter.next(); if (c.isInstance(obj)) { return true; } } // Check the outputs ... final Iterator outputIter = mappingNode.getOutputs().iterator(); while (outputIter.hasNext()) { final Object obj = outputIter.next(); if (c.isInstance(obj)) { return true; } } // None found ... return false; } /** * Process the difference report and skip all changes, additions and deletions that involve features that cannot be changed * (see {@link EStructuralFeature#isChangeable()}). * * @param differences */ public static int skipUnchangeableFeatures( final DifferenceReport differences ) { // Iterate through the mappings of root-level objects final Mapping rootMapping = differences.getMapping(); int count = 0; // Skip the root mapping, and go right to the nested of the root ... final List nestedMappings = rootMapping.getNested(); final Iterator iter = nestedMappings.iterator(); while (iter.hasNext()) { final Mapping nestedMapping = (Mapping)iter.next(); count += skipUnchangeableFeatures(nestedMapping, true); } return count; } /** * Process the difference report and skip all changes, additions and deletions that involve features that cannot be changed * (see {@link EStructuralFeature#isChangeable()}). Non-changeable features are sometimes not useful, especially in cases when * some features are derived from other features. * * @param differenceNode the node in a difference report; may not be null * @param recursive true if the same logic should also be applied to {@link Mapping#getNested() nested} mappings, or false if * only the logic should only be applied to the supplied node. * @return the number of differences (changed properties, additions, deletions) that were changed to skip */ public static int skipUnchangeableFeatures( final Mapping differenceNode, final boolean recursive ) { int count = 0; // Get the helper ... final DifferenceDescriptor desc = getDifferenceDescriptor(differenceNode); if (desc != null && !desc.isSkip()) { // don't need to check if already skipped final int type = desc.getType().getValue(); switch (type) { case DifferenceType.ADDITION: case DifferenceType.DELETION: // Determine if the added/deleted object is contained by a non-changeable feature ... final List inputs = differenceNode.getInputs(); final List outputs = differenceNode.getOutputs(); final EObject input = inputs.isEmpty() ? null : (EObject)inputs.get(0); final EObject output = outputs.isEmpty() ? null : (EObject)outputs.get(0); final EObject obj = input != null ? input : output; final EObject parent = obj.eContainer(); if (parent != null) { // Iterate through the features ... final EClass parentEClass = parent.eClass(); final Iterator iter = parentEClass.getEAllContainments().iterator(); while (iter.hasNext()) { final EReference ref = (EReference)iter.next(); if (!ref.isChangeable()) { if (ref.isMany()) { final List values = (List)parent.eGet(ref); if (values.contains(obj)) { desc.setSkip(true); ++count; continue; // obj may be in values for multiple EReferences } } else { final EObject value = (EObject)parent.eGet(ref); if (obj.equals(value)) { desc.setSkip(true); ++count; continue; // obj may be in values for multiple EReferences } } } } } break; case DifferenceType.CHANGE: // Go through the affected properties ... final Iterator iter = desc.getPropertyDifferences().iterator(); while (iter.hasNext()) { final PropertyDifference propDiff = (PropertyDifference)iter.next(); if (!propDiff.isSkip()) { final EStructuralFeature feature = propDiff.getAffectedFeature(); final boolean changeable = feature.isChangeable(); // Change to skip anything that is not changeable or a reference that is containment if (!changeable || (feature instanceof EReference && ((EReference)feature).isContainment())) { propDiff.setSkip(true); ++count; } } } break; } } // Recurse ... if (recursive) { final Iterator iter = differenceNode.getNested().iterator(); while (iter.hasNext()) { final Mapping nestedNode = (Mapping)iter.next(); count += skipUnchangeableFeatures(nestedNode, recursive); } } return count; } public static void skipDeletesOfStandardContainers( final DifferenceReport differences ) { // Iterate through the mappings of root-level objects final Mapping rootMapping = differences.getMapping(); final List nestedMappings = rootMapping.getNested(); final Iterator iter = nestedMappings.iterator(); while (iter.hasNext()) { final Mapping nestedMapping = (Mapping)iter.next(); if (CompareUtil.hasInstanceof(nestedMapping, ModelAnnotation.class)) { // Don't delete an existing ModelAnnotation ... final DifferenceDescriptor desc = CompareUtil.getDifferenceDescriptor(nestedMapping); if (desc != null && desc.isDeletion()) { desc.setSkip(true); } // Skip any change that would change a non-default value to a default (or null) value CompareUtil.skipChangesFromDefault(nestedMapping, true); } else if (CompareUtil.hasInstanceof(nestedMapping, AnnotationContainer.class)) { // Don't delete an existing AnnotationContainer ... final DifferenceDescriptor desc = CompareUtil.getDifferenceDescriptor(nestedMapping); if (desc != null && desc.isDeletion()) { desc.setSkip(true); } } else if (CompareUtil.hasInstanceof(nestedMapping, TransformationContainer.class)) { // Always skip anything to do with a transformaiton container ... final DifferenceDescriptor desc = CompareUtil.getDifferenceDescriptor(nestedMapping); desc.setSkip(true); } else if (CompareUtil.hasInstanceof(nestedMapping, DiagramContainer.class)) { // Always skip anything to do with a diagram container ... final DifferenceDescriptor desc = CompareUtil.getDifferenceDescriptor(nestedMapping); desc.setSkip(true); } } } public static void skipDeletesOfModelImports( final DifferenceReport differences ) { // Iterate through the mappings of root-level objects final Mapping rootMapping = differences.getMapping(); final List nestedMappings = rootMapping.getNested(); final Iterator iter = nestedMappings.iterator(); while (iter.hasNext()) { final Mapping nestedMapping = (Mapping)iter.next(); if (CompareUtil.hasInstanceof(nestedMapping, ModelAnnotation.class)) { // Get the nested mappings ... final Iterator nestedIter = nestedMapping.getNested().iterator(); while (nestedIter.hasNext()) { final Mapping nestedNested = (Mapping)nestedIter.next(); if (CompareUtil.hasInstanceof(nestedNested, ModelImport.class)) { // Don't delete an existing ModelImport ... final DifferenceDescriptor desc = CompareUtil.getDifferenceDescriptor(nestedMapping); if (desc != null && desc.isDeletion()) { desc.setSkip(true); } } } } } } /** * Utility method to print a difference report in a semi-readable fashion. * * @param report * @param stream */ public static void print( final DifferenceReport report, final PrintStream stream ) { print(report, stream, false); } /** * Utility method to print a difference report in a semi-readable fashion. * * @param report * @param stream */ public static void print( final DifferenceReport report, final PrintStream stream, final boolean showSkips ) { CoreArgCheck.isNotNull(report); CoreArgCheck.isNotNull(stream); stream.println(""); //$NON-NLS-1$ if (report.getTitle() != null && report.getTitle().trim().length() != 0) { stream.println("DifferenceReport: " + report.getTitle()); //$NON-NLS-1$ } else { stream.println("DifferenceReport"); //$NON-NLS-1$ } printMapping(report.getMapping(), " ", stream, showSkips); //$NON-NLS-1$ } protected static void printMapping( final Mapping mapping, final String prefix, final PrintStream stream, final boolean showSkips ) { String msg = prefix; List submsgs = null; final List inputs = mapping.getInputs(); final List outputs = mapping.getOutputs(); if (mapping.getNestedIn() == null /*inputs.size() > 1 || outputs.size() > 1*/) { msg = msg + " Difference Report Root Mapping"; //$NON-NLS-1$ } else { if (inputs.size() == 1 || outputs.size() == 1) { final EObject output = outputs.isEmpty() ? null : (EObject)outputs.get(0); final EObject input = inputs.isEmpty() ? null : (EObject)inputs.get(0); if (output != null) { msg = msg + ModelerCore.getModelEditor().getModelRelativePath(output); } else if (input != null) { msg = msg + ModelerCore.getModelEditor().getModelRelativePath(input); } final MappingHelper helper = mapping.getHelper(); if (helper != null && helper instanceof DifferenceDescriptor) { final DifferenceDescriptor desc = (DifferenceDescriptor)helper; if (showSkips || !desc.isSkip()) { final DifferenceType type = desc.getType(); if (type.getValue() == DifferenceType.ADDITION) { msg = msg + " (Added)"; //$NON-NLS-1$ } else if (type.getValue() == DifferenceType.DELETION) { msg = msg + " (Deleted)"; //$NON-NLS-1$ } else if (type.getValue() == DifferenceType.CHANGE) { msg = msg + " (Changed)"; //$NON-NLS-1$ submsgs = new ArrayList(desc.getPropertyDifferences().size()); final Iterator iter = desc.getPropertyDifferences().iterator(); while (iter.hasNext()) { final PropertyDifference propDiff = (PropertyDifference)iter.next(); if (showSkips || !propDiff.isSkip()) { String featureName = propDiff.getAffectedFeature().getName(); if (propDiff.isSkip()) { featureName = featureName + " [skip]"; //$NON-NLS-1$ } submsgs.add(featureName + " changed from " + propDiff.getOldValue()); //$NON-NLS-1$ final String slot = getString(' ', featureName.length()); submsgs.add(slot + " to " + propDiff.getNewValue()); //$NON-NLS-1$ } } } if (desc.isSkip()) { msg = msg + " [skip]"; //$NON-NLS-1$ } } } } } stream.println(msg); if (submsgs != null) { final Iterator iter = submsgs.iterator(); while (iter.hasNext()) { final String submsg = (String)iter.next(); stream.println(prefix + " (" + submsg + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } } // stream.println(prefix + mapping.toString()); final Iterator iter = mapping.getNested().iterator(); while (iter.hasNext()) { final Mapping nested = (Mapping)iter.next(); printMapping(nested, " " + prefix, stream, showSkips); //$NON-NLS-1$ } } protected static String getString( char c, int length ) { final StringBuffer sb = new StringBuffer(length); for (int i = 0; i < length; ++i) { sb.append(c); } return sb.toString(); } }