/* * 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.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; 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.ECollections; import org.eclipse.emf.common.util.EList; 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.ecore.resource.Resource; import org.eclipse.emf.mapping.Mapping; import org.eclipse.emf.mapping.MappingHelper; import org.teiid.core.designer.ModelerCoreException; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.compare.DifferenceDescriptor; import org.teiid.designer.compare.DifferenceProcessor; import org.teiid.designer.compare.DifferenceReport; import org.teiid.designer.compare.DifferenceType; import org.teiid.designer.compare.MergeProcessor; import org.teiid.designer.compare.ModelerComparePlugin; import org.teiid.designer.compare.PropertyDifference; import org.teiid.designer.compare.selector.ModelSelector; import org.teiid.designer.core.ModelEditor; import org.teiid.designer.core.ModelerCore; import org.teiid.designer.core.util.ModelVisitor; import org.teiid.designer.core.util.ModelVisitorProcessor; import org.teiid.designer.core.util.ModelVisitorWithFinish; /** * MergeProcessorImpl * * @since 8.0 */ public class MergeProcessorImpl implements MergeProcessor { private static final String PLUGINID = ModelerComparePlugin.PLUGIN_ID; // ------------------------------------------------------------------------- // MESSAGE CODE CONSTANTS // ------------------------------------------------------------------------- protected static final int PROCESSOR_ALREADY_CLOSED = 60001; protected static final int NO_PROBLEMS = 60002; protected static final int HAS_WARNINGS = 60003; protected static final int HAS_ERRORS = 60004; protected static final int HAS_WARNINGS_AND_ERRORS = 60005; protected static final int NO_WARNINGS_AND_ERRORS = 60006; protected static final int ERROR_PLANNING_MERGE = 60010; protected static final int ERROR_MERGING_ADDS_AND_DELETES = 60011; protected static final int ERROR_MERGING_CHANGES = 60012; protected static final int ERROR_RESOLVING_REFERENCES = 60013; // ------------------------------------------------------------------------- // PROGRESS MONITOR CONSTANTS // ------------------------------------------------------------------------- protected static final int AMOUNT_OF_WORK_FOR_PLANNING = 200; protected static final int AMOUNT_OF_WORK_FOR_MERGING_ADDS_AND_DELETES = 10000; protected static final int AMOUNT_OF_WORK_FOR_MERGING_CHANGES = AMOUNT_OF_WORK_FOR_MERGING_ADDS_AND_DELETES; protected static final int AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION = 300; protected static final int AMOUNT_OF_WORK_FOR_CREATING_RESULTS = 100; public static final boolean DEFAULT_ADDS_MOVED_FROM_STARTING_INTO_ENDING = false; private final DifferenceReport differenceReport; private final ModelSelector sourceModelSelector; // private final ModelSelector resultsModelSelector; private IProgressMonitor monitor; private List problems; private WorkInfo workInfo; private ModelEditor editor; private Map resultObjectToSourceObject; private final List newRoots; private boolean computeTasks; private boolean closed; private final boolean moveAddedObjectsFromStartingSelector; /** * Construct a MergeProcessor that merges the supplied changes/differences. The result of executing the merge processor is to * convert the original (starting) state of the difference report into the final (ending) state. * <p> * This is called by the {@link org.teiid.designer.compare.ModelerComparePlugin#createMergeProcessor(DifferenceProcessor)} * method when the resource has no unsaved changes (i.e., there are no differences}. * </p> * * @param resource the resource; may not be null * @param moveAddsRatherThanCopy true if objects that are considered "adds" should be <i>moved</i> rather than copied (will be * removed from the source model), or false the source model should be left unchanged and any "adds" be copied into the * output model */ public MergeProcessorImpl( final DifferenceReport differenceReport, final ModelSelector startingModelSelector, final ModelSelector endingModelSelector, final EObject[] externalReferences, final boolean moveAddsRatherThanCopy ) { super(); CoreArgCheck.isNotNull(differenceReport); CoreArgCheck.isNotNull(startingModelSelector); CoreArgCheck.isNotNull(endingModelSelector); this.differenceReport = differenceReport; this.sourceModelSelector = startingModelSelector; // this.resultsModelSelector = endingModelSelector; this.resultObjectToSourceObject = new HashMap(); this.newRoots = new LinkedList(); // Initialize the resultObjectToSourceObject adding EObject references // that are external to the starting or ending models being merged this.initializeResultToSourceMap(externalReferences); this.moveAddedObjectsFromStartingSelector = moveAddsRatherThanCopy; } /** * Construct a MergeProcessor that merges the supplied changes/differences. The result of executing the merge processor is to * convert the original (starting) state of the difference report into the final (ending) state. * <p> * This is called by the {@link org.teiid.designer.compare.ModelerComparePlugin#createMergeProcessor(DifferenceProcessor)} * method when the resource has no unsaved changes (i.e., there are no differences}. * </p> * * @param resource the resource; may not be null */ public MergeProcessorImpl( final DifferenceReport differenceReport, final ModelSelector startingModelSelector, final ModelSelector endingModelSelector, final EObject[] externalReferences ) { this(differenceReport, startingModelSelector, endingModelSelector, externalReferences, DEFAULT_ADDS_MOVED_FROM_STARTING_INTO_ENDING); } /** * Construct a MergeProcessor that merges the supplied changes/differences. The result of executing the merge processor is to * convert the original (starting) state of the difference report into the final (ending) state. * <p> * This is called by the {@link org.teiid.designer.compare.ModelerComparePlugin#createMergeProcessor(DifferenceProcessor)} * method when the resource has no unsaved changes (i.e., there are no differences}. * </p> * * @param resource the resource; may not be null */ public MergeProcessorImpl( final DifferenceReport differenceReport, final ModelSelector startingModelSelector, final ModelSelector endingModelSelector ) { this(differenceReport, startingModelSelector, endingModelSelector, null); } /** * Create a merge processor that merges the differences computed by the supplied processor. The result of executing the merge * processor is to convert the original (starting) state of the difference report into the final (ending) state. * * @param difference the difference report that specifies those differences that should be merged * @return the processor that can be used to execute the merge */ public MergeProcessorImpl( final DifferenceProcessorImpl differenceProcessor, final EObject[] externalReferences ) { this(differenceProcessor.getDifferenceReport(), differenceProcessor.getBeforeSelector(), differenceProcessor.getAfterSelector(), externalReferences); } /** * Create a merge processor that merges the differences computed by the supplied processor. The result of executing the merge * processor is to convert the original (starting) state of the difference report into the final (ending) state. * * @param difference the difference report that specifies those differences that should be merged * @param moveAddsRatherThanCopy true if objects that are considered "adds" should be <i>moved</i> rather than copied (will be * removed from the source model), or false the source model should be left unchanged and any "adds" be copied into the * output model * @return the processor that can be used to execute the merge */ public MergeProcessorImpl( final DifferenceProcessorImpl differenceProcessor, final EObject[] externalReferences, final boolean moveAddsRatherThanCopy ) { this(differenceProcessor.getDifferenceReport(), differenceProcessor.getBeforeSelector(), differenceProcessor.getAfterSelector(), externalReferences, moveAddsRatherThanCopy); } /** * Create a merge processor that merges the differences computed by the supplied processor. The result of executing the merge * processor is to convert the original (starting) state of the difference report into the final (ending) state. * * @param difference the difference report that specifies those differences that should be merged * @return the processor that can be used to execute the merge */ public MergeProcessorImpl( final DifferenceProcessorImpl differenceProcessor ) { this(differenceProcessor.getDifferenceReport(), differenceProcessor.getBeforeSelector(), differenceProcessor.getAfterSelector()); } /** * Pre-populate the resultObjectToSourceObject map with EObject instances that can be referenced from both the starting and * ending models but are not contained in either model. * * @param externalReferences * @since 4.2 */ private void initializeResultToSourceMap( final EObject[] externalReferences ) { if (externalReferences != null && externalReferences.length > 0) { for (int i = 0; i != externalReferences.length; ++i) { final EObject obj = externalReferences[i]; // Since this EObject is external to both the starting and // ending models we use it as both the result object // reference (key) and source object reference (value) this.resultObjectToSourceObject.put(obj, obj); } } } public void setEndingToSourceMapping( final Map mapping ) { this.resultObjectToSourceObject = mapping != null ? mapping : new HashMap(); } /** * Return whether objects that are "adds" in the ending selector are to be moved into the ending selector rather than copied * into the ending selector. By default, objects are copied (i.e., this method returns <code>false</code>). If it is * acceptable that the starting model is modified (the objects are <i>removed</i> from the starting model and inserted into * the ending model), then this can be set to <code>true</code> (which may result in a performance boost). * * @return true if the "adds" in the different report are to be moved from the starting model into the ending model, or false * if the starting model is to be left unmodified. * @since 4.2 */ public boolean isMoveAddedObjectsFromStartingSelector() { return this.moveAddedObjectsFromStartingSelector; } /** * @see org.teiid.designer.compare.MergeProcessor#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("MergeProcessorImpl.The_processor_has_already_been_closed"); //$NON-NLS-1$ final IStatus status = new Status(IStatus.ERROR, PLUGINID, code, msg, null); return status; } // ------------------------------------------------------------------------- // Initialize up the progress monitor ... // ------------------------------------------------------------------------- if (progressMonitor != null) { computeTasks = true; this.monitor = progressMonitor; } else { computeTasks = false; this.monitor = new NullProgressMonitor(); } final String taskName = doGetTaskName(); final int totalWork = doComputeTotalWork(); this.monitor.beginTask(taskName, totalWork); // ------------------------------------------------------------------------- // Do the real execution ... // ------------------------------------------------------------------------- final Object[] paramsExec = new Object[] {this.sourceModelSelector.getLabel()}; final String execSubTask = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Performing_merge", paramsExec); //$NON-NLS-1$ this.monitor.subTask(execSubTask); this.newRoots.clear(); this.editor = ModelerCore.getModelEditor(); this.problems = new LinkedList(); doExecute(); // ------------------------------------------------------------------------- // Analyze any problems ... // ------------------------------------------------------------------------- IStatus resultStatus = null; try { // Create a subtask ... final String analysisSubTask = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Analyzing_problems"); //$NON-NLS-1$ this.monitor.subTask(analysisSubTask); // Put all of the problems into a single IStatus ... if (this.problems.isEmpty()) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Execution_completed"); //$NON-NLS-1$ final IStatus status = new Status(IStatus.OK, PLUGINID, NO_PROBLEMS, msg, null); resultStatus = status; } else if (this.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[])this.problems.toArray(new IStatus[this.problems.size()]); if (numWarnings != 0 && numErrors == 0) { final Object[] params = new Object[] {new Integer(numWarnings)}; final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.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("MergeProcessorImpl.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("MergeProcessorImpl.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("MergeProcessorImpl.Execution_completed_with_no_warnings_or_errors"); //$NON-NLS-1$ resultStatus = new MultiStatus(PLUGINID, NO_WARNINGS_AND_ERRORS, statusArray, msg, null); } } } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_CREATING_RESULTS); } return resultStatus; } /** * @see org.teiid.designer.compare.MergeProcessor#reresolve(org.eclipse.core.runtime.IProgressMonitor) * @since 4.2 */ @Override public void reresolve( IProgressMonitor monitor ) { doReResolveAndRebuildImports(); } /** * @see org.teiid.designer.compare.MergeProcessor#close() */ @Override public void close() { if (this.closed) { return; } doClose(); 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 // ========================================================================= /** * Do the work of closing the processor. This method should not do anything with the {@link #closed} attribute, since that is * handled by the {@link #close()} method. */ protected void doClose() { this.problems = null; this.monitor = null; this.newRoots.clear(); this.resultObjectToSourceObject.clear(); } protected int doComputeTotalWork() { return AMOUNT_OF_WORK_FOR_PLANNING + AMOUNT_OF_WORK_FOR_MERGING_ADDS_AND_DELETES + AMOUNT_OF_WORK_FOR_MERGING_CHANGES + AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION + AMOUNT_OF_WORK_FOR_CREATING_RESULTS; } protected String doGetTaskName() { return ModelerComparePlugin.Util.getString("MergeProcessorImpl.taskName"); //$NON-NLS-1$ } protected void doExecute() { // ------------------------------------------------------------------------- // Phase 1: Plan how much work is required ... // ------------------------------------------------------------------------- final PlanningVisitor planningVisitor = new PlanningVisitor(); try { // First, iterate through the report and count up the number of Mapping instances ... final ModelVisitorProcessor processor = new ModelVisitorProcessor(planningVisitor); processor.walk(this.differenceReport, ModelVisitorProcessor.DEPTH_INFINITE); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_planning_the_merge"); //$NON-NLS-1$ final IStatus status = newError(ERROR_PLANNING_MERGE, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_PLANNING); } // Compute the amount of work per mapping ... final int numMappings = planningVisitor.getMappingCount(); this.workInfo = new WorkInfo(numMappings, AMOUNT_OF_WORK_FOR_MERGING_ADDS_AND_DELETES); // ------------------------------------------------------------------------- // Phase 2: Perform the adds and deletes ... // ------------------------------------------------------------------------- final AddAndDeleteVisitor addAndDeleteVisitor = new AddAndDeleteVisitor(); try { // First, iterate through the report and count up the number of Mapping instances ... final ModelVisitorProcessor processor = new ModelVisitorProcessor(addAndDeleteVisitor); processor.walk(this.differenceReport, ModelVisitorProcessor.DEPTH_INFINITE); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_merging_adds_and_deletes"); //$NON-NLS-1$ final IStatus status = newError(ERROR_MERGING_ADDS_AND_DELETES, msg, e); problems.add(status); } finally { this.monitor.worked(this.workInfo.workRemaining); } // ------------------------------------------------------------------------- // Phase 3: Perform the property changes ... // ------------------------------------------------------------------------- final ChangeVisitor changeVisitor = new ChangeVisitor(); try { // First, iterate through the report and count up the number of Mapping instances ... final ModelVisitorProcessor processor = new ModelVisitorProcessor(changeVisitor); processor.walk(this.differenceReport, ModelVisitorProcessor.DEPTH_INFINITE); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_merging_changes"); //$NON-NLS-1$ final IStatus status = newError(ERROR_MERGING_CHANGES, msg, e); problems.add(status); } finally { this.monitor.worked(this.workInfo.workRemaining); } // ------------------------------------------------------------------------- // Phase 4: Put the new objects into the resource ... // ------------------------------------------------------------------------- if (this.newRoots != null && !this.newRoots.isEmpty()) { try { // Figure out which is the last root that has been mapped... int indexOfLastMappedRoot = -1; int index = -1; final Iterator iter = this.sourceModelSelector.getRootObjects().iterator(); while (iter.hasNext()) { final Object root = iter.next(); ++index; if (this.resultObjectToSourceObject.containsValue(root)) { indexOfLastMappedRoot = index; } } // Add the new roots right after the last mapped root object ... this.sourceModelSelector.addRootObjects(this.newRoots, indexOfLastMappedRoot + 1); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_adding_new_root_objects"); //$NON-NLS-1$ final IStatus status = newError(ERROR_RESOLVING_REFERENCES, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION); } } // ------------------------------------------------------------------------- // Phase 5: Replace references in the (now modified) source graph // pointing to objects in the results graph. This is a side effect // of the 'add' steps (since the things added to the results were // copied and put into the source) ... // ------------------------------------------------------------------------- final ResolutionVisitor resolveVisitor = new ResolutionVisitor(); try { final ModelVisitorProcessor processor = new ModelVisitorProcessor(resolveVisitor); final List sourceRoots = doGetSourceRoots(); processor.walk(sourceRoots, ModelVisitorProcessor.DEPTH_INFINITE); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_resolving_references"); //$NON-NLS-1$ final IStatus status = newError(ERROR_RESOLVING_REFERENCES, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION); } // ------------------------------------------------------------------------- // Phase 6: Rebuild model imports ... // ------------------------------------------------------------------------- try { this.sourceModelSelector.rebuildModelImports(); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_rebuilding_imports"); //$NON-NLS-1$ final IStatus status = newError(ERROR_RESOLVING_REFERENCES, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION); } } protected void doReResolveAndRebuildImports() { final ResolutionVisitor resolveVisitor = new ResolutionVisitor(); try { final ModelVisitorProcessor processor = new ModelVisitorProcessor(resolveVisitor); final List sourceRoots = doGetSourceRoots(); processor.walk(sourceRoots, ModelVisitorProcessor.DEPTH_INFINITE); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_resolving_references"); //$NON-NLS-1$ final IStatus status = newError(ERROR_RESOLVING_REFERENCES, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION); } try { this.sourceModelSelector.rebuildModelImports(); } catch (Throwable e) { final String msg = ModelerComparePlugin.Util.getString("MergeProcessorImpl.Error_while_rebuilding_imports"); //$NON-NLS-1$ final IStatus status = newError(ERROR_RESOLVING_REFERENCES, msg, e); problems.add(status); } finally { this.monitor.worked(AMOUNT_OF_WORK_FOR_REFERENCE_RESOLUTION); } } protected List doGetSourceRoots() throws ModelerCoreException { final List roots = new LinkedList(this.sourceModelSelector.getRootObjects()); // Add the new roots that haven't been added yet ... final Iterator iter = this.newRoots.iterator(); while (iter.hasNext()) { final Object newRoot = iter.next(); if (!roots.contains(newRoot)) { roots.add(newRoot); } } return roots; } protected void doProcess( final Mapping mapping, final boolean adds, final boolean changes, final boolean deletes ) throws ModelerCoreException { try { final MappingHelper helper = mapping.getHelper(); if (helper != null && helper instanceof DifferenceDescriptor) { final DifferenceDescriptor diffDesc = (DifferenceDescriptor)helper; // Do this only if not skipping ... final boolean skip = diffDesc.isSkip(); if (!skip) { final DifferenceType type = diffDesc.getType(); final int typeValue = type.getValue(); switch (typeValue) { case DifferenceType.ADDITION: if (adds) { doAdd(mapping, diffDesc); } break; case DifferenceType.CHANGE: if (changes) { doChange(mapping, diffDesc); } break; case DifferenceType.DELETION: if (deletes) { doDelete(mapping, diffDesc); } break; case DifferenceType.NO_CHANGE: case DifferenceType.CHANGE_BELOW: default: // do nothing break; } } } } finally { this.monitor.worked(this.workInfo.workPerMapping); } } protected void doPostProcess( final Mapping mapping ) { try { final MappingHelper helper = mapping.getHelper(); if (helper != null && helper instanceof DifferenceDescriptor) { final DifferenceDescriptor diffDesc = (DifferenceDescriptor)helper; // Skip if this node is to be skipped OR if this is the root mapping ... final boolean skip = diffDesc.isSkip() || mapping.getNestedIn() == null; if (!skip) { final DifferenceType type = diffDesc.getType(); final int typeValue = type.getValue(); switch (typeValue) { case DifferenceType.NO_CHANGE: case DifferenceType.CHANGE_BELOW: case DifferenceType.CHANGE: doOrderContained(mapping, diffDesc); break; case DifferenceType.DELETION: case DifferenceType.ADDITION: default: // do nothing break; } } } } finally { this.monitor.worked(this.workInfo.workPerMapping); } } protected String computeSubtaskPath( final EObject object ) { // Return the path; we don't want the model in the path return this.editor.getModelRelativePath(object).toString(); } /** * Method the perform the 'add' operation defined by the supplied descriptor and mapping. * * @param mapping * @param diffDesc */ protected void doAdd( final Mapping mapping, final DifferenceDescriptor diffDesc ) throws ModelerCoreException { // There should be no input ... final List outputs = mapping.getOutputs(); final EObject newObject = (EObject)outputs.get(0); if (computeTasks) { final String path = computeSubtaskPath(newObject); final Object[] params = new Object[] {path}; final String loadingSubTask = ModelerComparePlugin.Util.getString("MergeProcessorImpl.AddingSubTask", params); //$NON-NLS-1$ this.monitor.subTask(loadingSubTask); } // Make a copy of the new object and it's contents. Here, the 'orinals to copies' map is // the 'resultObjectToSourceObject' map, since the 'original' of the copy operation is // the object in the 'result' model and since the 'copy' will get put into the 'source' model ... final EObject copy = this.moveAddedObjectsFromStartingSelector ? newObject : // if move, then just reference the copy this.editor.copy(newObject, this.resultObjectToSourceObject); // Add the copy under the correct feature ... final EStructuralFeature feature = newObject.eContainmentFeature(); if (feature == null) { // There is no container (i.e., this must be a root-level object) if (this.moveAddedObjectsFromStartingSelector) { // First remove from the original model ... newObject.eResource().getContents().remove(copy); } this.newRoots.add(copy); } else { // There is a container and this is the feature under which the copy should be placed // Get the parent, which is the input on the parent mapping ... final EObject parent = (EObject)this.resultObjectToSourceObject.get(newObject.eContainer()); if (feature.isMany()) { final List values = (List)parent.eGet(feature); values.add(copy); } else { parent.eSet(feature, copy); } } } /** * Method the perform the 'change' operation defined by the supplied descriptor and mapping. * * @param mapping * @param diffDesc */ protected void doChange( final Mapping mapping, final DifferenceDescriptor diffDesc ) { // There should both one input and one output ... final List inputs = mapping.getInputs(); final EObject oldObject = (EObject)inputs.get(0); // final List outputs = mapping.getOutputs(); // final EObject newObject = (EObject)outputs.get(0); if (computeTasks) { final String path = computeSubtaskPath(oldObject); final Object[] params = new Object[] {path}; final String loadingSubTask = ModelerComparePlugin.Util.getString("MergeProcessorImpl.ChangingSubTask", params); //$NON-NLS-1$ this.monitor.subTask(loadingSubTask); } // Go through all of the property differences ... final List propDiffs = diffDesc.getPropertyDifferences(); final Iterator iter = propDiffs.iterator(); while (iter.hasNext()) { final PropertyDifference propDiff = (PropertyDifference)iter.next(); if (!propDiff.isSkip()) { final EStructuralFeature feature = propDiff.getAffectedFeature(); final Object newValue = propDiff.getNewValue(); final Object oldValue = propDiff.getOldValue(); // Set the new (result) values onto the old (source) object if (feature.isMany()) { final EList newValues = (EList)newValue; final EList oldValues = (EList)oldValue; final List newValuesInSource = convertFromResultsToSource(newValues); ECollections.setEList(oldValues, newValuesInSource); } else { if (newValue instanceof EObject) { final EObject newValueInSource = (EObject)this.resultObjectToSourceObject.get(newValue); // The new value may be null (e.g., if the new value is an object outside of the model // being merged/updated); in such cases, use the original value ... if (newValueInSource == null) { oldObject.eSet(feature, newValue); // newValue is never null! } else { oldObject.eSet(feature, newValueInSource); } } else { oldObject.eSet(feature, newValue); } } } } } /** * Converts references to result objects into the reference to the associated object in the source * * @param newValues * @return */ protected List convertFromResultsToSource( final EList newValues ) { final List result = new ArrayList(newValues.size()); final Iterator iter = newValues.iterator(); while (iter.hasNext()) { final EObject newValue = (EObject)iter.next(); final EObject newValueInSource = (EObject)this.resultObjectToSourceObject.get(newValue); if (newValueInSource != null) { result.add(newValueInSource); } else { result.add(newValue); } } return result; } /** * Method the perform the 'delete' operation defined by the supplied descriptor and mapping. * * @param mapping * @param diffDesc */ protected void doDelete( final Mapping mapping, final DifferenceDescriptor diffDesc ) throws ModelerCoreException { // There should be no output ... final List inputs = mapping.getInputs(); final EObject deletedObject = (EObject)inputs.get(0); if (computeTasks) { final String path = computeSubtaskPath(deletedObject); final Object[] params = new Object[] {path}; final String loadingSubTask = ModelerComparePlugin.Util.getString("MergeProcessorImpl.RemovingSubTask", params); //$NON-NLS-1$ this.monitor.subTask(loadingSubTask); } // Delete the supplied object ... this.editor.delete(deletedObject); } /** * Resolves in source objects references to objects in the results, and converts them. * * @param mapping * @param diffDesc */ protected void doResolve( final EObject sourceObject ) { // There should be no output ... final EClass metaclass = sourceObject.eClass(); final List features = metaclass.getEAllReferences(); final Iterator iter = features.iterator(); while (iter.hasNext()) { final EReference ref = (EReference)iter.next(); // final EReference oppositeRef = ref.getEOpposite(); // Only want to process the non-containment references ... // if ( !ref.isContainment() || (oppositeRef != null && !oppositeRef.isContainment()) ) { if (!ref.isVolatile()) { if (ref.isMany()) { final List values = (List)sourceObject.eGet(ref); if (values.size() != 0) { final List newValues = new ArrayList(values.size()); boolean foundNewValue = false; for (int i = 0; i != values.size(); ++i) { final Object value = values.get(i); final Object sourceValue = this.resultObjectToSourceObject.get(value); if (sourceValue != null) { if (!newValues.contains(sourceValue)) { newValues.add(sourceValue); foundNewValue = true; } } else { if (!newValues.contains(value)) { newValues.add(value); } } } if (foundNewValue) { values.clear(); values.addAll(newValues); } } } else { final Object value = sourceObject.eGet(ref); final Object sourceValue = this.resultObjectToSourceObject.get(value); if (sourceValue != null) { sourceObject.eSet(ref, sourceValue); } } } } // while } protected void recordMapping( final Object source, final Object result ) { this.resultObjectToSourceObject.put(result, source); } /** * Method the perform the 'delete' operation defined by the supplied descriptor and mapping. * * @param mapping * @param diffDesc */ protected void doOrderContained( final Mapping mapping, final DifferenceDescriptor diffDesc ) { // There should both one input and one output ... final List inputs = mapping.getInputs(); final List outputs = mapping.getOutputs(); if (inputs.isEmpty() || outputs.isEmpty()) { return; } final EObject sourceObject = (EObject)inputs.get(0); final EObject resultObject = (EObject)outputs.get(0); final EClass sourceMetaclass = sourceObject.eClass(); // Go through all of the containment features of the input object, // and order the contained objects like in the output final Iterator iter = sourceMetaclass.getEAllContainments().iterator(); while (iter.hasNext()) { final EStructuralFeature feature = (EStructuralFeature)iter.next(); if (feature.isMany()) { // Only something to reorder if the feature is many-valued ... final List resultsValues = (List)resultObject.eGet(feature); final List sourceValuesFromResults = new ArrayList(); final Iterator resultValuesIter = resultsValues.iterator(); while (resultValuesIter.hasNext()) { final Object resultValue = resultValuesIter.next(); final Object sourceValue = this.resultObjectToSourceObject.get(resultValue); if (sourceValue != null) { if (!sourceValuesFromResults.contains(sourceValue)) { sourceValuesFromResults.add(sourceValue); } } } // Case 3364 - if a delete was unchecked in the differenceReport (meaning dont delete it), // it was getting lost here - essentially getting deleted because the 'undeleted' item was // not in the sourceValuesFromResults prototype list. Therefore simply adding it here. // This method is strictly for ordering - nothing should be lost... EList sourceValues = (EList)sourceObject.eGet(feature); Iterator sourceValueIter = sourceValues.iterator(); while (sourceValueIter.hasNext()) { Object obj = sourceValueIter.next(); if (!sourceValuesFromResults.contains(obj)) { sourceValuesFromResults.add(obj); } } ECollections.setEList(sourceValues, sourceValuesFromResults); } } } // ========================================================================= // Nested utility classes // ========================================================================= protected class PlanningVisitor implements ModelVisitor { int numMappings = 0; /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject) */ @Override public boolean visit( EObject object ) { if (object instanceof Mapping) { ++numMappings; final Mapping mapping = (Mapping)object; // Do this only for the non-root mappings if (mapping.getNestedIn() != null) { // Find the source and result objects ... final MappingHelper helper = mapping.getHelper(); if (helper != null && helper instanceof DifferenceDescriptor) { final List inputs = mapping.getInputs(); final List outputs = mapping.getOutputs(); if (inputs.size() == 1 && outputs.size() == 1) { final Object source = inputs.get(0); final Object result = outputs.get(0); MergeProcessorImpl.this.recordMapping(source, result); } } } } return true; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) */ @Override public boolean visit( Resource resource ) { return true; } public int getMappingCount() { return this.numMappings; } } protected class AddAndDeleteVisitor implements ModelVisitorWithFinish { /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject) */ @Override public boolean visit( EObject object ) throws ModelerCoreException { if (object instanceof Mapping) { MergeProcessorImpl.this.doProcess((Mapping)object, true, false, true); return true; } return true; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) */ @Override public boolean visit( Resource resource ) { return true; } /** * @see org.teiid.designer.core.util.ModelVisitorWithFinish#postVisit(org.eclipse.emf.ecore.EObject) */ @Override public void postVisit( EObject object ) { if (object instanceof Mapping) { MergeProcessorImpl.this.doPostProcess((Mapping)object); } } } protected class ChangeVisitor implements ModelVisitor { /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject) */ @Override public boolean visit( EObject object ) throws ModelerCoreException { if (object instanceof Mapping) { MergeProcessorImpl.this.doProcess((Mapping)object, false, true, false); return true; } return true; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) */ @Override public boolean visit( Resource resource ) { return true; } } protected class ResolutionVisitor implements ModelVisitor { /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.EObject) */ @Override public boolean visit( EObject object ) { MergeProcessorImpl.this.doResolve(object); return true; } /** * @see org.teiid.designer.core.util.ModelVisitor#visit(org.eclipse.emf.ecore.resource.Resource) */ @Override public boolean visit( Resource resource ) { return true; } } protected class WorkInfo { protected final int numMappings; protected final int workPerMapping; protected final int workRemaining; protected WorkInfo( final int numMappings, final int workForMerging ) { this.numMappings = numMappings; if (numMappings > 0) { this.workPerMapping = numMappings <= workForMerging ? workForMerging / numMappings : 0; this.workRemaining = workForMerging - (workPerMapping * numMappings); } else { this.workPerMapping = 0; this.workRemaining = workForMerging; } } } }