blob: 019347ce74adfbb8ec865015087e4ee4dc0d59e9 [file] [log] [blame]
/*
* 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("<", "&lt;");
fileContents = fileContents.replace(">", "&gt;");
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("<", "&lt;");
formattedSource = formattedSource.replace(">", "&gt;");
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;
}
}