Java tutorial
/* * Copyright (C) 2013 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 org.vaadin.rise.codegen.helpers; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static javax.tools.JavaFileObject.Kind.SOURCE; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteSource; import com.google.common.io.Resources; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.util.Arrays; import javax.tools.ForwardingJavaFileObject; import javax.tools.JavaFileObject; import javax.tools.JavaFileObject.Kind; import javax.tools.SimpleJavaFileObject; /** * A utility class for creating {@link JavaFileObject} instances. * * @author Gregory Kick */ public final class JavaFileObjects { private JavaFileObjects() { } /** * Creates a {@link JavaFileObject} with a path corresponding to the {@code fullyQualifiedName} * containing the give {@code source}. The returned object will always be read-only and have the * {@link Kind#SOURCE} {@linkplain JavaFileObject#getKind() kind}. * * <p>Note that this method makes no attempt to verify that the name matches the contents of the * source and compilation errors may result if they do not match. */ public static JavaFileObject forSourceString(String fullyQualifiedName, String source) { checkNotNull(fullyQualifiedName); if (fullyQualifiedName.startsWith("package ")) { throw new IllegalArgumentException( String.format("fullyQualifiedName starts with \"package\" (%s). Did you forget to " + "specify the name and specify just the source text?", fullyQualifiedName)); } return new StringSourceJavaFileObject(fullyQualifiedName, checkNotNull(source)); } private static final Joiner LINE_JOINER = Joiner.on('\n'); /** * Behaves exactly like {@link #forSourceString}, but joins lines so that multi-line source * strings may omit the newline characters. For example: <pre> {@code * * JavaFileObjects.forSourceLines("example.HelloWorld", * "package example;", * "", * "final class HelloWorld {", * " void sayHello() {", * " System.out.println(\"hello!\");", * " }", * "}"); * }</pre> */ public static JavaFileObject forSourceLines(String fullyQualifiedName, String... lines) { return forSourceLines(fullyQualifiedName, Arrays.asList(lines)); } /** An overload of {@code #forSourceLines} that takes an {@code Iterable<String>}. */ public static JavaFileObject forSourceLines(String fullyQualifiedName, Iterable<String> lines) { return forSourceString(fullyQualifiedName, LINE_JOINER.join(lines)); } private static final class StringSourceJavaFileObject extends SimpleJavaFileObject { final String source; final long lastModified; StringSourceJavaFileObject(String fullyQualifiedName, String source) { super(createUri(fullyQualifiedName), SOURCE); // TODO(gak): check that fullyQualifiedName looks like a fully qualified class name this.source = source; this.lastModified = System.currentTimeMillis(); } static URI createUri(String fullyQualifiedClassName) { return URI.create(CharMatcher.is('.').replaceFrom(fullyQualifiedClassName, '/') + SOURCE.extension); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return source; } @Override public OutputStream openOutputStream() { throw new IllegalStateException(); } @Override public InputStream openInputStream() { return new ByteArrayInputStream(source.getBytes(Charset.defaultCharset())); } @Override public Writer openWriter() { throw new IllegalStateException(); } @Override public Reader openReader(boolean ignoreEncodingErrors) { return new StringReader(source); } @Override public long getLastModified() { return lastModified; } } /** * Returns a {@link JavaFileObject} for the resource at the given {@link URL}. The returned object * will always be read-only and the {@linkplain JavaFileObject#getKind() kind} is inferred via * the {@link Kind#extension}. */ public static JavaFileObject forResource(URL resourceUrl) { if ("jar".equals(resourceUrl.getProtocol())) { return new JarFileJavaFileObject(resourceUrl); } else { return new ResourceSourceJavaFileObject(resourceUrl); } } /** * Returns a {@link JavaFileObject} for the class path resource with the given * {@code resourceName}. This method is equivalent to invoking * {@code forResource(Resources.getResource(resourceName))}. */ public static JavaFileObject forResource(String resourceName) { return forResource(Resources.getResource(resourceName)); } static Kind deduceKind(URI uri) { String path = uri.getPath(); for (Kind kind : Kind.values()) { if (path.endsWith(kind.extension)) { return kind; } } return Kind.OTHER; } static ByteSource asByteSource(final JavaFileObject javaFileObject) { return new ByteSource() { @Override public InputStream openStream() throws IOException { return javaFileObject.openInputStream(); } }; } private static final class JarFileJavaFileObject extends ForwardingJavaFileObject<ResourceSourceJavaFileObject> { final String name; JarFileJavaFileObject(URL jarUrl) { // this is a cheap way to give SimpleJavaFileObject a uri that satisfies the contract // then we just override the methods that we want to behave differently for jars super(new ResourceSourceJavaFileObject(jarUrl, getPathUri(jarUrl))); this.name = jarUrl.toString(); } static final Splitter JAR_URL_SPLITTER = Splitter.on('!'); static final URI getPathUri(URL jarUrl) { ImmutableList<String> parts = ImmutableList.copyOf(JAR_URL_SPLITTER.split(jarUrl.getPath())); checkArgument(parts.size() == 2, "The jar url separator (!) appeared more than once in the url: %s", jarUrl); String pathPart = parts.get(1); checkArgument(!pathPart.endsWith("/"), "cannot buildJavaCompatibleFQN a java file object for a directory: %s", pathPart); return URI.create(pathPart); } @Override public String getName() { return name; } } private static final class ResourceSourceJavaFileObject extends SimpleJavaFileObject { final ByteSource resourceByteSource; /** Only to avoid creating the URI twice. */ ResourceSourceJavaFileObject(URL resourceUrl, URI resourceUri) { super(resourceUri, deduceKind(resourceUri)); this.resourceByteSource = Resources.asByteSource(resourceUrl); } ResourceSourceJavaFileObject(URL resourceUrl) { this(resourceUrl, URI.create(resourceUrl.toString())); } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return resourceByteSource.asCharSource(Charset.defaultCharset()).read(); } @Override public InputStream openInputStream() throws IOException { return resourceByteSource.openStream(); } @Override public Reader openReader(boolean ignoreEncodingErrors) throws IOException { return resourceByteSource.asCharSource(Charset.defaultCharset()).openStream(); } } }