/* * 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.processor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingFactory; import org.eclipse.emf.mapping.MappingHelper; import org.eclipse.emf.mapping.impl.MappingFactoryImpl; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.compare.CompareFactory; import org.teiid.designer.compare.DifferenceDescriptor; import org.teiid.designer.compare.DifferenceGuidelines; import org.teiid.designer.compare.DifferenceProcessor; 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.compare.impl.CompareFactoryImpl; import org.teiid.designer.compare.selector.ModelSelector; import org.teiid.designer.core.MappingAdapterDescriptor; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.compare.EObjectMatcher; import org.teiid.designer.core.compare.EObjectMatcherCache; import org.teiid.designer.core.compare.EObjectMatcherFactory; import org.teiid.designer.core.compare.MappingProducer; import org.teiid.designer.core.workspace.ModelResource; import org.w3c.dom.Node; /** * DifferenceProcessorImpl * * @since 8.0 */ public class DifferenceProcessorImpl implements DifferenceProcessor { private static final String PLUGINID = ModelerComparePlugin.PLUGIN_ID; // ------------------------------------------------------------------------- // MESSAGE CODE CONSTANTS // ------------------------------------------------------------------------- protected static final int PROCESSOR_ALREADY_CLOSED = 50001; protected static final int ERROR_LOADING_RESOURCES = 50002; protected static final int ERROR_CREATING_MAPPING = 50003; protected static final int ERROR_COMPUTING_DIFFERENCES = 50004; protected static final int MISSING_MAPPING_ADAPTERS = 50005; protected static final int NO_PROBLEMS = 50010; protected static final int HAS_WARNINGS = 50011; protected static final int HAS_ERRORS = 50012; protected static final int HAS_WARNINGS_AND_ERRORS = 50013; protected static final int NO_WARNINGS_AND_ERRORS = 50014; // ------------------------------------------------------------------------- // PROGRESS MONITOR CONSTANTS // ------------------------------------------------------------------------- protected static final int AMOUNT_OF_WORK_FOR_LOADING_RESOURCES = 300; protected static final int AMOUNT_OF_WORK_FOR_MAPPING_RESOURCES = 1000; protected static final int AMOUNT_OF_WORK_FOR_COMUTING_DIFFERENCES = 400; protected static final int AMOUNT_OF_WORK_FOR_CREATING_RESULTS = 100; protected static final Collection DOM_FEATURE_NAMES = new ArrayList(); { DOM_FEATURE_NAMES.add("description"); //$NON-NLS-1$ DOM_FEATURE_NAMES.add("applicationInformation"); //$NON-NLS-1$ DOM_FEATURE_NAMES.add("userInformation"); //$NON-NLS-1$ } private final ModelSelector beforeSelector; private final ModelSelector afterSelector; private CompareFactory compareFactory; private MappingFactory mappingFactory; private DifferenceReport report; private boolean closed; private final List matcherFactories; private final HashMap inputsToObjects; private int totalAdditions; private int totalDeletions; private int totalChanges; private DifferenceGuidelines guidelines; private MappingProducer mappingProducer; private boolean isMultiModel; /** * Construct a DifferenceProcessor for a single model. This form of the constructor is used to signal that there are no * differences to be computed, but that a difference report should be generated when asked for. This is called by the * {@link org.teiid.designer.compare.ModelerComparePlugin#createDifferenceProcessor(ModelResource)} method when the * resource has no unsaved changes (i.e., there are no differences}. * * @param resource the resource; may not be null */ public DifferenceProcessorImpl( final ModelSelector selector ) { this(selector, selector); } /** * Construct a DifferenceProcessor that computes the differences between two models. * <p> * This processor computes the difference between the two models as if recording the actions necessary to change the * <i>before</i> model into the <i>after</i> model. * </p> * * @param beforeSelector the ModelSelector to the model considered the <i>before</i> state; may not be null * @param afterSelector the ModelSelector to the model considered the <i>after</i> state; may not be null */ public DifferenceProcessorImpl( final ModelSelector beforeSelector, final ModelSelector afterSelector ) { this(beforeSelector, afterSelector, null); } /** * Construct a DifferenceProcessor that computes the differences between two models. * <p> * This processor computes the difference between the two models as if recording the actions necessary to change the * <i>before</i> model into the <i>after</i> model. * </p> * * @param beforeSelector the ModelSelector to the model considered the <i>before</i> state; may not be null * @param afterSelector the ModelSelector to the model considered the <i>after</i> state; may not be null * @param mappings = Any mappings from a previous differencing that may be required for this comparison. */ public DifferenceProcessorImpl( final ModelSelector beforeSelector, final ModelSelector afterSelector, final HashMap mappings ) { super(); CoreArgCheck.isNotNull(beforeSelector); CoreArgCheck.isNotNull(afterSelector); this.beforeSelector = beforeSelector; this.afterSelector = afterSelector; this.closed = false; this.compareFactory = new CompareFactoryImpl(); this.mappingFactory = new MappingFactoryImpl(); this.matcherFactories = new ArrayList(); this.guidelines = NullDifferenceGuidelines.INSTANCE; if (mappings == null) { this.inputsToObjects = new HashMap(); } else { this.inputsToObjects = mappings; this.isMultiModel = true; } } /** * @see org.teiid.designer.compare.DifferenceProcessor#getDifferenceGuidelines() */ @Override public DifferenceGuidelines getDifferenceGuidelines() { return this.guidelines; } /** * @see org.teiid.designer.compare.DifferenceProcessor#setDifferenceGuidelines(org.teiid.designer.compare.DifferenceGuidelines) */ @Override public void setDifferenceGuidelines( final DifferenceGuidelines guidelines ) { this.guidelines = guidelines != null ? guidelines : NullDifferenceGuidelines.INSTANCE; } /** * @see org.teiid.designer.compare.DifferenceProcessor#execute(org.eclipse.core.runtime.IProgressMonitor) */ @Override public IStatus execute( final IProgressMonitor progressMonitor ) { if (closed) { final int code = PROCESSOR_ALREADY_CLOSED; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.The_processor_has_already_been_closed_and_cannot_be_reused"); //$NON-NLS-1$ final IStatus status = new Status(IStatus.ERROR, PLUGINID, code, msg, null); return status; } // ------------------------------------------------------------------------- // Phase 0: Set up the progress monitor ... // ------------------------------------------------------------------------- final IProgressMonitor monitor = progressMonitor != null ? progressMonitor : new NullProgressMonitor(); final Object[] taskParams = new Object[] {this.beforeSelector.getLabel(), this.afterSelector.getLabel()}; final String taskName = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.execute_task_name", taskParams); //$NON-NLS-1$ final int totalWork = AMOUNT_OF_WORK_FOR_LOADING_RESOURCES + AMOUNT_OF_WORK_FOR_MAPPING_RESOURCES + AMOUNT_OF_WORK_FOR_COMUTING_DIFFERENCES + AMOUNT_OF_WORK_FOR_CREATING_RESULTS; monitor.beginTask(taskName, totalWork); // Create the problems list ... final List problems = new LinkedList(); // ------------------------------------------------------------------------- // Phase 1: Create the report ... // ------------------------------------------------------------------------- try { this.report = createDifferenceReport(); } finally { monitor.worked(AMOUNT_OF_WORK_FOR_CREATING_RESULTS); } // ------------------------------------------------------------------------- // Phase 2: Load the resources and get the root objects in each resource ... // ------------------------------------------------------------------------- List beforeRoots = null; List afterRoots = null; ModelSelector selector = this.beforeSelector; try { // Create a subtask ... final String loadingSubTask = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Loading_resources_subtask_name"); //$NON-NLS-1$ monitor.subTask(loadingSubTask); // Open the model selectors and get their contents ... selector.open(); beforeRoots = selector.getRootObjects(); selector = this.afterSelector; selector.open(); afterRoots = selector.getRootObjects(); } catch (ModelerCoreException e) { final Object[] params = new Object[] {selector.getLabel(), e.getMessage()}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Error_loading_resource", params); //$NON-NLS-1$ final IStatus status = newError(ERROR_LOADING_RESOURCES, msg, e); problems.add(status); } finally { monitor.worked(AMOUNT_OF_WORK_FOR_LOADING_RESOURCES); } // ------------------------------------------------------------------------- // Phase 3: Map the input model and the output model ... // ------------------------------------------------------------------------- try { // Create a subtask ... final String analysisSubTask = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Performing_mapping"); //$NON-NLS-1$ monitor.subTask(analysisSubTask); if (beforeRoots != null && afterRoots != null) { final Mapping mapping = createMapping(beforeRoots, afterRoots, problems, monitor, analysisSubTask); this.report.setMapping(mapping); } } catch (Throwable e) { final Object[] params = new Object[] {e.getMessage()}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Error_performing_mapping", params); //$NON-NLS-1$ final IStatus status = newError(ERROR_CREATING_MAPPING, msg, e); problems.add(status); } finally { monitor.worked(AMOUNT_OF_WORK_FOR_MAPPING_RESOURCES); } // ------------------------------------------------------------------------- // Phase 4: Add the difference information to the mapping ... // ------------------------------------------------------------------------- try { if (this.report.getMapping() != null) { this.totalAdditions = 0; this.totalChanges = 0; this.totalDeletions = 0; // Create a subtask ... final String analysisSubTask = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Computing_differences"); //$NON-NLS-1$ monitor.subTask(analysisSubTask); final Mapping rootMapping = this.report.getMapping(); if (rootMapping != null) { addDifferencesInTransaction(rootMapping, monitor, analysisSubTask); } this.report.setTotalAdditions(this.totalAdditions); this.report.setTotalChanges(this.totalChanges); this.report.setTotalDeletions(this.totalDeletions); } } catch (Throwable e) { final Object[] params = new Object[] {e.getMessage()}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Error_computing_differences", params); //$NON-NLS-1$ final IStatus status = newError(ERROR_COMPUTING_DIFFERENCES, msg, e); problems.add(status); } finally { if (!this.isMultiModel) { this.inputsToObjects.clear(); } this.totalAdditions = 0; this.totalChanges = 0; this.totalDeletions = 0; monitor.worked(AMOUNT_OF_WORK_FOR_COMUTING_DIFFERENCES); } IStatus resultStatus = null; // ------------------------------------------------------------------------- // Phase 5: Analyze any problems ... // ------------------------------------------------------------------------- try { // Create a subtask ... final String analysisSubTask = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Analyzing_problems"); //$NON-NLS-1$ monitor.subTask(analysisSubTask); // Put all of the problems into a single IStatus ... if (problems.isEmpty()) { final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Execution_completed"); //$NON-NLS-1$ final IStatus status = new Status(IStatus.OK, PLUGINID, NO_PROBLEMS, msg, null); resultStatus = status; } else if (problems.size() == 1) { resultStatus = (IStatus)problems.get(0); } else { // There were problems, so determine whether there were warnings and errors ... int numErrors = 0; int numWarnings = 0; final Iterator iter = problems.iterator(); while (iter.hasNext()) { final IStatus aStatus = (IStatus)iter.next(); if (aStatus.getSeverity() == IStatus.WARNING) { ++numWarnings; } else if (aStatus.getSeverity() == IStatus.ERROR) { ++numErrors; } } // Create the final status ... final IStatus[] statusArray = (IStatus[])problems.toArray(new IStatus[problems.size()]); if (numWarnings != 0 && numErrors == 0) { final Object[] params = new Object[] {new Integer(numWarnings)}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Execution_resulted_in_warnings", params); //$NON-NLS-1$ resultStatus = new MultiStatus(PLUGINID, HAS_WARNINGS, statusArray, msg, null); } else if (numWarnings == 0 && numErrors != 0) { final Object[] params = new Object[] {new Integer(numErrors)}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Execution_resulted_in_errors", params); //$NON-NLS-1$ resultStatus = new MultiStatus(PLUGINID, HAS_ERRORS, statusArray, msg, null); } else if (numWarnings != 0 && numErrors != 0) { final Object[] params = new Object[] {new Integer(numWarnings), new Integer(numErrors)}; final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Execution_resulted_in_warnings_and_errors", params); //$NON-NLS-1$ resultStatus = new MultiStatus(PLUGINID, HAS_WARNINGS_AND_ERRORS, statusArray, msg, null); } else { final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.Execution_completed_with_no_warnings_or_errors"); //$NON-NLS-1$ resultStatus = new MultiStatus(PLUGINID, NO_WARNINGS_AND_ERRORS, statusArray, msg, null); } } } finally { monitor.worked(AMOUNT_OF_WORK_FOR_CREATING_RESULTS); } return resultStatus; } private void addDifferencesInTransaction( Mapping rootMapping, IProgressMonitor monitor, String analysisSubTask ) { // Let's wrap in transaction and make non-undoable.... boolean started = ModelerCore.startTxn(false, false, "Add Differences", this); //$NON-NLS-1$ boolean succeeded = false; try { addDifferences(rootMapping, monitor, analysisSubTask); succeeded = true; } catch (Exception ex) { ModelerCore.Util.log(IStatus.ERROR, ex, ex.getMessage()); } finally { if (started) { if (succeeded) { ModelerCore.commitTxn(); } else { // We don't want to roll this back. Not really changing anything in any model // ModelerCore.rollbackTxn(); } } } } /** * @see org.teiid.designer.compare.DifferenceProcessor#getDifferenceReport() */ @Override public DifferenceReport getDifferenceReport() { return this.report; } /** * @see org.teiid.designer.compare.DifferenceProcessor#close() */ @Override public void close() { if (this.closed) { return; } this.report = null; // Close the selectors ... if (this.beforeSelector != null) { this.beforeSelector.close(); } if (this.afterSelector != null) { this.afterSelector.close(); } this.closed = true; } // ========================================================================= // Helper methods // ========================================================================= protected IStatus newWarning( final int code, final String msg, final Throwable t ) { return new Status(IStatus.WARNING, PLUGINID, code, msg, t); } protected IStatus newInfo( final int code, final String msg, final Throwable t ) { return new Status(IStatus.INFO, PLUGINID, code, msg, t); } protected IStatus newError( final int code, final String msg, final Throwable t ) { return new Status(IStatus.ERROR, PLUGINID, code, msg, t); } protected IStatus newOk( final int code, final String msg, final Throwable t ) { return new Status(IStatus.OK, PLUGINID, code, msg, t); } // ========================================================================= // Methods that can be overridden to specialize behavior // ========================================================================= /** * Create the mapping between the supplied model objects. * * @param beforeSiblings the siblings in the <i>before</i> state; never null * @param afterSiblings the siblings in the <i>afterSiblings</i> state; never null * @param monitor the progress monitor; never null * @param subtaskPrefix the string for subtasks that can be used to prefix the names of the objects being compared */ protected Mapping createMapping( final List beforeSiblings, final List afterSiblings, final List problems, final IProgressMonitor monitor, final String subtaskPrefix ) { // Create a composite mapping adapter ... this.mappingProducer = new MappingProducer(this.inputsToObjects); final EObjectMatcherCache cache = mappingProducer.getEObjectMatcherCache(); // Add all of the mapping adapters referenced by this object ... cache.addEObjectMatcherFactories(this.matcherFactories); // Let the subclasses add any they need ... doAddMappingAdapters(cache); // Ensure there is at least one adapter ... if (cache.getEObjectMatcherFactories().isEmpty()) { final String msg = ModelerComparePlugin.Util.getString("DifferenceProcessorImpl.No_mapping_adapters"); //$NON-NLS-1$ final IStatus status = newError(MISSING_MAPPING_ADAPTERS, msg, null); problems.add(status); return null; } // Perform the mapping ... final MappingFactory factory = this.getMappingFactory(); final boolean recursive = true; final Mapping mapping = mappingProducer.createMappings(beforeSiblings, afterSiblings, recursive, factory, monitor); // Create the DifferenceDescriptor for the root mapping // (this really doesn't mean anything, but is there primarily so that every Mapping has a // DifferenceDescriptor helper object) final DifferenceDescriptor rootDesc = this.getCompareFactory().createDifferenceDescriptor(); mapping.setHelper(rootDesc); rootDesc.setType(DifferenceType.CHANGE_BELOW_LITERAL); // Get the mapping of inputs to outputs ... return mapping; } protected void addDifferences( final Mapping mapping, final IProgressMonitor monitor, final String subtaskPrefix ) { final List mappingsToRemove = new LinkedList(); final Iterator iter = mapping.getNested().iterator(); while (iter.hasNext()) { final Mapping nestedMapping = (Mapping)iter.next(); if (this.includeInReport(nestedMapping)) { // Get the inputs and outputs final List inputs = nestedMapping.getInputs(); final List outputs = nestedMapping.getOutputs(); if (inputs.isEmpty()) { // There are no inputs ... if (!outputs.isEmpty()) { // But there are outputs, so this is an addition setDescriptor(nestedMapping, DifferenceType.ADDITION_LITERAL); ++this.totalAdditions; } // Otherwise there are no outputs either; do nothing } else { // There are inputs ... if (outputs.isEmpty()) { // There are no outputs, so this is a deletion ... setDescriptor(nestedMapping, DifferenceType.DELETION_LITERAL); ++this.totalDeletions; } else { // There are outputs, so compute the differences ... final DifferenceDescriptor desc = setDescriptor(nestedMapping, DifferenceType.NO_CHANGE_LITERAL); final boolean changed = computeFeatureDifferences(desc, inputs, outputs); if (changed) { desc.setType(DifferenceType.CHANGE_LITERAL); ++this.totalChanges; } } } addDifferences(nestedMapping, monitor, subtaskPrefix); DifferenceDescriptor descriptor = (DifferenceDescriptor)mapping.getHelper(); if (((DifferenceDescriptor)nestedMapping.getHelper()).getType() != DifferenceType.NO_CHANGE_LITERAL && descriptor.getType() == DifferenceType.NO_CHANGE_LITERAL) { descriptor.setType(DifferenceType.CHANGE_BELOW_LITERAL); } } else { // Prune it from the result mappingsToRemove.add(nestedMapping); } } // Remove any mappings that should not have been there ... final Iterator iterator = mappingsToRemove.iterator(); while (iterator.hasNext()) { final Mapping nestedMapping = (Mapping)iterator.next(); nestedMapping.setNestedIn(null); // remove from parent } } /** * @param nestedMapping * @return */ protected boolean includeInReport( final Mapping nestedMapping ) { // Get the inputs and outputs final List inputs = nestedMapping.getInputs(); final List outputs = nestedMapping.getOutputs(); List objects = null; if (outputs != null && outputs.size() != 0) { objects = outputs; } if (inputs != null && inputs.size() != 0) { objects = inputs; } if (objects != null) { final Iterator iter = objects.iterator(); while (iter.hasNext()) { final EObject obj = (EObject)iter.next(); if (!includeInReport(obj)) { return false; } } return true; } return false; } protected boolean includeInReport( final EObject eobject ) { final EClass eclass = eobject.eClass(); final EPackage epkg = eclass.getEPackage(); if (!this.guidelines.includeMetamodel(epkg.getNsURI())) { return false; } if (!this.guidelines.includeMetaclass(eclass)) { return false; } return true; } protected boolean includeInReport( final EStructuralFeature feature ) { // Skip the feature if not changeable ... if (!feature.isChangeable()) { // There is no point in recording differences on a feature we can't change (must be changeable some other way) return false; } if (!this.guidelines.includeFeature(feature)) { return false; } return true; } protected DifferenceDescriptor setDescriptor( final Mapping mapping, final DifferenceType type ) { // Find a helper that is a DifferenceDescriptor ... final MappingHelper currentHelper = mapping.getHelper(); DifferenceDescriptor desc = null; if (currentHelper == null) { // This should be the case almost all of the time! desc = this.getCompareFactory().createDifferenceDescriptor(); desc.setType(type); mapping.setHelper(desc); } else { // There is already a helper ... if (currentHelper instanceof DifferenceDescriptor) { desc = (DifferenceDescriptor)currentHelper; if (desc.getType().getValue() == DifferenceType.CHANGE) { // Remove any existing property differences ... desc.getPropertyDifferences().clear(); } desc.setType(type); } else { // Find a child helper ... final Iterator iter = currentHelper.getNested().iterator(); while (iter.hasNext()) { final MappingHelper childHelper = (MappingHelper)iter.next(); if (childHelper instanceof DifferenceDescriptor) { desc = (DifferenceDescriptor)currentHelper; } } if (desc == null) { // We didn't find an existing child helper, so create one ... desc = this.getCompareFactory().createDifferenceDescriptor(); desc.setType(type); currentHelper.getNested().add(desc); } } } // At this point, we should always have a non-null reference return desc; } protected boolean computeFeatureDifferences( final DifferenceDescriptor descriptor, final List inputs, final List outputs ) { boolean changed = false; if (inputs.size() == 1 && outputs.size() == 1) { final EObject input = (EObject)inputs.get(0); final EObject output = (EObject)outputs.get(0); changed = computeFeatureDifferences(descriptor, input, output); } return changed; } protected boolean computeFeatureDifferences( final DifferenceDescriptor descriptor, final EObject input, final EObject output ) { final EClass eclass = input.eClass(); boolean changed = false; // Assume that if we are computing feature differences, that we're guaranteed the // input's EClass and output's EClass are identical final List features = eclass.getEAllStructuralFeatures(); final Iterator iter = features.iterator(); while (iter.hasNext()) { final EStructuralFeature feature = (EStructuralFeature)iter.next(); if (this.includeInReport(feature)) { // Look at only attributes and non-containment references ... if (feature instanceof EAttribute || (!((EReference)feature).isContainment() && !((EReference)feature).isContainer())) { final Object inputValue = input.eGet(feature); final Object outputValue = output.eGet(feature); final boolean sameValue = feature.isMany() ? isEqual(feature, (EList)inputValue, (EList)outputValue) : isEqual(feature, inputValue, outputValue); if (!sameValue) { final PropertyDifference diff = this.getCompareFactory().createPropertyDifference(); diff.setAffectedFeature(feature); diff.setDescriptor(descriptor); diff.setNewValue(outputValue); diff.setOldValue(inputValue); changed = true; } } } } return changed; } /** * @param object * @param object2 */ protected boolean isEqual( final EStructuralFeature feature, final Object object1, final Object object2 ) { if (object1 == null) { if (object2 == null) { // Both are null return true; } // object1 is null, object2 is NOT null return false; } // object1 is NOT null if (object2 == null) { // object1 is NOT null, object2 is null return false; } // both are NOT null if (feature instanceof EReference) { final EObject expectedOutput = (EObject)this.inputsToObjects.get(object1); if (!object2.equals(expectedOutput)) { // The instances don't match to the inputToObjects map, so // before saying they are different, check their URI's. If the URIs are the // same, then the references both point to logically the same object that is // external to the domain of objects being compared final URI uri1 = EcoreUtil.getURI((EObject)object1); final URI uri2 = EcoreUtil.getURI((EObject)object2); if (uri1 != null && uri2 != null) { if (uri1.equals(uri2)) { return true; } } // The URIs are not the same, so use the matchers ... if (this.mappingProducer != null) { final EReference reference = (EReference)feature; final List object1List = new ArrayList(1); final List object2List = new ArrayList(1); object1List.add(object1); object2List.add(object2); final List matchers = this.mappingProducer.getEObjectMatcherCache().getEObjectMatchers(reference); final Mapping temp = this.mappingFactory.createMapping(); final Iterator iter = matchers.iterator(); while (iter.hasNext()) { final EObjectMatcher matcher = (EObjectMatcher)iter.next(); matcher.addMappings(reference, object1List, object2List, temp, this.mappingFactory); if (object1List.isEmpty()) { return true; // matcher thinks they are equivalent ... } } } // The matchers didn't find matches, so consider them different objects return false; } return true; } else if (object1 instanceof Node && object2 instanceof Node) { if (DOM_FEATURE_NAMES.contains(feature.getName())) { final String txt1 = getChildText((Node)object1); final String txt2 = getChildText((Node)object2); final boolean txtMatch = txt1 == null ? txt2 == null : txt1.equals(txt2); return (txtMatch); } return true; } return object1.equals(object2); } /** * @param list * @param list2 */ protected boolean isEqual( final EStructuralFeature feature, final EList list1, final EList list2 ) { final int list1Size = list1.size(); final int list2Size = list2.size(); // Compare the sizes ... if (list1Size == 0 && list2Size == 0) { // Both are empty, so they are equal return true; } // At least one list has something in it ... if (list1Size != list2Size) { // They are different sizes, so they are different ... return false; } // The sizes are the same, so check the contents ... final Iterator iter1 = list1.iterator(); final Iterator iter2 = list2.iterator(); while (iter2.hasNext()) { final Object obj1 = iter1.next(); final Object obj2 = iter2.next(); final boolean valuesAreEqual = isEqual(feature, obj1, obj2); if (!valuesAreEqual) { return false; } } return true; } private String getChildText( final Node node ) { // is there anything to do? if (node == null) { return null; } // concatenate children text StringBuffer str = new StringBuffer(); Node child = node.getFirstChild(); while (child != null) { short type = child.getNodeType(); if (type == Node.TEXT_NODE) { str.append(child.getNodeValue()); } else if (type == Node.CDATA_SECTION_NODE) { str.append(getChildText(child)); } child = child.getNextSibling(); } // return text value return str.toString(); } /** * @return */ protected DifferenceReport createDifferenceReport() { final DifferenceReport reportObj = this.getCompareFactory().createDifferenceReport(); final long now = System.currentTimeMillis(); final String sourceUri = this.beforeSelector.getUri().toString(); final String resultUri = this.afterSelector.getUri().toString(); reportObj.setAnalysisTime(now); reportObj.setSourceUri(sourceUri); reportObj.setResultUri(resultUri); // By default, set the title to the label of the input ... final String label = this.beforeSelector.getLabel(); if (label != null && label.trim().length() != 0) { reportObj.setTitle(label); } return reportObj; } /** * @return */ public CompareFactory getCompareFactory() { return compareFactory; } /** * @return */ public MappingFactory getMappingFactory() { return mappingFactory; } /** * @param factory */ public void setCompareFactory( CompareFactory factory ) { compareFactory = factory; } /** * @param factory */ public void setMappingFactory( MappingFactory factory ) { mappingFactory = factory; } /** * Return the list of {@link MappingAdapterDescriptor} instances that will be used by this processor during * {@link #execute(IProgressMonitor) execution}. * * @return the mapping adapters; never null */ @Override public List getEObjectMatcherFactories() { return this.matcherFactories; } /** * Helper method to add to the list of {@link MappingAdapterDescriptor} instances that will be used by this processor during * {@link #execute(IProgressMonitor) execution}. * * @param adapters the new mapping adapters; may not be null * @see #getMappingAdapters() */ @Override public synchronized void addEObjectMatcherFactories( final List factories ) { final Iterator iter = factories.iterator(); while (iter.hasNext()) { final Object obj = iter.next(); if (obj instanceof EObjectMatcherFactory) { final EObjectMatcherFactory factory = (EObjectMatcherFactory)obj; if (!this.matcherFactories.contains(factory)) { this.matcherFactories.add(factory); } } } } /** * Method called to create any {@link EObjectMappingAdapter} instances and add them to those already in the list. */ protected void doAddMappingAdapters( final EObjectMatcherCache cache ) { } /** * @return */ public ModelSelector getAfterSelector() { return afterSelector; } /** * @return */ public ModelSelector getBeforeSelector() { return beforeSelector; } /** * @return Returns the isMultiModel. */ public boolean isMultiModel() { return this.isMultiModel; } }