All Downloads are FREE. Search and download functionalities are using the official Maven repository.

playn.rebind.AutoClientBundleGenerator Maven / Gradle / Ivy

There is a newer version: 2.0.8
Show newest version
/**
 * Copyright 2011 The PlayN Authors
 *
 * 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 playn.rebind;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.google.gwt.core.client.GWT;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.resource.Resource;
import com.google.gwt.dev.util.collect.HashMap;
import com.google.gwt.resources.client.ClientBundleWithLookup;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ResourcePrototype;
import com.google.gwt.resources.client.TextResource;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

/**
 * Automatically generate a client bundle from the resources available in the provided interface's
 * package directory and below.
 */
public class AutoClientBundleGenerator extends Generator {

  private static final Map EXTENSION_MAP = new HashMap();

  private static FileFilter fileFilter = new FileFilter() {
    @Override
    public boolean accept(File file) {
      if (file.isDirectory()) {
        // Skip .svn directories
        if (file.getName().equals(".svn")) {
          return false;
        }
        // By default descend into all directories
        return true;
      } else {
        // Include only all explicitly mapped extensions
        String extension = getExtension(file.getName());
        return EXTENSION_MAP.containsKey(extension);
      }
    }
  };

  private static final String WEB_INF_CLASSES = "WEB-INF/classes/";

  static {
    EXTENSION_MAP.put(".png", "image/png");
    EXTENSION_MAP.put(".gif", "image/gif");
    EXTENSION_MAP.put(".jpg", "image/jpeg");

    /*
     * Do not include WAV files, since HTML5 audio playback ended events are not yet a part of GWT
     * 2.2.0, which means we won't know when these sounds stop playing.
     *
     * EXTENSION_MAP.put(".wav", "audio/wav");
     */
    EXTENSION_MAP.put(".mp3", "audio/mp3");

    EXTENSION_MAP.put(".json", "text/json");
  }

  private static String getContentType(TreeLogger logger, Resource resource) {
    String name = resource.getPath().toLowerCase();
    int pos = name.lastIndexOf('.');
    String extension = pos == -1 ? "" : name.substring(pos);
    String contentType = EXTENSION_MAP.get(extension);
    if (contentType == null) {
      logger.log(
          TreeLogger.WARN,
          "No Content Type mapping for files with '" + extension
              + "' extension. Please add a mapping to the "
              + AutoClientBundleGenerator.class.getCanonicalName() + " class.");
      contentType = "application/octet-stream";
    }
    return contentType;
  }

  /**
   * Determine whether the provided method name is valid in Java.
   */
  private static boolean isValidMethodName(String methodName) {
    return methodName.matches("^[a-zA-Z_$][a-zA-Z0-9_$]*$"); // TODO: fix regex
  }

  /**
   * Strip off the filename extension, if it's present.
   */
  private static String stripExtension(String filename) {
    return filename.replaceFirst("\\.[^.]+$", "");
  }

  /**
   * Get the filename extension or return an empty string if there's no extension.
   */
  private static String getExtension(String filename) {
    return filename.replaceFirst(".*(\\.[^.]+)$", "$1");
  }

  @Override
  public String generate(TreeLogger logger, GeneratorContext context, String typeName)
      throws UnableToCompleteException {
    TypeOracle typeOracle = context.getTypeOracle();

    JClassType userType;
    try {
      userType = typeOracle.getType(typeName);
    } catch (NotFoundException e) {
      logger.log(TreeLogger.ERROR, "Unable to find metadata for type: " + typeName, e);
      throw new UnableToCompleteException();
    }
    String packageName = userType.getPackage().getName();
    String className = userType.getName();
    className = className.replace('.', '_');

    if (userType.isInterface() == null) {
      logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName() + " is not an interface", null);
      throw new UnableToCompleteException();
    }

    ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
        packageName, className + "Impl");
    composerFactory.addImplementedInterface(userType.getQualifiedSourceName());

    composerFactory.addImport(ClientBundleWithLookup.class.getName());
    composerFactory.addImport(DataResource.class.getName());
    composerFactory.addImport(GWT.class.getName());
    composerFactory.addImport(ImageResource.class.getName());
    composerFactory.addImport(ResourcePrototype.class.getName());
    composerFactory.addImport(TextResource.class.getName());

    File warDirectory = getWarDirectory(logger);
    assert warDirectory.isDirectory();

    File classesDirectory = new File(warDirectory, WEB_INF_CLASSES);
    assert classesDirectory.isDirectory();

    File resourcesDirectory = new File(classesDirectory, packageName.replace('.', '/'));
    assert resourcesDirectory.isDirectory();

    String baseClassesPath = classesDirectory.getPath();
    logger.log(TreeLogger.DEBUG, "baseClassesPath: " + baseClassesPath);

    Set resources = preferMp3(getResources(context, userType, fileFilter));
    Set methodNames = new HashSet();

    PrintWriter pw = context.tryCreate(logger, packageName, className + "Impl");
    if (pw != null) {
      SourceWriter sw = composerFactory.createSourceWriter(context, pw);

      // write out jump methods

      sw.println("public ResourcePrototype[] getResources() {");
      sw.indent();
      sw.println("return MyBundle.INSTANCE.getResources();");
      sw.outdent();
      sw.println("}");

      sw.println("public ResourcePrototype getResource(String name) {");
      sw.indent();
      sw.println("return MyBundle.INSTANCE.getResource(name);");
      sw.outdent();
      sw.println("}");

      // write out static ClientBundle interface

      sw.println("static interface MyBundle extends ClientBundleWithLookup {");
      sw.indent();
      sw.println("MyBundle INSTANCE = GWT.create(MyBundle.class);");

      for (Resource resource : resources) {
        String relativePath = resource.getPath();
        String filename = resource.getPath().substring(resource.getPath().lastIndexOf('/') + 1);
        String contentType = getContentType(logger, resource);
        String methodName = stripExtension(filename);

        if (!isValidMethodName(methodName)) {
          logger.log(TreeLogger.WARN, "Skipping invalid method name (" + methodName + ") due to: "
              + relativePath);
          continue;
        }
        if (!methodNames.add(methodName)) {
          logger.log(TreeLogger.WARN, "Skipping duplicate method name due to: " + relativePath);
          continue;
        }

        logger.log(TreeLogger.DEBUG, "Generating method for: " + relativePath);

        Class returnType = getResourcePrototype(contentType);

        // generate method
        sw.println();

        if (returnType == DataResource.class) {
          if (contentType.startsWith("audio/")) {
            // Prevent the use of data URLs, which Flash won't play
            sw.println("@DataResource.DoNotEmbed");
          } else {
            // Specify an explicit MIME type, for use in the data URL
            sw.println("@DataResource.MimeType(\"" + contentType + "\")");
          }
        }

        sw.println("@Source(\"" + relativePath + "\")");

        sw.println(returnType.getName() + " " + methodName + "();");
      }

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

      sw.commit(logger);
    }
    return composerFactory.getCreatedClassName();
  }

  /**
   * Filter file set, preferring *.mp3 files where alternatives exist.
   */
  private HashSet preferMp3(HashSet files) {
    HashMap map = new HashMap();
    for (Resource file : files) {
      String path = stripExtension(file.getPath());
      if (file.getPath().endsWith(".mp3") || !map.containsKey(path)) {
        map.put(path, file);
      }
    }
    return new HashSet(map.values());
  }

  /**
   * Get all related resources of the auto resource bundler.
   * 
   */
  private HashSet getResources(GeneratorContext context, JClassType userType, FileFilter filter) {
    Map map = context.getResourcesOracle().getResourceMap();
    final String pack = userType.getPackage().getName().replace('.', '/');
   
    HashSet resourceList = new HashSet();
    for (Entry entry : map.entrySet()) {
      String path = entry.getKey();
      if (!path.startsWith(pack))
        continue;
      String ext = getExtension(path);
      if (EXTENSION_MAP.containsKey(ext))
        resourceList.add(entry.getValue());
    }

    return resourceList;
  }

  private Class getResourcePrototype(String contentType) {
    Class returnType;
    if (contentType.startsWith("image/")) {
      returnType = ImageResource.class;
    } else if (contentType.startsWith("text/")) {
      returnType = TextResource.class;
    } else {
      returnType = DataResource.class;
    }
    return returnType;
  }

  /**
   * When invoking the GWT compiler from GPE, the working directory is the Eclipse project
   * directory. However, when launching a GPE project, the working directory is the project 'war'
   * directory. This methods returns the war directory in either case in a fairly naive and
   * non-robust manner.
   */
  private File getWarDirectory(TreeLogger logger) throws UnableToCompleteException {
    File currentDirectory = new File(".");
    try {
      String canonicalPath = currentDirectory.getCanonicalPath();
      logger.log(TreeLogger.INFO, "Current directory in which this generator is executing: "
          + canonicalPath);
      if (canonicalPath.endsWith("war")) {
        return currentDirectory;
      } else {
        return new File("war");
      }
    } catch (IOException e) {
      logger.log(TreeLogger.ERROR, "Failed to get canonical path", e);
      throw new UnableToCompleteException();
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy