/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.internal;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import com.github.anba.es6draft.Script;
import com.github.anba.es6draft.compiler.CompilationException;
import com.github.anba.es6draft.parser.ParserException;
/**
* Simple cache for compiled script files.
*/
public final class ScriptCache {
private static final int DEFAULT_MAX_SIZE = 10;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final float DEFAULT_LOAD_FACTOR = .75f;
private final Map<CacheKey, Script> cache;
@SuppressWarnings("serial")
private static final class Cache extends LinkedHashMap<CacheKey, Script> {
private final int maxSize;
Cache(int maxSize, int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<CacheKey, Script> eldest) {
return size() > maxSize;
}
}
private static final class CacheKey {
private final URI uri;
private final long size;
private final long lastModified;
CacheKey(URI uri, long size, long lastModified) {
this.uri = uri;
this.size = size;
this.lastModified = lastModified;
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != CacheKey.class) {
return false;
}
CacheKey other = (CacheKey) obj;
return size == other.size && lastModified == other.lastModified && uri.equals(other.uri);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (lastModified ^ (lastModified >>> 32));
result = prime * result + (int) (size ^ (size >>> 32));
result = prime * result + uri.hashCode();
return result;
}
}
private CacheKey keyFor(Path path) throws IOException {
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);
return new CacheKey(path.toUri(), attributes.size(), attributes.lastModifiedTime().toMillis());
}
private CacheKey keyFor(URL url) throws URISyntaxException {
return new CacheKey(url.toURI(), 0L, 0L);
}
/**
* Constructs a new {@link ScriptCache} object.
*/
public ScriptCache() {
this(DEFAULT_MAX_SIZE, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new {@link ScriptCache} object.
*
* @param maxSize
* the maximum size
*/
public ScriptCache(int maxSize) {
this(maxSize, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new {@link ScriptCache} object.
*
* @param maxSize
* the maximum capacity
* @param initialCapacity
* the initial capacity
* @param loadFactor
* the load factor
*/
public ScriptCache(int maxSize, int initialCapacity, float loadFactor) {
this.cache = Collections.synchronizedMap(new Cache(maxSize, initialCapacity, loadFactor));
}
/**
* Compiles {@code file} to a {@link Script} and caches the result.
*
* @param scriptLoader
* the script loader
* @param file
* the script file path
* @return the compiled script
* @throws IOException
* if there was any I/O error
* @throws ParserException
* if the source contains any syntax errors
* @throws CompilationException
* if the parsed source could not be compiled
*/
public Script get(ScriptLoader scriptLoader, Path file) throws IOException, ParserException, CompilationException {
CacheKey cacheKey = keyFor(file);
Script cachedScript = cache.get(cacheKey);
if (cachedScript != null) {
return cachedScript;
}
Source source = new Source(file, Objects.requireNonNull(file.getFileName()).toString(), 1);
Script script = scriptLoader.script(source, file);
cache.put(cacheKey, script);
return script;
}
/**
* Compiles {@code file} to a {@link Script} and caches the result.
*
* @param scriptLoader
* the script loader
* @param file
* the script file URL
* @return the compiled script
* @throws IOException
* if there was any I/O error
* @throws URISyntaxException
* if the URL is not a valid URI
* @throws ParserException
* if the source contains any syntax errors
* @throws CompilationException
* if the parsed source could not be compiled
*/
public Script get(ScriptLoader scriptLoader, URL file)
throws IOException, URISyntaxException, ParserException, CompilationException {
CacheKey cacheKey = keyFor(file);
Script cachedScript = cache.get(cacheKey);
if (cachedScript != null) {
return cachedScript;
}
Source source = new Source(file.getPath(), 1);
Script script = scriptLoader.script(source, file);
cache.put(cacheKey, script);
return script;
}
}