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

com.google.gwt.resources.rebind.context.AbstractClientBundleGenerator Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.google.gwt.resources.rebind.context;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.CachedPropertyInformation;
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.PropertyOracle;
import com.google.gwt.core.ext.RebindMode;
import com.google.gwt.core.ext.RebindResult;
import com.google.gwt.core.ext.SelectionProperty;
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.JGenericType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JRealClassType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.dev.util.Util;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ClientBundleWithLookup;
import com.google.gwt.resources.client.ResourcePrototype;
import com.google.gwt.resources.ext.ClientBundleFields;
import com.google.gwt.resources.ext.ClientBundleRequirements;
import com.google.gwt.resources.ext.ResourceContext;
import com.google.gwt.resources.ext.ResourceGenerator;
import com.google.gwt.resources.ext.ResourceGeneratorType;
import com.google.gwt.resources.ext.ResourceGeneratorUtil;
import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
import com.google.gwt.resources.rg.BundleResourceGenerator;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.beans.Beans;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * The base class for creating new ClientBundle implementations.
 * 

* The general structure of the generated class is as follows: * *

 * private void resourceInitializer() {
 *   resource = new Resource();
 * }
 * private static class cellTreeClosedItemInitializer {
 *   // Using a static initializer so the compiler can optimize clinit calls.
 *   // Refers back to an instance method. See comment below.
 *   static {
 *     _instance0.resourceInitializer();
 *   }
 *   static ResourceType get() {
 *     return resource;
 *   }
 * }
 * public ResourceType resource() {
 *   return cellTreeClosedItemInitializer.get();
 * }
 * // Other ResourceGenerator-defined fields
 * private static ResourceType resource;
 * private static HashMap<String, ResourcePrototype> resourceMap;
 * public ResourcePrototype[] getResources() {
 *   return new ResourcePrototype[] { resource() };
 * }
 * public ResourcePrototype getResource(String name) {
 *   if (GWT.isScript()) {
 *     return getResourceNative(name);
 *   } else {
 *     if (resourceMap == null) {
 *       resourceMap = new HashMap();
 *       resourceMap.put("resource", resource());
 *     }
 *     return resourceMap.get(name);
 *   }
 * }
 * private native ResourcePrototype getResourceNative(String name) /-{
 *   switch (name) {
 *     case 'resource': return this.@...::resource()();
 *   }
 *   return null;
 * }-/
 * 
* The instantiation of the individual ResourcePrototypes is done in the content * of an instance of the ClientBundle type so that resources can refer to one * another by simply emitting a call to resource(). */ public abstract class AbstractClientBundleGenerator extends IncrementalGenerator { private static final String CACHED_PROPERTY_INFORMATION = "cached-property-info"; private static final String CACHED_RESOURCE_INFORMATION = "cached-resource-info"; private static final String CACHED_TYPE_INFORMATION = "cached-type-info"; private static final String INSTANCE_NAME = "_instance0"; /* * A version id. Increment this as needed, when structural changes are made to * the generated output, specifically with respect to it's effect on the * caching and reuse of previous generator results. Previously cached * generator results will be invalidated automatically if they were generated * by a version of this generator with a different version id. */ private static final long GENERATOR_VERSION_ID = 1L; /** * An implementation of ClientBundleFields. */ protected static class FieldsImpl implements ClientBundleFields { private final NameFactory factory = new NameFactory(); /** * It is necessary to maintain order in case one field refers to another. */ private final Map fieldsToDeclarations = new LinkedHashMap(); private final Map fieldsToInitializers = new HashMap(); @Override public String define(JType type, String name) { return define(type, name, null, true, false); } @Override public String define(JType type, String name, String initializer, boolean isStatic, boolean isFinal) { assert Util.isValidJavaIdent(name) : name + " is not a valid Java identifier"; String ident = factory.createName(name); StringBuilder sb = new StringBuilder(); sb.append("private "); if (isStatic) { sb.append("static "); } if (isFinal) { sb.append("final "); } sb.append(type.getParameterizedQualifiedSourceName()); sb.append(" "); sb.append(ident); fieldsToDeclarations.put(ident, sb.toString()); if (initializer != null) { fieldsToInitializers.put(ident, initializer); } return ident; } /** * This can be called to reset the initializer expression on an * already-defined field. * * @param ident an identifier previously returned by {@link #define} * @param initializer a Java expression that will be used to initialize the * field */ public void setInitializer(String ident, String initializer) { assert fieldsToDeclarations.containsKey(ident) : ident + " not defined"; fieldsToInitializers.put(ident, initializer); } private String getCode() { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : fieldsToDeclarations.entrySet()) { String ident = entry.getKey(); sb.append(entry.getValue()); String initializer = fieldsToInitializers.get(ident); if (initializer != null) { sb.append(" = ").append(initializer); } sb.append(";\n"); } return sb.toString(); } } private static class RequirementsImpl implements ClientBundleRequirements { private final Set axes; private boolean axesLocked = false; private final boolean canBeCacheable; private final Set configProps; private final PropertyOracle propertyOracle; private final Map resolvedResources; private final Set types; public RequirementsImpl(PropertyOracle propertyOracle, boolean canBeCacheable) { this.propertyOracle = propertyOracle; this.canBeCacheable = canBeCacheable; // always need to track permuationAxes axes = new HashSet(); // only need to track these if generator caching is a possibility if (canBeCacheable) { configProps = new HashSet(); types = new HashSet(); resolvedResources = new HashMap(); } else { configProps = null; types = null; resolvedResources = null; } } @Override public void addConfigurationProperty(String propertyName) throws BadPropertyValueException { if (!canBeCacheable) { return; } // Ensure the property exists propertyOracle.getConfigurationProperty(propertyName).getValues(); configProps.add(propertyName); } @Override public void addPermutationAxis(String propertyName) throws BadPropertyValueException { if (axes.contains(propertyName)) { return; } // Ensure adding of permutationAxes has not been locked if (axesLocked) { throw new IllegalStateException( "addPermutationAxis failed, axes have been locked"); } // Ensure the property exists and add a permutation axis if the // property is a deferred binding property. try { propertyOracle.getSelectionProperty( TreeLogger.NULL, propertyName).getCurrentValue(); axes.add(propertyName); } catch (BadPropertyValueException e) { addConfigurationProperty(propertyName); } } @Override public void addResolvedResource(String partialPath, URL resolvedResourceUrl) { if (!canBeCacheable) { return; } resolvedResources.put(partialPath, resolvedResourceUrl); } @Override public void addTypeHierarchy(JClassType type) { if (!canBeCacheable) { return; } if (!types.add(type)) { return; } Set superTypes = type.getFlattenedSupertypeHierarchy(); types.addAll(superTypes); } public Collection getConfigurationPropertyNames() { if (!canBeCacheable) { return null; } return configProps; } public Collection getPermutationAxes() { return axes; } public Map getResolvedResources() { if (!canBeCacheable) { return null; } return resolvedResources; } public Map getTypeLastModifiedTimes() { if (!canBeCacheable) { return null; } Map typeLastModifiedTimeMap = new HashMap(); for (JClassType type : types) { String typeName = type.getQualifiedSourceName(); assert type instanceof JRealClassType; JRealClassType sourceRealType = (JRealClassType) type; typeLastModifiedTimeMap.put(typeName, sourceRealType.getLastModifiedTime()); } return typeLastModifiedTimeMap; } /* * No further permutation axes can be added after this is called */ public void lockPermutationAxes() { axesLocked = true; } } @Override public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext generatorContext, String typeName) throws UnableToCompleteException { /* * Do a series of checks to see if we can use a previously cached result, * and if so, we can skip further execution and return immediately. */ boolean useCache = false; if (checkCachedPropertyInformation(logger, generatorContext) && checkCachedSourceTypes(logger, generatorContext) && checkCachedDependentResources(logger, generatorContext)) { useCache = true; } if (logger.isLoggable(TreeLogger.TRACE)) { if (generatorContext.isGeneratorResultCachingEnabled()) { String msg; if (useCache) { msg = "Reusing cached client bundle for " + typeName; } else { msg = "Can't use cached client bundle for " + typeName; } logger.log(TreeLogger.TRACE, msg); } } if (useCache) { return new RebindResult(RebindMode.USE_ALL_CACHED, typeName); } // The TypeOracle knows about all types in the type system TypeOracle typeOracle = generatorContext.getTypeOracle(); // Get a reference to the type that the generator should implement JClassType sourceType = typeOracle.findType(typeName); // Ensure that the requested type exists if (sourceType == null) { logger.log(TreeLogger.ERROR, "Could not find requested typeName"); throw new UnableToCompleteException(); } else if (sourceType.isInterface() == null) { // The incoming type wasn't a plain interface, we don't support // abstract base classes logger.log(TreeLogger.ERROR, sourceType.getQualifiedSourceName() + " is not an interface.", null); throw new UnableToCompleteException(); } /* * This associates the methods to implement with the ResourceGenerator class * that will generate the implementations of those methods. */ Map, List> taskList = createTaskList( logger, typeOracle, sourceType); /* * Check the resource generators associated with our taskList, and see if * they all support generator result caching. */ boolean canBeCacheable = checkResourceGeneratorCacheability( generatorContext, taskList); /* * Additional objects that hold state during the generation process. */ AbstractResourceContext resourceContext = createResourceContext(logger, generatorContext, sourceType); FieldsImpl fields = new FieldsImpl(); RequirementsImpl requirements = new RequirementsImpl( generatorContext.getPropertyOracle(), canBeCacheable); resourceContext.setRequirements(requirements); doAddFieldsAndRequirements(logger, generatorContext, fields, requirements); /* * Add our source type (and it's supertypes) as a requirement. Note further * types may be added during the processing of the taskList. */ requirements.addTypeHierarchy(sourceType); /* * Initialize the ResourceGenerators and prepare them for subsequent code * generation. */ Map> generators = initAndPrepare(logger, taskList, resourceContext, requirements); /* * Now that the ResourceGenerators have been initialized and prepared, we * can compute the actual name of the implementation class in order to * ensure that we use a distinct name between permutations. */ String generatedSimpleSourceName = generateSimpleSourceName(logger, resourceContext, requirements); String packageName = sourceType.getPackage().getName(); String createdClassName = packageName + "." + generatedSimpleSourceName; PrintWriter out = generatorContext.tryCreate(logger, packageName, generatedSimpleSourceName); // If an implementation already exists, we don't need to do any work if (out != null) { // There is actual work to do doCreateBundleForPermutation(logger, generatorContext, fields, generatedSimpleSourceName); // Begin writing the generated source. ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory( packageName, generatedSimpleSourceName); // The generated class needs to be able to determine the module base URL f.addImport(GWT.class.getName()); // Used by the map methods f.addImport(ResourcePrototype.class.getName()); // The whole point of this exercise f.addImplementedInterface(sourceType.getQualifiedSourceName()); // All source gets written through this Writer SourceWriter sw = f.createSourceWriter(generatorContext, out); // Set the now-calculated simple source name resourceContext.setSimpleSourceName(generatedSimpleSourceName); JParameterizedType hashMapStringResource = getHashMapStringResource(typeOracle); String resourceMapField = fields.define(hashMapStringResource, "resourceMap"); // Write a static instance for use by the static initializers. sw.print("private static " + generatedSimpleSourceName + " "); sw.println(INSTANCE_NAME + " = new " + generatedSimpleSourceName + "();"); // Write the generated code to disk createFieldsAndAssignments(logger, sw, generators, resourceContext, fields); // Print the accumulated field definitions sw.println(fields.getCode()); /* * The map-accessor methods use JSNI and need a fully-qualified class * name, but should not include any sub-bundles. */ taskList.remove(BundleResourceGenerator.class); writeMapMethods(sw, taskList, hashMapStringResource, resourceMapField); sw.commit(logger); } finish(logger, resourceContext, generators.keySet()); doFinish(logger); if (canBeCacheable) { // remember the current set of required properties, and their values CachedPropertyInformation cpi = new CachedPropertyInformation(logger, generatorContext.getPropertyOracle(), requirements.getPermutationAxes(), requirements.getConfigurationPropertyNames()); // remember the last modified times for required source types Map cti = requirements.getTypeLastModifiedTimes(); // remember the required resources Map cri = requirements.getResolvedResources(); // create a new cacheable result RebindResult result = new RebindResult(RebindMode.USE_ALL_NEW, createdClassName); // add data to be returned the next time the generator is run result.putClientData(CACHED_PROPERTY_INFORMATION, cpi); result.putClientData(CACHED_RESOURCE_INFORMATION, (Serializable) cri); result.putClientData(CACHED_TYPE_INFORMATION, (Serializable) cti); return result; } else { // If we can't be cacheable, don't return a cacheable result return new RebindResult(RebindMode.USE_ALL_NEW_WITH_NO_CACHING, createdClassName); } } @Override public long getVersionId() { return GENERATOR_VERSION_ID; } /** * Create the ResourceContext object that will be used by * {@link ResourceGenerator} subclasses. This is the primary way to implement * custom logic in the resource generation pass. */ protected abstract AbstractResourceContext createResourceContext( TreeLogger logger, GeneratorContext context, JClassType resourceBundleType) throws UnableToCompleteException; /** * Provides a hook for subtypes to add additional fields or requirements to * the bundle. * * @param logger a TreeLogger * @param context the GeneratorContext * @param fields ClentBundle fields * @param requirements ClientBundleRequirements * * @throws UnableToCompleteException if an error occurs. */ protected void doAddFieldsAndRequirements(TreeLogger logger, GeneratorContext context, FieldsImpl fields, ClientBundleRequirements requirements) throws UnableToCompleteException { } /** * This method is called after the ClientBundleRequirements have been * evaluated and a new ClientBundle implementation is being created. * * @param logger a TreeLogger * @param generatorContext the GeneratoContext * @param fields ClientBundle fields * @param generatedSimpleSourceName a String * * @throws UnableToCompleteException if an error occurs. */ protected void doCreateBundleForPermutation(TreeLogger logger, GeneratorContext generatorContext, FieldsImpl fields, String generatedSimpleSourceName) throws UnableToCompleteException { } /** * Provides a hook for finalizing generated resources. * * @param logger a TreeLogger * * @throws UnableToCompleteException if an error occurs. */ protected void doFinish(TreeLogger logger) throws UnableToCompleteException { } /** * Check cached dependent resources. */ private boolean checkCachedDependentResources(TreeLogger logger, GeneratorContext genContext) { CachedGeneratorResult lastRebindResult = genContext.getCachedGeneratorResult(); if (lastRebindResult == null || !genContext.isGeneratorResultCachingEnabled()) { return false; } long lastTimeGenerated = lastRebindResult.getTimeGenerated(); // check that resource URL's haven't moved, and haven't been modified @SuppressWarnings("unchecked") Map cachedResolvedResources = (Map) lastRebindResult.getClientData(CACHED_RESOURCE_INFORMATION); if (cachedResolvedResources == null) { return false; } for (Entry entry : cachedResolvedResources.entrySet()) { String resourceName = entry.getKey(); URL resolvedUrl = entry.getValue(); URL currentUrl = ResourceGeneratorUtil.tryFindResource(logger, genContext, null, resourceName); if (resolvedUrl == null) { if (currentUrl == null) { continue; } else { logger.log(TreeLogger.TRACE, "Found newly available dependent resource: " + resourceName); return false; } } else if (currentUrl == null || !resolvedUrl.toExternalForm().equals(currentUrl.toExternalForm())) { logger.log(TreeLogger.TRACE, "Found dependent resource that has moved or no longer exists: " + resourceName); return false; } // Check whether the resource referenced by the provided URL is up to date long modifiedTime = Util.getResourceModifiedTime(resolvedUrl); if (modifiedTime == 0L || modifiedTime > lastTimeGenerated) { logger.log(TreeLogger.TRACE, "Found dependent resource that has changed: " + resourceName); return false; } } return true; } /** * Check cached properties. */ private boolean checkCachedPropertyInformation(TreeLogger logger, GeneratorContext genContext) { CachedGeneratorResult lastRebindResult = genContext.getCachedGeneratorResult(); if (lastRebindResult == null || !genContext.isGeneratorResultCachingEnabled()) { return false; } /* * Do a check of deferred-binding and configuration properties, comparing * the cached values saved previously with the current properties. */ CachedPropertyInformation cpi = (CachedPropertyInformation) lastRebindResult.getClientData(CACHED_PROPERTY_INFORMATION); return cpi != null && cpi.checkPropertiesWithPropertyOracle(logger, genContext.getPropertyOracle()); } /** * Check cached source types. */ private boolean checkCachedSourceTypes(TreeLogger logger, GeneratorContext genContext) { CachedGeneratorResult lastRebindResult = genContext.getCachedGeneratorResult(); if (lastRebindResult == null || !genContext.isGeneratorResultCachingEnabled()) { return false; } /* * Do a check over the cached list of types that were previously flagged as * required. Check that none of these types has undergone a version change * since the previous cached result was generated. */ @SuppressWarnings("unchecked") Map cachedTypeLastModifiedTimes = (Map) lastRebindResult.getClientData(CACHED_TYPE_INFORMATION); return cachedTypeLastModifiedTimes != null && checkCachedTypeLastModifiedTimes(logger, genContext, cachedTypeLastModifiedTimes); } /** * Check that the cached last modified times match those from the current * typeOracle. */ private boolean checkCachedTypeLastModifiedTimes(TreeLogger logger, GeneratorContext generatorContext, Map typeLastModifiedTimes) { TypeOracle oracle = generatorContext.getTypeOracle(); for (String sourceTypeName : typeLastModifiedTimes.keySet()) { JClassType sourceType = oracle.findType(sourceTypeName); if (sourceType == null) { logger.log(TreeLogger.TRACE, "Found previously dependent type that's no longer present: " + sourceTypeName); return false; } assert sourceType instanceof JRealClassType; JRealClassType sourceRealType = (JRealClassType) sourceType; if (sourceRealType.getLastModifiedTime() != typeLastModifiedTimes.get(sourceTypeName)) { logger.log(TreeLogger.TRACE, "Found dependent type that has changed: " + sourceTypeName); return false; } } return true; } /** * Check cacheability for resource generators in taskList. */ private boolean checkResourceGeneratorCacheability(GeneratorContext genContext, Map, List> taskList) { if (!genContext.isGeneratorResultCachingEnabled()) { return false; } /* * Loop through each of our ResouceGenerator classes, and check those that * implement the SupportsGeneratorResultCaching interface. */ for (Class rgClass : taskList.keySet()) { if (!SupportsGeneratorResultCaching.class.isAssignableFrom(rgClass)) { return false; } } return true; } /** * Create fields and assignments for a single ResourceGenerator. */ private boolean createFieldsAndAssignments(TreeLogger logger, AbstractResourceContext resourceContext, ResourceGenerator rg, List generatorMethods, SourceWriter sw, ClientBundleFields fields) { // Defer failure until this phase has ended boolean fail = false; resourceContext.setCurrentResourceGenerator(rg); // Write all field values try { rg.createFields(logger.branch(TreeLogger.DEBUG, "Creating fields"), resourceContext, fields); } catch (UnableToCompleteException e) { return false; } // Create the instance variables in the IRB subclass by calling // writeAssignment() on the ResourceGenerator for (JMethod m : generatorMethods) { String rhs; try { rhs = rg.createAssignment(logger.branch(TreeLogger.DEBUG, "Creating assignment for " + m.getName() + "()"), resourceContext, m); } catch (UnableToCompleteException e) { fail = true; continue; } // Define a field that will hold the ResourcePrototype String ident = fields.define(m.getReturnType().isClassOrInterface(), m.getName(), null, true, false); /* * Create an initializer method in the context of an instance so that * resources can refer to one another by simply emitting a call to * resource(). */ String initializerName = m.getName() + "Initializer"; sw.println("private void " + initializerName + "() {"); sw.indentln(ident + " = " + rhs + ";"); sw.println("}"); /* * Create a static Initializer class to lazily initialize the field on * first access. The compiler can efficiently optimize this static class * using clinits. */ sw.println("private static class " + initializerName + " {"); sw.indent(); sw.println("static {"); sw.indentln(INSTANCE_NAME + "." + initializerName + "();"); sw.println("}"); sw.print("static "); sw.print(m.getReturnType().getParameterizedQualifiedSourceName()); sw.println(" get() {"); sw.indentln("return " + ident + ";"); sw.println("}"); sw.outdent(); sw.println("}"); // Strip off all but the access modifiers sw.print(m.getReadableDeclaration(false, true, true, true, true)); sw.println(" {"); sw.indentln("return " + initializerName + ".get();"); sw.println("}"); } if (fail) { return false; } return true; } /** * Create fields and assignments for multiple ResourceGenerators. */ private void createFieldsAndAssignments(TreeLogger logger, SourceWriter sw, Map> generators, AbstractResourceContext resourceContext, ClientBundleFields fields) throws UnableToCompleteException { // Try to provide as many errors as possible before failing. boolean success = true; // Run the ResourceGenerators to generate implementations of the methods for (Map.Entry> entry : generators.entrySet()) { success &= createFieldsAndAssignments(logger, resourceContext, entry.getKey(), entry.getValue(), sw, fields); } if (!success) { throw new UnableToCompleteException(); } } /** * Given a ClientBundle subtype, compute which ResourceGenerators should * implement which methods. The data returned from this method should be * stable across identical modules. */ private Map, List> createTaskList( TreeLogger logger, TypeOracle typeOracle, JClassType sourceType) throws UnableToCompleteException { Map, List> toReturn = new LinkedHashMap, List>(); JClassType bundleType = typeOracle.findType(ClientBundle.class.getName()); assert bundleType != null; JClassType bundleWithLookupType = typeOracle.findType(ClientBundleWithLookup.class.getName()); assert bundleWithLookupType != null; JClassType resourcePrototypeType = typeOracle.findType(ResourcePrototype.class.getName()); assert resourcePrototypeType != null; // Accumulate as many errors as possible before failing boolean throwException = false; // Using overridable methods allows composition of interface types for (JMethod m : sourceType.getOverridableMethods()) { JClassType returnType = m.getReturnType().isClassOrInterface(); if (m.getEnclosingType().equals(bundleType) || m.getEnclosingType().equals(bundleWithLookupType)) { // Methods that we must generate, but that are not resources continue; } else if (!m.isAbstract()) { // Covers the case of an abstract class base type continue; } else if (returnType == null || !(returnType.isAssignableTo(resourcePrototypeType) || returnType.isAssignableTo(bundleType))) { // Primitives and random other abstract methods logger.log(TreeLogger.ERROR, "Unable to implement " + m.getName() + " because it does not derive from " + resourcePrototypeType.getQualifiedSourceName() + " or " + bundleType.getQualifiedSourceName()); throwException = true; continue; } try { Class clazz = findResourceGenerator( logger, m); List generatorMethods; if (toReturn.containsKey(clazz)) { generatorMethods = toReturn.get(clazz); } else { generatorMethods = new ArrayList(); toReturn.put(clazz, generatorMethods); } generatorMethods.add(m); } catch (UnableToCompleteException e) { throwException = true; } } if (throwException) { throw new UnableToCompleteException(); } return toReturn; } /** * Given a JMethod, find the a ResourceGenerator class that will be able to * provide an implementation of the method. */ private Class findResourceGenerator( TreeLogger logger, JMethod method) throws UnableToCompleteException { JClassType resourceType = method.getReturnType().isClassOrInterface(); assert resourceType != null; ResourceGeneratorType annotation = resourceType.findAnnotationInTypeHierarchy(ResourceGeneratorType.class); if (annotation == null) { logger.log(TreeLogger.ERROR, "No @" + ResourceGeneratorType.class.getName() + " was specifed for type " + resourceType.getQualifiedSourceName() + " or its supertypes"); throw new UnableToCompleteException(); } return annotation.value(); } /** * Call finish() on several ResourceGenerators. */ private void finish(TreeLogger logger, AbstractResourceContext context, Collection generators) throws UnableToCompleteException { boolean fail = false; // Finalize the ResourceGenerator for (ResourceGenerator rg : generators) { context.setCurrentResourceGenerator(rg); try { rg.finish( logger.branch(TreeLogger.DEBUG, "Finishing ResourceGenerator"), context); } catch (UnableToCompleteException e) { fail = true; } } if (fail) { throw new UnableToCompleteException(); } } /** * Given a user-defined type name, determine the type name for the generated * class based on accumulated requirements. */ private String generateSimpleSourceName(TreeLogger logger, ResourceContext context, RequirementsImpl requirements) { StringBuilder toReturn = new StringBuilder( context.getClientBundleType().getName().replaceAll("[.$]", "_")); try { // always add the locale property requirements.addPermutationAxis("locale"); } catch (BadPropertyValueException e) { } try { // no further additions to the permutation axes allowed after this point requirements.lockPermutationAxes(); PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle(); for (String property : requirements.getPermutationAxes()) { SelectionProperty prop = oracle.getSelectionProperty(logger, property); String value = prop.getCurrentValue(); toReturn.append("_" + value); } } catch (BadPropertyValueException e) { } toReturn.append("_" + getClass().getSimpleName()); // If design time, generate new class each time to allow reloading. if (Beans.isDesignTime()) { toReturn.append("_designTime" + System.currentTimeMillis()); } return toReturn.toString(); } /** * Returns HashMap<String, ResourcePrototype>. */ private JParameterizedType getHashMapStringResource(TypeOracle typeOracle) { JGenericType hashMap = (JGenericType) typeOracle.findType(HashMap.class.getName()); assert hashMap != null; JClassType string = typeOracle.findType(String.class.getName()); assert string != null; JClassType resourcePrototype = typeOracle.findType(ResourcePrototype.class.getName()); assert resourcePrototype != null; JParameterizedType mapStringRes = typeOracle.getParameterizedType(hashMap, new JClassType[]{string, resourcePrototype}); return mapStringRes; } private boolean initAndPrepare(TreeLogger logger, AbstractResourceContext resourceContext, ResourceGenerator rg, List generatorMethods, ClientBundleRequirements requirements) { try { resourceContext.setCurrentResourceGenerator(rg); rg.init( logger.branch(TreeLogger.DEBUG, "Initializing ResourceGenerator"), resourceContext); } catch (UnableToCompleteException e) { return false; } boolean fail = false; // Prepare the ResourceGenerator by telling it all methods that it is // expected to produce. for (JMethod m : generatorMethods) { try { rg.prepare(logger.branch(TreeLogger.DEBUG, "Preparing method " + m.getName()), resourceContext, requirements, m); } catch (UnableToCompleteException e) { fail = true; } } return !fail; } private Map> initAndPrepare( TreeLogger logger, Map, List> taskList, AbstractResourceContext resourceContext, ClientBundleRequirements requirements) throws UnableToCompleteException { // Try to provide as many errors as possible before failing. boolean success = true; Map> toReturn = new LinkedHashMap>(); // Run the ResourceGenerators to generate implementations of the methods for (Map.Entry, List> entry : taskList.entrySet()) { ResourceGenerator rg = instantiateResourceGenerator(logger, entry.getKey()); toReturn.put(rg, entry.getValue()); success &= initAndPrepare(logger, resourceContext, rg, entry.getValue(), requirements); } if (!success) { throw new UnableToCompleteException(); } return toReturn; } /** * Utility method to construct a ResourceGenerator that logs errors. */ private T instantiateResourceGenerator( TreeLogger logger, Class generatorClass) throws UnableToCompleteException { try { return generatorClass.newInstance(); } catch (InstantiationException e) { logger.log(TreeLogger.ERROR, "Unable to initialize ResourceGenerator", e); } catch (IllegalAccessException e) { logger.log(TreeLogger.ERROR, "Unable to instantiate ResourceGenerator. " + "Does it have a public default constructor?", e); } throw new UnableToCompleteException(); } /** * Emits getResources() and getResourceMap() implementations. * * @param sw the output writer * @param taskList the list of methods to map by name * @param resourceMapField field containing the Java String to Resource map */ private void writeMapMethods(SourceWriter sw, Map, List> taskList, JParameterizedType resourceMapType, String resourceMapField) { // Complete the IRB contract sw.println("public ResourcePrototype[] getResources() {"); sw.indent(); sw.println("return new ResourcePrototype[] {"); sw.indent(); for (List methods : taskList.values()) { for (JMethod m : methods) { sw.println(m.getName() + "(), "); } } sw.outdent(); sw.println("};"); sw.outdent(); sw.println("}"); // Map implementation for dev mode. sw.println("public ResourcePrototype getResource(String name) {"); sw.indent(); sw.println("if (GWT.isScript()) {"); sw.indent(); sw.println("return getResourceNative(name);"); sw.outdent(); sw.println("} else {"); sw.indent(); sw.println("if (" + resourceMapField + " == null) {"); sw.indent(); sw.println(resourceMapField + " = new " + resourceMapType.getParameterizedQualifiedSourceName() + "();"); for (List list : taskList.values()) { for (JMethod m : list) { sw.println(resourceMapField + ".put(\"" + m.getName() + "\", " + m.getName() + "());"); } } sw.outdent(); sw.println("}"); sw.println("return resourceMap.get(name);"); sw.outdent(); sw.println("}"); sw.outdent(); sw.println("}"); // Use a switch statement as a fast map for script mode. sw.println("private native ResourcePrototype " + "getResourceNative(String name) /*-{"); sw.indent(); sw.println("switch (name) {"); sw.indent(); for (List list : taskList.values()) { for (JMethod m : list) { sw.println("case '" + m.getName() + "': return this." + m.getJsniSignature() + "();"); } } sw.outdent(); sw.println("}"); sw.println("return null;"); sw.outdent(); sw.println("}-*/;"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy