/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * 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: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.tooling.engine; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.util.Map; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import melnorme.lang.tooling.ast.ILanguageElement; import melnorme.lang.tooling.context.ISemanticContext; import melnorme.lang.tooling.engine.LoopDetector.ResolutionLoopException; /** * A class responsible for doing semantic analysis. * Each instance is bound to a specific {@link ILanguageElement}. * * This class uses the {@link #hashCode()} and {@link #equals()} of Object, such that each instance of * this class can be seperately inserted in a {@link Map}. */ public abstract class ElementSemantics<ER> implements IElementSemanticData { protected final ISemanticContext context; public ElementSemantics(PickedElement<?> pickedElement) { this.context = pickedElement.context; } /* ----------------- Note #equals and #hashCode contract ----------------- */ @Override public final boolean equals(Object obj) { return super.equals(obj); } @Override public final int hashCode() { return super.hashCode(); } /* ----------------- ----------------- */ private ER resolution; private ReentrantLock resolutionMetadataLock = new ReentrantLock(); private long resolutionThreadId = 0; private Condition completionCondition; /** Internal method made public: for use by tests only. */ public boolean isResolved() { return resolution != null; } /** @return not null. */ protected final ER getElementResolution() { return getOrCreateElementResolution(); } protected ER getOrCreateElementResolution() { resolutionMetadataLock.lock(); try { if(resolution != null) { return resolution; } // First, check if a thread is already analysing this node if(resolutionThreadId != 0) { awaitCompletionByOtherThread(); assertNotNull(resolution); return resolution; } // If not, the current thread will do the analysis // First mark, this node as being processed by this thread: resolutionThreadId = Thread.currentThread().getId(); completionCondition = resolutionMetadataLock.newCondition(); // condition for other threads to wait on. ER createdResolution; resolutionMetadataLock.unlock(); try { // perform the computation. createdResolution = assertNotNull(createResolution()); } finally { resolutionMetadataLock.lock(); } resolution = createdResolution; completionCondition.signalAll(); } finally { resolutionMetadataLock.unlock(); } return resolution; } private static LoopDetector loopDetector = new LoopDetector(); private void awaitCompletionByOtherThread() { assertTrue(resolutionMetadataLock.isLocked()); long currentThreadId = Thread.currentThread().getId(); try { // Mark this thread as waiting on resolutionThreadId loopDetector.registerWaitingThread(currentThreadId, resolutionThreadId); } catch (ResolutionLoopException e) { resolution = createLoopResolution(); return; } try { // await for other thread to complete on the completion condition assertTrue(completionCondition != null); completionCondition.awaitUninterruptibly(); // TODO: make interruptable } finally { loopDetector.unregisterWaitingThread(currentThreadId); } } // TODO: optimization: put information about a partial result that can be resolved without a context // in the ILanguageElement itself. // This way, such information can be re-used a new resolution is created in a different context. /** * Create and return the resolution object for this semantics analysis. Non-null. * The resulting object must also be immutable! */ protected abstract ER createResolution(); /** * Create a resolution object representing a loop error. * No further node/element analysis can be performed after this! */ protected abstract ER createLoopResolution(); }