net.sf.mmm.util.gwt.base.rebind.AbstractIncrementalGenerator.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.mmm.util.gwt.base.rebind.AbstractIncrementalGenerator.java

Source

/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.util.gwt.base.rebind;

import java.io.File;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;

import net.sf.mmm.util.exception.api.IllegalCaseException;

import com.google.gwt.core.ext.CachedGeneratorResult;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.IncrementalGenerator;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * This is the abstract base implementation for {@link IncrementalGenerator}s for GWT environments, that
 * generates a {@link Class} from a single input type. If the input type has not been modified since the last
 * generation the {@link #isCachedResultObsolete(CachedGeneratorResult, String) generated result is reused by
 * default}.
 * 
 * @author Joerg Hohwiller (hohwille at users.sourceforge.net)
 * @since 4.0.0
 */
public abstract class AbstractIncrementalGenerator extends IncrementalGenerator {

    /**
     * The constructor.
     */
    public AbstractIncrementalGenerator() {

        super();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {

        CachedGeneratorResult cachedGeneratorResult = context.getCachedGeneratorResult();
        if (cachedGeneratorResult != null) {
            boolean obsolete = isCachedResultObsolete(cachedGeneratorResult, typeName);
            if (!obsolete) {
                return new RebindResult(RebindMode.USE_ALL_CACHED, cachedGeneratorResult.getResultTypeName());
            }
        }
        TypeOracle typeOracle = context.getTypeOracle();
        JClassType inputType = typeOracle.findType(typeName);
        String resultType = generate(inputType, logger, context);
        return new RebindResult(RebindMode.USE_ALL_NEW, resultType);
    }

    /**
     * This method performs the actual generation of the {@link Class} to rebind.
     * 
     * @param inputType is the {@link JClassType} reflecting the input-type that triggered the generation via
     *        {@link com.google.gwt.core.client.GWT#create(Class)}.
     * @param logger is the {@link TreeLogger}.
     * @param context is the {@link GeneratorContext}.
     * @return the fully qualified name of the generated class.
     */
    protected String generate(JClassType inputType, TreeLogger logger, GeneratorContext context) {

        String packageName = inputType.getPackage().getName();
        String simpleName = createClassName(inputType);
        logger.log(TreeLogger.DEBUG, getClass().getSimpleName() + ": Generating " + simpleName);
        ClassSourceFileComposerFactory sourceComposerFactory = new ClassSourceFileComposerFactory(packageName,
                simpleName);
        // Import statements
        generateImportStatements(inputType, logger, sourceComposerFactory, context);

        // Class declaration
        generateClassDeclaration(inputType, logger, sourceComposerFactory, context);

        PrintWriter writer = context.tryCreate(logger, packageName, simpleName);
        if (writer != null) {
            SourceWriter sourceWriter = sourceComposerFactory.createSourceWriter(context, writer);

            generateClassContents(inputType, logger, sourceWriter, simpleName, context);
            sourceWriter.commit(logger);
        }
        return sourceComposerFactory.getCreatedClassName();
    }

    /**
     * This method generates the import statements. It shall only perform invocations of
     * {@link ClassSourceFileComposerFactory#addImport(String)} to the given
     * {@link ClassSourceFileComposerFactory}.
     * 
     * @param inputType is the {@link JClassType} reflecting the input-type that triggered the generation via
     *        {@link com.google.gwt.core.client.GWT#create(Class)}.
     * @param logger is the {@link TreeLogger}.
     * @param sourceComposerFactory is the {@link ClassSourceFileComposerFactory}.
     * @param context is the {@link GeneratorContext}.
     */
    protected abstract void generateImportStatements(JClassType inputType, TreeLogger logger,
            ClassSourceFileComposerFactory sourceComposerFactory, GeneratorContext context);

    /**
     * This method generates the additional things for the class declaration. This is the place where to invoke
     * methods like
     * <ul>
     * <li>{@link ClassSourceFileComposerFactory#setSuperclass(String)}</li>
     * <li>{@link ClassSourceFileComposerFactory#addImplementedInterface(String)}</li>
     * <li>{@link ClassSourceFileComposerFactory#addAnnotationDeclaration(String)}</li>
     * <li>{@link ClassSourceFileComposerFactory#makeInterface()}</li>
     * <li>{@link ClassSourceFileComposerFactory#setJavaDocCommentForClass(String)}</li>
     * </ul>
     * on the given {@link ClassSourceFileComposerFactory}. E.g.
     * 
     * <pre>
     * sourceComposerFactory.addImplementedInterface(inputType.getQualifiedSourceName());
     * </pre>
     * 
     * @param inputType is the {@link JClassType} reflecting the input-type that triggered the generation via
     *        {@link com.google.gwt.core.client.GWT#create(Class)}.
     * @param logger is the {@link TreeLogger}.
     * @param sourceComposerFactory is the {@link ClassSourceFileComposerFactory}.
     * @param context is the {@link GeneratorContext}.
     */
    protected abstract void generateClassDeclaration(JClassType inputType, TreeLogger logger,
            ClassSourceFileComposerFactory sourceComposerFactory, GeneratorContext context);

    /**
     * This method generates the actual contents of the {@link Class} to generate.
     * 
     * @param inputType is the {@link JClassType} reflecting the input-type that triggered the generation via
     *        {@link com.google.gwt.core.client.GWT#create(Class)}.
     * @param logger is the {@link TreeLogger}.
     * @param sourceWriter is the {@link SourceWriter} where to {@link SourceWriter#print(String) write} the
     *        source code to.
     * @param simpleName is the {@link Class#getSimpleName() simple name} of the {@link Class} to generate.
     * @param context is the {@link GeneratorContext}.
     */
    protected abstract void generateClassContents(JClassType inputType, TreeLogger logger,
            SourceWriter sourceWriter, String simpleName, GeneratorContext context);

    /**
     * This method creates the {@link Class#getSimpleName() simple name} of the {@link Class} to generate.
     * Typically derived from {@link JClassType#getName()} of the given <code>inputType</code> with a static
     * suffix or prefix that is specific enough to avoid clashing with existing classes.
     * 
     * @param inputType is the {@link JClassType} reflecting the input-type that triggered the generation via
     *        {@link com.google.gwt.core.client.GWT#create(Class)}.
     * @return the {@link Class#getSimpleName() simple name} of the {@link Class} to generate.
     */
    protected String createClassName(JClassType inputType) {

        return inputType.getName() + "_GwtImpl";

    }

    /**
     * Generates the the default constructor.
     * 
     * @param sourceWriter is the {@link SourceWriter}.
     * @param simpleName is the {@link Class#getSimpleName() simple name}.
     */
    protected void generateDefaultConstructor(SourceWriter sourceWriter, String simpleName) {

        generateSourcePublicConstructorDeclaration(sourceWriter, simpleName);
        sourceWriter.println("super();");
        generateSourceCloseBlock(sourceWriter);
    }

    /**
     * This method generates the source code for a public method or constructor including the opening brace and
     * indentation.
     * 
     * @param sourceWriter is the {@link SourceWriter}.
     * @param methodName is the name of the method (or the {@link Class#getSimpleName() simple class name} for a
     *        constructor}.
     */
    protected final void generateSourcePublicConstructorDeclaration(SourceWriter sourceWriter, String methodName) {

        generateSourcePublicMethodDeclaration(sourceWriter, "", methodName, "", false);
    }

    /**
     * This method generates the source code for a public method declaration including the opening brace and
     * indentation.
     * 
     * @param sourceWriter is the {@link SourceWriter}.
     * @param method is the {@link JMethod} to implement.
     */
    protected final void generateSourcePublicMethodDeclaration(SourceWriter sourceWriter, JMethod method) {

        StringBuilder arguments = new StringBuilder();
        for (JParameter parameter : method.getParameters()) {
            if (arguments.length() > 0) {
                arguments.append(", ");
            }
            arguments.append(parameter.getType().getQualifiedSourceName());
            arguments.append(" ");
            arguments.append(parameter.getName());
        }
        generateSourcePublicMethodDeclaration(sourceWriter, method.getReturnType().getQualifiedSourceName(),
                method.getName(), arguments.toString(), false);
    }

    /**
     * This method generates the source code for a public method or constructor including the opening brace and
     * indentation.
     * 
     * @param sourceWriter is the {@link SourceWriter}.
     * @param returnType is the return type of the method.
     * @param methodName is the name of the method (or the {@link Class#getSimpleName() simple class name} for a
     *        constructor}.
     * @param arguments is the source line with the arguments to the method or constructor.
     * @param override - <code>true</code> if an {@link Override} annotation shall be added.
     */
    protected final void generateSourcePublicMethodDeclaration(SourceWriter sourceWriter, String returnType,
            String methodName, String arguments, boolean override) {

        if (override) {
            sourceWriter.println("@Override");
        }
        sourceWriter.print("public ");
        if (returnType != null) {
            sourceWriter.print(returnType);
            sourceWriter.print(" ");
        }
        sourceWriter.print(methodName);
        sourceWriter.print("(");
        sourceWriter.print(arguments);
        sourceWriter.println(") {");
        sourceWriter.indent();
    }

    /**
     * This method generates the source code to close a block (method body, if-block, while-block, etc.).
     * 
     * @param sourceWriter is the {@link SourceWriter}.
     */
    protected final void generateSourceCloseBlock(SourceWriter sourceWriter) {

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

    /**
     * This method determines whether a {@link CachedGeneratorResult} is obsolete or can be reused.
     * 
     * @param cachedGeneratorResult is the {@link CachedGeneratorResult}.
     * @param typeName is the full-qualified name of the {@link Class} to generate.
     * @return <code>true</code> if the {@link CachedGeneratorResult} is obsolete and has to be re-generated,
     *         <code>false</code> otherwise (if it can be reused).
     */
    protected boolean isCachedResultObsolete(CachedGeneratorResult cachedGeneratorResult, String typeName) {

        try {
            URL javaFileUrl = Thread.currentThread().getContextClassLoader()
                    .getResource(typeName.replace('.', '/') + ".java");
            String protocol = javaFileUrl.getProtocol().toLowerCase();
            if ("file".equals(protocol)) {
                String urlString = URLDecoder.decode(javaFileUrl.getFile(), "UTF-8");
                File javaFile = new File(urlString);
                long lastModified = javaFile.lastModified();
                long timeGenerated = cachedGeneratorResult.getTimeGenerated();
                return (lastModified > timeGenerated);
            } else {
                throw new IllegalCaseException(protocol);
            }
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException(e);
        }
    }

}