/* * 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.sample.showcase.generator; 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.sample.showcase.client.ContentWidget; import com.google.gwt.sample.showcase.client.Showcase; import com.google.gwt.sample.showcase.client.ShowcaseConstants; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseData; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseRaw; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseSource; import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseStyle; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * Generate the source code, css styles, and raw source used in the Showcase * examples. */ public class ShowcaseGenerator extends Generator { /** * The paths to the CSS style sheets used in Showcase. The paths are relative * to the root path of the {@link ClassLoader}. The variable $THEME will be * replaced by the current theme. The variable $RTL will be replaced by "_rtl" * for RTL locales. */ private static final String[] SRC_CSS = { "com/google/gwt/user/theme/$THEME/public/gwt/$THEME/$THEME$RTL.css", "com/google/gwt/sample/showcase/client/Showcase.css"}; /** * The class loader used to get resources. */ private ClassLoader classLoader = null; /** * The generator context. */ private GeneratorContext context = null; /** * The {@link TreeLogger} used to log messages. */ private TreeLogger logger = null; /** * The set of raw files that have already been generated. Raw files can be * reused by different examples, but we only generate them once. */ private Set<String> rawFiles = new HashSet<String>(); @Override public String generate( TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException { this.logger = logger; this.context = context; this.classLoader = Thread.currentThread().getContextClassLoader(); // Only generate files on the first permutation if (!isFirstPass()) { return null; } // Get the Showcase ContentWidget subtypes to examine JClassType cwType = null; try { cwType = context.getTypeOracle().getType(ContentWidget.class.getName()); } catch (NotFoundException e) { logger.log(TreeLogger.ERROR, "Cannot find ContentWidget class", e); throw new UnableToCompleteException(); } JClassType[] types = cwType.getSubtypes(); // Generate the source and raw source files for (JClassType type : types) { generateRawFiles(type); generateSourceFiles(type); } // Generate the CSS source files String[] themes = new String[]{Showcase.THEME}; for (String theme : themes) { String styleDefsLTR = getStyleDefinitions(theme, false); String styleDefsRTL = getStyleDefinitions(theme, true); String outDirLTR = ShowcaseConstants.DST_SOURCE_STYLE + theme + "/"; String outDirRTL = ShowcaseConstants.DST_SOURCE_STYLE + theme + "_rtl/"; for (JClassType type : types) { generateStyleFiles(type, styleDefsLTR, outDirLTR); generateStyleFiles(type, styleDefsRTL, outDirRTL); } } return null; } /** * Set the full contents of a resource in the public directory. * * @param partialPath the path to the file relative to the public directory * @param contents the file contents */ private void createPublicResource(String partialPath, String contents) throws UnableToCompleteException { try { OutputStream outStream = context.tryCreateResource(logger, partialPath); if (outStream == null) { String message = "Attempting to generate duplicate public resource: " + partialPath + ".\nAll generated source files must have unique names."; logger.log(TreeLogger.ERROR, message); throw new UnableToCompleteException(); } outStream.write(contents.getBytes()); context.commitResource(logger, outStream); } catch (IOException e) { logger.log(TreeLogger.ERROR, "Error writing file: " + partialPath, e); throw new UnableToCompleteException(); } } /** * Generate the raw files used by a {@link ContentWidget}. * * @param type the {@link ContentWidget} subclass */ private void generateRawFiles(JClassType type) throws UnableToCompleteException { // Look for annotation if (!type.isAnnotationPresent(ShowcaseRaw.class)) { return; } // Get the package info String pkgName = type.getPackage().getName(); String pkgPath = pkgName.replace('.', '/') + "/"; // Generate each raw source file String[] filenames = type.getAnnotation(ShowcaseRaw.class).value(); for (String filename : filenames) { // Check if the file has already been generated. String path = pkgName + filename; if (rawFiles.contains(path)) { continue; } rawFiles.add(path); // Get the file contents String fileContents = getResourceContents(pkgPath + filename); // Make the source pretty fileContents = fileContents.replace("<", "<"); fileContents = fileContents.replace(">", ">"); fileContents = fileContents.replace("* \n */\n", "*/\n"); fileContents = "<pre>" + fileContents + "</pre>"; // Save the raw source in the public directory String dstPath = ShowcaseConstants.DST_SOURCE_RAW + filename + ".html"; createPublicResource(dstPath, fileContents); } } /** * Generate the formatted source code for a {@link ContentWidget}. * * @param type the {@link ContentWidget} subclass */ private void generateSourceFiles(JClassType type) throws UnableToCompleteException { // Get the file contents String filename = type.getQualifiedSourceName().replace('.', '/') + ".java"; String fileContents = getResourceContents(filename); // Get each data code block String formattedSource = ""; String dataTag = "@" + ShowcaseData.class.getSimpleName(); String sourceTag = "@" + ShowcaseSource.class.getSimpleName(); int dataTagIndex = fileContents.indexOf(dataTag); int srcTagIndex = fileContents.indexOf(sourceTag); while (dataTagIndex >= 0 || srcTagIndex >= 0) { if (dataTagIndex >= 0 && (dataTagIndex < srcTagIndex || srcTagIndex < 0)) { // Get the boundaries of a DATA tag int beginIndex = fileContents.lastIndexOf(" /*", dataTagIndex); int beginTagIndex = fileContents.lastIndexOf("\n", dataTagIndex) + 1; int endTagIndex = fileContents.indexOf("\n", dataTagIndex) + 1; int endIndex = fileContents.indexOf(";", beginIndex) + 1; // Add to the formatted source String srcData = fileContents.substring(beginIndex, beginTagIndex) + fileContents.substring(endTagIndex, endIndex); formattedSource += srcData + "\n\n"; // Get next tag dataTagIndex = fileContents.indexOf(dataTag, endIndex + 1); } else { // Get the boundaries of a SRC tag int beginIndex = fileContents.lastIndexOf("/*", srcTagIndex) - 2; int beginTagIndex = fileContents.lastIndexOf("\n", srcTagIndex) + 1; int endTagIndex = fileContents.indexOf("\n", srcTagIndex) + 1; int endIndex = fileContents.indexOf("\n }", beginIndex) + 4; // Add to the formatted source String srcCode = fileContents.substring(beginIndex, beginTagIndex) + fileContents.substring(endTagIndex, endIndex); formattedSource += srcCode + "\n\n"; // Get the next tag srcTagIndex = fileContents.indexOf(sourceTag, endIndex + 1); } } // Make the source pretty formattedSource = formattedSource.replace("<", "<"); formattedSource = formattedSource.replace(">", ">"); formattedSource = formattedSource.replace("* \n */\n", "*/\n"); formattedSource = "<pre>" + formattedSource + "</pre>"; // Save the source code to a file String dstPath = ShowcaseConstants.DST_SOURCE_EXAMPLE + type.getSimpleSourceName() + ".html"; createPublicResource(dstPath, formattedSource); } /** * Generate the styles used by a {@link ContentWidget}. * * @param type the {@link ContentWidget} subclass * @param styleDefs the concatenated style definitions * @param outDir the output directory */ private void generateStyleFiles( JClassType type, String styleDefs, String outDir) throws UnableToCompleteException { // Look for annotation if (!type.isAnnotationPresent(ShowcaseStyle.class)) { return; } // Generate a style file for each theme/RTL mode pair String[] prefixes = type.getAnnotation(ShowcaseStyle.class).value(); Map<String, String> matched = new LinkedHashMap<String, String>(); for (String prefix : prefixes) { // Get the start location of the style code in the source file boolean foundStyle = false; int start = styleDefs.indexOf("\n" + prefix); while (start >= 0) { // Get the cssContents string name pattern int end = styleDefs.indexOf("{", start); String matchedName = styleDefs.substring(start, end).trim(); // Get the style code end = styleDefs.indexOf("}", start) + 1; String styleDef = "<pre>" + styleDefs.substring(start, end) + "</pre>"; matched.put(matchedName, styleDef); // Goto the next match foundStyle = true; start = styleDefs.indexOf("\n" + prefix, end); } // No style exists if (!foundStyle) { matched.put(prefix, "<pre>" + prefix + " {\n}</pre>"); } } // Combine all of the styles into one formatted string String formattedStyle = ""; for (String styleDef : matched.values()) { formattedStyle += styleDef; } // Save the raw source in the public directory String dstPath = outDir + type.getSimpleSourceName() + ".html"; createPublicResource(dstPath, formattedStyle); } /** * Get the full contents of a resource. * * @param path the path to the resource * @return the contents of the resource */ private String getResourceContents(String path) throws UnableToCompleteException { InputStream in = classLoader.getResourceAsStream(path); if (in == null) { logger.log(TreeLogger.ERROR, "Resource not found: " + path); throw new UnableToCompleteException(); } StringBuffer fileContentsBuf = new StringBuffer(); BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(in)); String temp; while ((temp = br.readLine()) != null) { fileContentsBuf.append(temp).append('\n'); } } catch (IOException e) { logger.log(TreeLogger.ERROR, "Cannot read resource", e); throw new UnableToCompleteException(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } } // Return the file contents as a string return fileContentsBuf.toString(); } /** * Load the contents of all CSS files used in the Showcase for a given * theme/RTL mode, concatenated into one string. * * @param theme the style theme * @param isRTL true if RTL mode * @return the concatenated styles */ private String getStyleDefinitions(String theme, boolean isRTL) throws UnableToCompleteException { String cssContents = ""; for (String path : SRC_CSS) { path = path.replace("$THEME", theme); if (isRTL) { path = path.replace("$RTL", "_rtl"); } else { path = path.replace("$RTL", ""); } cssContents += getResourceContents(path) + "\n\n"; } return cssContents; } /** * Ensure that we only generate files once by creating a placeholder file, * then looking for it on subsequent generates. * * @return true if this is the first pass, false if not */ private boolean isFirstPass() { String placeholder = ShowcaseConstants.DST_SOURCE + "generated"; try { OutputStream outStream = context.tryCreateResource(logger, placeholder); if (outStream == null) { return false; } else { context.commitResource(logger, outStream); } } catch (UnableToCompleteException e) { logger.log(TreeLogger.ERROR, "Unable to generate", e); return false; } return true; } }