/*******************************************************************************
* Copyright (c) 2012, 2013 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.internal.spec;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterators.addAll;
import static com.google.common.collect.Lists.newArrayList;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.common.util.AbstractEList;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.impl.ComparisonImpl;
import org.eclipse.emf.compare.internal.DiffCrossReferencer;
import org.eclipse.emf.compare.internal.MatchCrossReferencer;
import org.eclipse.emf.compare.match.DefaultMatchEngine;
import org.eclipse.emf.compare.utils.EqualityHelper;
import org.eclipse.emf.compare.utils.IEqualityHelper;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* This specialization of the {@link ComparisonImpl} class allows us to define the derived features and
* operations implementations.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public class ComparisonSpec extends ComparisonImpl {
/** Keeps a reference to our match cross referencer. */
private MatchCrossReferencer matchCrossReferencer;
/** Keeps a reference to our diff cross referencer. */
private DiffCrossReferencer diffCrossReferencer;
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.impl.ComparisonImpl#getDifferences()
*/
@Override
public EList<Diff> getDifferences() {
final Iterator<Diff> diffIterator = Iterators.filter(eAllContents(), Diff.class);
final EList<Diff> allDifferences = new BasicEList<Diff>();
while (diffIterator.hasNext()) {
((AbstractEList<Diff>)allDifferences).addUnique(diffIterator.next());
}
return allDifferences;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.impl.ComparisonImpl#getDifferences(org.eclipse.emf.ecore.EObject)
*/
@Override
public EList<Diff> getDifferences(EObject element) {
if (element == null) {
return new BasicEList<Diff>();
}
if (diffCrossReferencer == null) {
diffCrossReferencer = new DiffCrossReferencer();
eAdapters().add(diffCrossReferencer);
}
final EList<Diff> result;
final Match match = getMatch(element);
if (match != null) {
result = getInverseReferences(match);
} else {
final Collection<EStructuralFeature.Setting> settings = diffCrossReferencer
.getNonNavigableInverseReferences(element, false);
result = new BasicEList<Diff>(settings.size());
for (EStructuralFeature.Setting setting : settings) {
EObject eObject = setting.getEObject();
result.add((Diff)eObject);
}
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.impl.ComparisonImpl#getMatch(org.eclipse.emf.ecore.EObject)
*/
@Override
public Match getMatch(EObject element) {
if (element != null) {
if (matchCrossReferencer == null) {
matchCrossReferencer = new MatchCrossReferencer();
eAdapters().add(matchCrossReferencer);
}
final Collection<EStructuralFeature.Setting> settings = matchCrossReferencer
.getNonNavigableInverseReferences(element, false);
Iterator<Setting> settingsIterator = settings.iterator();
if (settingsIterator.hasNext()) {
// cast to Match without testing
// the matchCrossReferencer will only return Match from #getNonNavigableInverseReferences
return (Match)settingsIterator.next().getEObject();
}
}
return null;
}
/**
* Returns an {@link EList} of Diff being inverse references of sides of the given {@code match} as stored
* by {@link #diffCrossReferencer}.
*
* @param match
* the target of the search cross references.
* @return a possibly empty {@link EList} of inverse references.
*/
private EList<Diff> getInverseReferences(Match match) {
final Collection<EStructuralFeature.Setting> leftInverseReference = safGetNonNavigableInverseReferences(
match.getLeft(), diffCrossReferencer);
final Collection<EStructuralFeature.Setting> rightInverseReference = safGetNonNavigableInverseReferences(
match.getRight(), diffCrossReferencer);
final Collection<EStructuralFeature.Setting> originInverseReference = safGetNonNavigableInverseReferences(
match.getOrigin(), diffCrossReferencer);
// creates a list eagerly as we have to know the size beforehand and we will iterate on it later.
List<ResourceAttachmentChange> resourceAttachmentChanges = newArrayList(
filter(match.getDifferences(), ResourceAttachmentChange.class));
final int maxExpectedSize = leftInverseReference.size() + rightInverseReference.size()
+ originInverseReference.size() + resourceAttachmentChanges.size();
EList<Diff> result = new UniqueEList.FastCompare<Diff>(maxExpectedSize);
addSettingEObjectsTo(result, leftInverseReference);
addSettingEObjectsTo(result, rightInverseReference);
addSettingEObjectsTo(result, originInverseReference);
addAll(result, resourceAttachmentChanges.iterator());
return result;
}
/**
* Iterates on the given {@code settings} and retrieve the asociated EObject before adding it to
* {@code addTo} List.
* <p>
* It <b>modifies</b> the {@code addTo} {@link List} and <b>expect</b> the settings to all be from
* {@link Diff}. This method only reason to exists is to factorize a loop and <em>should not</em> be used
* outside {@link #getInverseReferences(Match)}.
*
* @param addTo
* the list to which {@link Setting}'s {@link EObject} will be added.
* @param settings
* the list of {@link Setting} to iterate on.
*/
private static void addSettingEObjectsTo(List<Diff> addTo,
final Collection<EStructuralFeature.Setting> settings) {
for (EStructuralFeature.Setting setting : settings) {
// cast to Diff without testing
// the diffCrossReferencer will only return Diff from #getNonNavigableInverseReferences
addTo.add((Diff)setting.getEObject());
}
}
/**
* Returns the inverse reference of the given {@code eObject} regarding the given {@code crossReferencer}.
* It is {@code safe} to call it with <code>null</code> {@code eObject}: it will return an empty
* collection.
*
* @param eObject
* the target of the search cross references.
* @param crossReferencer
* the cross referencer adapter to use for searching.
* @return a possibly empty {@link EList} of inverse references.
*/
private Collection<EStructuralFeature.Setting> safGetNonNavigableInverseReferences(EObject eObject,
ECrossReferenceAdapter crossReferencer) {
final Collection<EStructuralFeature.Setting> inverseReference;
if (eObject != null) {
inverseReference = crossReferencer.getNonNavigableInverseReferences(eObject, false);
} else {
inverseReference = ImmutableList.of();
}
return inverseReference;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.impl.ComparisonImpl#getEqualityHelper()
*/
@Override
public IEqualityHelper getEqualityHelper() {
IEqualityHelper ret = (IEqualityHelper)EcoreUtil.getExistingAdapter(this, IEqualityHelper.class);
if (ret == null) {
ret = new EqualityHelper(EqualityHelper.createDefaultCache(CacheBuilder.newBuilder()
.maximumSize(DefaultMatchEngine.DEFAULT_EOBJECT_URI_CACHE_MAX_SIZE)));
this.eAdapters().add(ret);
ret.setTarget(this);
}
return ret;
}
}