/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.flex.compiler.internal.units.requests;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.flex.compiler.internal.workspaces.Workspace;
import org.apache.flex.compiler.units.requests.IRequest;
import org.apache.flex.compiler.units.requests.IRequestResult;
/**
* Creates implementations of the IRequest, where the object returned from the
* get method of the IRequest is returned from a Callable returned from the
* getCallable method implemented by subclasses of this class.
* <p>
* This class hides the complicated process of creating and reusing request
* objects. Sub classes just need to return a Callable from getCallable that
* will return the result of the request.
*
* @param <ResultType> The class that is used for the result of the request.
* @param <RequesteeType> The type of a context parameter to pass from calls to getRequest
* to getCallable.
*/
public abstract class RequestMaker<ResultType extends IRequestResult, RequesteeInterfaceType, RequesteeType extends RequesteeInterfaceType >
{
/**
* If you run with -Dthrow.assertions=true, then an AssertionError
* will not get turned into an internal compiler problem
* and instead will be caught by JUnit as an error.
*/
private static final boolean THROW_ASSERTIONS = System.getProperty("throw.assertions", "false").equals("true");
private static class Request<V extends IRequestResult, W> implements IRequest<V, W>
{
private Future<V> future;
private Lock lock;
private Condition haveFuture;
private final long timestamp;
private final W requestee;
public Request(W requestee)
{
lock = new ReentrantLock();
haveFuture = lock.newCondition();
timestamp = System.currentTimeMillis();
this.requestee = requestee;
}
@Override
public V get() throws InterruptedException
{
V result = null;
try
{
// blocks till request is done
result = getFuture().get();
}
catch (ExecutionException executionException)
{
Throwable cause = executionException.getCause();
if (THROW_ASSERTIONS && (cause instanceof AssertionError))
throw (AssertionError)cause;
/*
* We don't expect to ever get an ExecutionException because we
* eat all the Throwable's that are not the InterruptedException
* in the Callable we wrap around the Callable we got from the
* abstract getCallable method.
*/
assert false : "Unexpected ExecutionException!";
executionException.printStackTrace();
}
return result;
}
@Override
public boolean isDone()
{
lock.lock();
try
{
if (future == null)
return false;
return future.isDone();
}
finally
{
lock.unlock();
}
}
private Future<V> getFuture()
{
lock.lock();
try
{
while (future == null)
haveFuture.awaitUninterruptibly();
return future;
}
finally
{
lock.unlock();
}
}
private void setFuture(Future<V> future)
{
lock.lock();
try
{
this.future = future;
haveFuture.signalAll();
}
finally
{
lock.unlock();
}
}
@Override
public long getTimeStamp()
{
return timestamp;
}
@Override
public W getRequestee()
{
return requestee;
}
}
/**
* Gets a reference to a request object, by either creating a new IRequest
* or returning an existing one from the specified AtomicReference.
* <p>
* If a new IRequest is created the atomicRef is updated to point at it.
*
* @param u Parameter that is passed through to getCallable if a new Request
* is created.
* @param atomicRef An AtomicReference which contains a reference to an
* existing IRequest or which will be updated to point to the IRequest this
* method creates.
* @param workspace The workspace which contains the ExecutorService which
* is used to schedule processing to compute the IRequestResult.
* @param isNeededForFileScope true if the request is needed to build a file
* scope.
* @return An IRequest referenced by the specified AtomicReference, or a new
* IRequest.
*/
public final IRequest<ResultType, RequesteeInterfaceType> getRequest(RequesteeType u, AtomicReference<IRequest<ResultType, RequesteeInterfaceType>> atomicRef, Workspace workspace, boolean isNeededForFileScope)
{
// This check is purely an optimization to avoid the alloc of the request.
final IRequest<ResultType, RequesteeInterfaceType> existingRequest = atomicRef.get();
if (existingRequest == null)
{
workspace.startRequest(isNeededForFileScope);
final Request<ResultType, RequesteeInterfaceType> request = new Request<ResultType, RequesteeInterfaceType>(u);
if (atomicRef.compareAndSet(null, request))
{
ExecutorService exec = workspace.getExecutorService();
request.setFuture(exec.submit(wrapCallable(u, getCallable(u), workspace)));
}
else
{
workspace.endRequest();
}
assert atomicRef.get() != null;
return atomicRef.get();
}
else
{
return existingRequest;
}
}
/**
* Creates a new Callable that calls the specified Callable and catches any
* Throwable's except for a InterruptedException that were not caught by the
* specified Callable. When a Throwable other than InterruptedException was
* not caught specified Callable, the protected abstract
* getResultForThrowable method of this class is called to construct a
* result object for the new Callable.
*
* @param u Parameter that is passed through to getResultForThrowable if the
* specified Callable does not catch a thrown Throwable.
* @param c Callable the resulting Callable calls and that may not catch all
* Throwable that are thrown.
* @param workspace The workspace to notify once this callable finishes.
* @return A new callable that will not throw any Throwable other than
* InterruptedException.
*/
private Callable<ResultType> wrapCallable(final RequesteeType u, final Callable<ResultType> c, final Workspace workspace)
{
return new Callable<ResultType>()
{
@Override
public ResultType call() throws InterruptedException
{
try
{
return c.call();
}
catch (InterruptedException e)
{
throw e;
}
catch (AssertionError ae)
{
if (THROW_ASSERTIONS)
throw ae;
return getResultForThrowable(u, ae);
}
catch (Exception e)
{
return getResultForThrowable(u, e);
}
finally
{
workspace.endRequest();
}
}
};
}
/**
* Called to get the callable that computes the result of the request.
*
* @param u Parameter that was passed to getRequest.
* @return A Callable that returns the result of the request.
*/
protected abstract Callable<ResultType> getCallable(RequesteeType u);
/**
* Called to get the Result when an uncaught throwable was detected.
*
* @param u Parameter that was passed to getRequest.
* @param t Throwable that was thrown from the Callable returned from
* getCallable.
*/
protected abstract ResultType getResultForThrowable(RequesteeType u, Throwable t);
}