| /* | |
| * Copyright 2011 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.web.bindery.requestfactory.apt; | |
| import com.google.gwt.dev.util.Name.BinaryName; | |
| import java.io.ByteArrayOutputStream; | |
| import java.io.File; | |
| import java.io.FileNotFoundException; | |
| import java.io.FileOutputStream; | |
| import java.io.IOException; | |
| import java.io.OutputStream; | |
| import java.io.StringWriter; | |
| import java.io.Writer; | |
| import java.net.URI; | |
| import java.net.URISyntaxException; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.List; | |
| import java.util.jar.JarOutputStream; | |
| import java.util.zip.ZipEntry; | |
| import javax.lang.model.SourceVersion; | |
| import javax.tools.FileObject; | |
| import javax.tools.ForwardingJavaFileManager; | |
| import javax.tools.JavaCompiler; | |
| import javax.tools.JavaCompiler.CompilationTask; | |
| import javax.tools.JavaFileManager; | |
| import javax.tools.JavaFileObject; | |
| import javax.tools.JavaFileObject.Kind; | |
| import javax.tools.SimpleJavaFileObject; | |
| import javax.tools.StandardLocation; | |
| import javax.tools.ToolProvider; | |
| /** | |
| * Provides "late" validation services when server types aren't available to the | |
| * shared-interface compilation process. This tool is provided the name of an | |
| * output jar and the binary names of RequestFactory interfaces that should be | |
| * validated. The validation process will provide pre-computed type map builders | |
| * for use by the ServiceLayer. | |
| * | |
| * @see http://code.google.com/p/google-web-toolkit/wiki/ | |
| * RequestFactoryInterfaceValidation | |
| */ | |
| public class ValidationTool { | |
| /** | |
| * A JavaFileManager that writes the class outputs into a jar file or a | |
| * directory. | |
| */ | |
| static class JarOrDirectoryOutputFileManager extends ForwardingJavaFileManager<JavaFileManager> { | |
| private final List<MemoryJavaFileObject> toOutput = new ArrayList<MemoryJavaFileObject>(); | |
| private final File output; | |
| JarOrDirectoryOutputFileManager(File output, JavaFileManager fileManager) { | |
| super(fileManager); | |
| this.output = output; | |
| } | |
| @Override | |
| public void close() throws IOException { | |
| if (output.isDirectory()) { | |
| writeToDirectory(); | |
| } else { | |
| writeToJar(); | |
| } | |
| } | |
| /** | |
| * Not expected to be called. Overridden to prevent accidental writes to | |
| * disk. | |
| */ | |
| @Override | |
| public FileObject getFileForOutput(Location location, String packageName, String relativeName, | |
| FileObject sibling) throws IOException { | |
| throw new UnsupportedOperationException("Not expecting to write " + packageName + "/" | |
| + relativeName); | |
| } | |
| /** | |
| * This method will receive generated source and class files. | |
| */ | |
| @Override | |
| public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, | |
| FileObject sibling) throws IOException { | |
| String path = BinaryName.toInternalName(className); | |
| String suffix; | |
| switch (kind) { | |
| case CLASS: | |
| suffix = ".class"; | |
| break; | |
| case SOURCE: | |
| suffix = ".java"; | |
| break; | |
| default: | |
| throw new UnsupportedOperationException("Unexpected kind " + kind); | |
| } | |
| MemoryJavaFileObject toReturn = | |
| new MemoryJavaFileObject(uri("memory:///" + path + suffix), kind); | |
| if (StandardLocation.CLASS_OUTPUT.equals(location) && Kind.CLASS.equals(kind)) { | |
| toOutput.add(toReturn); | |
| } | |
| return toReturn; | |
| } | |
| @Override | |
| public boolean isSameFile(FileObject a, FileObject b) { | |
| if (a instanceof MemoryJavaFileObject && b instanceof MemoryJavaFileObject) { | |
| MemoryJavaFileObject memoryA = (MemoryJavaFileObject) a; | |
| MemoryJavaFileObject memoryB = (MemoryJavaFileObject) b; | |
| return memoryA.getKind().equals(memoryB.getKind()) | |
| && memoryA.toUri().equals(memoryB.toUri()); | |
| } | |
| if (a instanceof FakeJavaFileObject && b instanceof FakeJavaFileObject) { | |
| // Only one file ever created | |
| return true; | |
| } | |
| return super.isSameFile(a, b); | |
| } | |
| private void writeToDirectory() throws IOException { | |
| for (MemoryJavaFileObject file : toOutput) { | |
| String path = file.toUri().getPath(); | |
| if (path.equals("/fake/Fake.class")) { | |
| // ignore dummy class | |
| continue; | |
| } | |
| File target = new File(output, path); | |
| target.getParentFile().mkdirs(); | |
| FileOutputStream out = new FileOutputStream(target); | |
| out.write(file.bytes.toByteArray()); | |
| out.close(); | |
| } | |
| } | |
| private void writeToJar() throws IOException, FileNotFoundException { | |
| JarOutputStream jar = new JarOutputStream(new FileOutputStream(output)); | |
| for (MemoryJavaFileObject file : toOutput) { | |
| String path = file.toUri().getPath(); | |
| if (path.equals("/fake/Fake.class")) { | |
| // ignore dummy class | |
| continue; | |
| } | |
| // Strip leading / | |
| ZipEntry entry = new ZipEntry(path.substring(1)); | |
| jar.putNextEntry(entry); | |
| jar.write(file.bytes.toByteArray()); | |
| } | |
| jar.close(); | |
| } | |
| } | |
| /** | |
| * Provides a fake type to seed the compilation process with. | |
| */ | |
| private static class FakeJavaFileObject extends SimpleJavaFileObject { | |
| public FakeJavaFileObject() { | |
| super(uri("fake:///fake/Fake.java"), JavaFileObject.Kind.SOURCE); | |
| } | |
| @Override | |
| public CharSequence getCharContent(boolean arg0) throws IOException { | |
| return "package fake; interface Fake {}"; | |
| } | |
| } | |
| /** | |
| * An in-memory implementation of JavaFileObject. | |
| */ | |
| private static class MemoryJavaFileObject extends SimpleJavaFileObject { | |
| private ByteArrayOutputStream bytes; | |
| private StringWriter charContents; | |
| public MemoryJavaFileObject(URI uri, JavaFileObject.Kind kind) { | |
| super(uri, kind); | |
| } | |
| @Override | |
| public CharSequence getCharContent(boolean ignored) throws IOException { | |
| return charContents.toString(); | |
| } | |
| @Override | |
| public OutputStream openOutputStream() throws IOException { | |
| bytes = new ByteArrayOutputStream(); | |
| return bytes; | |
| } | |
| @Override | |
| public Writer openWriter() throws IOException { | |
| charContents = new StringWriter(); | |
| return charContents; | |
| } | |
| } | |
| public static void main(String[] args) throws IOException { | |
| System.exit(exec(args) ? 0 : -1); | |
| } | |
| /** | |
| * A testable "main" method. | |
| */ | |
| public static boolean exec(String[] args) throws IOException { | |
| return exec(args, ToolProvider.getSystemJavaCompiler()); | |
| } | |
| public static boolean exec(String[] args, JavaCompiler compiler) throws IOException { | |
| return exec(args, compiler, null); | |
| } | |
| public static boolean exec(String[] args, JavaCompiler compiler, Iterable<String> javacOpts) | |
| throws IOException { | |
| if (args.length < 2) { | |
| System.err.println("java -cp requestfactory-client.jar:your_server-code.jar " | |
| + ValidationTool.class.getCanonicalName() | |
| + " (/some/directory | output.jar) com.example.shared.MyRequestFactory"); | |
| System.err.println("See " | |
| + "http://code.google.com/p/google-web-toolkit/wiki/RequestFactoryInterfaceValidation " | |
| + "for more information."); | |
| return false; | |
| } | |
| if (compiler == null) { | |
| System.err.println("This tool must be run with a JDK, not a JRE"); | |
| return false; | |
| } | |
| if (!compiler.getSourceVersions().contains(SourceVersion.RELEASE_6)) { | |
| System.err.println("This tool must be run with a Java 1.6 compiler"); | |
| return false; | |
| } | |
| boolean clientOnly = false; | |
| List<String> argList = new ArrayList<String>(Arrays.asList(args)); | |
| if (argList.get(0).equals("-client")) { | |
| clientOnly = true; | |
| argList.remove(0); | |
| } | |
| // Control how the compile process writes data to disk | |
| JavaFileManager fileManager = | |
| new JarOrDirectoryOutputFileManager(new File(argList.remove(0)), compiler | |
| .getStandardFileManager(null, null, null)); | |
| // Create a validator and require it to process the specified types | |
| RfValidator processor = new RfValidator(); | |
| if (clientOnly) { | |
| processor.setClientOnly(true); | |
| } else { | |
| processor.setMustResolveAllMappings(true); | |
| } | |
| processor.setRootOverride(argList); | |
| // Create the compilation task | |
| CompilationTask task = | |
| compiler.getTask(null, fileManager, null, javacOpts, null, Arrays | |
| .asList(new FakeJavaFileObject())); | |
| task.setProcessors(Arrays.asList(processor)); | |
| if (!task.call()) { | |
| return false; | |
| } | |
| // Save data only on successful compilation | |
| fileManager.close(); | |
| return true; | |
| } | |
| /** | |
| * Convenience method for discarding {@link URISyntaxException}. | |
| */ | |
| private static URI uri(String contents) { | |
| try { | |
| return new URI(contents); | |
| } catch (URISyntaxException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } | |
| } |