| /* |
| * 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.sun.tools.javac.util.Context; |
| import com.sun.tools.javac.util.JCDiagnostic; |
| import com.sun.tools.javac.util.List; |
| import com.sun.tools.javac.util.ListBuffer; |
| |
| import javax.lang.model.SourceVersion; |
| import javax.tools.FileObject; |
| import javax.tools.JavaFileObject; |
| import java.io.*; |
| import java.lang.ref.SoftReference; |
| import java.lang.reflect.Field; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.charset.*; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * WARNING: Loaded via reflection, do not delete |
| * |
| * @author nik |
| * @noinspection UnusedDeclaration |
| */ |
| class OptimizedFileManager extends DefaultFileManager { |
| private boolean myUseZipFileIndex; |
| private final Map<File, Archive> myArchives; |
| private final Map<File, Boolean> myIsFile = new HashMap<File, Boolean>(); |
| private final Map<File, File[]> myDirectoryCache = new HashMap<File, File[]>(); |
| public static final File[] NULL_FILE_ARRAY = new File[0]; |
| |
| private static final boolean ourUseContentCache = Boolean.valueOf(System.getProperty("javac.use.content.cache", "false")); |
| private final Map<InputFileObject, SoftReference<CharBuffer>> myContentCache = ourUseContentCache? new HashMap<InputFileObject, SoftReference<CharBuffer>>() : Collections.<InputFileObject, SoftReference<CharBuffer>>emptyMap(); |
| |
| public OptimizedFileManager() throws Throwable { |
| super(new Context(), true, null); |
| final Field archivesField = DefaultFileManager.class.getDeclaredField("archives"); |
| archivesField.setAccessible(true); |
| myArchives = (Map<File, Archive>) archivesField.get(this); |
| |
| try { |
| final Field useZipFileIndexField = DefaultFileManager.class.getDeclaredField("useZipFileIndex"); |
| useZipFileIndexField.setAccessible(true); |
| myUseZipFileIndex = (Boolean) useZipFileIndexField.get(this); |
| } |
| catch (Exception e) { |
| myUseZipFileIndex = false; |
| } |
| } |
| |
| @Override |
| public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { |
| final String name = packageName == null || packageName.isEmpty() ? relativeName.replace('\\', '/') : (packageName.replace('.', '/') + "/" + relativeName.replace('\\', '/')); |
| return getFileForInput(location, name); |
| } |
| |
| @Override |
| public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { |
| final String name = className.replace('.', '/') + kind.extension; |
| return getFileForInput(location, name); |
| } |
| |
| @Override |
| public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) { |
| java.util.List<InputFileObject> result; |
| if (files instanceof Collection) { |
| result = new ArrayList<InputFileObject>(((Collection)files).size()); |
| } |
| else { |
| result = new ArrayList<InputFileObject>(); |
| } |
| for (File f: files) { |
| result.add(new InputFileObject(f)); |
| } |
| return result; |
| } |
| |
| @Override |
| public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException { |
| Iterable<? extends File> locationRoots = getLocation(location); |
| if (locationRoots == null) { |
| return Collections.emptyList(); |
| } |
| |
| final String relativePath = packageName.replace('.', File.separatorChar); |
| ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>(); |
| |
| for (File root : locationRoots) { |
| final Archive archive = myArchives.get(root); |
| final boolean isFile; |
| if (archive != null) { |
| isFile = true; |
| } |
| else { |
| isFile = isFile(root); |
| } |
| if (isFile) { |
| collectFromArchive(root, archive, relativePath, kinds, recurse, results); |
| } |
| else { |
| final File directory = relativePath.length() != 0 ? new File(root, relativePath) : root; |
| if (recurse) { |
| collectFromDirectoryRecursively(directory, kinds, results, true); |
| } |
| else { |
| collectFromDirectory(directory, kinds, results); |
| } |
| } |
| } |
| |
| return results.toList(); |
| } |
| |
| // important! called via reflection, so avoid renaming or signature changing or rename carefully |
| public void fileGenerated(File file) { |
| final File parent = file.getParentFile(); |
| if (parent != null) { |
| myDirectoryCache.remove(parent); |
| } |
| } |
| |
| private boolean isFile(File root) { |
| Boolean cachedIsFile = myIsFile.get(root); |
| if (cachedIsFile == null) { |
| cachedIsFile = Boolean.valueOf(root.isFile()); |
| myIsFile.put(root, cachedIsFile); |
| } |
| return cachedIsFile.booleanValue(); |
| } |
| |
| 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; |
| } |
| } |
| final 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, ListBuffer<JavaFileObject> result) { |
| final File[] children = listChildren(directory); |
| if (children != null) { |
| final boolean acceptUnknownFiles = fileKinds.contains(JavaFileObject.Kind.OTHER); |
| for (File child : children) { |
| if (isValidFile(child.getName(), fileKinds)) { |
| if (acceptUnknownFiles && !isFile(child)) { |
| continue; |
| } |
| final JavaFileObject fe = new InputFileObject(child); |
| result.append(fe); |
| } |
| } |
| } |
| } |
| |
| private void collectFromDirectoryRecursively(File file, Set<JavaFileObject.Kind> fileKinds, ListBuffer<JavaFileObject> result, boolean isRootCall) { |
| final File[] children = listChildren(file); |
| final String name = file.getName(); |
| if (children != null) { // is directory |
| if (isRootCall || SourceVersion.isIdentifier(name)) { |
| for (File child : children) { |
| collectFromDirectoryRecursively(child, fileKinds, result, false); |
| } |
| } |
| } |
| else { |
| if (isValidFile(name, fileKinds)) { |
| JavaFileObject fe = new InputFileObject(file); |
| result.append(fe); |
| } |
| } |
| } |
| |
| private File[] listChildren(File file) { |
| File[] cached = myDirectoryCache.get(file); |
| if (cached == null) { |
| cached = file.listFiles(); |
| myDirectoryCache.put(file, cached != null? cached : NULL_FILE_ARRAY); |
| } |
| return cached == NULL_FILE_ARRAY ? null : cached; |
| } |
| |
| 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); |
| } |
| |
| private JavaFileObject getFileForInput(Location location, String name) throws IOException { |
| Iterable<? extends File> path = getLocation(location); |
| if (path == null) { |
| return null; |
| } |
| |
| for (File root : path) { |
| Archive archive = myArchives.get(root); |
| final boolean isFile; |
| if (archive != null) { |
| isFile = true; |
| } |
| else { |
| isFile = isFile(root); |
| } |
| if (isFile) { |
| if (archive == null) { |
| try { |
| archive = openArchive(root); |
| } |
| catch (IOException ex) { |
| log.error("error.reading.file", root, ex.getLocalizedMessage()); |
| break; |
| } |
| } |
| if (archive.contains(name)) { |
| int i = name.lastIndexOf('/'); |
| String dirname = name.substring(0, i+1); |
| String basename = name.substring(i+1); |
| return archive.getFileObject(dirname, basename); |
| } |
| } |
| else { |
| final File f = new File(root, name.replace('/', File.separatorChar)); |
| if (f.exists()) { |
| return new InputFileObject(f); |
| } |
| } |
| } |
| return null; |
| } |
| |
| //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, '.'); |
| } |
| |
| private class InputFileObject extends BaseFileObject { |
| |
| /** The underlying file. |
| */ |
| final File f; |
| |
| public InputFileObject(File f) { |
| this.f = f; |
| } |
| |
| public InputStream openInputStream() throws IOException { |
| return new FileInputStream(f); |
| } |
| |
| public Reader openReader(boolean ignoreEncodingErrors) throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public OutputStream openOutputStream() throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public Writer openWriter() throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public String getName() { |
| return f.getPath(); |
| } |
| |
| public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) { |
| final String n = simpleName + kind.extension; |
| final String fileName = f.getName(); |
| if (fileName.equals(n)) { |
| return true; |
| } |
| if (fileName.equalsIgnoreCase(n)) { |
| try { |
| // allow for Windows |
| return (f.getCanonicalFile().getName().equals(n)); |
| } |
| catch (IOException e) { |
| } |
| } |
| return false; |
| } |
| |
| /** @deprecated see bug 6410637 */ |
| @Deprecated |
| public String getPath() { |
| return f.getPath(); |
| } |
| |
| public long getLastModified() { |
| return f.lastModified(); |
| } |
| |
| public boolean delete() { |
| return f.delete(); |
| } |
| |
| public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException { |
| CharBuffer cb; |
| if (ourUseContentCache) { |
| SoftReference<CharBuffer> ref = myContentCache.get(this); |
| cb = (ref != null) ? ref.get() : null; |
| if (cb == null) { |
| cb = loadFileContent(ignoreEncodingErrors); |
| if (!ignoreEncodingErrors) { |
| myContentCache.put(this, new SoftReference<CharBuffer>(cb)); |
| } |
| } |
| } |
| else { |
| cb = loadFileContent(ignoreEncodingErrors); |
| } |
| return cb; |
| } |
| |
| private CharBuffer loadFileContent(boolean ignoreEncodingErrors) throws IOException { |
| final InputStream in = new FileInputStream(f); |
| final ByteBuffer bb = makeByteBuffer(in); |
| JavaFileObject prev = log.useSource(this); |
| try { |
| return decode(bb, ignoreEncodingErrors); |
| } |
| finally { |
| log.useSource(prev); |
| myByteBufferCache.put(bb); // save for next time |
| in.close(); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof InputFileObject)) { |
| return false; |
| } |
| InputFileObject o = (InputFileObject) other; |
| try { |
| return f.equals(o.f) || f.getCanonicalFile().equals(o.f.getCanonicalFile()); |
| } |
| catch (IOException e) { |
| return false; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return f.hashCode(); |
| } |
| |
| public URI toUri() { |
| try { |
| return convertToURI(f.getPath()); |
| } |
| catch (Throwable ex) { |
| return f.toURI().normalize(); |
| } |
| } |
| } |
| |
| private static URI convertToURI(String localPath) throws URISyntaxException { |
| String p = localPath.replace('\\', '/'); |
| if (!p.startsWith("/")) { |
| p = "/" + p; |
| } |
| if (!p.startsWith("//")) { |
| p = "//" + p; |
| } |
| return new URI("file", null, p, null); |
| } |
| |
| private ByteBuffer makeByteBuffer(InputStream in) throws IOException { |
| int limit = in.available(); |
| if (limit < 1024) { |
| limit = 1024; |
| } |
| ByteBuffer result = myByteBufferCache.get(limit); |
| int position = 0; |
| while (in.available() != 0) { |
| if (position >= limit) { |
| // expand buffer |
| result = ByteBuffer.allocate(limit <<= 1).put((ByteBuffer)result.flip()); |
| } |
| final int count = in.read(result.array(), position, limit - position); |
| if (count < 0) { |
| break; |
| } |
| result.position(position += count); |
| } |
| return (ByteBuffer)result.flip(); |
| } |
| |
| private CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) { |
| CharsetDecoder decoder; |
| String encodingName = getEncodingName(); |
| try { |
| Charset charset = (this.charset == null) ? Charset.forName(encodingName) : this.charset; |
| decoder = charset.newDecoder(); |
| |
| CodingErrorAction action; |
| if (ignoreEncodingErrors) { |
| action = CodingErrorAction.REPLACE; |
| } |
| else { |
| action = CodingErrorAction.REPORT; |
| } |
| |
| decoder.onMalformedInput(action).onUnmappableCharacter(action); |
| } |
| catch (IllegalCharsetNameException e) { |
| log.error("unsupported.encoding", encodingName); |
| return (CharBuffer)CharBuffer.allocate(1).flip(); |
| } |
| catch (UnsupportedCharsetException e) { |
| log.error("unsupported.encoding", encodingName); |
| return (CharBuffer)CharBuffer.allocate(1).flip(); |
| } |
| |
| // slightly overestimate the buffer size to avoid reallocation. |
| final float factor = decoder.averageCharsPerByte() * 0.8f + decoder.maxCharsPerByte() * 0.2f; |
| CharBuffer dest = CharBuffer.allocate(10 + (int)(inbuf.remaining() * factor)); |
| |
| while (true) { |
| CoderResult result = decoder.decode(inbuf, dest, true); |
| dest.flip(); |
| |
| if (result.isUnderflow()) { // done reading |
| // make sure there is at least one extra character |
| if (dest.limit() == dest.capacity()) { |
| dest = CharBuffer.allocate(dest.capacity()+1).put(dest); |
| dest.flip(); |
| } |
| return dest; |
| } |
| else if (result.isOverflow()) { // buffer too small; expand |
| int newCapacity = 10 + dest.capacity() + (int)(inbuf.remaining()*decoder.maxCharsPerByte()); |
| dest = CharBuffer.allocate(newCapacity).put(dest); |
| } |
| else if (result.isMalformed() || result.isUnmappable()) { |
| // bad character in input |
| |
| // report coding error (warn only pre 1.5) |
| if (!getSource().allowEncodingErrors()) { |
| log.error(new JCDiagnostic.SimpleDiagnosticPosition(dest.limit()), "illegal.char.for.encoding", charset == null ? encodingName : charset.name()); |
| } |
| else { |
| log.warning(new JCDiagnostic.SimpleDiagnosticPosition(dest.limit()), "illegal.char.for.encoding", charset == null ? encodingName : charset.name()); |
| } |
| |
| // skip past the coding error |
| inbuf.position(inbuf.position() + result.length()); |
| |
| // undo the flip() to prepare the output buffer |
| // for more translation |
| dest.position(dest.limit()); |
| dest.limit(dest.capacity()); |
| dest.put((char)0xfffd); // backward compatible |
| } |
| else { |
| throw new AssertionError(result); |
| } |
| } |
| // unreached |
| } |
| |
| private static class ByteBufferCache { |
| private AtomicReference<ByteBuffer> myCached = new AtomicReference<ByteBuffer>(null); |
| |
| ByteBuffer get(int capacity) { |
| if (capacity < 20480) { |
| capacity = 20480; |
| } |
| final ByteBuffer cached = myCached.getAndSet(null); |
| return (cached != null && cached.capacity() >= capacity) ? (ByteBuffer)cached.clear() : ByteBuffer.allocate(capacity + capacity>>1); |
| } |
| |
| void put(ByteBuffer x) { |
| myCached.set(x); |
| } |
| |
| void clear() { |
| myCached.set(null); |
| } |
| } |
| private final ByteBufferCache myByteBufferCache = new ByteBufferCache(); |
| |
| |
| private static volatile boolean ourPathCacheClearProblem = false; |
| |
| public void close() { |
| try { |
| super.close(); |
| } |
| finally { |
| // archives are cleared in super.close() |
| if (ourUseContentCache) { |
| myContentCache.clear(); |
| } |
| myDirectoryCache.clear(); |
| myByteBufferCache.clear(); |
| myIsFile.clear(); |
| if (!ourPathCacheClearProblem) { |
| try { |
| Paths.clearPathExistanceCache(); |
| } |
| catch (Throwable ignored) { |
| ourPathCacheClearProblem = true; |
| } |
| } |
| } |
| } |
| } |