blob: 3b0f143d4fbfe335c47fe0cfd34799bcd4cd8006 [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.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;
}
}
}
}
}