KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > rebind > ui > ImageBundleGenerator


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.user.rebind.ui;
17
18 import com.google.gwt.core.ext.Generator;
19 import com.google.gwt.core.ext.GeneratorContext;
20 import com.google.gwt.core.ext.TreeLogger;
21 import com.google.gwt.core.ext.UnableToCompleteException;
22 import com.google.gwt.core.ext.typeinfo.JClassType;
23 import com.google.gwt.core.ext.typeinfo.JMethod;
24 import com.google.gwt.core.ext.typeinfo.NotFoundException;
25 import com.google.gwt.core.ext.typeinfo.TypeOracle;
26 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
27 import com.google.gwt.user.rebind.SourceWriter;
28
29 import java.io.PrintWriter JavaDoc;
30 import java.net.URL JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.Map JavaDoc;
34
35 /**
36  * Generates an implementation of a user-defined interface <code>T</code> that
37  * extends {@link com.google.gwt.user.client.ui.ImageBundle}.
38  *
39  * Each method in <code>T</code> must be declared to return
40  * {@link com.google.gwt.user.client.ui.AbstractImagePrototype}, take no
41  * parameters, and optionally specify the metadata tag <code>gwt.resource</code>
42  * as the name of an image that can be found in the classpath. In the absence of
43  * the metatadata tag, the method name with an extension of
44  * <code>.png, .jpg, or .gif</code> defines the name of the image, and the
45  * image file must be located in the same package as <code>T</code>.
46  */

47 public class ImageBundleGenerator extends Generator {
48
49   private static final String JavaDoc ABSTRACTIMAGEPROTOTYPE_QNAME = "com.google.gwt.user.client.ui.AbstractImagePrototype";
50
51   private static final String JavaDoc CLIPPEDIMAGEPROTOTYPE_QNAME = "com.google.gwt.user.client.ui.impl.ClippedImagePrototype";
52
53   private static final String JavaDoc GWT_QNAME = "com.google.gwt.core.client.GWT";
54
55   private static final String JavaDoc[] IMAGE_FILE_EXTENSIONS = {"png", "gif", "jpg"};
56
57   private static final String JavaDoc IMAGEBUNDLE_QNAME = "com.google.gwt.user.client.ui.ImageBundle";
58
59   private static final String JavaDoc METADATA_TAG = "gwt.resource";
60
61   public ImageBundleGenerator() {
62   }
63
64   public String JavaDoc generate(TreeLogger logger, GeneratorContext context,
65       String JavaDoc typeName) throws UnableToCompleteException {
66
67     TypeOracle typeOracle = context.getTypeOracle();
68
69     // Get metadata describing the user's class.
70
JClassType userType = getValidUserType(logger, typeName, typeOracle);
71
72     // Get the methods that correspond to constituent images.
73
JMethod[] imgMethods = getValidImageMethods(logger, userType);
74
75     // Write the new class.
76
String JavaDoc resultName = generateImpl(logger, context, userType, imgMethods);
77
78     // Return the complete name of the generated class.
79
return resultName;
80   }
81
82   private String JavaDoc computeSubclassName(JClassType userType) {
83     String JavaDoc baseName = userType.getName().replace('.', '_');
84     return baseName + "_generatedBundle";
85   }
86
87   private void generateImageMethod(TreeLogger logger,
88       ImageBundleBuilder compositeImage, SourceWriter sw, JMethod method)
89       throws UnableToCompleteException {
90
91     String JavaDoc imageName = getImageUrlFromMetaDataOrMethodName(logger, method);
92     String JavaDoc decl = method.getReadableDeclaration(false, true, true, true, true);
93
94     {
95       sw.indent();
96
97       // Create a singleton that this method can return. There is no need to
98
// create a new instance every time this method is called, since
99
// ClippedImagePrototype is immutable
100

101       ImageBundleBuilder.ImageRect imageRect = compositeImage.getMapping(imageName);
102       String JavaDoc singletonName = method.getName() + "_SINGLETON";
103
104       sw.print("private static final ClippedImagePrototype ");
105       sw.print(singletonName);
106       sw.print(" = new ClippedImagePrototype(IMAGE_BUNDLE_URL, ");
107       sw.print(Integer.toString(imageRect.left));
108       sw.print(", 0, ");
109       sw.print(Integer.toString(imageRect.width));
110       sw.print(", ");
111       sw.print(Integer.toString(imageRect.height));
112       sw.println(");");
113
114       sw.print(decl);
115       sw.println(" {");
116
117       {
118         sw.indent();
119         sw.print("return ");
120         sw.print(singletonName);
121         sw.println(";");
122         sw.outdent();
123       }
124
125       sw.println("}");
126       sw.outdent();
127     }
128   }
129
130   private String JavaDoc generateImpl(TreeLogger logger, GeneratorContext context,
131       JClassType userType, JMethod[] imageMethods)
132       throws UnableToCompleteException {
133     // Compute the package and class names of the generated class.
134
String JavaDoc pkgName = userType.getPackage().getName();
135     String JavaDoc subName = computeSubclassName(userType);
136
137     // Begin writing the generated source.
138
ClassSourceFileComposerFactory f = new ClassSourceFileComposerFactory(
139         pkgName, subName);
140     f.addImport(ABSTRACTIMAGEPROTOTYPE_QNAME);
141     f.addImport(CLIPPEDIMAGEPROTOTYPE_QNAME);
142     f.addImport(GWT_QNAME);
143     f.addImplementedInterface(userType.getQualifiedSourceName());
144
145     PrintWriter JavaDoc pw = context.tryCreate(logger, pkgName, subName);
146     if (pw != null) {
147       SourceWriter sw = f.createSourceWriter(context, pw);
148
149       // Build a compound image from each individual image.
150
ImageBundleBuilder bulder = new ImageBundleBuilder();
151
152       for (int i = 0; i < imageMethods.length; i++) {
153         JMethod method = imageMethods[i];
154         String JavaDoc imageUrl = getImageUrlFromMetaDataOrMethodName(logger, method);
155         assert (imageUrl != null);
156         bulder.assimilate(logger, imageUrl);
157       }
158
159       // Write the compound image into the output directory.
160
String JavaDoc bundledImageUrl = bulder.writeBundledImage(logger, context);
161
162       // Emit a constant for the composite URL. Note that we prepend the
163
// module's base URL so that the module can reference its own resources
164
// independently of the host HTML page.
165
sw.print("private static final String IMAGE_BUNDLE_URL = GWT.getModuleBaseURL() + \"");
166       sw.print(escape(bundledImageUrl));
167       sw.println("\";");
168
169       // Generate an implementation of each method.
170
for (int i = 0; i < imageMethods.length; i++) {
171         JMethod method = imageMethods[i];
172         generateImageMethod(logger, bulder, sw, method);
173       }
174
175       // Finish.
176
sw.commit(logger);
177     }
178
179     return f.getCreatedClassName();
180   }
181
182   // Assume this is only called for valid methods.
183
private String JavaDoc getImageUrlFromMetaDataOrMethodName(TreeLogger logger,
184       JMethod method) throws UnableToCompleteException {
185
186     String JavaDoc[][] md = method.getMetaData(METADATA_TAG);
187
188     if (md.length == 1) {
189       // Metadata is available, so get the image url from the metadata
190
int lastTagIndex = md.length - 1;
191       int lastValueIndex = md[lastTagIndex].length - 1;
192       String JavaDoc imageNameFromMetaData = md[lastTagIndex][lastValueIndex];
193
194       // Make sure the name is either absolute or package-relative.
195
if (imageNameFromMetaData.indexOf("/") == -1) {
196         String JavaDoc pkgName = method.getEnclosingType().getPackage().getName();
197         // This construction handles the default package correctly, too.
198
imageNameFromMetaData = pkgName.replace('.', '/') + "/"
199             + imageNameFromMetaData;
200       }
201
202       // Make sure that the resource exists on the classpath. In the future,
203
// this code will have to be changed if images are loaded from the
204
// source path or public path.
205
URL JavaDoc imageResourceURL = getClass().getClassLoader().getResource(
206           imageNameFromMetaData);
207       if (imageResourceURL == null) {
208         logger.log(
209             TreeLogger.ERROR,
210             "Resource "
211                 + imageNameFromMetaData
212                 + " not found on classpath (is the name specified as Class.getResource() would expect?)",
213             null);
214         throw new UnableToCompleteException();
215       }
216
217       return imageNameFromMetaData;
218     }
219
220     String JavaDoc imageNameFromMethod = null;
221     String JavaDoc packageAndMethodName = method.getEnclosingType().getPackage().getName().replace(
222         '.', '/')
223         + '/' + method.getName();
224     // There is no metadata available, so the image url will be generated from
225
// the method name with an image file extension.
226
for (int i = 0; i < IMAGE_FILE_EXTENSIONS.length; i++) {
227       String JavaDoc possibleImageName = packageAndMethodName + '.'
228           + IMAGE_FILE_EXTENSIONS[i];
229       // Check to see if the resource exists on the classpath for each possible
230
// image file extension. This code will have to be changed if images are
231
// loaded from the source path or the public path.
232
URL JavaDoc imageResourceURL = getClass().getClassLoader().getResource(
233           possibleImageName);
234       if (imageResourceURL != null) {
235         imageNameFromMethod = possibleImageName;
236         break;
237       }
238     }
239
240     if (imageNameFromMethod == null) {
241
242       StringBuffer JavaDoc errorStringBuf = new StringBuffer JavaDoc();
243       for (int i = 0; i < IMAGE_FILE_EXTENSIONS.length; i++) {
244
245         errorStringBuf.append(IMAGE_FILE_EXTENSIONS[i]);
246
247         if (i != IMAGE_FILE_EXTENSIONS.length - 1) {
248           errorStringBuf.append(", ");
249         }
250       }
251
252       logger.log(
253           TreeLogger.ERROR,
254           "Resource "
255               + packageAndMethodName
256               + ".("
257               + errorStringBuf.toString()
258               + ") not found on classpath (is the name specified as Class.getResource() would expect?)",
259           null);
260       throw new UnableToCompleteException();
261     }
262
263     return imageNameFromMethod;
264   }
265
266   private JMethod[] getValidImageMethods(TreeLogger logger, JClassType userType)
267       throws UnableToCompleteException {
268
269     logger = logger.branch(TreeLogger.TRACE, "Analyzing methods on "
270         + userType.getQualifiedSourceName(), null);
271
272     final JClassType imageClass;
273     try {
274       imageClass = userType.getOracle().getType(ABSTRACTIMAGEPROTOTYPE_QNAME);
275     } catch (NotFoundException e) {
276       logger.log(TreeLogger.ERROR, "GWT " + ABSTRACTIMAGEPROTOTYPE_QNAME
277           + "class is not available", e);
278       throw new UnableToCompleteException();
279     }
280
281     Map JavaDoc rejectedMethodsAndWhy = new HashMap JavaDoc();
282     JMethod[] leafMethods = userType.getOverridableMethods();
283     for (int i = 0; i < leafMethods.length; i++) {
284       JMethod method = leafMethods[i];
285
286       if (method.getReturnType() != imageClass) {
287         rejectedMethodsAndWhy.put(method, "Return type must be "
288             + ABSTRACTIMAGEPROTOTYPE_QNAME);
289         continue;
290       }
291
292       if (method.getParameters().length > 0) {
293         rejectedMethodsAndWhy.put(method, "Method cannot take parameters");
294         continue;
295       }
296
297       String JavaDoc[][] md = method.getMetaData(METADATA_TAG);
298       if ((md.length > 1) || (md.length == 1 && md[0].length != 1)) {
299         rejectedMethodsAndWhy.put(
300             method,
301             "Expecting either no metadata tags, or one metadata tag of the form '@gwt.resource <resource-name>'");
302       }
303     }
304
305     // Make sure there aren't any invalid methods.
306
if (!rejectedMethodsAndWhy.isEmpty()) {
307       logger = logger.branch(TreeLogger.ERROR,
308           "The following methods are invalid on an image bundle:", null);
309       for (Iterator JavaDoc iter = rejectedMethodsAndWhy.entrySet().iterator(); iter.hasNext();) {
310         Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
311         JMethod badMethod = (JMethod) entry.getKey();
312         String JavaDoc reason = (String JavaDoc) entry.getValue();
313         TreeLogger branch = logger.branch(TreeLogger.ERROR,
314             badMethod.getReadableDeclaration(), null);
315         branch.log(TreeLogger.ERROR, reason, null);
316       }
317       throw new UnableToCompleteException();
318     }
319
320     return leafMethods;
321   }
322
323   private JClassType getValidUserType(TreeLogger logger, String JavaDoc typeName,
324       TypeOracle typeOracle) throws UnableToCompleteException {
325     try {
326       // Get the type that the user is introducing.
327
JClassType userType = typeOracle.getType(typeName);
328
329       // Get the type this generator is designed to support.
330
JClassType magicType = typeOracle.findType(IMAGEBUNDLE_QNAME);
331
332       // Ensure it's an interface.
333
if (userType.isInterface() == null) {
334         logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName()
335             + " must be an interface", null);
336         throw new UnableToCompleteException();
337       }
338
339       // Ensure proper derivation.
340
if (!userType.isAssignableTo(magicType)) {
341         logger.log(TreeLogger.ERROR, userType.getQualifiedSourceName()
342             + " must be assignable to " + magicType.getQualifiedSourceName(),
343             null);
344         throw new UnableToCompleteException();
345       }
346
347       return userType;
348
349     } catch (NotFoundException e) {
350       logger.log(TreeLogger.ERROR, "Unable to find required type(s)", e);
351       throw new UnableToCompleteException();
352     }
353   }
354
355 }
356
Popular Tags