xapi.dev.generators.AbstractInjectionGenerator.java Source code

Java tutorial

Introduction

Here is the source code for xapi.dev.generators.AbstractInjectionGenerator.java

Source

/*
 * Copyright 2012, We The Internet Ltd.
 *
 * All rights reserved.
 *
 * Distributed under a modified BSD License as follow:
 *
 * 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, unless otherwise
 * agreed to in a written document signed by a director of We The Internet Ltd.
 *
 * Neither the name of We The Internet 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 HOLDER 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.
 *
 */
package xapi.dev.generators;

import static java.io.File.separator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import xapi.annotation.inject.SingletonOverride;
import xapi.collect.api.Fifo;
import xapi.dev.util.DefermentWriter;
import xapi.dev.util.GwtInjectionMap;
import xapi.dev.util.InjectionCallbackArtifact;
import xapi.dev.util.InjectionUtils;
import xapi.dev.util.DefermentWriter.DefermentStrategy;
import xapi.gwt.collect.JsFifo;
import xapi.inject.AsyncProxy;
import xapi.inject.impl.SingletonInitializer;
import xapi.inject.impl.SingletonProvider;
import xapi.source.read.SourceUtil;
import xapi.util.api.ApplyMethod;
import xapi.util.api.ReceivesValue;
import xapi.util.impl.ReceiverAdapter;

import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * <br>Base class for all generators providing injected singletons.
 * <br>
 * <br>This class is used in both gwt-dev and gwt-prod,
 * <br>with static utility methods being provided to subclasses and other package-level utilities.
 * <br>
 * <br>Important methods:
 * <br>{@link #ensureCallbackClass(TreeLogger, String, String, String, String, GeneratorContext)}
 * <br>- Ensures that there is an asynchronous (code splitting) provider for the injected class
 * <br>{@link #ensureProviderClass(TreeLogger, String, String, String, String, GeneratorContext)}
 * <br>- Ensures that there is a static provider of a given singleton.
 * <br>
 * <br>Note that if an async provider is accessed before there is a synchronous provider,
 * <br>then the synchronous provider will route through the async callback method,
 * <br>to prevent code splitting from being ruined.
 * <br>
 * <br>If your code accesses the synchronous provider before the async provider,
 * <br>the async provider will skip using a code split (as it wouldn't work anyway).
 * <br>To override this behaviour, set {@link SingletonOverride#forceAsync()} to true
 * <br>
 * @author James X. Nelson (james@wetheinter.net, @james)
 *
 */
public abstract class AbstractInjectionGenerator extends IncrementalGenerator {

    /**
     * The GwtInjectionMap is a very heavyweight object to initialize,
     * but we need a different one per permutation, so we encapsulate it behind a ThreadLocal.
     *
     * This will allow a single initialization the first time it is used,
     *
     */
    private static final ThreadLocal<GwtInjectionMap> injectionThreadlocal = new ThreadLocal<GwtInjectionMap>();

    protected static GwtInjectionMap getInjectionMap(TreeLogger logger, GeneratorContext ctx) {
        //we still have to synchronize because we're passing state through a field.
        GwtInjectionMap map = injectionThreadlocal.get();
        if (map == null)//only synchronize if we have to.
            synchronized (Thread.currentThread()) {//synchro on the lock the threadlocal needs
                //we have to double checked lock because we can't override ThreadLocal#initialValue,
                //as we need the generator context :/
                map = injectionThreadlocal.get();
                if (map != null)
                    return map;
                map = new GwtInjectionMap(logger, ctx);
                injectionThreadlocal.set(map);
                //TODO: clean up leaky threadlocals...
            }
        return map;
    }

    @Override
    public long getVersionId() {
        return 0;//update this any time generated class structure changes
    }

    static String packageName = "xapi.inject";

    protected void tryWriteMetaInf(TreeLogger logger, Class<?> cls, JClassType impl, GeneratorContext context) {
        String serviceInterface = cls.getName();
        String serviceImplementation = impl.getQualifiedBinaryName();
        ArrayList<File> outputDirs = new ArrayList<File>();
        PropertyOracle properties = context.getPropertyOracle();
        try {
            File root = new File("");
            if (root.getAbsolutePath().endsWith("war"))
                root = new File(root.getAbsolutePath().replace(separator + "war", "") + separator);
            else
                root = new File(root.getAbsolutePath());
            ConfigurationProperty output = properties.getConfigurationProperty("xinject.output.dir");
            for (String dir : output.getValues()) {
                File f = new File(root, dir);
                if (f.isDirectory()) {
                    outputDirs.add(f);
                    f = new File(f, "META-INF" + separator + "services");
                    if (!f.exists()) {
                        if (!f.mkdirs()) {
                            logger.log(Type.WARN,
                                    "Unable to create META-INF" + separator + "services " + " in "
                                            + f.getAbsolutePath() + " "
                                            + "Please ensure this directory exists, and is writable.");
                        }
                    }
                } else {
                    logger.log(Type.WARN, "Missing xinject output directory: " + f.getAbsolutePath() + ". "
                            + "Please set xinject.output.dir to existing source directories; current value: "
                            + output.getValues());
                }
            }
        } catch (BadPropertyValueException e1) {
            logger.log(Type.WARN, "Unexpected propery exception for xinject.output.dir", e1);
        }
        try {
            String prefix = ".." + separator + "WEB-INF" + separator + "classes" + separator + "META-INF"
                    + separator + "singletons" + separator;
            //TODO use a typed artifact to let the linker have a peak if it needs to
            OutputStream res = context.tryCreateResource(logger, prefix + serviceInterface);
            res.write(serviceImplementation.getBytes());
            context.commitResource(logger, res).setVisibility(Visibility.Public);
        } catch (UnableToCompleteException e) {
            logger.log(Type.ERROR, "Couldn't write java services to META-INF/singeltons", e);
        } catch (IOException e) {
            logger.log(Type.ERROR,
                    "Couldn't write java services to META-INF/singletons; please ensure the war folder has full write access and the disk is not full.",
                    e);
            e.printStackTrace();
        }

        logger.log(Type.TRACE,
                "Saving META-INF/singletons for " + serviceInterface + " -> " + serviceImplementation);
        exports: for (File output : outputDirs) {
            String knownContent = null;
            //check for existing META-INF/singletons entries, so we don't clobber anything
            File existing = new File(output, "META-INF" + separator + "services" + separator + serviceInterface);
            logger.log(Type.TRACE, "Saving ServiceLoader descriptor to " + existing.getAbsolutePath());
            if (existing.isFile()) {
                //need to read in existing manifest, and skip if service already exists
                BufferedReader reader = null;
                FileInputStream in = null;
                try {
                    in = new FileInputStream(existing);
                    reader = new BufferedReader(new InputStreamReader(in));

                    String line;
                    StringBuilder b = new StringBuilder();
                    while ((line = reader.readLine()) != null) {
                        if (line.equals(serviceImplementation)) {
                            //the service impl already exists; skip to next output dir
                            //TODO put in a flag to override top permission.
                            try {
                                ConfigurationProperty prop = context.getPropertyOracle()
                                        .getConfigurationProperty("xinject.overwrite.existing");
                                List<String> values = prop.getValues();
                                if (values.size() > 0)
                                    if (values.get(0).matches("true")) {
                                        //if we're supposed to overwrite the value, but it's already on top
                                        if (b.length() == 0) {
                                            continue exports;//skip the file write
                                        }
                                        continue;//this erases the existing value so we can put it back over top.
                                        //it also skips the breaking-continue below.
                                    }
                            } catch (BadPropertyValueException e) {
                                logger.log(Type.TRACE, "", e);
                            }
                            //if we've found the service, and are not allowed to move it to top,
                            continue exports;//carry on in the loop above.
                        }
                        b.append(line + "\n");
                    }
                    knownContent = b.toString().substring(0, b.length() - 1);
                } catch (IOException e) {
                    logger.log(Type.WARN,
                            "Received io exception writing META-INF/service for " + existing.getAbsolutePath());
                } finally {
                    try {

                        if (in != null)
                            in.close();
                        if (reader != null)
                            reader.close();
                    } catch (IOException e) {
                    }
                }
            }
            //save a new java service descriptor
            FileWriter writer = null;
            try {
                try {
                    boolean exists = existing.isFile();
                    if (!exists) {
                        if (!existing.createNewFile()) {
                            logger.log(Type.WARN, "Could not create output file for " + existing);
                            continue exports;
                        }
                    }
                    writer = new FileWriter(existing, false);
                    if (knownContent == null) {
                        writer.append(serviceImplementation);
                    } else {
                        writer.append(serviceImplementation);
                        writer.append('\n');
                        writer.append(knownContent);
                    }
                } finally {
                    if (writer != null) {
                        writer.close();
                    }
                }
            } catch (IOException e) {
                logger.log(Type.WARN, "File write exception trying to save META-INF/singletons for " + existing, e);
            }
        }

    }

    protected static boolean isAsyncProvided(TreeLogger logger, String packageName, String simpleSourceName,
            GeneratorContext context) {
        try {
            JClassType type = context.getTypeOracle()
                    .findType(packageName + "." + InjectionUtils.generatedAsyncProviderName(simpleSourceName));
            return type != null;
        } catch (Exception e) {
            return false;
        }
    }

    protected static boolean isCallbackInjected(TreeLogger logger, String packageName, String simpleSourceName,
            GeneratorContext context) {
        try {
            JClassType type = context.getTypeOracle()
                    .findType(packageName + "." + InjectionUtils.generatedCallbackName(simpleSourceName));
            return type != null;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        injectionThreadlocal.set(null);
    }

    public static InjectionCallbackArtifact ensureAsyncInjected(TreeLogger logger, String packageName,
            String className, String outputClass, GeneratorContext ctx) throws UnableToCompleteException {

        GwtInjectionMap gwtInjectionMap = getInjectionMap(logger, ctx);
        InjectionCallbackArtifact artifact = gwtInjectionMap.getOrCreateArtifact(ctx, packageName, className);
        if (artifact.isTargetUnbound()) {
            artifact.bindTo(outputClass);
            ensureProviderClass(logger, packageName, artifact.getSimpleName(), artifact.getCanonicalName(),
                    artifact.getBoundTarget(), ctx);
            logger = logger.branch(Type.TRACE,
                    "Creating asynchronous callback for " + artifact.getCanonicalName() + " -> " + outputClass);
            String generatedName = InjectionUtils.generatedAsyncProviderName(artifact.getGeneratedName());
            String implPackage = artifact.getImplementationPackage();
            PrintWriter printWriter = ctx.tryCreate(logger, implPackage, generatedName);
            if (printWriter == null) {
                logger.log(Type.WARN,
                        "Could not create the source writer for " + implPackage + "." + generatedName);
                return artifact;
            }
            artifact.addCallback(implPackage + "." + generatedName + ".Deproxy");
            ctx.commitArtifact(logger, artifact);

            ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(implPackage,
                    generatedName);
            composer.setPrivacy("public final");

            composer.addImport(com.google.gwt.core.client.GWT.class.getName());
            composer.addImport(RunAsyncCallback.class.getName());
            composer.addImport(Fifo.class.getName());
            composer.addImport(JsFifo.class.getName());
            composer.addImport(AsyncProxy.class.getName());
            composer.addImport(ApplyMethod.class.getName());
            composer.addImport(ReceivesValue.class.getName());
            composer.addImport(ReceiverAdapter.class.getCanonicalName());
            composer.addImport(artifact.getCanonicalName());

            String simpleName = artifact.getSimpleName();
            SourceWriter sw = composer.createSourceWriter(ctx, printWriter);

            sw.println();
            sw.println("static final class Callbacks implements ApplyMethod{");
            sw.indent();
            sw.println("public void apply(Object ... args){");
            sw.println("}");
            sw.outdent();
            sw.println("}");
            sw.println();

            sw.println("static final class Deproxy implements ReceivesValue<" + simpleName + ">{");
            sw.indent();
            sw.println("public final void set(final " + simpleName + " value){");
            sw.indentln("getter = new ReceiverAdapter<" + simpleName + ">(value);");
            sw.println("}");
            sw.outdent();
            sw.println("}");
            sw.println();

            sw.println("private static final class Proxy implements ReceivesValue<ReceivesValue<" + simpleName
                    + ">>{");
            sw.indent();
            sw.println("public final void set(final ReceivesValue<" + simpleName + "> receiver){");
            sw.indent();

            sw.print("GWT.runAsync(");
            sw.println(artifact.getCanonicalName() + ".class,new Request(receiver));");

            sw.outdent();
            sw.println("}");
            sw.outdent();
            sw.println("}");
            sw.println();

            sw.println("private static final class Request");
            sw.indent();
            sw.println("extends AsyncProxy<" + simpleName + "> ");
            sw.println("implements RunAsyncCallback{");

            DefermentWriter defer = new DefermentWriter(sw);
            defer.setStrategy(DefermentStrategy.NONE);

            sw.println("private static final Fifo<ReceivesValue<" + simpleName + ">> pending =");
            sw.indentln("JsFifo.newFifo();");
            sw.println();

            sw.println("protected Request(ReceivesValue<" + simpleName + "> receiver){");
            sw.indentln("accept(receiver);");
            sw.println("}");
            sw.println();

            sw.println("@Override");
            sw.println("protected final Fifo<ReceivesValue<" + simpleName + ">> pending(){");
            sw.indentln("return pending;");
            sw.println("}");
            sw.println();

            sw.println("protected final void dispatch(){");
            sw.indentln("go();");
            sw.println("}");
            sw.println();

            sw.println("public final void onSuccess(){");
            sw.indent();
            defer.printStart();

            sw.println("final " + simpleName + " value = ");
            sw.print(packageName + "." + InjectionUtils.generatedProviderName(simpleName));
            sw.println(".theProvider.get();");
            sw.println();

            sw.print("final ApplyMethod callbacks = GWT.create(");
            sw.print(packageName + ".impl." + generatedName + ".Callbacks.class");
            sw.println(");");
            sw.println("callbacks.apply(value);");
            sw.println();

            sw.println("apply(value);");
            sw.outdent();
            sw.println("}");
            sw.outdent();
            defer.printFinish();
            sw.println("}");
            sw.println();

            sw.println("private static ReceivesValue<ReceivesValue<" + simpleName + ">> getter = new Proxy();");
            sw.println();

            sw.println("static void request(final ReceivesValue<" + simpleName + "> request){");
            sw.indentln("getter.set(request);");
            sw.println("}");
            sw.println();

            sw.println("static void go(){");
            sw.indentln("request(null);");
            sw.println("}");
            sw.println();

            sw.println("private " + generatedName + "(){}");

            sw.commit(logger);

        } else {
            assert artifact.getBoundTarget().equals(outputClass) : "The injection target "
                    + artifact.getCanonicalName() + " was bound " + "to " + artifact.getBoundTarget()
                    + ", but you tried to bind it again, " + "to a different class: " + outputClass;
        }
        return artifact;
    }

    public static boolean ensureProviderClass(TreeLogger logger, String packageName, String simpleName0,
            String canonical, String qualifiedSourceName, GeneratorContext ctx) {
        String simpleName = SourceUtil.toSourceName(simpleName0);
        String generatedName = InjectionUtils.generatedProviderName(simpleName);
        String cleanedCanonical = SourceUtil.toSourceName(canonical);
        logger.log(Type.DEBUG, "Creating provider for " + packageName + "." + generatedName);

        PrintWriter printWriter = ctx.tryCreate(logger, packageName, generatedName);
        if (printWriter == null) {
            logger.log(Type.TRACE, "Already generated " + generatedName);
            return false;
        }
        logger.log(Type.TRACE, "Newly Generating provider " + generatedName + " <- " + qualifiedSourceName);

        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, generatedName);
        composer.setSuperclass(SingletonInitializer.class.getName() + "<" + simpleName0 + ">");
        composer.addImport(cleanedCanonical);
        composer.addImport(GWT.class.getName());
        composer.addImport(SingletonProvider.class.getName());

        SourceWriter sw = composer.createSourceWriter(ctx, printWriter);

        sw.println("@Override");
        sw.println("public " + simpleName + " initialValue(){");
        sw.indent();

        sw.print("return GWT.<" + cleanedCanonical + ">create(");
        sw.print(SourceUtil.toSourceName(qualifiedSourceName) + ".class");
        sw.println(");");

        sw.outdent();
        sw.println("}");
        sw.println();
        //now, print a static final provider instance
        sw.print("public static final SingletonProvider<");
        sw.print(simpleName0 + "> ");
        sw.print("theProvider = ");
        sw.println("new " + generatedName + "();");
        sw.commit(logger);
        return true;
    }
}