com.eleven.rebind.SkinBundleGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.eleven.rebind.SkinBundleGenerator.java

Source

/*
 * Copyright 2008 Google Inc.
 *
 * 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 com.eleven.rebind;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.client.ui.ImageBundle;
import com.google.gwt.user.client.ui.ImageBundle.Resource;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.io.File;
import java.io.PrintWriter;

import java.net.URL;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

/**
 * Generates an implementation of a user-defined interface <code>T</code> that
 * extends {@link com.google.gwt.user.client.ui.ImageBundle}.
 *
 * Each method in <code>T</code> must be declared to return
 * {@link com.google.gwt.user.client.ui.AbstractImagePrototype}, take no
 * parameters, and optionally specify the metadata tag <code>gwt.resource</code>
 * as the name of an image that can be found in the classpath. In the absence of
 * the metatadata tag, the method name with an extension of
 * <code>.png, .jpg, or .gif</code> defines the name of the image, and the
 * image file must be located in the same package as <code>T</code>.
 */
public class SkinBundleGenerator extends Generator {

    /* private */ static final String MSG_NO_FILE_BASED_ON_METHOD_NAME = "No matching image resource was found; any of the following filenames would have matched had they been present:";

    private static final String ABSTRACTIMAGEPROTOTYPE_QNAME = "com.google.gwt.user.client.ui.AbstractImagePrototype";

    private static final String CLIPPEDIMAGEPROTOTYPE_QNAME = "com.google.gwt.user.client.ui.impl.ClippedImagePrototype";

    private static final String IMAGE_QNAME = "com.google.gwt.user.client.ui.Image";

    private static final String HASHMAP_QNAME = "java.util.HashMap";

    private static final String GWT_QNAME = "com.google.gwt.core.client.GWT";

    private static final String[] IMAGE_FILE_EXTENSIONS = { "png", "gif", "jpg" };

    private static final String IMAGEBUNDLE_QNAME = "com.eleven.client.core.SkinBundle";

    private final ResourceLocator resLocator;

    /**
     * Default constructor for image bundle. Locates resources using this
     * class's own class loader.
     */
    public SkinBundleGenerator() {
        this(new ResourceLocator() {
            @Override
            public boolean isResourcePresent(final String resName) {
                URL url = this.getClass().getClassLoader().getResource(resName);
                return url != null;
            }
        });
    }

    /**
     * Default access so that it can be accessed by unit tests.
     */
    /* private */ SkinBundleGenerator(final ResourceLocator resourceLocator) {
        assert (resourceLocator != null);
        this.resLocator = resourceLocator;
    }

    /* private */ static String msgCannotFindImageFromMetaData(final String imgResName) {
        return "Unable to find image resource '" + imgResName + "'";
    }

    @Override
    public String generate(final TreeLogger logger, final GeneratorContext context, final String typeName)
            throws UnableToCompleteException {

        TypeOracle typeOracle = context.getTypeOracle();

        // Get metadata describing the user's class.
        JClassType userType = getValidUserType(logger, typeName, typeOracle);

        // Write the new class.
        JMethod[] imgMethods = userType.getOverridableMethods();
        String resultName = generateImplClass(logger, context, userType, imgMethods);

        // Return the complete name of the generated class.
        return resultName;
    }

    /**
     * Gets the resource name of the image associated with the specified image
     * bundle method in a form that can be passed to
     * <code>ClassLoader.getResource()</code>.
     *
     * @param logger
     *                    the main logger
     * @param method
     *                    the image bundle method whose image name is being sought
     * @return a resource name that is suitable to be passed into
     *                      <code>ClassLoader.getResource()</code>; never returns
     *                      <code>null</code>
     * @throws UnableToCompleteException
     *                     thrown if a resource was specified but could not be found on
     *                     the classpath
     */
    /* private */ String getImageResourceName(final TreeLogger logger, final JMethodOracle method)
            throws UnableToCompleteException {
        String imgName = tryGetImageNameFromMetaData(logger, method);
        if (imgName != null)
            return imgName;
        else
            return getImageNameFromMethodName(logger, method);
    }

    private String computeSubclassName(final JClassType userType) {
        String baseName = userType.getName().replace('.', '_');
        return baseName + "_generatedBundle";
    }

    private void generateImageMethod(final SkinBundleBuilder compositeImage, final SourceWriter sw,
            final JMethod method, final String imgResName) {

        String decl = method.getReadableDeclaration(false, true, true, true, true);

        {
            sw.indent();

            // Create a singleton that this method can return. There is no need
            // to
            // create a new instance every time this method is called, since
            // ClippedImagePrototype is immutable

            SkinBundleBuilder.ImageRect imageRect = compositeImage.getMapping(imgResName);
            String singletonName = method.getName() + "_SINGLETON";

            sw.print("private static final ClippedImagePrototype ");
            sw.print(singletonName);
            sw.print(" = new ClippedImagePrototype(IMAGE_BUNDLE_URL, ");
            sw.print(Integer.toString(imageRect.getLeft()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getTop()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getWidth()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getHeight()));
            sw.println(");");

            sw.print(decl);
            sw.println(" {");
            sw.indent();
            sw.print("return ");
            sw.print(singletonName);
            sw.println(";");
            sw.outdent();

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

    private void generateImageDef(final SkinBundleBuilder compositeImage, final SourceWriter sw,
            final List<String> imgResNames) {
        sw.indent();

        sw.println("private static final HashMap<String, ClippedImagePrototype> imageMap"
                + "= new HashMap<String, ClippedImagePrototype>() {");

        sw.indent();
        sw.println("{");

        sw.indent();

        for (String imgResName : imgResNames) {
            SkinBundleBuilder.ImageRect imageRect = compositeImage.getMapping(imgResName);

            sw.print("put (\"" + imgResName.replace(File.separatorChar, '/') + "\", ");
            sw.print("new ClippedImagePrototype(IMAGE_BUNDLE_URL, ");
            sw.print(Integer.toString(imageRect.getLeft()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getTop()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getWidth()));
            sw.print(", ");
            sw.print(Integer.toString(imageRect.getHeight()));
            sw.println("));");
        }

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

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

        sw.println("@Override");
        sw.println("public void prefetchAll() {");
        sw.indent();
        for (String imgResName : imgResNames)
            sw.println("Image.prefetch(\"" + imgResName.replace(File.separatorChar, '/') + "\");");

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

        sw.println("@Override");
        sw.println("public void prefetchBundle() {");
        sw.indent();
        sw.println("Image.prefetch(IMAGE_BUNDLE_URL);");
        sw.println("}");
        sw.outdent();
    }

    /**
     * Generates the image bundle implementation class, checking each method for
     * validity as it is encountered.
     */
    private String generateImplClass(final TreeLogger logger, final GeneratorContext context,
            final JClassType userType, final JMethod[] imageMethods) throws UnableToCompleteException {
        // Lookup the type info for AbstractImagePrototype so that we can check
        // for
        // the proper return type
        // on image bundle methods.
        final JClassType abstractImagePrototype;
        try {
            abstractImagePrototype = userType.getOracle().getType(ABSTRACTIMAGEPROTOTYPE_QNAME);
        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "GWT " + ABSTRACTIMAGEPROTOTYPE_QNAME + " class is not available", e);
            throw new UnableToCompleteException();
        }

        // Compute the package and class names of the generated class.
        String pkgName = userType.getPackage().getName();
        String subName = computeSubclassName(userType);

        // 11pc
        String pkgPrefix = pkgName.replace('.', '/');

        if (pkgPrefix.length() > 0)
            pkgPrefix += "/";

        // Begin writing the generated source.
        ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(pkgName, subName);
        f.addImport(ABSTRACTIMAGEPROTOTYPE_QNAME);
        f.addImport(CLIPPEDIMAGEPROTOTYPE_QNAME);
        f.addImport(GWT_QNAME);
        f.addImport(HASHMAP_QNAME);
        f.addImport(IMAGE_QNAME);
        f.addImplementedInterface(userType.getQualifiedSourceName());

        PrintWriter pw = context.tryCreate(logger, pkgName, subName);

        if (pw != null) {
            SourceWriter sw = f.createSourceWriter(context, pw);

            // Build a compound image from each individual image.
            SkinBundleBuilder bulder = new SkinBundleBuilder();

            // Store the computed image names so that we don't have to lookup
            // them up again.

            // 11pc
            List<String> imageResNames = getSkinImages(logger);

            for (String imageName : imageResNames)
                bulder.assimilate(logger, imageName);

            /*
            for (JMethod method : imageMethods) {
                        String branchMsg = "Analyzing method '" + method.getName()
                              + "' in type " + userType.getQualifiedSourceName();
                        TreeLogger branch = logger.branch(TreeLogger.DEBUG, branchMsg,
                              null);
                
                        // Verify that this method is valid on an image bundle.
                        if (method.getReturnType() != abstractImagePrototype) {
                           branch.log(TreeLogger.ERROR, "Return type must be "
                                 + ABSTRACTIMAGEPROTOTYPE_QNAME, null);
                           throw new UnableToCompleteException();
                        }
                
                        if (method.getParameters().length > 0) {
                           branch.log(TreeLogger.ERROR,
                                 "Method must have zero parameters", null);
                           throw new UnableToCompleteException();
                        }
                
                        // Find the associated imaged resource.
                        String imageResName = getImageResourceName(branch,
                              new JMethodOracleImpl(method));
                        assert (imageResName != null);
                        imageResNames.add(imageResName);
                        bulder.assimilate(logger, imageResName);
            }
            */
            // Write the compound image into the output directory.
            //         String bundledImageUrl = bulder.writeBundledImage(logger, context);
            String bundledImageUrl = "yeayeaa";

            // Emit a constant for the composite URL. Note that we prepend the
            // module's base URL so that the module can reference its own
            // resources
            // independently of the host HTML page.
            sw.print("private static final String IMAGE_BUNDLE_URL = GWT.getModuleBaseURL() + \"");
            sw.print(escape(bundledImageUrl));
            sw.println("\";");
            /*
                     // Generate an implementation of each method.
                     int imageResNameIndex = 0;
                     for (JMethod method : imageMethods) {
                        generateImageMethod(bulder, sw, method, imageResNames
              .get(imageResNameIndex++));
                     }
            */

            generateImageDef(bulder, sw, imageResNames);

            sw.println("@Override");
            sw.println("public AbstractImagePrototype get(String image) {");
            sw.indent();
            sw.println("return imageMap.get(image);");
            sw.outdent();
            sw.print("}");

            sw.println("@Override");
            sw.println("public HashMap<String, HashMap<String, String>>   getProperties() {");
            sw.indent();
            sw.println("return null;");
            sw.outdent();
            sw.print("}");

            // Finish.
            sw.commit(logger);
        }

        return f.getCreatedClassName();
    }

    /**
     * Attempts to get the image name from the name of the method itself by
     * speculatively appending various image-like file extensions in a
     * prioritized order. The first image found, if any, is used.
     *
     * @param logger
     *                    if no matching image resource is found, an explanatory message
     *                    will be logged
     * @param method
     *                    the method whose name is being examined for matching image
     *                    resources
     * @return a resource name that is suitable to be passed into
     *                      <code>ClassLoader.getResource()</code>; never returns
     *                      <code>null</code>
     * @throws UnableToCompleteException
     *                     thrown when no image can be found based on the method name
     */
    private String getImageNameFromMethodName(final TreeLogger logger, final JMethodOracle method)
            throws UnableToCompleteException {
        String pkgName = method.getPackageName();
        String pkgPrefix = pkgName.replace('.', '/');
        if (pkgPrefix.length() > 0)
            pkgPrefix += "/";

        String methodName = method.getName();
        String pkgAndMethodName = pkgPrefix + methodName;
        List<String> testImgNames = new ArrayList<String>();
        for (int i = 0; i < IMAGE_FILE_EXTENSIONS.length; i++) {
            String testImgName = pkgAndMethodName + '.' + IMAGE_FILE_EXTENSIONS[i];
            if (resLocator.isResourcePresent(testImgName))
                return testImgName;

            testImgNames.add(testImgName);
        }

        TreeLogger branch = logger.branch(TreeLogger.ERROR, MSG_NO_FILE_BASED_ON_METHOD_NAME, null);
        for (String testImgName : testImgNames)
            branch.log(TreeLogger.ERROR, testImgName, null);

        throw new UnableToCompleteException();
    }

    private JClassType getValidUserType(final TreeLogger logger, final String typeName, final TypeOracle typeOracle)
            throws UnableToCompleteException {
        try {
            // Get the type that the user is introducing.
            JClassType userType = typeOracle.getType(typeName);

            // Get the type this generator is designed to support.
            JClassType magicType = typeOracle.findType(IMAGEBUNDLE_QNAME);

            // Ensure it's an interface.
            if (userType.isInterface() == null) {
                logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName() + " must be an interface", null);
                throw new UnableToCompleteException();
            }

            // Ensure proper derivation.
            if (!userType.isAssignableTo(magicType)) {
                logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName() + " must be assignable to "
                        + magicType.getQualifiedSourceName(), null);
                throw new UnableToCompleteException();
            }

            return userType;

        } catch (NotFoundException e) {
            logger.log(TreeLogger.ERROR, "Unable to find required type(s)", e);
            throw new UnableToCompleteException();
        }
    }

    /**
     * Attempts to get the image name (verbatim) from an annotation.
     *
     * @return the string specified in in the {@link ImageBundle.Resource}
     *                      annotation, or <code>null</code>
     */
    private String tryGetImageNameFromAnnotation(final JMethodOracle method) {
        ImageBundle.Resource imgResAnn = method.getAnnotation(ImageBundle.Resource.class);
        String imgName = null;
        if (imgResAnn != null)
            imgName = imgResAnn.value();

        return imgName;
    }

    /**
     * Attempts to get the image name from an annotation.
     *
     * @param logger
     *                    if an annotation is found but the specified resource isn't
     *                    available, an error is logged
     * @param method
     *                    the image bundle method whose associated image resource is
     *                    being sought
     * @return a resource name that is suitable to be passed into
     *                      <code>ClassLoader.getResource()</code>, or <code>null</code>
     *                      if metadata wasn't provided
     * @throws UnableToCompleteException
     *                     thrown when metadata is provided but the resource cannot be
     *                     found
     */
    private String tryGetImageNameFromMetaData(final TreeLogger logger, final JMethodOracle method)
            throws UnableToCompleteException {
        String imgFileName = tryGetImageNameFromAnnotation(method);
        if (imgFileName == null)
            // Exit early because neither an annotation nor javadoc was found.
            return null;

        // If the name has no slashes (that is, it isn't a fully-qualified
        // resource
        // name), then prepend the enclosing package name automatically, being
        // careful about the default package.
        if (imgFileName.indexOf("/") == -1) {
            String pkgName = method.getPackageName();
            if (!"".equals(pkgName))
                imgFileName = pkgName.replace('.', '/') + "/" + imgFileName;
        }

        if (!resLocator.isResourcePresent(imgFileName)) {
            // Not found.
            logger.log(TreeLogger.ERROR, msgCannotFindImageFromMetaData(imgFileName), null);
            throw new UnableToCompleteException();
        }

        // Success.
        return imgFileName;
    }

    // 11 percent
    private void loadFile(final Vector<String> imageNames, final String name) {
        File file = new File(name);
        String[] dirFiles;

        if (file.getParentFile() == null)
            return;

        dirFiles = file.getParentFile().list();

        if (dirFiles == null || dirFiles.length == 0)
            return;

        for (String dirFile : dirFiles)
            if (dirFile.matches(file.getName() + "[^\\\\/\\" + File.separatorChar + "]*\\.(png|gif|jpg)"))
                if (!imageNames.contains(file.getParent() + File.separatorChar + dirFile))
                    imageNames.add(file.getParent() + File.separatorChar + dirFile);
    }

    private List<String> getSkinImages(final TreeLogger logger) {
        /*
              Vector<String> skinUris;
              Vector<String> imageNames = new Vector<String>();
              String         value;
              String[]      values;
            
              Skin.initURIs();
            
              skinUris = URICache.getURIsForType(URI.TYPE_SKIN);
            
              for (String uri : skinUris) {
                 URIContent content = URICache.get(uri);
            
                 for (String parameter : content.getParameters()) {
            
        if ((value = content.getParameter(parameter)) != null)
           loadFile(imageNames, value);
        /*
        else if((values = content.getArrayParameter(parameter)) != null) {
           for(String v : values)
              loadFile(imageNames, v);
        }
            
                 }
              }
            
              return imageNames;
        */
        return Collections.emptyList();
    }

    /**
     * Simple wrapper around JMethod that allows for unit test mocking.
     */
    interface JMethodOracle {

        Resource getAnnotation(Class<Resource> clazz);

        String getName();

        String getPackageName();
    }

    /**
     * Indirection around the act of looking up a resource that allows for unit
     * test mocking.
     */
    /* private */ interface ResourceLocator {
        /**
         *
         * @param resName
         *         the resource name in a format that could be passed to
         *         <code>ClassLoader.getResource()</code>
         * @return <code>true</code> if the resource is present
         */
        boolean isResourcePresent(String resName);
    }

    private static class JMethodOracleImpl implements JMethodOracle {
        private final JMethod delegate;

        public JMethodOracleImpl(final JMethod delegate) {
            this.delegate = delegate;
        }

        @Override
        public Resource getAnnotation(final Class<Resource> clazz) {
            return delegate.getAnnotation(clazz);
        }

        @Override
        public String getName() {
            return delegate.getName();
        }

        @Override
        public String getPackageName() {
            return delegate.getEnclosingType().getPackage().getName();
        }
    }
}