package org.jetbrains.jps.builders.javacApi; import com.intellij.ant.InstrumentationUtil; import com.intellij.ant.PseudoClassLoader; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.DefaultFileManager; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; import org.jetbrains.ether.dependencyView.Callbacks; import org.jetbrains.jps.PathUtil; import org.objectweb.asm.ClassReader; import javax.lang.model.SourceVersion; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileObject; import javax.tools.JavaFileObject; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * @author nik */ public class OptimizedFileManager extends DefaultFileManager { private interface DelayedClassFileWriter { public void commit() throws IOException; } private java.util.List<DelayedClassFileWriter> myWriters = new ArrayList<DelayedClassFileWriter>(); private boolean myUseZipFileIndex; private final Map<File, Archive> myArchives; private final Map<File, Boolean> myIsFile = new ConcurrentHashMap<File, Boolean>(); private Callbacks.Backend callback; private PseudoClassLoader loader; public void setProperties(final Callbacks.Backend c, final PseudoClassLoader l) { callback = c; loader = l; } public OptimizedFileManager() { super(new Context(), true, null); try { final Field archivesField = DefaultFileManager.class.getDeclaredField("archives"); archivesField.setAccessible(true); myArchives = (Map<File, Archive>) archivesField.get(this); } catch (Exception e) { throw new RuntimeException(e); } try { final Field useZipFileIndexField = DefaultFileManager.class.getDeclaredField("useZipFileIndex"); useZipFileIndexField.setAccessible(true); myUseZipFileIndex = (Boolean) useZipFileIndexField.get(this); } catch (Exception e) { myUseZipFileIndex = false; } } @Override public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException { Iterable<? extends File> path = getLocation(location); if (path == null) return Collections.emptyList(); String relativePath = packageName.replace('.', File.separatorChar); ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>(); for (File root : path) { Archive archive = myArchives.get(root); final boolean isFile; if (archive != null) { isFile = true; } else { Boolean cachedIsFile = myIsFile.get(root); if (cachedIsFile == null) { cachedIsFile = root.isFile(); myIsFile.put(root, cachedIsFile); } isFile = cachedIsFile.booleanValue(); } if (isFile) { collectFromArchive(root, archive, relativePath, kinds, recurse, results); } else { File directory = relativePath.length() != 0 ? new File(root, relativePath) : root; collectFromDirectory(directory, kinds, recurse, results); } } return results.toList(); } private void collectFromArchive(File root, Archive archive, String relativePath, Set<JavaFileObject.Kind> kinds, boolean recurse, ListBuffer<JavaFileObject> result) { if (archive == null) { try { archive = openArchive(root); } catch (IOException ex) { log.error("error.reading.file", root, ex.getLocalizedMessage()); return; } } String separator = myUseZipFileIndex ? File.separator : "/"; if (relativePath.length() != 0) { if (!myUseZipFileIndex) { relativePath = relativePath.replace('\\', '/'); } if (!relativePath.endsWith(separator)) relativePath = relativePath + separator; } collectArchiveFiles(archive, relativePath, kinds, result); if (recurse) { for (String s : archive.getSubdirectories()) { if (s.startsWith(relativePath) && !s.equals(relativePath)) { if (!s.endsWith(separator)) { s += separator; } collectArchiveFiles(archive, s, kinds, result); } } } } private void collectFromDirectory(File directory, Set<JavaFileObject.Kind> fileKinds, boolean recurse, ListBuffer<JavaFileObject> result) { File[] children = directory.listFiles(); if (children == null) return; for (File child : children) { String name = child.getName(); if (child.isDirectory()) { if (recurse && SourceVersion.isIdentifier(name)) { collectFromDirectory(directory, fileKinds, recurse, result); } } else { if (isValidFile(name, fileKinds)) { JavaFileObject fe = getRegularFile(child); result.append(fe); } } } } private void collectArchiveFiles(Archive archive, String relativePath, Set<JavaFileObject.Kind> fileKinds, ListBuffer<JavaFileObject> result) { List<String> files = archive.getFiles(relativePath); if (files != null) { for (String file; !files.isEmpty(); files = files.tail) { file = files.head; if (isValidFile(file, fileKinds)) { result.append(archive.getFileObject(relativePath, file)); } } } } private boolean isValidFile(String name, Set<JavaFileObject.Kind> fileKinds) { int dot = name.lastIndexOf("."); JavaFileObject.Kind kind = getKind(dot == -1 ? name : name.substring(dot)); return fileKinds.contains(kind); } //actually Javac doesn't check if this method returns null. It always get substring of the returned string starting from the last dot. @Override public String inferBinaryName(Location location, JavaFileObject file) { final String name = file.getName(); int dot = name.lastIndexOf('.'); final String relativePath = dot != -1 ? name.substring(0, dot) : name; return relativePath.replace(File.separatorChar, '.'); } @Override public JavaFileObject getJavaFileForOutput(Location location, final String className, final JavaFileObject.Kind kind, FileObject fileObject) throws IOException { final JavaFileObject result = super.getJavaFileForOutput(location, className, kind, fileObject); final String classFileName = PathUtil.toPath(result.toUri()); final String sourceFileName = PathUtil.toPath(fileObject.toUri()); return new ForwardingJavaFileObject<JavaFileObject>(result) { private OutputStream superOpenOutputStream() throws IOException { return super.openOutputStream(); } @Override public OutputStream openOutputStream() throws IOException { return new OutputStream() { public void flush() throws IOException { } public void close() throws IOException { } public void write(int b) throws IOException { assert (false); } public void write(byte[] b) throws IOException { assert (false); } public void write(final byte[] b, final int off, final int len) throws IOException { final byte[] buffer = Arrays.copyOfRange(b, off, len); if (kind.equals(JavaFileObject.Kind.CLASS)) { loader.defineClass(className.replaceAll("\\.", "/"), buffer); if (callback != null) { final ClassReader reader = new ClassReader(buffer); callback.associate(classFileName, Callbacks.getDefaultLookup(sourceFileName), reader); } myWriters.add(new DelayedClassFileWriter() { public void commit() throws IOException { final OutputStream result = superOpenOutputStream(); final byte[] instrumented = InstrumentationUtil.instrumentNotNull(buffer, loader); if (instrumented != null) { result.write(instrumented); } else { result.write(buffer); } result.close(); } }); } else { final OutputStream result = superOpenOutputStream(); result.write(buffer); result.close(); } } }; } }; } @Override public void flush() { super.flush(); for (DelayedClassFileWriter f : myWriters) { try { f.commit(); } catch (IOException e) { throw new RuntimeException(e); } } myWriters.clear(); } }