package org.sugarj.driver; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.spoofax.interpreter.terms.IStrategoTerm; import org.spoofax.jsglr.shared.BadTokenException; import org.sugarj.common.ATermCommands; import org.sugarj.common.Environment; import org.sugarj.common.FileCommands; import org.sugarj.common.path.Path; import org.sugarj.common.path.RelativePath; import org.sugarj.util.AppendableObjectOutputStream; import org.sugarj.util.Pair; /** * @author Sebastian Erdweg <seba at informatik uni-marburg de> */ public class Result { /** * Caching for results */ private static HashMap<Path, Result> results = new HashMap<Path, Result>(); /** * Path and hash of the disk-stored version of this result. * If the result was not stored yet, both variables are null. */ private Path persistentPath; private Integer persistentHash = null; /** * This is a parse result if it was produced during parsing. */ private Path parseResultPath; private Map<Path, Integer> dependencies = new HashMap<Path, Integer>(); private Set<Path> circularDependencies = new HashSet<Path>(); private Map<Path, Integer> generatedFileHashes = new HashMap<Path, Integer>(); private Map<RelativePath, Integer> dependingFileHashes = new HashMap<RelativePath, Integer>(); private Set<IStrategoTerm> editorServices = new HashSet<IStrategoTerm>(); private List<String> collectedErrors = new LinkedList<String>(); private Set<BadTokenException> parseErrors = new HashSet<BadTokenException>(); private IStrategoTerm sugaredSyntaxTree = null; private IStrategoTerm desugaredSyntaxTree; private Path parseTableFile; private Path desugaringsFile; private RelativePath sourceFile; private Integer sourceFileHash; private Map<Path, Integer> allDependentFiles = new HashMap<Path, Integer>(); private boolean failed = false; private Path generationLog; /** * deferred source files (*.sugj) -> * to-be-compiled source files (e.g., *.java + generated SourceFileContent) */ private Map<Path, Pair<Path, String>> deferredSourceFiles = new HashMap<Path, Pair<Path, String>>(); public Result() { } public Result(Path parseResultPath) { this.parseResultPath = parseResultPath; } public void addFileDependency(RelativePath file) { int hash; try { hash = FileCommands.fileHash(file); } catch (IOException e) { e.printStackTrace(); hash = -1; } dependingFileHashes.put(file, hash); allDependentFiles.put(file, hash); } void addCircularDependency(Path depFile) throws IOException { circularDependencies.add(depFile); } public void addDependency(Result result) throws IOException { if (result.persistentPath != null && !result.hasPersistentVersionChanged()) dependencies.put(result.persistentPath, result.persistentHash); allDependentFiles.putAll(result.getTransitiveFileDependencies()); } public void addDependency(Path depFile) throws IOException { dependencies.put(depFile, FileCommands.fileHash(depFile)); Result result = readDependencyFile(depFile); allDependentFiles.putAll(result.getTransitiveFileDependencies()); } public boolean hasDependency(Path otherDep, Environment env) throws IOException { if (dependencies.containsKey(otherDep)) return true; for (Path dep : dependencies.keySet()) if (Result.readDependencyFile(dep).hasDependency(otherDep, env)) return true; return false; } public Map<RelativePath, Integer> getFileDependencies() { return dependingFileHashes; } public Map<Path, Integer> getTransitiveFileDependencies() throws IOException { if (allDependentFiles == null) { Map<Path, Integer> deps = new HashMap<Path, Integer>(); deps.putAll(generatedFileHashes); deps.putAll(dependingFileHashes); for (Path depFile : dependencies.keySet()) deps.putAll(readDependencyFile(depFile).getTransitiveFileDependencies()); synchronized(this) { allDependentFiles = deps; } } return allDependentFiles; } public Collection<Path> getCircularFileDependencies(Environment env) throws IOException { assert persistentPath != null; Set<Path> dependencies = new HashSet<Path>(); Set<Path> visited = new HashSet<Path>(); LinkedList<Path> queue = new LinkedList<Path>(); queue.add(persistentPath); visited.add(persistentPath); while (!queue.isEmpty()) { Path dep = queue.pop(); Result res = readDependencyFile(dep); for (Path p : res.generatedFileHashes.keySet()) if (!dependencies.contains(p) && FileCommands.exists(p)) dependencies.add(p); for (Path p : res.dependingFileHashes.keySet()) if (!dependencies.contains(p) && FileCommands.exists(p)) dependencies.add(p); for (Path nextDep : res.dependencies.keySet()) if (!visited.contains(nextDep)) { queue.addFirst(nextDep); visited.add(dep); } for (Path nextDep : res.circularDependencies) if (!visited.contains(nextDep)) { queue.addFirst(nextDep); visited.add(dep); } } return dependencies; } public Set<Path> getDirectlyGeneratedFiles() { return generatedFileHashes.keySet(); } public void setGenerationLog(Path file) { this.generationLog = file; } public Path getGenerationLog() { return generationLog; } private void logGeneration(Object o) throws IOException { if (generationLog != null) { boolean exists = FileCommands.exists(generationLog); if (!exists) FileCommands.createFile(generationLog); ObjectOutputStream oos = exists ? new AppendableObjectOutputStream(new FileOutputStream(generationLog.getFile(), true)) : new ObjectOutputStream(new FileOutputStream(generationLog.getFile())); try { oos.writeObject(o); } finally { oos.close(); } } } public void generateFile(RelativePath file, String content) throws IOException { FileCommands.writeToFile(file, content); int hash = FileCommands.fileHash(file); logFileGeneration(file, hash); } private void logFileGeneration(Path file, int hash) throws IOException { generatedFileHashes.put(file, hash); allDependentFiles.put(file, hash); if (file instanceof RelativePath) dependingFileHashes.put((RelativePath) file, hash); logGeneration(file); } public void addEditorService(IStrategoTerm service) { editorServices.add(service); } public Set<IStrategoTerm> getEditorServices() { return editorServices; } public boolean hasPersistentVersionChanged() throws IOException { return persistentPath != null && persistentHash != null && FileCommands.fileHash(persistentPath) != persistentHash; } public boolean hasSourceFileChanged(Path inputFile) throws IOException { return inputFile == null || hasSourceFileChanged(FileCommands.fileHash(inputFile)); } private boolean hasSourceFileChanged(int inputHash) { return sourceFileHash == null || inputHash != sourceFileHash; } public boolean isUpToDateShallow(Path inputFile, Environment env) throws IOException { return isUpToDateShallow(FileCommands.fileHash(inputFile), env); } public boolean isUpToDate(Environment env) throws IOException { return isUpToDate(persistentPath, env); } public boolean isUpToDate(Path inputFile, Environment env) throws IOException { return isUpToDate(FileCommands.fileHash(inputFile), env); } private boolean isUpToDateShallow(int inputHash, Environment env) throws IOException { if (hasPersistentVersionChanged()) return false; if (hasSourceFileChanged(inputHash)) return false; if (desugaringsFile != null && !FileCommands.exists(desugaringsFile)) return false; for (Entry<Path, Integer> entry : generatedFileHashes.entrySet()) if (FileCommands.fileHash(entry.getKey()) != entry.getValue()) return false; for (Entry<RelativePath, Integer> entry : dependingFileHashes.entrySet()) if (FileCommands.fileHash(entry.getKey()) != entry.getValue()) return false; for (Entry<Path, Integer> entry : dependencies.entrySet()) if (FileCommands.fileHash(entry.getKey()) != entry.getValue()) return false; return true; } public boolean isUpToDate(int inputHash, Environment env) throws IOException { if (!isUpToDateShallow(inputHash, env)) return false; Set<Path> checked = new HashSet<Path>(); Set<Path> dependends = new HashSet<Path>(dependencies.keySet()); while (!dependends.isEmpty()) { Path path = dependends.iterator().next(); dependends.remove(path); if (checked.contains(path)) continue; Result r = Result.readDependencyFile(path); if (!isParseResult() && r.isParseResult()) // rebuild if compiled-result dependency to parsed-result dependency return false; if (r == null || !r.isUpToDateShallow(r.getSourceFile(), env)) return false; dependends.addAll(r.dependencies.keySet()); checked.add(path); } return true; } public void logError(String error) { collectedErrors.add(error); } public List<String> getCollectedErrors() { return collectedErrors; } public void logParseError(BadTokenException e) { parseErrors.add(e); } public Set<BadTokenException> getParseErrors() { return parseErrors; } public void setSugaredSyntaxTree(IStrategoTerm sugaredSyntaxTree) { this.sugaredSyntaxTree = sugaredSyntaxTree; } public IStrategoTerm getSugaredSyntaxTree() { return sugaredSyntaxTree; } public void setDesugaredSyntaxTree(IStrategoTerm desugaredSyntaxTree) { this.desugaredSyntaxTree = desugaredSyntaxTree; } public IStrategoTerm getDesugaredSyntaxTree() { return desugaredSyntaxTree; } void delegateCompilation(Result delegate, Path compileFile, String source, boolean hasNonBaseDec) { delegate.deferredSourceFiles.putAll(deferredSourceFiles); if (!source.isEmpty() || hasNonBaseDec) delegate.deferredSourceFiles.put(sourceFile, Pair.create(compileFile, source)); } boolean isDelegateOf(Path compileFile) { return deferredSourceFiles.containsKey(compileFile); } void resetDelegation() { deferredSourceFiles.clear(); } public void registerParseTable(Path tbl) { this.parseTableFile = tbl; } public Path getParseTable() { return parseTableFile; } public void registerEditorDesugarings(Path jarfile) throws IOException { desugaringsFile = jarfile; editorServices = new HashSet<IStrategoTerm>(ATermCommands.registerSemanticProvider(editorServices, jarfile)); } public Path getDesugaringsFile() { return desugaringsFile; } private void setPersistentPath(Path dep) throws IOException { persistentPath = dep; persistentHash = FileCommands.fileHash(dep); } public Path getPersistentPath() { return persistentPath; } public void setSourceFile(RelativePath sourceFile, int sourceFileHash) { this.sourceFile = sourceFile; this.sourceFileHash = sourceFileHash; } public RelativePath getSourceFile() { return sourceFile; } public boolean hasFailed() { return failed; } public void setFailed(boolean hasFailed) { this.failed = hasFailed; } public boolean isGenerated() { return sourceFile != null && "model".equals(FileCommands.getExtension(sourceFile)); } public Map<Path, Pair<Path, String>> getDeferredSourceFiles() { return deferredSourceFiles; } public Map<Path, Integer> getGeneratedFileHashes() { return generatedFileHashes; } public boolean isParseResult() { return parseResultPath != null; } public Path getParseResultPath() { return parseResultPath; } public void rewriteDependencyFile() throws IOException { if (persistentPath == null) throw new IllegalStateException("Result not previously written to file."); writeDependencyFile(persistentPath); } public void writeDependencyFile(Path dep) throws IOException { logGeneration(dep); ObjectOutputStream oos = null; try { FileCommands.createFile(dep); oos = new ObjectOutputStream(new FileOutputStream(dep.getFile())); oos.writeObject(parseResultPath); oos.writeObject(sourceFile); oos.writeInt(sourceFileHash); oos.writeObject(dependencies); oos.writeObject(circularDependencies); oos.writeObject(generatedFileHashes); oos.writeObject(dependingFileHashes); oos.writeObject(deferredSourceFiles); cacheInMemory(dep, this); } finally { if (oos != null) oos.close(); } setPersistentPath(dep); } @SuppressWarnings("unchecked") public static Result readDependencyFile(Path dep) throws IOException { Result result = getCachedResult(dep); if (result != null) return result; result = new Result(); result.allDependentFiles = null; ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(dep.getFile())); result.parseResultPath = (Path) ois.readObject(); result.sourceFile = (RelativePath) ois.readObject(); result.sourceFileHash = ois.readInt(); result.dependencies = (Map<Path, Integer>) ois.readObject(); result.circularDependencies = (Set<Path>) ois.readObject(); result.generatedFileHashes = (Map<Path, Integer>) ois.readObject(); result.dependingFileHashes = (Map<RelativePath, Integer>) ois.readObject(); result.deferredSourceFiles = (Map<Path, Pair<Path, String>>) ois.readObject(); cacheInMemory(dep, result); } catch (FileNotFoundException e) { return null; } catch (ClassNotFoundException e) { e.printStackTrace(); throw new IOException(e); } catch (Exception e) { return null; } finally { if (ois != null) ois.close(); } result.setPersistentPath(dep); return result; } /** * Moves this result and the files generated by this result to the given target directory. * @return the moved result. */ public Result moveTo(Path targetDir, boolean isParseResult) throws IOException { Result res = new Result(isParseResult ? targetDir : null); res.dependencies = new HashMap<Path, Integer>(); for (Path dep : dependencies.keySet()) { Result other = readDependencyFile(dep); if (other.isParseResult()) other.moveTo(targetDir, isParseResult); res.addDependency(other); } res.circularDependencies = circularDependencies; res.dependingFileHashes = dependingFileHashes; res.collectedErrors = collectedErrors; res.parseErrors = parseErrors; res.sugaredSyntaxTree = sugaredSyntaxTree; res.sourceFile = sourceFile; res.sourceFileHash = sourceFileHash; res.allDependentFiles = allDependentFiles; res.failed = failed; res.deferredSourceFiles = deferredSourceFiles; res.editorServices = editorServices; res.desugaringsFile = FileCommands.tryCopyFile(parseResultPath, targetDir, desugaringsFile); res.parseTableFile = FileCommands.tryCopyFile(parseResultPath, targetDir, parseTableFile); res.generationLog = FileCommands.tryCopyFile(parseResultPath, targetDir, generationLog); res.generatedFileHashes = new HashMap<Path, Integer>(generatedFileHashes.size()); for (Entry<Path, Integer> e : generatedFileHashes.entrySet()) { Path p = FileCommands.tryCopyFile(parseResultPath, targetDir, e.getKey()); res.logFileGeneration(p, FileCommands.fileHash(p)); } RelativePath wasDep = FileCommands.getRelativePath(parseResultPath, persistentPath); Path dep = persistentPath; if (wasDep != null) dep = new RelativePath(targetDir, wasDep.getRelativePath()); res.writeDependencyFile(dep); return res; } public static void cacheInMemory(Path dep, Result result) { synchronized (results) { results.put(dep, result); // results.put(dep, new SoftReference<Result>(result)); } } public static Result getCachedResult(Path dep) throws IOException { Result result = null; synchronized (results) { result = results.get(dep); // SoftReference<Result> ref = results.get(dep); // if (ref != null) // result = ref.get(); } if (result != null && !result.hasPersistentVersionChanged()) return result; return null; } }