blob: 4ea95635373f20a674bb6675164ec5fa5dd492b0 [file] [log] [blame]
/*
* Copyright (c) 2012, 2013, 2015 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();
}
}
}