package lombok.core.configuration;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import lombok.ConfigurationKeys;
import lombok.core.configuration.ConfigurationSource.Result;
import lombok.core.debug.ProblemReporter;
public class FileSystemSourceCache {
private static final String LOMBOK_CONFIG_FILENAME = "lombok.config";
private static final long FULL_CACHE_CLEAR_INTERVAL = TimeUnit.MINUTES.toMillis(30);
private static final long RECHECK_FILESYSTEM = TimeUnit.SECONDS.toMillis(2);
private static final long NEVER_CHECKED = -1;
private static final long MISSING = -88;
private final ConcurrentMap<File, Content> dirCache = new ConcurrentHashMap<File, Content>();
private final ConcurrentMap<URI, File> uriCache = new ConcurrentHashMap<URI, File>();
private volatile long lastCacheClear = System.currentTimeMillis();
private void cacheClear() {
long now = System.currentTimeMillis();
long delta = now - lastCacheClear;
if (delta > FULL_CACHE_CLEAR_INTERVAL) {
lastCacheClear = now;
dirCache.clear();
uriCache.clear();
}
}
public Iterable<ConfigurationSource> sourcesForJavaFile(URI javaFile, ConfigurationProblemReporter reporter) {
if (javaFile == null) return Collections.emptyList();
cacheClear();
File dir = uriCache.get(javaFile);
if (dir == null) {
URI uri = javaFile.normalize();
if (!uri.isAbsolute()) uri = URI.create("file:" + uri.toString());
try {
File file = new File(uri);
if (!file.exists()) throw new IllegalArgumentException("File does not exist: " + uri);
dir = file.isDirectory() ? file : file.getParentFile();
if (dir != null) uriCache.put(javaFile, dir);
} catch (IllegalArgumentException e) {
} catch (Exception e) {
ProblemReporter.error("Can't find absolute path of file being compiled: " + javaFile, e);
}
}
if (dir != null) {
try {
return sourcesForDirectory(dir, reporter);
} catch (Exception e) {
ProblemReporter.error("Can't resolve config stack for dir: " + dir.getAbsolutePath(), e);
}
}
return Collections.emptyList();
}
public Iterable<ConfigurationSource> sourcesForDirectory(URI directory, ConfigurationProblemReporter reporter) {
return sourcesForJavaFile(directory, reporter);
}
private Iterable<ConfigurationSource> sourcesForDirectory(final File directory, final ConfigurationProblemReporter reporter) {
return new Iterable<ConfigurationSource>() {
@Override
public Iterator<ConfigurationSource> iterator() {
return new Iterator<ConfigurationSource>() {
File currentDirectory = directory;
ConfigurationSource next;
boolean stopBubbling = false;
@Override
public boolean hasNext() {
if (next != null) return true;
if (stopBubbling) return false;
next = findNext();
return next != null;
}
@Override
public ConfigurationSource next() {
if (!hasNext()) throw new NoSuchElementException();
ConfigurationSource result = next;
next = null;
return result;
}
private ConfigurationSource findNext() {
while (currentDirectory != null && next == null) {
next = getSourceForDirectory(currentDirectory, reporter);
currentDirectory = currentDirectory.getParentFile();
}
if (next != null) {
Result stop = next.resolve(ConfigurationKeys.STOP_BUBBLING);
stopBubbling = (stop != null && Boolean.TRUE.equals(stop.getValue()));
}
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
ConfigurationSource getSourceForDirectory(File directory, ConfigurationProblemReporter reporter) {
long now = System.currentTimeMillis();
File configFile = new File(directory, LOMBOK_CONFIG_FILENAME);
Content content = ensureContent(directory);
synchronized (content) {
if (content.lastChecked != NEVER_CHECKED && now - content.lastChecked < RECHECK_FILESYSTEM) {
return content.source;
}
content.lastChecked = now;
long previouslyModified = content.lastModified;
content.lastModified = getLastModifiedOrMissing(configFile);
if (content.lastModified != previouslyModified) content.source = content.lastModified == MISSING ? null : parse(configFile, reporter);
return content.source;
}
}
private Content ensureContent(File directory) {
Content content = dirCache.get(directory);
if (content != null) {
return content;
}
dirCache.putIfAbsent(directory, Content.empty());
return dirCache.get(directory);
}
private ConfigurationSource parse(File configFile, ConfigurationProblemReporter reporter) {
String contentDescription = configFile.getAbsolutePath();
try {
return StringConfigurationSource.forString(fileToString(configFile), reporter, contentDescription);
} catch (Exception e) {
reporter.report(contentDescription, "Exception while reading file: " + e.getMessage(), 0, null);
return null;
}
}
private static final ThreadLocal<byte[]> buffers = new ThreadLocal<byte[]>() {
protected byte[] initialValue() {
return new byte[65536];
}
};
static String fileToString(File configFile) throws Exception {
byte[] b = buffers.get();
FileInputStream fis = new FileInputStream(configFile);
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (true) {
int r = fis.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
return new String(out.toByteArray(), "UTF-8");
} finally {
fis.close();
}
}
private static final long getLastModifiedOrMissing(File file) {
if (!file.exists() || !file.isFile()) return MISSING;
return file.lastModified();
}
private static class Content {
ConfigurationSource source;
long lastModified;
long lastChecked;
private Content(ConfigurationSource source, long lastModified, long lastChecked) {
this.source = source;
this.lastModified = lastModified;
this.lastChecked = lastChecked;
}
static Content empty() {
return new Content(null, MISSING, NEVER_CHECKED);
}
}
}