org.eclipse.emf.compare.utils.EqualityHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.utils.EqualityHelper.java

Source

/*******************************************************************************
 * Copyright (c) 2012, 2015 Obeo 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:
 *     Obeo - initial API and implementation
 *     Stefan Dirix - bug 473730 (ignore URI type descriptions)
 *******************************************************************************/
package org.eclipse.emf.compare.utils;

import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.concurrent.ExecutionException;

import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.match.DefaultMatchEngine;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.FeatureMap;

/**
 * EMF Compare needs its own rules for "equality", which are based on similarity instead of strict equality.
 * These will be used throughout the process.
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 */
public class EqualityHelper extends AdapterImpl implements IEqualityHelper {
    /** A cache keeping track of the URIs for EObjects. */
    private final LoadingCache<EObject, URI> uriCache;

    /**
     * {@link #matchingEObjects(EObject, EObject)} is called _a lot_ of successive times with the same first
     * parameter... we use this as a poor man's cache.
     */
    private WeakReference<EObject> lastMatchedEObject;

    /** See #lastMatchedEObject. */
    private WeakReference<Match> lastMatch;

    /**
     * Creates a new EqualityHelper.
     * 
     * @deprecated use the EqualityHelper(Cache) constructor instead.
     */
    @Deprecated
    public EqualityHelper() {
        this(createDefaultCache(
                CacheBuilder.newBuilder().maximumSize(DefaultMatchEngine.DEFAULT_EOBJECT_URI_CACHE_MAX_SIZE)));
    }

    /**
     * Creates a new EqualityHelper with the given cache.
     * 
     * @param uriCache
     *            the cache to be used for {@link EcoreUtil#getURI(EObject)} calls.
     */
    public EqualityHelper(LoadingCache<EObject, URI> uriCache) {
        this.uriCache = uriCache;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.common.notify.impl.AdapterImpl#getTarget()
     */
    @Override
    public Comparison getTarget() {
        return (Comparison) super.getTarget();
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.common.notify.impl.AdapterImpl#isAdapterForType(java.lang.Object)
     */
    @Override
    public boolean isAdapterForType(Object type) {
        return type == IEqualityHelper.class;
    }

    /**
     * Check that the two given values are "equal", considering the specifics of EMF.
     * 
     * @param comparison
     *            Provides us with the Match necessary for EObject comparison.
     * @param object1
     *            First of the two objects to compare here.
     * @param object2
     *            Second of the two objects to compare here.
     * @return <code>true</code> if both objects are to be considered equal, <code>false</code> otherwise.
     * @see #matchingValues(Object, Object)
     */
    @Deprecated
    public boolean matchingValues(Comparison comparison, Object object1, Object object2) {
        return matchingValues(object1, object2);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.utils.IEqualityHelper#matchingValues(java.lang.Object, java.lang.Object)
     */
    public boolean matchingValues(Object object1, Object object2) {
        final boolean equal;
        if (object1 == object2) {
            equal = true;
        } else if (object1 instanceof EObject && object2 instanceof EObject) {
            // [248442] This will handle FeatureMapEntries detection
            equal = matchingEObjects((EObject) object1, (EObject) object2);
        } else if (isNullOrEmptyString(object1) && isNullOrEmptyString(object2)) {
            // Special case, consider that the empty String is equal to null (unset attributes)
            equal = true;
        } else if (object1 instanceof String || object1 instanceof Integer || object1 instanceof Boolean) {
            // primitives and String are much more common than arrays... and isArray() is expensive.
            equal = object1.equals(object2);
        } else if (object1 != null && object1.getClass().isArray() && object2 != null
                && object2.getClass().isArray()) {
            // [299641] compare arrays by their content instead of instance equality
            equal = matchingArrays(object1, object2);
        } else if (object1 instanceof FeatureMap.Entry && object2 instanceof FeatureMap.Entry) {
            EStructuralFeature key1 = ((FeatureMap.Entry) object1).getEStructuralFeature();
            EStructuralFeature key2 = ((FeatureMap.Entry) object2).getEStructuralFeature();
            Object value1 = ((FeatureMap.Entry) object1).getValue();
            Object value2 = ((FeatureMap.Entry) object2).getValue();

            equal = key1.equals(key2) && matchingValues(value1, value2);
        } else {
            equal = object1 != null && object1.equals(object2);
        }
        return equal;
    }

    /**
     * Returns {@code true} if the given {@code object} is {@code null} or the empty String.
     * 
     * @param object
     *            The object we need to test.
     * @return {@code true} if the given {@code object} is {@code null} or the empty String.
     */
    private boolean isNullOrEmptyString(Object object) {
        return object == null || object instanceof String && ((String) object).length() == 0;
    }

    /**
     * Compares two values as EObjects, using their Match if it can be found, comparing through their URIs
     * otherwise.
     * 
     * @param object1
     *            First of the two objects to compare here.
     * @param object2
     *            Second of the two objects to compare here.
     * @return <code>true</code> if these two EObjects are to be considered equal, <code>false</code>
     *         otherwise.
     */
    protected boolean matchingEObjects(EObject object1, EObject object2) {
        final Match match = getMatch(object1);

        final boolean equal;
        // Match could be null if the value is out of the scope
        if (match != null) {
            equal = match.getLeft() == object2 || match.getRight() == object2 || match.getOrigin() == object2;
            // use getTarget().getMatch() to avoid invalidating the cache here
        } else if (getTarget().getMatch(object2) != null) {
            equal = false;
        } else if (object1.eClass() != object2.eClass()) {
            equal = false;
        } else {
            equal = matchingURIs(object1, object2);
        }

        return equal;
    }

    /**
     * Retrieves the match of the given EObject. This will cache the latest access so as to avoid a hashmap
     * lookup in the comparison's inverse cross referencer. This is most useful when computing the LCS of two
     * sequences, where we call {@link #matchingEObjects(EObject, EObject)} over and over with the same first
     * object.
     * 
     * @param o
     *            The object for which we need the associated Match.
     * @return Match of this EObject if any, <code>null</code> otherwise.
     */
    protected Match getMatch(EObject o) {
        final Match match;
        if (lastMatchedEObject != null && lastMatchedEObject.get() == o) {
            Match temp = lastMatch.get();
            if (temp != null) {
                match = temp;
            } else {
                match = getTarget().getMatch(o);
                lastMatch = new WeakReference<Match>(match);
            }
        } else {
            match = getTarget().getMatch(o);
            lastMatchedEObject = new WeakReference<EObject>(o);
            lastMatch = new WeakReference<Match>(match);
        }
        return match;
    }

    /**
     * Compare the URIs (of similar concept) of EObjects.
     * 
     * @param object1
     *            First of the two objects to compare here.
     * @param object2
     *            Second of the two objects to compare here.
     * @return <code>true</code> if these two EObjects have the same URIs, <code>false</code> otherwise.
     */
    protected boolean matchingURIs(EObject object1, EObject object2) {
        // An object that is uncontained and is not a proxy has no URI. bypass them.
        if (!object1.eIsProxy() && isUncontained(object1) || !object2.eIsProxy() && isUncontained(object2)) {
            return false;
        }

        final boolean equal;
        final URI uri1 = uriCache.getUnchecked(object1);
        final URI uri2 = uriCache.getUnchecked(object2);
        if (uri1.hasFragment() && uri2.hasFragment()) {
            final String uri1Fragment = removeURIAttachment(uri1.fragment());
            final String uri2Fragment = removeURIAttachment(uri2.fragment());
            equal = uri1Fragment.equals(uri2Fragment);
        } else {
            equal = uri1.equals(uri2);
        }
        return equal;
    }

    /**
     * To some {@link URI}s a human friendly description is attached describing the type the {@link URI}
     * is pointing to. The description is marked by a "?" at the beginning and end. This method returns
     * the fragment without the attached type description.
     * 
     * @param fragment
     *                The {@link URI} fragment to check for a type description attachment
     * @return The fragment of the {@link URI} stripped from type description if it has one, otherwise the
     *         original fragment is returned.
     */
    private String removeURIAttachment(String fragment) {
        // check if fragment contains at least two question marks
        final int questionMark1 = fragment.indexOf('?');
        final boolean hasTwoQuestionMarks = questionMark1 != -1 && fragment.indexOf('?', questionMark1 + 1) != -1;
        if (hasTwoQuestionMarks) {
            return fragment.substring(0, questionMark1);
        }
        return fragment;
    }

    /**
     * Checks whether the given object is contained anywhere.
     * 
     * @param object
     *            The object whose container we are to check.
     * @return <code>true</code> if the object has no reachable container, <code>false</code> otherwise.
     */
    private boolean isUncontained(EObject object) {
        return object.eContainer() == null && object.eResource() == null;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.compare.utils.IEqualityHelper#matchingAttributeValues(java.lang.Object,
     *      java.lang.Object)
     */

    public boolean matchingAttributeValues(Object object1, Object object2) {
        // The default equality helper handles attributes and references the same.
        return matchingValues(object1, object2);
    }

    /**
     * Compares two values as arrays, checking that their length and content match each other.
     * 
     * @param object1
     *            First of the two objects to compare here.
     * @param object2
     *            Second of the two objects to compare here.
     * @return <code>true</code> if these two arrays are to be considered equal, <code>false</code> otherwise.
     */
    private boolean matchingArrays(Object object1, Object object2) {
        boolean equal = true;
        final int length1 = Array.getLength(object1);
        if (length1 != Array.getLength(object2)) {
            equal = false;
        } else {
            for (int i = 0; i < length1 && equal; i++) {
                final Object element1 = Array.get(object1, i);
                final Object element2 = Array.get(object2, i);
                equal = matchingValues(element1, element2);
            }
        }
        return equal;
    }

    /**
     * The EqualityHelper often needs to get an EObject uri. As such it has an internal cache that clients
     * might leverage through this method.
     * 
     * @param object
     *            any EObject.
     * @return the URI of the given EObject, or {@code null} if we somehow could not compute it.
     */
    @Deprecated
    public URI getURI(EObject object) {
        try {
            return uriCache.get(object);
        } catch (ExecutionException e) {
            return null;
        }
    }

    /**
     * Returns the cache used by this object.
     * 
     * @return the cache used by this object.
     */
    @Deprecated
    public Cache<EObject, URI> getCache() {
        return uriCache;
    }

    /**
     * Create a cache as required by EqualityHelper.
     * 
     * @param cacheBuilder
     *            The builder to use to instantiate the cache.
     * @return the new cache.
     */
    public static LoadingCache<EObject, URI> createDefaultCache(CacheBuilder<Object, Object> cacheBuilder) {
        return cacheBuilder.build(CacheLoader.from(new URICacheFunction()));
    }

    /**
     * This is the function that will be used by our {@link #uriCache} to compute its values.
     * 
     * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
     */
    private static class URICacheFunction implements Function<EObject, URI> {
        /**
         * {@inheritDoc}
         * 
         * @see com.google.common.base.Function#apply(java.lang.Object)
         */
        public URI apply(EObject input) {
            if (input == null) {
                return null;
            }
            return EcoreUtil.getURI(input);
        }
    }
}