Package org.cojen.util

Source Code of org.cojen.util.BelatedCreator$CreateThread

/*
*  Copyright 2007-2010 Brian S O'Neill
*
*  Licensed 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.
*/

/*
* Copyright 2006 Amazon Technologies, Inc. or its affiliates.
* Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
* of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
*
* Licensed 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.cojen.util;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Map;

import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

import java.security.AccessController;
import java.security.PrivilegedAction;

import org.cojen.classfile.CodeBuilder;
import org.cojen.classfile.ClassFile;
import org.cojen.classfile.Label;
import org.cojen.classfile.MethodDesc;
import org.cojen.classfile.Modifiers;
import org.cojen.classfile.RuntimeClassFile;
import org.cojen.classfile.TypeDesc;

/**
* Generic one-shot factory which supports late object creation. If the object
* creation results in an exception or is taking too long, the object produced
* instead is a bogus one. After retrying, if the real object is created, then
* the bogus object turns into a wrapper to the real object.
*
* <p>Note: If a bogus object is created, the wrapper cannot always be a drop-in
* replacement for the real object. If the wrapper is cloned, it won't have the
* same behavior as cloning the real object. Also, synchronizing on the wrapper
* will not synchronize the real object.
*
* @author Brian S O'Neill
* @since 2.1
*/
public abstract class BelatedCreator<T, E extends Exception> {
    private static final String REF_FIELD_NAME = "ref";

    private static final Cache<Class<?>, Class<?>> cWrapperCache;

    private static final ExecutorService cThreadPool;

    static {
        cWrapperCache = new SoftValueCache(17);
        cThreadPool = Executors.newCachedThreadPool(new TFactory());
    }

    private final Class<T> mType;
    final int mMinRetryDelayMillis;

    private T mReal;
    private boolean mFailed;
    private Throwable mFailedError;
    private T mBogus;
    private AtomicReference<T> mRef;

    private CreateThread mCreateThread;

    /**
     * @param type type of object created
     * @param minRetryDelayMillis minimum milliseconds to wait before retrying
     * to create object after failure; if negative, never retry
     * @throws IllegalArgumentException if type is null or is not an interface
     */
    protected BelatedCreator(Class<T> type, int minRetryDelayMillis) {
        if (type == null) {
            throw new IllegalArgumentException("Type is null");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Type must be an interface: " + type);
        }
        mType = type;
        mMinRetryDelayMillis = minRetryDelayMillis;
    }

    /**
     * Returns real or bogus object. If real object is returned, then future
     * invocations of this method return the same real object instance. This
     * method waits for the real object to be created, if it is blocked. If
     * real object creation fails immediately, then this method will not wait,
     * returning a bogus object immediately instead.
     *
     * @param timeoutMillis maximum time to wait for real object before
     * returning bogus one; if negative, potentially wait forever
     * @throws E exception thrown from createReal
     */
    public synchronized T get(final int timeoutMillis) throws E {
        if (mReal != null) {
            return mReal;
        }

        if (mBogus != null && mMinRetryDelayMillis < 0) {
            return mBogus;
        }

        if (mCreateThread == null) {
            mCreateThread = new CreateThread();
            cThreadPool.submit(mCreateThread);
        }

        if (timeoutMillis != 0) {
            final long start = System.nanoTime();

            try {
                if (timeoutMillis < 0) {
                    while (mReal == null && mCreateThread != null) {
                        if (timeoutMillis < 0) {
                            wait();
                        }
                    }
                } else {
                    long remaining = timeoutMillis;
                    while (mReal == null && mCreateThread != null && !mFailed) {
                        wait(remaining);
                        long elapsed = (System.nanoTime() - start) / 1000000L;
                        if ((remaining -= elapsed) <= 0) {
                            break;
                        }
                    }
                }
            } catch (InterruptedException e) {
            }

            if (mReal != null) {
                return mReal;
            }

            long elapsed = (System.nanoTime() - start) / 1000000L;

            if (elapsed >= timeoutMillis) {
                timedOutNotification(elapsed);
            }
        }

        if (mFailedError != null) {
            // Take ownership of error. Also stitch traces together for
            // context. This gives the illusion that the creation attempt
            // occurred in this thread.

            Throwable error = mFailedError;
            mFailedError = null;

            StackTraceElement[] trace = error.getStackTrace();
            error.fillInStackTrace();
            StackTraceElement[] localTrace = error.getStackTrace();
            StackTraceElement[] completeTrace =
                new StackTraceElement[trace.length + localTrace.length];
            System.arraycopy(trace, 0, completeTrace, 0, trace.length);
            System.arraycopy(localTrace, 0, completeTrace, trace.length, localTrace.length);
            error.setStackTrace(completeTrace);

            ThrowUnchecked.fire(error);
        }

        if (mBogus == null) {
            mRef = new AtomicReference<T>(createBogus());

            mBogus = AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    try {
                        return getWrapper().newInstance(mRef);
                    } catch (Exception e) {
                        ThrowUnchecked.fire(e);
                        return null;
                    }
                }
            });
        }

        return mBogus;
    }

    /**
     * Create instance of real object. If there is a recoverable error creating
     * the object, return null. Any error logging must be performed by the
     * implementation of this method. If null is returned, expect this method
     * to be called again in the future.
     *
     * @return real object, or null if there was a recoverable error
     * @throws E unrecoverable error
     */
    protected abstract T createReal() throws E;

    /**
     * Create instance of bogus object.
     */
    protected abstract T createBogus();

    /**
     * Notification that createReal is taking too long. This can be used to log
     * a message.
     *
     * @param timedOutMillis milliseconds waited before giving up
     */
    protected abstract void timedOutNotification(long timedOutMillis);

    /**
     * Notification that createReal has produced the real object. The default
     * implementation does nothing.
     */
    protected void createdNotification(T object) {
    }

    synchronized void created(T object) {
        mReal = object;
        if (mBogus != null) {
            mBogus = null;
            if (mRef != null) {
                // Changing reference to real object. The ref object is also
                // held by the auto-generated wrapper, so changing here changes
                // the wrapper's behavior.
                mRef.set(object);
            }
        }
        mFailed = false;
        notifyAll();
        createdNotification(object);
    }

    synchronized void failed() {
        if (mReal == null) {
            mFailed = true;
        }
        notifyAll();
    }

    /**
     * @param error optional error to indicate thread is exiting because of this
     */
    synchronized void handleThreadExit(Throwable error) {
        if (mReal == null) {
            mFailed = true;
            if (error != null) {
                mFailedError = error;
            }
        }
        mCreateThread = null;
        notifyAll();
    }

    /**
     * Returns a Constructor that accepts an AtomicReference to the wrapped
     * object.
     */
    private Constructor<T> getWrapper() {
        Class<T> clazz;
        synchronized (cWrapperCache) {
            clazz = (Class<T>) cWrapperCache.get(mType);
            if (clazz == null) {
                clazz = createWrapper();
                cWrapperCache.put(mType, clazz);
            }
        }

        try {
            return clazz.getConstructor(AtomicReference.class);
        } catch (NoSuchMethodException e) {
            ThrowUnchecked.fire(e);
            return null;
        }
    }

    private Class<T> createWrapper() {
        RuntimeClassFile cf = new RuntimeClassFile(mType.getName());
        cf.addInterface(mType);
        cf.markSynthetic();
        cf.setSourceFile(BelatedCreator.class.getName());
        cf.setTarget("1.5");

        final TypeDesc atomicRefType = TypeDesc.forClass(AtomicReference.class);

        cf.addField(Modifiers.PRIVATE.toFinal(true), REF_FIELD_NAME, atomicRefType);

        CodeBuilder b = new CodeBuilder(cf.addConstructor(Modifiers.PUBLIC,
                                                          new TypeDesc[] {atomicRefType}));
        b.loadThis();
        b.invokeSuperConstructor(null);
        b.loadThis();
        b.loadLocal(b.getParameter(0));
        b.storeField(REF_FIELD_NAME, atomicRefType);
        b.returnVoid();

        // Now define all interface methods to call wrapped object.

        for (Method m : mType.getMethods()) {
            try {
                Object.class.getMethod(m.getName(), m.getParameterTypes());
                // Defined in object too, so skip it for now
                continue;
            } catch (NoSuchMethodException e) {
            }

            addWrappedCall(cf, new CodeBuilder(cf.addMethod(m)), m);
        }

        // Also wrap non-final public methods from Object. mType is an
        // interface, so we don't have to worry about any superclasses --
        // except Object.  We want to make sure that all (non-final) Object
        // methods delegate to the generated proxy.  For example, one would
        // expect toString to call the wrapped object, not the wrapper itself.

        for (Method m : Object.class.getMethods()) {
            int modifiers = m.getModifiers();
            if (!Modifier.isFinal(modifiers) && Modifier.isPublic(modifiers)) {
                b = new CodeBuilder
                    (cf.addMethod(Modifiers.PUBLIC, m.getName(), MethodDesc.forMethod(m)));
                addWrappedCall(cf, b, m);
            }
        }

        Class<T> clazz = cf.defineClass();
        return clazz;
    }

    private void addWrappedCall(ClassFile cf, CodeBuilder b, Method m) {
        // Special behavior for equals method
        boolean isEqualsMethod = false;
        if (m.getName().equals("equals") && m.getReturnType().equals(boolean.class)) {
            Class[] paramTypes = m.getParameterTypes();
            isEqualsMethod = paramTypes.length == 1 && paramTypes[0].equals(Object.class);
        }

        if (isEqualsMethod) {
            b.loadThis();
            b.loadLocal(b.getParameter(0));
            Label notEqual = b.createLabel();
            b.ifEqualBranch(notEqual, false);
            b.loadConstant(true);
            b.returnValue(TypeDesc.BOOLEAN);

            notEqual.setLocation();

            // Check if object is our type.
            b.loadLocal(b.getParameter(0));
            b.instanceOf(cf.getType());
            Label isInstance = b.createLabel();
            b.ifZeroComparisonBranch(isInstance, "!=");

            b.loadConstant(false);
            b.returnValue(TypeDesc.BOOLEAN);

            isInstance.setLocation();
        }

        final TypeDesc atomicRefType = TypeDesc.forClass(AtomicReference.class);

        // Load wrapped object...
        b.loadThis();
        b.loadField(REF_FIELD_NAME, atomicRefType);
        b.invokeVirtual(atomicRefType, "get", TypeDesc.OBJECT, null);
        b.checkCast(TypeDesc.forClass(mType));

        // Load parameters...
        for (int i=0; i<b.getParameterCount(); i++) {
            b.loadLocal(b.getParameter(i));
        }

        // Special behavior for equals method
        if (isEqualsMethod) {
            // Extract wrapped object.
            b.checkCast(cf.getType());
            b.loadField(REF_FIELD_NAME, atomicRefType);
            b.invokeVirtual(atomicRefType, "get", TypeDesc.OBJECT, null);
            b.checkCast(TypeDesc.forClass(mType));
        }

        // Invoke wrapped method...
        b.invoke(m);

        if (m.getReturnType() == void.class) {
            b.returnVoid();
        } else {
            b.returnValue(TypeDesc.forClass(m.getReturnType()));
        }
    }

    private class CreateThread implements Runnable {
        public void run() {
            try {
                while (true) {
                    T real = createReal();
                    if (real != null) {
                        created(real);
                        break;
                    }
                    failed();
                    if (mMinRetryDelayMillis < 0) {
                        break;
                    }
                    try {
                        Thread.sleep(mMinRetryDelayMillis);
                    } catch (InterruptedException e) {
                        break;
                    }
                }
                handleThreadExit(null);
            } catch (Throwable e) {
                handleThreadExit(e);
            }
        }
    }

    private static class TFactory implements ThreadFactory {
        private static int cCount;

        private static synchronized int nextID() {
            return ++cCount;
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName("BelatedCreator-" + nextID());
            return t;
        }
    }
}
TOP

Related Classes of org.cojen.util.BelatedCreator$CreateThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.