| /* |
| * 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.file.BaseFileObject; |
| import com.sun.tools.javac.file.JavacFileManager; |
| import com.sun.tools.javac.file.RelativePath; |
| import com.sun.tools.javac.util.Context; |
| import com.sun.tools.javac.util.List; |
| import com.sun.tools.javac.util.ListBuffer; |
| |
| import javax.lang.model.SourceVersion; |
| import javax.tools.JavaFileObject; |
| import java.io.*; |
| import java.lang.ref.Reference; |
| 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.CharsetDecoder; |
| import java.nio.file.LinkOption; |
| import java.util.*; |
| |
| /** |
| * WARNING: Loaded via reflection, do not delete |
| * |
| * @noinspection UnusedDeclaration |
| */ |
| class OptimizedFileManager17 extends com.sun.tools.javac.file.JavacFileManager { |
| 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 String _OS_NAME = System.getProperty("os.name").toLowerCase(Locale.US); |
| private static final boolean isWindows = _OS_NAME.startsWith("windows"); |
| private static final boolean isOS2 = _OS_NAME.startsWith("os/2") || _OS_NAME.startsWith("os2"); |
| private static final boolean isMac = _OS_NAME.startsWith("mac"); |
| private static final boolean isFileSystemCaseSensitive = !isWindows && !isOS2 && !isMac; |
| private static final boolean ourUseContentCache = Boolean.valueOf(System.getProperty("javac.use.content.cache", "false")); |
| |
| public OptimizedFileManager17() throws Throwable { |
| super(new Context(), true, null); |
| final Field archivesField = com.sun.tools.javac.file.JavacFileManager.class.getDeclaredField("archives"); |
| archivesField.setAccessible(true); |
| myArchives = (Map<File, Archive>) archivesField.get(this); |
| } |
| |
| @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(this, 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 List.nil(); |
| } |
| |
| RelativePath.RelativeDirectory subdirectory = new RelativePath.RelativeDirectory(packageName.replace('.', '/')); |
| |
| ListBuffer<JavaFileObject> results = new ListBuffer<JavaFileObject>(); |
| |
| for (File root : locationRoots) { |
| Archive archive = myArchives.get(root); |
| |
| final boolean isFile; |
| if (archive != null) { |
| isFile = true; |
| } |
| else { |
| isFile = isFile(root); |
| } |
| |
| if (isFile) { |
| // Not a directory; either a file or non-existant, create the archive |
| try { |
| if (archive == null) { |
| archive = openArchive(root); |
| } |
| listArchive(archive, subdirectory, kinds, recurse, results); |
| } |
| catch (IOException ex) { |
| log.error("error.reading.file", root, getMessage(ex)); |
| } |
| } |
| else { |
| final File dir = subdirectory.getFile(root); |
| if (recurse) { |
| listDirectoryRecursively(dir, kinds, results, true); |
| } |
| else { |
| listDirectory(dir, kinds, results); |
| } |
| } |
| |
| } |
| return results.toList(); |
| } |
| |
| private static void listArchive(Archive archive, RelativePath.RelativeDirectory subdirectory, Set<JavaFileObject.Kind> fileKinds, boolean recurse, ListBuffer<JavaFileObject> resultList) { |
| // Get the files directly in the subdir |
| List<String> files = archive.getFiles(subdirectory); |
| if (files != null) { |
| for (; !files.isEmpty(); files = files.tail) { |
| String file = files.head; |
| if (isValidFile(file, fileKinds)) { |
| resultList.append(archive.getFileObject(subdirectory, file)); |
| } |
| } |
| } |
| if (recurse) { |
| for (RelativePath.RelativeDirectory s: archive.getSubdirectories()) { |
| if (contains(subdirectory, s)) { |
| // Because the archive map is a flat list of directories, |
| // the enclosing loop will pick up all child subdirectories. |
| // Therefore, there is no need to recurse deeper. |
| listArchive(archive, s, fileKinds, false, resultList); |
| } |
| } |
| } |
| } |
| |
| private void listDirectory(File directory, Set<JavaFileObject.Kind> fileKinds, ListBuffer<JavaFileObject> resultList) { |
| final File[] files = listChildren(directory); |
| if (files != null) { |
| if (sortFiles != null) { |
| Arrays.sort(files, sortFiles); |
| } |
| final boolean acceptUnknownFiles = fileKinds.contains(JavaFileObject.Kind.OTHER); |
| for (File f: files) { |
| final String fileName = f.getName(); |
| if (isValidFile(fileName, fileKinds)) { |
| if (acceptUnknownFiles && !isFile(f)) { |
| continue; |
| } |
| final JavaFileObject fe = new InputFileObject(this, f); |
| resultList.append(fe); |
| } |
| } |
| } |
| } |
| |
| private void listDirectoryRecursively(File file, Set<JavaFileObject.Kind> fileKinds, ListBuffer<JavaFileObject> resultList, boolean isRootCall) { |
| final File[] children = listChildren(file); |
| final String fileName = file.getName(); |
| if (children != null) { // is directory |
| if (isRootCall || SourceVersion.isIdentifier(fileName)) { |
| if (sortFiles != null) { |
| Arrays.sort(children, sortFiles); |
| } |
| for (File child : children) { |
| listDirectoryRecursively(child, fileKinds, resultList, false); |
| } |
| } |
| } |
| else { |
| if (isValidFile(fileName, fileKinds)) { |
| JavaFileObject fe = new InputFileObject(this, file); |
| resultList.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; |
| } |
| |
| // 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 static boolean contains(RelativePath.RelativeDirectory subdirectory, RelativePath.RelativeDirectory other) { |
| final String subdirPath = subdirectory.getPath(); |
| final String otherPath = other.getPath(); |
| return otherPath.length() > subdirPath.length() && otherPath.startsWith(subdirPath); |
| } |
| |
| private static boolean isValidFile(String name, Set<JavaFileObject.Kind> fileKinds) { |
| return fileKinds.contains(getKind(name)); |
| } |
| |
| private static class InputFileObject extends BaseFileObject { |
| private static final Kind[] ourAvailableKinds = Kind.values(); |
| private final String name; |
| private final File file; |
| private final Kind kind; |
| private Reference<File> absFileRef; |
| |
| public InputFileObject(JavacFileManager fileManager, File f) { |
| this(fileManager, f.getName(), f); |
| } |
| |
| public InputFileObject(JavacFileManager fileManager, String name, File f) { |
| super(fileManager); |
| this.name = name; |
| this.file = f; |
| kind = findKind(name); |
| } |
| |
| @Override |
| public URI toUri() { |
| try { |
| return convertToURI(file.getPath()); |
| } |
| catch (Throwable e) { |
| return file.toURI().normalize(); // fallback |
| } |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public String getName() { |
| return file.getPath(); |
| } |
| |
| @Override |
| public String getShortName() { |
| return name; |
| } |
| |
| @Override |
| public JavaFileObject.Kind getKind() { |
| return kind; |
| } |
| |
| private static JavaFileObject.Kind findKind(String name) { |
| for (Kind kind : ourAvailableKinds) { |
| if (kind != Kind.OTHER && name.endsWith(kind.extension)) { |
| return kind; |
| } |
| } |
| return Kind.OTHER; |
| } |
| |
| @Override |
| public InputStream openInputStream() throws IOException { |
| return new FileInputStream(file); |
| } |
| |
| @Override |
| public OutputStream openOutputStream() throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public Writer openWriter() throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public long getLastModified() { |
| return file.lastModified(); |
| } |
| |
| @Override |
| public boolean delete() { |
| return file.delete(); |
| } |
| |
| @Override |
| protected CharsetDecoder getDecoder(boolean ignoreEncodingErrors) { |
| return fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors); |
| } |
| |
| @Override |
| protected String inferBinaryName(Iterable<? extends File> path) { |
| final String fPath = file.getPath(); |
| for (File dir: path) { |
| String dirPath = dir.getPath(); |
| if (dirPath.length() == 0) { |
| dirPath = System.getProperty("user.dir"); |
| } |
| if (!fPath.regionMatches(!isFileSystemCaseSensitive, 0, dirPath, 0, dirPath.length())) { |
| continue; |
| } |
| final int pathLength = fPath.length(); |
| final boolean endsWithSeparator = dirPath.endsWith(File.separator); |
| if (!endsWithSeparator) { |
| // need to check if the next char in fPath is file separator |
| final int separatorIdx = dirPath.length(); |
| if (pathLength <= separatorIdx || fPath.charAt(separatorIdx) != File.separatorChar) { |
| continue; |
| } |
| } |
| // fPath starts with dirPath |
| final int startIndex = endsWithSeparator ? dirPath.length() : dirPath.length() + 1; |
| int endIndex = fPath.lastIndexOf('.'); |
| if (endIndex <= startIndex) { |
| endIndex = fPath.length(); |
| } |
| final int length = endIndex - startIndex; |
| final StringBuilder buf = new StringBuilder(length).append(fPath, startIndex, endIndex); |
| for (int idx = 0; idx < length; idx++) { |
| if (buf.charAt(idx) == File.separatorChar) { |
| buf.setCharAt(idx, '.'); |
| } |
| } |
| return buf.toString(); |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean isNameCompatible(String cn, JavaFileObject.Kind kind) { |
| cn.getClass(); |
| // null check |
| if (kind == Kind.OTHER && getKind() != kind) { |
| return false; |
| } |
| final String n = cn + kind.extension; |
| if (name.equals(n)) { |
| return true; |
| } |
| if (name.equalsIgnoreCase(n)) { |
| // if we are on a case-insensitive file system, |
| // try to compare against the real (exactly as on the disk) file name |
| // |
| try { |
| //noinspection Since15 |
| return n.equals(file.toPath().toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString()); |
| } |
| catch (IOException ignored) { |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check if two file objects are equal. |
| * Two RegularFileObjects are equal if the absolute paths of the underlying |
| * files are equal. |
| */ |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) |
| return true; |
| |
| if (!(other instanceof InputFileObject)) |
| return false; |
| |
| InputFileObject o = (InputFileObject) other; |
| return getAbsoluteFile().equals(o.getAbsoluteFile()); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getAbsoluteFile().hashCode(); |
| } |
| |
| private File getAbsoluteFile() { |
| File absFile = (absFileRef == null ? null : absFileRef.get()); |
| if (absFile == null) { |
| absFile = file.getAbsoluteFile(); |
| absFileRef = new SoftReference<File>(absFile); |
| } |
| return absFile; |
| } |
| |
| public CharBuffer getCharContent(boolean ignoreEncodingErrors) throws IOException { |
| CharBuffer cb = ourUseContentCache? fileManager.getCachedContent(this) : null; |
| if (cb == null) { |
| InputStream in = new FileInputStream(file); |
| try { |
| final ByteBuffer bb = fileManager.makeByteBuffer(in); |
| final JavaFileObject prev = fileManager.log.useSource(this); |
| try { |
| cb = fileManager.decode(bb, ignoreEncodingErrors); |
| } |
| finally { |
| fileManager.log.useSource(prev); |
| } |
| fileManager.recycleByteBuffer(bb); |
| if (ourUseContentCache && !ignoreEncodingErrors) { |
| fileManager.cache(this, cb); |
| } |
| } |
| finally { |
| in.close(); |
| } |
| } |
| return cb; |
| } |
| } |
| |
| public void close() { |
| try { |
| super.close(); |
| } |
| finally { |
| // archives are cleared in super.close() |
| myDirectoryCache.clear(); |
| myIsFile.clear(); |
| } |
| } |
| } |