/* * Copyright (c) 2012, 2013, 2015, 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.compare; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOState; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; import org.eclipse.emf.cdo.common.revision.CDORevisionData; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.eresource.CDOResourceNode; import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.ObjectNotFoundException; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.cdo.view.CDOViewOpener; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.util.AbstractTreeIterator; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.scope.AbstractComparisonScope; import org.eclipse.emf.compare.scope.IComparisonScope; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.EcoreUtil.ProperContentIterator; import org.eclipse.emf.spi.cdo.InternalCDOSession; import org.eclipse.emf.spi.cdo.InternalCDOSession.MergeData; import org.eclipse.emf.spi.cdo.InternalCDOView; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * A CDO-specific base implementation of a {@link IComparisonScope comparison scope}. * * @author Eike Stepper */ public abstract class CDOComparisonScope extends AbstractComparisonScope { private static final CDOViewOpener DEFAULT_VIEW_OPENER = CDOCompareUtil.DEFAULT_VIEW_OPENER; private boolean resolveProxies = true; public CDOComparisonScope(Notifier left, Notifier right, Notifier origin) { super(left, right, origin); } public Iterator<? extends Resource> getCoveredResources(ResourceSet resourceSet) { return Iterators.emptyIterator(); } public Iterator<? extends EObject> getCoveredEObjects(Resource resource) { return Iterators.emptyIterator(); } public final boolean isResolveProxies() { return resolveProxies; } public final void setResolveProxies(boolean resolveProxies) { this.resolveProxies = resolveProxies; } private static CDOView openOriginView(CDOView leftView, CDOView rightView, CDOView[] originView, CDOViewOpener viewOpener) { if (leftView.getSession() != rightView.getSession()) { throw new IllegalArgumentException("Sessions are different"); } if (originView != null) { if (originView.length != 1) { throw new IllegalArgumentException("originView.length != 1"); } if (originView[0] != null) { throw new IllegalArgumentException("originView[0] != null"); } CDOBranchPoint ancestor = CDOBranchUtil.getAncestor(leftView, rightView); if (!ancestor.equals(leftView) && !ancestor.equals(rightView)) { if (viewOpener == DEFAULT_VIEW_OPENER) { viewOpener = leftView.getSession(); } originView[0] = viewOpener.openView(ancestor, new ResourceSetImpl()); return originView[0]; } } return null; } /** * Takes an arbitrary {@link CDOObject object} (including {@link CDOResourceNode resource nodes}) * and returns {@link Match matches} for <b>all</b> elements of its {@link EObject#eAllContents() content tree}. This scope has the advantage that the comparison can * be rooted at specific objects that are different from (below of) the root resource. The disadvantage is that all the transitive children of this specific object are * matched, whether they differ or not. Major parts of huge repositories can be loaded to the client side easily, if no attention is paid. * The following method returns comparisons that are based on this scope algorithm: * <ul> * <li>{@link CDOCompareUtil#compare(CDOObject, CDOView, CDOView[])} * </ul> * * @author Eike Stepper */ public static class AllContents extends CDOComparisonScope { public AllContents(Notifier left, Notifier right, Notifier origin) { super(left, right, origin); } public Iterator<? extends EObject> getChildren(EObject eObject) { return EcoreUtil.getAllProperContents(eObject, isResolveProxies()); } /** * Takes an arbitrary {@link CDOObject object} (including {@link CDOResourceNode resource nodes}) and returns {@link Match matches} for <b>all</b> elements of its {@link EObject#eAllContents() content tree}. This scope has the advantage that the comparison can * be rooted at specific objects that are different from (below of) the root resource. The disadvantage is that all the transitive children of this specific object are * matched, whether they differ or not. Major parts of huge repositories can be loaded to the client side easily, if no attention is paid. */ public static AllContents create(CDOObject left, CDOView rightView, CDOView[] originView) { return create(left, rightView, originView, DEFAULT_VIEW_OPENER); } /** * Takes an arbitrary {@link CDOObject object} (including {@link CDOResourceNode resource nodes}) and returns {@link Match matches} for <b>all</b> elements of its {@link EObject#eAllContents() content tree}. This scope has the advantage that the comparison can * be rooted at specific objects that are different from (below of) the root resource. The disadvantage is that all the transitive children of this specific object are * matched, whether they differ or not. Major parts of huge repositories can be loaded to the client side easily, if no attention is paid. * * @since 4.3 */ public static AllContents create(CDOObject left, CDOView rightView, CDOView[] originView, CDOViewOpener viewOpener) { CDOView leftView = left.cdoView(); CDOView view = openOriginView(leftView, rightView, originView, viewOpener); CDOObject right = CDOUtil.getCDOObject(rightView.getObject(left)); CDOObject origin = view == null ? null : CDOUtil.getCDOObject(view.getObject(left)); return new CDOComparisonScope.AllContents(left, right, origin); } } /** * Takes a {@link CDOView view}/{@link CDOTransaction transaction} * and returns {@link Match matches} only for the <b>changed</b> elements of the entire content tree of its {@link CDOView#getRootResource() root resource}. * The advantage of this scope is that CDO-specific mechanisms are used to efficiently (remotely) determine the set of changed objects. Only those and their container * objects are considered as matches, making this scope scale seamlessly with the overall size of a repository. * The following method returns comparisons that are based on this scope algorithm: * <ul> * <li>{@link CDOCompareUtil#compare(CDOView, CDOView, CDOView[])} * </ul> * * @author Eike Stepper */ public static class Minimal extends CDOComparisonScope implements Predicate<EObject> { private Set<CDOID> ids; public Minimal(CDOView leftView, CDOView rightView, CDOView originView, Set<CDOID> ids) { super(getRoot(leftView), getRoot(rightView), getRoot(originView)); this.ids = ids; Set<CDOID> requiredParentIDs = new HashSet<CDOID>(); for (CDOID id : ids) { collectRequiredParentID(leftView, id, requiredParentIDs); collectRequiredParentID(rightView, id, requiredParentIDs); if (originView != null) { collectRequiredParentID(originView, id, requiredParentIDs); } } ids.addAll(requiredParentIDs); CDOResource rootResource = (CDOResource)getLeft(); ids.remove(rootResource.cdoID()); } public Iterator<? extends EObject> getChildren(EObject eObject) { return new AbstractTreeIterator<EObject>(eObject, false) { private static final long serialVersionUID = 1L; @Override public Iterator<EObject> getChildren(Object object) { if (object instanceof Resource) { Iterator<EObject> iterator = ((Resource)object).getContents().iterator(); return Iterators.filter(iterator, Minimal.this); } Iterator<EObject> iterator = new ProperContentIterator<EObject>((EObject)object, isResolveProxies()); return Iterators.filter(iterator, Minimal.this); } }; } public boolean apply(EObject input) { CDOObject object = CDOUtil.getCDOObject(input); CDOID id = object.cdoID(); return ids.contains(id); } private void collectRequiredParentIDs(CDOObject object, Set<CDOID> requiredParentIDs) { CDOState state = object.cdoState(); if (state == CDOState.TRANSIENT) { return; } CDOView view = object.cdoView(); if (state == CDOState.PROXY) { CDOUtil.load(object, view); } CDORevisionData revisionData = object.cdoRevision().data(); CDOID resourceID = revisionData.getResourceID(); if (!CDOIDUtil.isNull(resourceID)) { collectRequiredParentIDs(view, resourceID, requiredParentIDs); } else { CDOID containerID; Object containerOrID = revisionData.getContainerID(); if (containerOrID instanceof EObject) { containerID = (CDOID)((InternalCDOView)object.cdoView()).convertObjectToID(containerOrID); } else { containerID = (CDOID)containerOrID; } collectRequiredParentIDs(view, containerID, requiredParentIDs); } } private void collectRequiredParentIDs(CDOView view, CDOID id, Set<CDOID> requiredParentIDs) { if (!CDOIDUtil.isNull(id)) { if (!ids.contains(id) && requiredParentIDs.add(id)) { collectRequiredParentID(view, id, requiredParentIDs); } } } protected void collectRequiredParentID(CDOView view, CDOID id, Set<CDOID> requiredParentIDs) { try { CDOObject object = view.getObject(id); if (object != null) { collectRequiredParentIDs(object, requiredParentIDs); } } catch (ObjectNotFoundException ex) { //$FALL-THROUGH$ } } public static IComparisonScope create(CDOView leftView, CDOView rightView, CDOView[] originView) { return create(leftView, rightView, originView, DEFAULT_VIEW_OPENER); } /** * @since 4.3 */ public static IComparisonScope create(CDOView leftView, CDOView rightView, CDOView[] originView, CDOViewOpener viewOpener) { CDOView view = openOriginView(leftView, rightView, originView, viewOpener); Set<CDOID> ids = getAffectedIDs(leftView, rightView, view); addDirtyIDs(ids, leftView); addDirtyIDs(ids, rightView); return new CDOComparisonScope.Minimal(leftView, rightView, view, ids); } public static IComparisonScope create(CDOView leftView, CDOView rightView, CDOView[] originView, Set<CDOID> ids) { return create(leftView, rightView, originView, ids, DEFAULT_VIEW_OPENER); } /** * @since 4.3 */ public static IComparisonScope create(CDOView leftView, CDOView rightView, CDOView[] originView, Set<CDOID> ids, CDOViewOpener viewOpener) { CDOView view = openOriginView(leftView, rightView, originView, viewOpener); return new CDOComparisonScope.Minimal(leftView, rightView, view, ids); } public static IComparisonScope create(CDOTransaction transaction) { return create(transaction, DEFAULT_VIEW_OPENER); } /** * @since 4.3 */ public static IComparisonScope create(CDOTransaction transaction, CDOViewOpener viewOpener) { if (viewOpener == null) { viewOpener = transaction.getSession(); } CDOBranchPoint lastUpdate = transaction.getBranch().getPoint(transaction.getLastUpdateTime()); CDOView lastView = viewOpener.openView(lastUpdate, new ResourceSetImpl()); Set<CDOID> ids = new HashSet<CDOID>(); ids.addAll(transaction.getNewObjects().keySet()); ids.addAll(transaction.getDirtyObjects().keySet()); ids.addAll(transaction.getDetachedObjects().keySet()); return new CDOComparisonScope.Minimal(transaction, lastView, null, ids); } private static Set<CDOID> getAffectedIDs(CDOView leftView, CDOView rightView, CDOView originView) { if (originView != null) { InternalCDOSession session = (InternalCDOSession)leftView.getSession(); MergeData mergeData = session.getMergeData(leftView, rightView, originView, false); return mergeData.getIDs(); } CDOChangeSetData changeSetData = leftView.compareRevisions(rightView); return new HashSet<CDOID>(changeSetData.getChangeKinds().keySet()); } private static void addDirtyIDs(Set<CDOID> ids, CDOView view) { if (view instanceof CDOTransaction) { CDOChangeSetData changeSetData = ((CDOTransaction)view).getChangeSetData(); addDirtyIDs(ids, changeSetData.getNewObjects()); addDirtyIDs(ids, changeSetData.getChangedObjects()); addDirtyIDs(ids, changeSetData.getDetachedObjects()); } } private static void addDirtyIDs(Set<CDOID> ids, List<? extends CDOIDAndVersion> keys) { for (CDOIDAndVersion key : keys) { ids.add(key.getID()); } } private static CDOResource getRoot(CDOView view) { if (view == null) { return null; } return view.getRootResource(); } } }