org.eclipse.xtext.xbase.resource.BatchLinkableResource Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2011 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.xbase.resource;
import java.util.LinkedHashSet;
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.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtext.common.types.JvmMember;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.TypesPackage;
import org.eclipse.xtext.common.types.impl.JvmDeclaredTypeImplCustom;
import org.eclipse.xtext.common.types.xtext.JvmMemberInitializableResource;
import org.eclipse.xtext.diagnostics.DiagnosticMessage;
import org.eclipse.xtext.diagnostics.ExceptionDiagnostic;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.linking.lazy.LazyURIEncoder;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.parser.IParseResult;
import org.eclipse.xtext.resource.CompilerPhases;
import org.eclipse.xtext.resource.DerivedStateAwareResource;
import org.eclipse.xtext.resource.IBatchLinkableResource;
import org.eclipse.xtext.resource.ISynchronizable;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.Triple;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import com.google.inject.Inject;
/**
* A specialized EMF resource that is capable of resolving proxies in batch mode.
* That is, on {@link #getEObject(String)}, the {@link BatchLinkingService} is used
* to resolve a chunk of proxies.
*
* @author Sebastian Zarnekow - Linking assumptions
*/
public class BatchLinkableResource extends DerivedStateAwareResource implements ISynchronizable, IBatchLinkableResource, JvmMemberInitializableResource {
private static final Logger log = Logger.getLogger(BatchLinkableResource.class);
@Inject
private BatchLinkingService batchLinkingService;
@Inject
private CompilerPhases compilerPhases;
private Set jvmMemberInitializers = null;
private boolean hasJvmMemberInitializers = false;
private boolean isInitializingJvmMembers = false;
/**
* Returns the lock of the owning {@link ResourceSet}, if it exposes such a lock.
* Otherwise this resource itself is used as the lock context.
*/
/* @NonNull */
@Override
public Object getLock() {
ResourceSet resourceSet = getResourceSet();
if (resourceSet instanceof ISynchronizable>) {
return ((ISynchronizable>) resourceSet).getLock();
}
return this;
}
/**
* {@inheritDoc}
*
* @since 2.4
*/
/* @Nullable */
@Override
public Result execute(/* @NonNull */ IUnitOfWork unit) throws Exception {
synchronized (getLock()) {
return unit.exec(this);
}
}
/**
* {@inheritDoc}
*
* Delegates to the {@link BatchLinkingService} if the requested reference is
* {@link BatchLinkingService#isBatchLinkable(EReference) linkeable in batch mode}.
*
* Implementation detail: This specialization of {@link #getEObject(String) getEObject}
* synchronizes on the {@link #getLock() lock} which is exposed by the synchronizable
* resource rather than on the resource directly. This guards against reentrant resolution
* from different threads that could block each other.
*
* Usually one would want to lock only in the {@link BatchLinkingService} but we could
* have intermixed {@link LazyURIEncoder#isCrossLinkFragment(org.eclipse.emf.ecore.resource.Resource, String)
* lazy cross reference} and vanilla EMF cross references which again could lead to a
* dead lock.
*/
@Override
public EObject getEObject(String uriFragment) {
synchronized (getLock()) {
try {
if (getEncoder().isCrossLinkFragment(this, uriFragment) && !isLoadedFromStorage()) {
if (!getUnresolvableURIFragments().contains(uriFragment)) {
Triple triple = getEncoder().decode(this, uriFragment);
if (batchLinkingService.isBatchLinkable(triple.getSecond())) {
if (compilerPhases.isIndexing(this))
log.error("Don't resolve expressions during indexing!", new IllegalStateException("Resource URI : "+getURI()+", fragment : "+uriFragment));
return batchLinkingService.resolveBatched(triple.getFirst(), triple.getSecond(), uriFragment);
}
return super.getEObject(uriFragment, triple);
} else {
return null;
}
}
return super.getEObject(uriFragment);
} catch (RuntimeException e) {
operationCanceledManager.propagateAsErrorIfCancelException(e);
getErrors().add(new ExceptionDiagnostic(e));
log.error("resolution of uriFragment '" + uriFragment + "' failed.", e);
// wrapped because the javaDoc of this method states that WrappedExceptions are thrown
// logged because EcoreUtil.resolve will ignore any exceptions.
throw new WrappedException(e);
}
}
}
/**
* {@inheritDoc}
*
* Implementation detail: Overridden to use the shared {@link #getLock() lock}.
*/
@SuppressWarnings("sync-override")
@Override
public EList getContents() {
synchronized (getLock()) {
if (isLoaded && !isLoading && !isInitializing && !isUpdating && !fullyInitialized && !isLoadedFromStorage()) {
try {
eSetDeliver(false);
installDerivedState(false);
} finally {
eSetDeliver(true);
}
}
return doGetContents();
}
}
/**
* Delegates to the BatchLinkingService to resolve all references. The linking service
* is responsible to lock the resource or resource set.
*/
@Override
public void resolveLazyCrossReferences(CancelIndicator monitor) {
IParseResult parseResult = getParseResult();
if (parseResult != null) {
batchLinkingService.resolveBatched(parseResult.getRootASTElement(), monitor);
}
operationCanceledManager.checkCanceled(monitor);
super.resolveLazyCrossReferences(monitor);
}
@Override
protected EObject handleCyclicResolution(Triple triple) throws AssertionError {
if (!isValidationDisabled()) {
EObject context = triple.getFirst();
if (context.eClass() == TypesPackage.Literals.JVM_PARAMETERIZED_TYPE_REFERENCE) {
// here we may end up with cyclic resolution requests in rare situations, e.g. for input types
// like :
/*
* package p;
* class C extends p.C.Bogus {}
*/
return null;
}
DiagnosticMessage message = new DiagnosticMessage("Cyclic linking detected : " + getReferences(triple, resolving), Severity.ERROR, "cyclic-resolution");
List list = getDiagnosticList(message);
Diagnostic diagnostic = createDiagnostic(triple, message);
if (!list.contains(diagnostic))
list.add(diagnostic);
}
return null;
}
@Override
public void linkBatched(CancelIndicator monitor) {
resolveLazyCrossReferences(monitor);
}
// Lazy initialization
/**
* Executes any {@link Runnable}s added through {@link #addJvmMemberInitializer(Runnable)}
*
* @since 2.8
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void ensureJvmMembersInitialized() {
if (jvmMemberInitializers == null)
return;
Set localRunnables = null;
synchronized(this) {
localRunnables = jvmMemberInitializers;
jvmMemberInitializers = null;
hasJvmMemberInitializers = false;
}
if (localRunnables == null)
return;
boolean wasDeliver = eDeliver();
LinkedHashSet> before = resolving;
try {
eSetDeliver(false);
if (!before.isEmpty()) {
resolving = new LinkedHashSet>();
}
if (isInitializingJvmMembers) {
throw new IllegalStateException("Reentrant attempt to initialize JvmMembers");
}
try {
isInitializingJvmMembers = true;
for (Runnable runnable : localRunnables) {
runnable.run();
}
} finally {
isInitializingJvmMembers = false;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (!before.isEmpty()) {
resolving = before;
}
eSetDeliver(wasDeliver);
}
}
@Override
public boolean isInitializingJvmMembers() {
return isInitializingJvmMembers;
}
@Override
protected void doDiscardDerivedState() {
this.jvmMemberInitializers = null;
this.hasJvmMemberInitializers = false;
super.doDiscardDerivedState();
}
/**
* Register runnables to be executed when the {@link JvmMember members} of {@link JvmType types}
* in this resource are initialized.
*
* @since 2.8
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void addJvmMemberInitializer(Runnable runnable) {
if (isInitializingJvmMembers) {
throw new IllegalStateException("Cannot enqueue runnables during JvmMemberInitialization");
}
if (this.jvmMemberInitializers == null) {
this.jvmMemberInitializers = new LinkedHashSet();
this.hasJvmMemberInitializers = true;
for (EObject obj : getContents()) {
if (obj instanceof JvmDeclaredTypeImplCustom) {
JvmDeclaredTypeImplCustom type = (JvmDeclaredTypeImplCustom) obj;
markPendingInitialization(type);
}
}
}
this.jvmMemberInitializers.add(runnable);
}
/**
* Recursively traverse the types in this resource to mark them as lazy initialized types.
*/
private void markPendingInitialization(JvmDeclaredTypeImplCustom type) {
type.setPendingInitialization(true);
for (JvmMember member : type.basicGetMembers()) {
if (member instanceof JvmDeclaredTypeImplCustom) {
markPendingInitialization((JvmDeclaredTypeImplCustom) member);
}
}
}
@Override
public boolean hasJvmMemberInitializers() {
return hasJvmMemberInitializers;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy