org.eclipse.xtext.linking.lazy.LazyLinker.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.linking.lazy.LazyLinker.java

Source

/*******************************************************************************
 * Copyright (c) 2008 itemis AG (http://www.itemis.eu) 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
 *******************************************************************************/
package org.eclipse.xtext.linking.lazy;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.xtext.AbstractMetamodelDeclaration;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.IGrammarAccess;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.diagnostics.IDiagnosticConsumer;
import org.eclipse.xtext.diagnostics.IDiagnosticProducer;
import org.eclipse.xtext.linking.impl.AbstractCleaningLinker;
import org.eclipse.xtext.linking.impl.LinkingDiagnosticProducer;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.util.EcoreGenericsUtil;
import org.eclipse.xtext.util.OnChangeEvictingCache;
import org.eclipse.xtext.util.SimpleCache;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;

import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
 * @author Sven Efftinge - Initial contribution and API
 * @author Knut Wannheden
 */
public class LazyLinker extends AbstractCleaningLinker {

    private static final Logger log = Logger.getLogger(LazyLinker.class);

    private SimpleCache<EClass, EClass> instantiableSubTypes = new SimpleCache<EClass, EClass>(
            new Function<EClass, EClass>() {
                @Override
                public EClass apply(EClass from) {
                    return findInstantiableCompatible(from);
                }
            });

    @Inject
    private LazyURIEncoder encoder;

    @Inject
    private Registry registry;

    @Inject
    private EcoreGenericsUtil ecoreGenericsUtil;

    @Inject
    private IGrammarAccess grammarAccess;

    @Inject
    private OnChangeEvictingCache cache;

    @Override
    protected void doLinkModel(final EObject model, IDiagnosticConsumer consumer) {
        final Multimap<EStructuralFeature.Setting, INode> settingsToLink = ArrayListMultimap.create();
        final LinkingDiagnosticProducer producer = new LinkingDiagnosticProducer(consumer);
        cache.execWithoutCacheClear(model.eResource(), new IUnitOfWork.Void<Resource>() {
            @Override
            public void process(Resource state) throws Exception {
                TreeIterator<EObject> iterator = getAllLinkableContents(model);
                boolean clearAllReferencesRequired = isClearAllReferencesRequired(state);
                while (iterator.hasNext()) {
                    EObject eObject = iterator.next();
                    if (clearAllReferencesRequired) {
                        clearReferences(eObject);
                    }
                    installProxies(eObject, producer, settingsToLink);
                }
            }
        });
        installQueuedLinks(settingsToLink);
    }

    protected void installProxies(EObject obj, IDiagnosticProducer producer,
            Multimap<EStructuralFeature.Setting, INode> settingsToLink) {
        ICompositeNode node = NodeModelUtils.getNode(obj);
        if (node == null)
            return;
        installProxies(obj, producer, settingsToLink, node, false);
    }

    private void installProxies(EObject obj, IDiagnosticProducer producer,
            Multimap<EStructuralFeature.Setting, INode> settingsToLink, ICompositeNode parentNode,
            boolean dontCheckParent) {
        final EClass eClass = obj.eClass();
        if (eClass.getEAllReferences().size() - eClass.getEAllContainments().size() == 0)
            return;

        for (INode node = parentNode.getFirstChild(); node != null; node = node.getNextSibling()) {
            EObject grammarElement = node.getGrammarElement();
            if (grammarElement instanceof CrossReference && hasLeafNodes(node)) {
                producer.setNode(node);
                CrossReference crossReference = (CrossReference) grammarElement;
                final EReference eRef = GrammarUtil.getReference(crossReference, eClass);
                if (eRef == null) {
                    ParserRule parserRule = GrammarUtil.containingParserRule(crossReference);
                    final String feature = GrammarUtil.containingAssignment(crossReference).getFeature();
                    throw new IllegalStateException(
                            "Couldn't find EReference for crossreference '" + eClass.getName() + "::" + feature
                                    + "' in parser rule '" + parserRule.getName() + "'.");
                }
                if (!eRef
                        .isResolveProxies() /*|| eRef.getEOpposite() != null see https://bugs.eclipse.org/bugs/show_bug.cgi?id=282486*/) {
                    final EStructuralFeature.Setting setting = ((InternalEObject) obj).eSetting(eRef);
                    settingsToLink.put(new SettingDelegate(setting), node);
                } else {
                    createAndSetProxy(obj, node, eRef);
                    afterCreateAndSetProxy(obj, node, eRef, crossReference, producer);
                }
            } else if (grammarElement instanceof RuleCall && node instanceof ICompositeNode) {
                RuleCall ruleCall = (RuleCall) grammarElement;
                AbstractRule calledRule = ruleCall.getRule();
                if (calledRule instanceof ParserRule && ((ParserRule) calledRule).isFragment()) {
                    installProxies(obj, producer, settingsToLink, (ICompositeNode) node, true);
                }
            }
        }
        if (!dontCheckParent && shouldCheckParentNode(parentNode)) {
            installProxies(obj, producer, settingsToLink, parentNode.getParent(), dontCheckParent);
        }
    }

    /**
     * @since 2.7
     */
    protected void afterCreateAndSetProxy(EObject obj, INode node, EReference eRef, CrossReference crossReference,
            IDiagnosticProducer producer) {
    }

    /**
     * @since 2.4
     */
    protected boolean hasLeafNodes(INode node) {
        return !Iterables.isEmpty(node.getLeafNodes());
    }

    @SuppressWarnings("unchecked")
    protected void installQueuedLinks(Multimap<EStructuralFeature.Setting, INode> settingsToLink) {
        for (EStructuralFeature.Setting setting : settingsToLink.keySet()) {
            final EObject eObject = setting.getEObject();
            final EReference eRef = (EReference) setting.getEStructuralFeature();
            final Collection<INode> nodes = settingsToLink.get(setting);
            if (setting.getEStructuralFeature().isMany()) {
                EList<EObject> list = (EList<EObject>) setting.get(false);
                for (INode node : nodes) {
                    final EObject proxy = createProxy(eObject, node, eRef);
                    list.add(EcoreUtil.resolve(proxy, eObject));
                }
            } else {
                final INode node = nodes.iterator().next();
                final EObject proxy = createProxy(eObject, node, eRef);
                setting.set(EcoreUtil.resolve(proxy, eObject));
            }
        }
    }

    @SuppressWarnings("unchecked")
    protected void createAndSetProxy(EObject obj, INode node, EReference eRef) {
        final EObject proxy = createProxy(obj, node, eRef);
        if (eRef.isMany()) {
            ((InternalEList<EObject>) obj.eGet(eRef, false)).addUnique(proxy);
        } else {
            obj.eSet(eRef, proxy);
        }
    }

    protected EObject createProxy(EObject obj, INode node, EReference eRef) {
        final Resource resource = obj.eResource();
        if (resource == null)
            throw new IllegalStateException("object must be contained in a resource");
        final URI uri = resource.getURI();
        final URI encodedLink = uri.appendFragment(encoder.encode(obj, eRef, node));
        EClass referenceType = getProxyType(obj, eRef);
        final EObject proxy = EcoreUtil.create(referenceType);
        ((InternalEObject) proxy).eSetProxyURI(encodedLink);
        return proxy;
    }

    /**
     * @since 2.7
     */
    protected EClass getProxyType(EObject obj, EReference eRef) {
        EClass referenceType = ecoreGenericsUtil.getReferenceType(eRef, obj.eClass());
        EClass instantiableType = instantiableSubTypes.get(referenceType);
        return instantiableType;
    }

    protected EClass findInstantiableCompatible(EClass eType) {
        if (!isInstantiatableSubType(eType, eType)) {
            // check local Package
            EPackage ePackage = eType.getEPackage();
            EClass eClass = findSubTypeInEPackage(ePackage, eType);
            if (eClass != null)
                return eClass;
            return globalFindInstantiableCompatible(eType);
        }
        return eType;
    }

    protected EClass globalFindInstantiableCompatible(EClass eType) {
        Set<String> visitedPackages = Sets.newHashSet(eType.getEPackage().getNsURI());
        for (AbstractMetamodelDeclaration metamodel : GrammarUtil
                .allMetamodelDeclarations(grammarAccess.getGrammar())) {
            if (visitedPackages.add(metamodel.getEPackage().getNsURI())) {
                EClass result = findSubTypeInEPackage(metamodel.getEPackage(), eType);
                if (result != null)
                    return result;
            }
        }
        log.warn("Traversing EPackage registry to find instantiable subtype for '" + eType.getName() + "'");
        log.warn("You may override LazyLinker#globalFindInstantiableCompatible(..) to prevent this.");
        for (String nsURI : getRegisteredNsUris()) {
            if (visitedPackages.add(nsURI)) {
                try {
                    EClass result = findSubTypeInEPackage(getRegistry().getEPackage(nsURI), eType);
                    if (result != null)
                        return result;
                } catch (WrappedException ex) {
                    log.error("Error when loading EPackage '" + nsURI + "'");
                    log.error("You may override LazyLinker#globalFindInstantiableCompatible(..) to prevent this.");
                    log.error("Error when loading EPackage '" + nsURI + "'", ex);
                }
            }
        }
        throw new IllegalStateException("Could not find an instantiable subtype for type: '" + eType.getName()
                + "' (" + eType.getEPackage().getNsURI() + ").");
    }

    private List<String> getRegisteredNsUris() {
        Set<String> keySet = getRegistry().keySet();
        // copy to avoid ConcurrentModificationException while iterating over the EPackageMap
        List<String> copy = Lists.newArrayList(keySet);
        return copy;
    }

    protected EClass findSubTypeInEPackage(EPackage ePackage, EClass superType) {
        EList<EClassifier> classifiers = ePackage.getEClassifiers();
        for (EClassifier eClassifier : classifiers) {
            if (eClassifier instanceof EClass) {
                EClass c = (EClass) eClassifier;
                if (isInstantiatableSubType(c, superType))
                    return c;
            }
        }
        return null;
    }

    private boolean isInstantiatableSubType(EClass c, EClass superType) {
        return !c.isAbstract() && !c.isInterface() && EcoreUtil2.isAssignableFrom(superType, c);
    }

    public LazyURIEncoder getEncoder() {
        return encoder;
    }

    public Registry getRegistry() {
        return registry;
    }

    public void setRegistry(Registry registry) {
        this.registry = registry;
    }

    public void setEncoder(LazyURIEncoder encoder) {
        this.encoder = encoder;
    }

    public void setGrammarAccess(IGrammarAccess grammarAccess) {
        this.grammarAccess = grammarAccess;
    }

    public IGrammarAccess getGrammarAccess() {
        return grammarAccess;
    }

    /**
     * @since 2.4
     */
    protected OnChangeEvictingCache getCache() {
        return cache;
    }
}