blob: 9a7afe1cfa8d0b0f69164a8ac3cb96717785bce9 [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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.jetbrains.jps.javac;
import com.intellij.openapi.util.io.FileUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.builders.java.JavaSourceTransformer;
import org.jetbrains.jps.incremental.Utils;
import javax.tools.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: 9/24/11
*/
class JavacFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> implements StandardJavaFileManager{
private final Context myContext;
private final Collection<JavaSourceTransformer> mySourceTransformers;
private Map<File, Set<File>> myOutputsMap = Collections.emptyMap();
@Nullable
private String myEncodingName;
interface Context {
boolean isCanceled();
StandardJavaFileManager getStandardFileManager();
void consumeOutputFile(@NotNull OutputFileObject obj);
void reportMessage(final Diagnostic.Kind kind, String message);
}
public JavacFileManager(Context context, Collection<JavaSourceTransformer> transformers) {
super(context.getStandardFileManager());
myContext = context;
mySourceTransformers = transformers;
}
public void setOutputDirectories(final Map<File, Set<File>> outputDirToSrcRoots) throws IOException{
for (File outputDir : outputDirToSrcRoots.keySet()) {
// this will validate output dirs
setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(outputDir));
}
myOutputsMap = outputDirToSrcRoots;
}
@Override
public boolean handleOption(String current, final Iterator<String> remaining) {
if ("-encoding".equalsIgnoreCase(current) && remaining.hasNext()) {
final String encoding = remaining.next();
myEncodingName = encoding;
return super.handleOption(current, new Iterator<String>() {
private boolean encodingConsumed = false;
@Override
public boolean hasNext() {
return !encodingConsumed || remaining.hasNext();
}
@Override
public String next() {
if (!encodingConsumed) {
encodingConsumed = true;
return encoding;
}
return remaining.next();
}
@Override
public void remove() {
if (encodingConsumed) {
remaining.remove();
}
}
});
}
return super.handleOption(current, remaining);
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
return super.inferBinaryName(location, unwrapFileObject(file));
}
public void setLocation(Location location, Iterable<? extends File> path) throws IOException{
getStdManager().setLocation(location, path);
}
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
return wrapJavaFileObjects(getStdManager().getJavaFileObjectsFromFiles(files));
}
public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
return wrapJavaFileObjects(getStdManager().getJavaFileObjects(files));
}
public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
return wrapJavaFileObjects(getStdManager().getJavaFileObjectsFromStrings(names));
}
public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
return wrapJavaFileObjects(getStdManager().getJavaFileObjects(names));
}
public Iterable<? extends File> getLocation(Location location) {
return getStdManager().getLocation(location);
}
public boolean isSameFile(FileObject a, FileObject b) {
if (a instanceof OutputFileObject || b instanceof OutputFileObject) {
return a.equals(b);
}
return super.isSameFile(unwrapFileObject(a), unwrapFileObject(b));
}
private static FileObject unwrapFileObject(FileObject a) {
return a instanceof TransformableJavaFileObject ? ((TransformableJavaFileObject)a).getOriginal() : a;
}
private static JavaFileObject unwrapFileObject(JavaFileObject a) {
return a instanceof TransformableJavaFileObject ? ((TransformableJavaFileObject)a).getOriginal() : a;
}
@Override
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
checkCanceled();
final FileObject fo = super.getFileForInput(location, packageName, relativeName);
if (fo == null) {
// workaround javac bug (missing null-check): throwing exception here instead of returning null
throw new FileNotFoundException("Resource does not exist : " + location + '/' + packageName + '/' + relativeName);
}
return fo;
}
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
final Iterable<JavaFileObject> objects = super.list(location, packageName, kinds, recurse);
//noinspection unchecked
return kinds.contains(JavaFileObject.Kind.SOURCE)? (Iterable<JavaFileObject>)wrapJavaFileObjects(objects) : objects;
}
private Iterable<? extends JavaFileObject> wrapJavaFileObjects(Iterable<? extends JavaFileObject> originalObjects) {
if (mySourceTransformers.isEmpty()) {
return originalObjects;
}
final List<JavaFileObject> wrapped = new ArrayList<JavaFileObject>();
for (JavaFileObject fo : originalObjects) {
wrapped.add(JavaFileObject.Kind.SOURCE.equals(fo.getKind())? new TransformableJavaFileObject(fo, mySourceTransformers) : fo);
}
return wrapped;
}
@Override
public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
checkCanceled();
final JavaFileObject fo = super.getJavaFileForInput(location, className, kind);
if (fo == null) {
// workaround javac bug (missing null-check): throwing exception here instead of returning null
throw new FileNotFoundException("Java resource does not exist : " + location + '/' + kind + '/' + className);
}
return mySourceTransformers.isEmpty()? fo : new TransformableJavaFileObject(fo, mySourceTransformers);
}
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (kind != JavaFileObject.Kind.SOURCE && kind != JavaFileObject.Kind.CLASS) {
throw new IllegalArgumentException("Invalid kind " + kind);
}
return getFileForOutput(location, kind, externalizeFileName(className, kind), className, sibling);
}
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
final StringBuilder name = new StringBuilder();
if (packageName.isEmpty()) {
name.append(relativeName);
}
else {
name.append(externalizeFileName(packageName)).append(File.separatorChar).append(relativeName);
}
final String fileName = name.toString();
return getFileForOutput(location, getKind(fileName), fileName, null, sibling);
}
private OutputFileObject getFileForOutput(Location location, JavaFileObject.Kind kind, String fileName, @Nullable String className, FileObject sibling) throws IOException {
checkCanceled();
JavaFileObject src = null;
if (sibling instanceof JavaFileObject) {
final JavaFileObject javaFileObject = (JavaFileObject)sibling;
if (javaFileObject.getKind() == JavaFileObject.Kind.SOURCE) {
src = javaFileObject;
}
}
File dir = getSingleOutputDirectory(location, src);
if (location == StandardLocation.CLASS_OUTPUT) {
if (dir == null) {
throw new IOException("Output directory is not specified");
}
}
else if (location == StandardLocation.SOURCE_OUTPUT) {
if (dir == null) {
dir = getSingleOutputDirectory(StandardLocation.CLASS_OUTPUT, src);
if (dir == null) {
throw new IOException("Neither class output directory nor source output are specified");
}
}
}
final File file = (dir == null? new File(fileName).getAbsoluteFile() : new File(dir, fileName));
return new OutputFileObject(myContext, dir, fileName, file, kind, className, src != null? src.toUri() : null, myEncodingName);
}
@Override
public ClassLoader getClassLoader(Location location) {
final Iterable<? extends File> path = getLocation(location);
if (path == null) {
return null;
}
final List<URL> urls = new ArrayList<URL>();
for (File f: path) {
try {
urls.add(f.toURI().toURL());
}
catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
// ensure processor's loader will not resolve against JPS classes and libraries used in JPS
return new URLClassLoader(urls.toArray(new URL[urls.size()]), myContext.getStandardFileManager().getClass().getClassLoader());
}
private File getSingleOutputDirectory(final Location loc, final JavaFileObject sourceFile) {
if (loc == StandardLocation.CLASS_OUTPUT) {
if (myOutputsMap.size() > 1 && sourceFile != null) {
// multiple outputs case
final File outputDir = findOutputDir(Utils.convertToFile(sourceFile.toUri()));
if (outputDir != null) {
return outputDir;
}
}
}
final Iterable<? extends File> location = getStdManager().getLocation(loc);
if (location != null) {
final Iterator<? extends File> it = location.iterator();
if (it.hasNext()) {
return it.next();
}
}
return null;
}
private File findOutputDir(File src) {
File file = FileUtilRt.getParentFile(src);
while (file != null) {
for (Map.Entry<File, Set<File>> entry : myOutputsMap.entrySet()) {
if (entry.getValue().contains(file)) {
return entry.getKey();
}
}
file = FileUtilRt.getParentFile(file);
}
return null;
}
@NotNull
private StandardJavaFileManager getStdManager() {
return fileManager;
}
@Override
public void close() {
try {
super.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
finally {
myOutputsMap = Collections.emptyMap();
}
}
private static JavaFileObject.Kind getKind(String name) {
if (name.endsWith(JavaFileObject.Kind.CLASS.extension)){
return JavaFileObject.Kind.CLASS;
}
if (name.endsWith(JavaFileObject.Kind.SOURCE.extension)) {
return JavaFileObject.Kind.SOURCE;
}
if (name.endsWith(JavaFileObject.Kind.HTML.extension)) {
return JavaFileObject.Kind.HTML;
}
return JavaFileObject.Kind.OTHER;
}
private static String externalizeFileName(CharSequence cs, JavaFileObject.Kind kind) {
return externalizeFileName(cs) + kind.extension;
}
private static String externalizeFileName(CharSequence name) {
return name.toString().replace('.', File.separatorChar);
}
private int myChecksCounter = 0;
private void checkCanceled() {
final int counter = (myChecksCounter + 1) % 10;
myChecksCounter = counter;
if (counter == 0 && myContext.isCanceled()) {
throw new CompilationCanceledException();
}
}
public Context getContext() {
return myContext;
}
}