/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * ForeignEntityProvider.java * Created: May 18, 2007 * By: Joseph Wong */ package org.openquark.cal.compiler; import java.lang.reflect.AccessibleObject; import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration; /** * A ForeignEntityProvider represents a "thunk" whose {@link #get} method returns the same object on * each invocation. This object corresponds to a CAL foreign type or foreign function, and can be a * Class object, or a Method, Field, or Constructor object. * <p> * * The main purpose of this class (and its subclasses) is to facilitate the <em>lazy loading</em> of * classes which are serialized as part of {@link ForeignTypeInfo} and {@link ForeignFunctionInfo}. * By default, such references to classes, methods, fields and constructors are not loaded and resolved until * they are needed (this is the lazy mode). By changing the machine configuration, it is possible for these entities * to be loaded eagerly at deserialization time. * <p> * * The lazy mode makes it possible to write and deploy CAL modules where * some of the foreign dependencies are not available at runtime. As long as the associated foreign types and foreign * functions are not accessed at runtime, no errors will be reported. Running in lazy mode may also bring about * a minor performance improvement in the time required to initialize a CAL workspace (as classloading of foreign * classes are deferred). Strict mode is mainly meant for debugging and for development time, when it is useful * to catch resolution failures early. * <p> * * The ForeignEntityProvider class itself is an abstract base class with two subclasses: * {@link org.openquark.cal.compiler.ForeignEntityProvider.Lazy Lazy} and * {@link org.openquark.cal.compiler.ForeignEntityProvider.Strict Strict}. * <ul> * <li> * The Lazy variant takes a {@link org.openquark.cal.compiler.ForeignEntityProvider.Resolver Resolver} * on construction. The Resolver represents a piece of logic for resolving the underlying object from its specs. * On the first call to {@link org.openquark.cal.compiler.ForeignEntityProvider.Lazy#get() get()}, the * underlying object is resolved, and is then cached in a field. Subsequent calls would return the * cached value. * * <li> * The Strict variant, on the other hand, requires that the underlying object be available on construction, stores * it directly, and returns it on calls to * {@link org.openquark.cal.compiler.ForeignEntityProvider.Strict#get() get()}. * </ul> * This abstract base class is not meant to have other subclasses. * <p> * * The inner class {@link org.openquark.cal.compiler.ForeignEntityProvider.MessageHandler MessageHandler} * is an abstract base class for providing different mechanisms for handling {@link CompilerMessage}s * that are generated during the resolution process, including error messages corresponding to resolution failure. * Once again, there are two variants * {@link org.openquark.cal.compiler.ForeignEntityProvider.MessageHandler.Lazy Lazy} and * {@link org.openquark.cal.compiler.ForeignEntityProvider.MessageHandler.Strict Strict}. * <ul> * <li> * The Lazy variant, which is used with the Lazy provider, turns a message of severity error or above into an * {@link UnableToResolveForeignEntityException} and throws it. * * <li> * The Strict variant, which is used with the Strict provider, handles a compiler * message by immediately logging it to a {@link CompilerMessageLogger}. * </ul> * * Typically, a client defines an implementation of * {@link org.openquark.cal.compiler.ForeignEntityProvider.Resolver Resolver}, and then calls the * static {@link #make} method to construct an instance of this class. Depending on the current * machine configuration, the {@link #make} method returns either a Strict or a Lazy variant. If it * is to return the Strict variant, it first uses the Resolver to resolve the underlying object. * Otherwise, it passes the Resolver directly to the Lazy variant. * * @param <T> the type of entity provided by the provider - should be a {@link Class} or an {@link AccessibleObject}. * * @author Joseph Wong */ abstract class ForeignEntityProvider<T> { /** * A debugging flag controlling whether each lazy entity resolution should be displayed. */ private static final boolean DISPLAY_LAZY_LOADING_DEBUG_INFO = false; /** * The Resolver is an abstract base class representing a piece of logic for resolving an object from its specs. * * @author Joseph Wong */ static abstract class Resolver<T> { /** * A display string to be returned by {@link #toString()}. */ private final String displayString; /** * Constructor for this abstract base class. * @param displayString a display string to be returned by {@link #toString()} */ Resolver(final String displayString) { if (displayString == null) { throw new NullPointerException(); } this.displayString = displayString; } /** * {@inheritDoc} */ @Override public String toString() { return displayString; } /** * Resolves an object from its specs. * @param messageHandler the MessageHandler to use for handling {@link CompilerMessage}s. * @return the resolved object. * @throws UnableToResolveForeignEntityException if this exception is thrown by the message handler, it is propagated. */ abstract T resolve(ForeignEntityProvider.MessageHandler messageHandler) throws UnableToResolveForeignEntityException; } /** * An abstract base class for providing different mechanisms for handling {@link CompilerMessage}s * that are generated during the resolution process, including error messages corresponding to resolution failure. * * @author Joseph Wong */ static abstract class MessageHandler { /** * A message handler that handles a compiler message by immediately logging it to a {@link CompilerMessageLogger}. * * @author Joseph Wong */ private static final class Strict extends ForeignEntityProvider.MessageHandler { /** * The message logger to which compiler messages will be logged. */ private final CompilerMessageLogger logger; /** * Constructs an instance of this class. * @param logger the message logger to which compiler messages will be logged. */ private Strict(final CompilerMessageLogger logger) { if (logger == null) { throw new NullPointerException(); } this.logger = logger; } /** * {@inheritDoc} */ @Override void handleMessage(final CompilerMessage message) { logger.logMessage(message); } } /** * A message handler that turns a message of severity error or above into an * {@link UnableToResolveForeignEntityException} and throws it. * * @author Joseph Wong */ private static final class Lazy extends ForeignEntityProvider.MessageHandler { /** Private constructor. */ private Lazy() {} /** * {@inheritDoc} */ @Override void handleMessage(final CompilerMessage message) throws UnableToResolveForeignEntityException { if (message.getSeverity().compareTo(CompilerMessage.Severity.ERROR) >= 0) { throw new UnableToResolveForeignEntityException(message); } } } /** Package-scoped constructor. */ MessageHandler() {} /** * Handles a compiler message arising during the resolution of an object from its specs. * @param message the compiler message. * @throws UnableToResolveForeignEntityException an implementation can choose to throw this exception for error messages. */ abstract void handleMessage(CompilerMessage message) throws UnableToResolveForeignEntityException; } /** * A ForeignEntityProvider implementation that requires the underlying object be available on * construction, stores it directly, and returns it on calls to {@link #get()}. * * @author Joseph Wong */ private static final class Strict<T> extends ForeignEntityProvider<T> { /** * The underlying object to be provided via {@link #get()}. Cannot be null. */ private final T foreignEntity; /** * Constructs an instance of this class. * @param foreignEntity the underlying object to be provided via {@link #get()}. Cannot be null. */ private Strict(final T foreignEntity) { if (foreignEntity == null) { throw new NullPointerException("foreignEntity must not be null"); } this.foreignEntity = foreignEntity; } /** * {@inheritDoc} */ @Override T get() { return foreignEntity; } /** * {@inheritDoc} */ @Override public String toString() { return String.valueOf(foreignEntity); // String.valueOf handles the case of null } } /** * A ForeignEntityProvider implementation that takes a * {@link org.openquark.cal.compiler.ForeignEntityProvider.Resolver Resolver} on construction. * The Resolver represents a piece of logic for resolving the underlying object from its specs. * On the first call to {@link #get()}, the underlying object is resolved, and is then cached * in a field. Subsequent calls would return the cached value. * * @author Joseph Wong */ private static final class Lazy<T> extends ForeignEntityProvider<T> { /** * A cache of the underlying object. Initially null. After the first call to {@link #get()}, this * field holds the resolved value (which cannot be null). This field must be accessed in a thread-safe manner. */ private T foreignEntity; /** * The associated resolver. Initially non-null. This resolver is used in the first call to {@link #get()}, * which nulls out this field in the process. If the resolver is null, it means that resolution has occurred, * and the value in {@link #foreignEntity} is the result of the resolution. */ private ForeignEntityProvider.Resolver<T> resolver; /** * Constructs an instance of this class. * @param resolver the associated resolver. Cannot be null. */ private Lazy(final ForeignEntityProvider.Resolver<T> resolver) { if (resolver == null) { throw new NullPointerException(); } this.resolver = resolver; } /** * {@inheritDoc} */ @Override synchronized T get() throws UnableToResolveForeignEntityException { if (resolver != null) { foreignEntity = resolver.resolve(new MessageHandler.Lazy()); resolver = null; // Now that we have resolved the entity, perform the checks if (foreignEntity == null) { throw new NullPointerException("foreignEntity must not be null"); } if (DISPLAY_LAZY_LOADING_DEBUG_INFO) { System.out.println("Lazy-loaded foreign entity: " + foreignEntity); } } return foreignEntity; } /** * {@inheritDoc} */ @Override public synchronized String toString() { if (resolver != null) { return resolver.toString(); } else { return String.valueOf(foreignEntity); // String.valueOf handles the case of null } } } /** * Factory method for constructing an instance of ForeignEntityProvider suitable for the current mode (strict or lazy). * @param logger the message logger to use for logging compiler messages, if we are in strict mode. * @param resolver the resolver encapsulating the entity resolution logic. * @return a ForeignEntityProvider instance, or null if we are in strict mode and the resolved foreign entity is null. */ static final <T> ForeignEntityProvider<T> make(final CompilerMessageLogger logger, final ForeignEntityProvider.Resolver<T> resolver) { if (LECCMachineConfiguration.useLazyForeignEntityLoading()) { // If we are in lazy mode, just wrap the resolver with a Lazy instance. return new Lazy<T>(resolver); } else { // Otherwise resolve the entity and return a Strict instance. return makeStrict(logger, resolver); } } /** * Factory method for constructing a strict instance of ForeignEntityProvider. * @param logger the message logger to use for logging compiler messages. * @param resolver the resolver encapsulating the entity resolution logic. * @return a ForeignEntityProvider instance, or null if the resolved foreign entity is null. */ static <T> ForeignEntityProvider<T> makeStrict(final CompilerMessageLogger logger, final ForeignEntityProvider.Resolver<T> resolver) { // Attempt to resolve the entity. final T resolvedEntity; try { resolvedEntity = resolver.resolve(new MessageHandler.Strict(logger)); } catch (final UnableToResolveForeignEntityException e) { // this should never happen with a logger-based MessageHandler final IllegalStateException illegalStateException = new IllegalStateException("An UnableToResolveForeignEntityException should not be thrown by a logger-based MessageHandler"); illegalStateException.initCause(e); throw illegalStateException; } // If the resolved entity is null, we just return null. Otherwise, we wrap the entity with a Strict instance. if (resolvedEntity == null) { return null; } else { return new Strict<T>(resolvedEntity); } } /** * Factory method for constructing a strict instance of ForeignEntityProvider with a resolved foreign class. * @param foreignClass the resolved foreign class. * @return a ForeignEntityProvider instance encapsulating the resolved class. */ static final ForeignEntityProvider<Class<?>> makeStrict(final Class<?> foreignClass) { return new Strict<Class<?>>(foreignClass); } /** Private constructor. */ private ForeignEntityProvider() {} /** * Returns the resolved entity. * @return the resolved entity. * @throws UnableToResolveForeignEntityException an implementation can choose to throw this exception if the entity cannot be resolved. */ abstract T get() throws UnableToResolveForeignEntityException; /** * {@inheritDoc} */ /* Subclasses must declare explicit toString() implementations. */ @Override public abstract String toString(); }