org.vaadin.rise.codegen.helpers.JavaFileObjects.java Source code

Java tutorial

Introduction

Here is the source code for org.vaadin.rise.codegen.helpers.JavaFileObjects.java

Source

/*
 * 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();
        }
    }
}