Java tutorial
/* * 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(); } } }