blob: eba228a8db39650f2770a090603aa1ea68dc74df [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.user.tools;
import com.google.gwt.dev.About;
import com.google.gwt.dev.ArgProcessorBase;
import com.google.gwt.dev.Compiler;
import com.google.gwt.dev.DevMode;
import com.google.gwt.dev.GwtVersion;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.collect.HashSet;
import com.google.gwt.user.tools.util.ArgHandlerIgnore;
import com.google.gwt.user.tools.util.ArgHandlerOverwrite;
import com.google.gwt.user.tools.util.CreatorUtilities;
import com.google.gwt.util.tools.ArgHandlerExtra;
import com.google.gwt.util.tools.ArgHandlerFlag;
import com.google.gwt.util.tools.ArgHandlerOutDir;
import com.google.gwt.util.tools.ArgHandlerString;
import com.google.gwt.util.tools.Utility;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Creates a GWT application skeleton.
*/
public final class WebAppCreator {
class ArgProcessor extends ArgProcessorBase {
private final class ArgHandlerOutDirExtension extends ArgHandlerOutDir {
@Override
public void setDir(File dir) {
outDir = dir;
}
}
public ArgProcessor() {
registerHandler(new ArgHandlerOverwriteExtension());
registerHandler(new ArgHandlerIgnoreExtension());
registerHandler(new ArgHandlerTemplates());
registerHandler(new ArgHandlerModuleName());
registerHandler(new ArgHandlerOutDirExtension());
registerHandler(new ArgHandlerCreateEclipseProject());
registerHandler(new ArgHandlerCreateEclipseProjectOnly());
registerHandler(new ArgHandlerJUnitPath());
registerHandler(new ArgHandlerCreateMavenProject());
registerHandler(new ArgHandlerCreateAntFile());
}
@Override
protected String getName() {
return WebAppCreator.class.getName();
}
}
private final class ArgHandlerIgnoreExtension extends ArgHandlerIgnore {
@Override
public boolean setFlag(boolean value) {
if (value && overwrite) {
System.err.println("-ignore cannot be used with -overwrite");
return false;
}
ignore = value;
return true;
}
@Override
public boolean getDefaultValue() {
return ignore;
}
}
private final class ArgHandlerJUnitPath extends ArgHandlerString {
@Override
public String[] getDefaultArgs() {
return null;
}
@Override
public String getPurpose() {
return "Specifies the path to your junit.jar (optional)";
}
@Override
public String getTag() {
return "-junit";
}
@Override
public String[] getTagArgs() {
return new String[] {"pathToJUnitJar"};
}
@Override
public boolean isRequired() {
return false;
}
@Override
public boolean setString(String str) {
File f = new File(str);
if (!f.exists() || !f.isFile()) {
System.err.println("File not found: " + str);
return false;
}
junitPath = str;
return true;
}
}
private final class ArgHandlerCreateMavenProject extends ArgHandlerFlag {
@Override
public String getPurposeSnippet() {
return "DEPRECATED: Create a maven2 project structure and pom file (default disabled). "
+ "Equivalent to specifying 'maven' in the list of templates.";
}
@Override
public String getLabel() {
return "maven";
}
@Override
public boolean setFlag(boolean value) {
if (value && onlyEclipse) {
System.err.println("-maven and -XonlyEclipse cannot be used at the same time.");
return false;
}
if (value) {
templates.add("maven");
} else {
templates.remove("maven");
}
return true;
}
@Override
public boolean getDefaultValue() {
return templates.contains("maven");
}
}
private final class ArgHandlerModuleName extends ArgHandlerExtra {
@Override
public boolean addExtraArg(String arg) {
if (moduleName != null) {
System.err.println("Too many arguments.");
return false;
}
if (!CreatorUtilities.isValidModuleName(arg)) {
System.err.println("'"
+ arg
+ "' does not appear to be a valid fully-qualified Java class name.");
return false;
}
moduleName = arg;
return true;
}
@Override
public String getPurpose() {
return "The name of the module to create (e.g. com.example.myapp.MyApp)";
}
@Override
public String[] getTagArgs() {
return new String[] {"moduleName"};
}
@Override
public boolean isRequired() {
return true;
}
}
private final class ArgHandlerCreateAntFile extends ArgHandlerFlag {
@Override
public String getPurposeSnippet() {
return "DEPRECATED: Create an ant configuration file. "
+ "Equivalent to specifying 'ant' in the list of templates.";
}
@Override
public String getLabel() {
return "ant";
}
@Override
public boolean setFlag(final boolean value) {
argProcessingToDos.add(new Procrastinator() {
@Override
public void stopProcratinating() {
if (!value) {
if (templates.contains("maven")) {
System.err.println("-maven and -noant are redundant. Continuing.");
}
if (templates.contains("ant")) {
System.err.println("Removing ant template from generated output.");
templates.remove("ant");
}
} else {
if (!templates.contains("ant")) {
System.err.println("Adding ant template to generated output.");
templates.add("ant");
}
}
}
});
return true;
}
@Override
public boolean getDefaultValue() {
return templates.contains("ant");
}
}
private final class ArgHandlerCreateEclipseProject extends ArgHandlerFlag {
public ArgHandlerCreateEclipseProject() {
addTagValue("-XnoEclipse", false);
}
@Override
public String getPurposeSnippet() {
return "DEPRECATED: Generate eclipse files. Equivalent to specifying "
+ "'eclipse' in the list of templates.";
}
@Override
public String getLabel() {
return "createEclipseProject";
}
@Override
public boolean isUndocumented() {
return true;
}
@Override
public boolean setFlag(boolean value) {
if (!value) {
if (onlyEclipse) {
System.err.println("-XonlyEclipse and -XnoEclipse cannot be used at the same time.");
return false;
}
if (!templates.contains("maven")) {
System.err.println("-maven and -XnoEclipse are redundant. Continuing.");
}
noEclipse = true;
argProcessingToDos.add(new Procrastinator() {
@Override
public void stopProcratinating() {
if (noEclipse && templates.contains("eclipse")) {
System.err.println("Removing eclipse template from generated output.");
templates.remove("eclipse");
}
}
});
} else {
noEclipse = false;
}
return true;
}
@Override
public boolean isExperimental() {
return true;
}
@Override
public boolean getDefaultValue() {
return !noEclipse;
}
}
private final class ArgHandlerCreateEclipseProjectOnly extends ArgHandlerFlag {
public ArgHandlerCreateEclipseProjectOnly() {
addTagValue("-XonlyEclipse", true);
}
@Override
public String getPurposeSnippet() {
return "DEPRECATED: Generate only eclipse files. "
+ "Equivalent to only specifying 'eclipse' in the list of templates.";
}
@Override
public String getLabel() {
return "createEclipseProjectOnly";
}
@Override
public boolean isUndocumented() {
return true;
}
@Override
public boolean setFlag(boolean value) {
if (value) {
if (noEclipse) {
System.err.println("-XonlyEclipse and -XnoEclipse cannot be used at the same time.");
return false;
}
if (templates.contains("maven")) {
System.err.println("-maven and -XonlyEclipse cannot be used at the same time.");
return false;
}
onlyEclipse = true;
argProcessingToDos.add(new Procrastinator() {
@Override
public void stopProcratinating() {
if (onlyEclipse) {
System.err.println("Removing all templates but 'eclipse' from generated output.");
templates.clear();
templates.add("eclipse");
}
}
});
} else {
onlyEclipse = false;
}
return true;
}
@Override
public boolean isExperimental() {
return true;
}
@Override
public boolean getDefaultValue() {
return onlyEclipse;
}
}
private final class ArgHandlerOverwriteExtension extends ArgHandlerOverwrite {
@Override
public boolean setFlag(boolean value) {
if (value && ignore) {
System.err.println("-overwrite cannot be used with -ignore");
return false;
}
overwrite = value;
return true;
}
@Override
public boolean getDefaultValue() {
return overwrite;
}
}
private final class ArgHandlerTemplates extends ArgHandlerString {
@Override
public String[] getDefaultArgs() {
return new String[] {getTag(), "sample, ant, eclipse, readme"};
}
@Override
public String getPurpose() {
return "Specifies the template(s) to use (comma separeted)."
+ " Defaults to 'sample,ant,eclipse,readme'";
}
@Override
public String getTag() {
return "-templates";
}
@Override
public String[] getTagArgs() {
return new String[] {"template1,template2,..."};
}
@Override
public boolean isRequired() {
return false;
}
@Override
public boolean setString(String str) {
String[] templateList = str.split(", *");
for (String template : templateList) {
URL url = getTemplateRoot(template);
if (url == null) {
System.err.println("Template not found: " + template);
return false;
}
templates.add(template);
}
return true;
}
}
private static final class FileCreator {
private final File destDir;
private final String destName;
private final boolean isBinary;
private final String sourceName;
public FileCreator(File destDir, String destName, String sourceName, boolean isBinary) {
this.destDir = destDir;
this.sourceName = sourceName;
this.destName = destName;
this.isBinary = isBinary;
}
@Override
public String toString() {
return "FileCreator [destDir=" + destDir + ", destName=" + destName
+ ", sourceName=" + sourceName + ", isBinary=" + isBinary + "]";
}
}
private abstract static class Procrastinator {
public abstract void stopProcratinating();
}
public static void main(String[] args) {
System.exit(doMain(args) ? 0 : 1);
}
protected static boolean doMain(String... args) {
WebAppCreator creator = new WebAppCreator();
ArgProcessor argProcessor = creator.new ArgProcessor();
if (argProcessor.processArgs(args)) {
return creator.run();
}
return false;
}
private static String getTemplateBasePath(String template) {
return "/" + WebAppCreator.class.getPackage().getName().replace('.', '/') + "/templates/"
+ template + "/";
}
private static URL getTemplateRoot(String template) {
return WebAppCreator.class.getResource("templates/" + template);
}
private static String replaceFileName(Map<String, String> replacements, String name) {
String replacedContents = name;
Set<Entry<String, String>> entries = replacements.entrySet();
for (Iterator<Entry<String, String>> iter = entries.iterator(); iter.hasNext();) {
Entry<String, String> entry = iter.next();
String replaceThis = entry.getKey();
replaceThis = replaceThis.replaceAll("@(.*)", "_$1_");
String withThis = entry.getValue();
withThis = withThis.replaceAll("\\\\", "\\\\\\\\");
withThis = withThis.replaceAll("\\$", "\\\\\\$");
replacedContents = replacedContents.replaceAll(replaceThis, withThis);
}
return replacedContents;
}
private ArrayList<Procrastinator> argProcessingToDos = new ArrayList<Procrastinator>();
private boolean ignore = false;
private String junitPath = null;
private String moduleName;
private boolean noEclipse;
private boolean onlyEclipse;
private File outDir;
private boolean overwrite = false;
private HashSet<String> templates = new HashSet<String>();
public List<FileCreator> getFiles(Map<String, String> replacements)
throws IOException, WebAppCreatorException {
List<FileCreator> files = new ArrayList<FileCreator>();
Utility.getDirectory(outDir.getPath(), true);
for (String template : templates) {
URL templateUrl = getTemplateRoot(template);
if ("jar".equals(templateUrl.getProtocol())) {
files.addAll(getTemplateFilesFromZip(replacements, templateUrl, outDir));
} else if ("file".equals(templateUrl.getProtocol())) {
File templateRoot = new File(templateUrl.getPath());
files.addAll(getTemplateFiles(replacements, templateRoot, outDir,
getTemplateBasePath(template)));
} else {
throw new WebAppCreatorException("Cannot handle template '" + template + "' protocol: "
+ templateUrl.getProtocol());
}
}
return files;
}
public Map<String, String> getReplacements(String installPath, String theModuleName) {
// GWT libraries
String gwtUserPath = installPath + '/' + "gwt-user.jar";
String gwtDevPath = installPath + '/' + "gwt-dev.jar";
String gwtCodeServerPath = installPath + '/' + "gwt-codeserver.jar";
String gwtValidationPath = installPath + '/' + "validation-api-1.0.0.GA.jar";
String gwtValidationSourcesPath = installPath + '/' + "validation-api-1.0.0.GA-sources.jar";
// Generate a DTD reference.
String gwtModuleDtd = "\n<!-- Custom-built GWT. You probably want to uncomment the DOCTYPE "
+ "and point it to your GWT checkout"
+ "\n<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD Google Web Toolkit "
+ About.getGwtVersionNum()
+ "//EN\" \"file:///path/to/gwt/checkout/distro-source/core/src/gwt-module.dtd\">"
+ "\n-->";
GwtVersion gwtVersion = About.getGwtVersionObject();
if (!gwtVersion.isNoNagVersion() && !gwtVersion.equals(new GwtVersion(null))) {
gwtModuleDtd = "\n<!--"
+ "\n When updating your version of GWT, you should also update this DTD reference,"
+ "\n so that your app can take advantage of the latest GWT module capabilities."
+ "\n-->"
+ "\n<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD Google Web Toolkit "
+ About.getGwtVersionNum() + "//EN\""
+ "\n \"http://gwtproject.org/doctype/" + About.getGwtVersionNum() + "/gwt-module.dtd\">";
}
// Compute module package and name.
int pos = theModuleName.lastIndexOf('.');
String modulePackageName = theModuleName.substring(0, pos);
String moduleShortName = theModuleName.substring(pos + 1);
// Create a map of replacements
Map<String, String> replacements = new HashMap<String, String>();
// Compute module name and directories
String srcFolder = templates.contains("maven") ? "src/main/java" : "src";
String testFolder = templates.contains("maven") ? "src/test/java" : "test";
String warFolder = templates.contains("maven") ? "src/main/webapp" : "war";
{
// pro-actively let ant user know that this script can also create tests.
if (junitPath == null) {
System.err.println("Not creating tests because -junit argument was not specified.\n");
}
String testTargetsBegin = "";
String testTargetsEnd = "";
String junitJarPath = junitPath;
String eclipseTestDir = "";
if (junitPath != null) {
eclipseTestDir = "\n <classpathentry kind=\"src\" path=\""
+ testFolder + "\"/>";
}
if (junitPath == null) {
testTargetsBegin = "\n<!--"
+ "\n"
+ "Test targets suppressed because -junit argument was not specified when running"
+ " webAppCreator.\n";
testTargetsEnd = "-->\n";
junitJarPath = "path_to_the_junit_jar";
}
replacements.put("@testTargetsBegin", testTargetsBegin);
replacements.put("@testTargetsEnd", testTargetsEnd);
replacements.put("@junitJar", junitJarPath);
replacements.put("@eclipseTestDir", eclipseTestDir);
}
replacements.put("@srcFolder", srcFolder);
replacements.put("@testFolder", testFolder);
replacements.put("@warFolder", warFolder);
replacements.put("@moduleShortName", moduleShortName);
replacements.put("@modulePackageName", modulePackageName);
replacements.put("@moduleFolder", modulePackageName.replace('.', '/'));
replacements.put("@moduleName", theModuleName);
replacements.put("@clientPackage", modulePackageName + ".client");
replacements.put("@serverPackage", modulePackageName + ".server");
replacements.put("@sharedPackage", modulePackageName + ".shared");
replacements.put("@gwtSdk", installPath);
replacements.put("@gwtUserPath", gwtUserPath);
replacements.put("@gwtDevPath", gwtDevPath);
replacements.put("@gwtCodeServerPath", gwtCodeServerPath);
replacements.put("@gwtValidationPath", gwtValidationPath);
replacements.put("@gwtValidationSourcesPath", gwtValidationSourcesPath);
replacements.put("@gwtVersion", About.getGwtVersionNum());
replacements.put("@gwtModuleDtd", gwtModuleDtd);
replacements.put("@shellClass", DevMode.class.getName());
replacements.put("@compileClass", Compiler.class.getName());
replacements.put("@startupUrl", moduleShortName + ".html");
replacements.put("@renameTo", moduleShortName.toLowerCase(Locale.ROOT));
replacements.put("@moduleNameJUnit", theModuleName + "JUnit");
// Add command to copy gwt-servlet-deps.jar into libs, unless this is a
// maven project. Maven projects should include libs as maven dependencies.
String copyServletDeps = "";
copyServletDeps = "<copy todir=\"" + warFolder + "/WEB-INF/lib\" "
+ "file=\"${gwt.sdk}/gwt-servlet-deps.jar\" />";
replacements.put("@copyServletDeps", copyServletDeps);
// Collect the list of server libs to include on the eclipse classpath.
File libDirectory = new File(outDir + "/" + warFolder + "/WEB-INF/lib");
StringBuilder serverLibs = new StringBuilder();
if (libDirectory.exists()) {
for (File file : libDirectory.listFiles()) {
if (file.getName().toLowerCase(Locale.ROOT).endsWith(".jar")) {
serverLibs.append(" <classpathentry kind=\"lib\" path=\"" + warFolder + "/WEB-INF/lib/");
serverLibs.append(file.getName());
serverLibs.append("\"/>\n");
}
}
}
replacements.put("@serverClasspathLibs", serverLibs.toString());
String antEclipseRule = "";
if (!templates.contains("eclipse")) {
/*
* Generate a rule into the build file that allows for the generation of
* an eclipse project later on. This is primarily for distro samples. This
* is a quick and dirty way to inject a build rule, but it works.
*/
antEclipseRule = "\n\n"
+ " <target name=\"eclipse.generate\" depends=\"libs\" description"
+ "=\"Generate eclipse project\">\n"
+ " <java failonerror=\"true\" fork=\"true\" classname=\""
+ this.getClass().getName() + "\">\n" + " <classpath>\n"
+ " <path refid=\"project.class.path\"/>\n"
+ " </classpath>\n" + " <arg value=\"-XonlyEclipse\"/>\n"
+ " <arg value=\"-ignore\"/>\n" + " <arg value=\""
+ theModuleName + "\"/>\n" + " </java>\n" + " </target>";
} else {
antEclipseRule = "";
}
replacements.put("@antEclipseRule", antEclipseRule);
return replacements;
}
/**
* Create the sample app.
*
* @throws IOException if any disk write fails
* @throws WebAppCreatorException if any tag expansion of template processing fails
* @deprecated as of GWT 2.1, replaced by {@link #doRun(String)}
*/
@Deprecated
protected void doRun() throws IOException, WebAppCreatorException {
doRun(Utility.getInstallPath());
}
/**
* Create the sample app.
*
* @param installPath directory containing GWT libraries
* @throws IOException if any disk write fails
* @throws WebAppCreatorException if any tag expansion of template processing fails
*/
protected void doRun(String installPath) throws IOException, WebAppCreatorException {
for (Procrastinator toDo : argProcessingToDos) {
toDo.stopProcratinating();
}
// Maven projects do not need Ant nor Eclipse files
if (templates.contains("maven")) {
junitPath = "junit-provided-by-maven";
if (templates.contains("eclipse")) {
System.err.println("'maven' template is being generated removing 'eclipse'"
+ " template from generated output.");
templates.remove("eclipse");
}
if (templates.contains("ant")) {
System.err.println("'maven' template is being generated removing 'ant'"
+ " template from generated output.");
templates.remove("ant");
}
}
// Eagerly look for test templates
if (junitPath != null) {
ArrayList<String> testTemplates = new ArrayList<String>();
for (String template : templates) {
String testTemplateName = "_" + template + "-test";
if (getTemplateRoot(testTemplateName) != null) {
testTemplates.add(testTemplateName);
}
}
templates.addAll(testTemplates);
}
System.out.println("Generating from templates: " + templates);
// Generate string replacements
Map<String, String> replacements = getReplacements(installPath, moduleName);
// Create a list with the files to create
List<FileCreator> files = getFiles(replacements);
// copy source files, replacing the content as needed
for (FileCreator fileCreator : files) {
URL url = WebAppCreator.class.getResource(fileCreator.sourceName);
if (url == null) {
throw new WebAppCreatorException("Could not find " + fileCreator.sourceName);
}
File file = Utility.createNormalFile(fileCreator.destDir,
fileCreator.destName, overwrite, ignore);
if (file == null) {
continue;
}
if (fileCreator.isBinary) {
byte[] data = Util.readURLAsBytes(url);
Utility.writeTemplateBinaryFile(file, data);
} else {
String data = Util.readURLAsString(url);
Utility.writeTemplateFile(file, data, replacements);
}
}
}
protected boolean run() {
try {
doRun(Utility.getInstallPath());
return true;
} catch (IOException e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
return false;
} catch (WebAppCreatorException e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
return false;
}
}
private Collection<? extends FileCreator> getTemplateFiles(
Map<String, String> replacements, File srcDirectory, File destDirectory,
String templateClassRoot) throws IOException {
List<FileCreator> files = new ArrayList<FileCreator>();
File[] filesInDir = srcDirectory.listFiles();
for (File srcFile : filesInDir) {
String replacedName = replaceFileName(replacements, srcFile.getName());
if (srcFile.isDirectory()) {
File newDirectory = Utility.getDirectory(destDirectory, replacedName, true);
files.addAll(getTemplateFiles(replacements, srcFile, newDirectory, templateClassRoot
+ srcFile.getName() + "/"));
} else if (srcFile.getName().endsWith("src")) {
String srcFilename = templateClassRoot + srcFile.getName();
String destName = replacedName.substring(0, replacedName.length() - 3);
files.add(new FileCreator(destDirectory, destName, srcFilename, false));
} else if (srcFile.getName().endsWith("bin")) {
String srcFilename = templateClassRoot + srcFile.getName();
String destName = replacedName.substring(0, replacedName.length() - 3);
files.add(new FileCreator(destDirectory, destName, srcFilename, true));
} // else ... ignore everything not a directory, "*src" nor "*bin"
}
return files;
}
private Collection<? extends FileCreator> getTemplateFilesFromZip(
Map<String, String> replacements, URL zipUrl, File destDirectory)
throws WebAppCreatorException, IOException {
String zipPath = zipUrl.getFile();
int separator = zipPath.indexOf('!');
if (separator == -1) {
throw new WebAppCreatorException("Error opening template zip file. '!' not found in "
+ zipUrl);
}
String zipFilename = zipPath.substring(0, separator);
String templateDirName = zipPath.substring(separator + 2);
ZipFile zipFile;
try {
zipFile = new ZipFile(new File(new URI(zipFilename)));
} catch (URISyntaxException e) {
throw new WebAppCreatorException("Could not open Zip file. Malformed URI", e);
}
Enumeration<? extends ZipEntry> allZipEntries = zipFile.entries();
ArrayList<FileCreator> templateEntries = new ArrayList<FileCreator>();
while (allZipEntries.hasMoreElements()) {
ZipEntry entry = allZipEntries.nextElement();
String fullName = entry.getName();
if (fullName.startsWith(templateDirName + "/")) {
String relativeName = fullName.substring(templateDirName.length());
String replacedName = replaceFileName(replacements, relativeName);
if (entry.isDirectory()) {
Utility.getDirectory(destDirectory, replacedName, true);
} else if (fullName.endsWith("src")) {
// remove the src suffix
String destName = replacedName.substring(0, replacedName.length() - 3);
templateEntries.add(new FileCreator(destDirectory, destName, "/" + fullName, false));
} else if (fullName.endsWith("bin")) {
String destName = replacedName.substring(0, replacedName.length() - 3);
templateEntries.add(new FileCreator(destDirectory, destName, "/" + fullName, true));
}
} // else ... ignore everything not a directory, "*src" nor "*bin"
}
return templateEntries;
}
}