/*******************************************************************************
* Copyright (c) 2009-2011 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
* * Bas Basten - Bas.Basten@cwi.nl (CWI)
* * Jouke Stoel - Jouke.Stoel@cwi.nl (CWI)
* * Mark Hills - Mark.Hills@cwi.nl (CWI)
* * Arnold Lankamp - Arnold.Lankamp@cwi.nl
* * Anastasia Izmaylova - A.Izmaylova@cwi.nl - CWI
* * Davy Landman - Davy.Landman@cwi.nl (CWI)
*******************************************************************************/
package org.rascalmpl.library.lang.java.m3.internal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.function.BiConsumer;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.rascalmpl.interpreter.IEvaluatorContext;
import org.rascalmpl.interpreter.control_exceptions.InterruptException;
import org.rascalmpl.interpreter.utils.RuntimeExceptionFactory;
import org.rascalmpl.library.experiments.Compiler.RVM.Interpreter.RascalRuntimeException;
import org.rascalmpl.parser.gtd.io.InputConverter;
import org.rascalmpl.unicode.UnicodeDetector;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.value.IBool;
import org.rascalmpl.value.IList;
import org.rascalmpl.value.IListWriter;
import org.rascalmpl.value.ISet;
import org.rascalmpl.value.ISetWriter;
import org.rascalmpl.value.ISourceLocation;
import org.rascalmpl.value.IString;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.IValueFactory;
import org.rascalmpl.value.type.TypeStore;
public class EclipseJavaCompiler {
protected final IValueFactory VF;
public EclipseJavaCompiler(IValueFactory vf) {
this.VF = vf;
}
public IValue createM3FromJarClass(ISourceLocation jarLoc, IEvaluatorContext eval) {
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::Core").getStore());
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
JarConverter converter = new JarConverter(store, new HashMap<>());
converter.convert(jarLoc, eval);
return converter.getModel(false);
}
public IValue createM3sFromFiles(ISet files, IBool errorRecovery, IList sourcePath, IList classPath, IString javaVersion, IEvaluatorContext eval) {
try {
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::Core").getStore());
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
Map<String, ISourceLocation> cache = new HashMap<>();
ISetWriter result = VF.setWriter();
buildCompilationUnits(files, true, errorRecovery.getValue(), sourcePath, classPath, javaVersion, (loc, cu) -> {
checkInterrupted(eval);
result.insert(convertToM3(store, cache, loc, cu));
});
return result.done();
} catch (IOException e) {
throw RuntimeExceptionFactory.io(VF.string(e.getMessage()), null, null);
}
}
private void checkInterrupted(IEvaluatorContext eval) {
if (eval.isInterrupted()) {
throw new InterruptException(eval.getStackTrace(), eval.getCurrentAST().getLocation());
}
}
public IValue createM3sAndAstsFromFiles(ISet files, IBool errorRecovery, IList sourcePath, IList classPath, IString javaVersion, IEvaluatorContext eval) {
try {
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::Core").getStore());
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
Map<String, ISourceLocation> cache = new HashMap<>();
ISetWriter m3s = VF.setWriter();
ISetWriter asts = VF.setWriter();
buildCompilationUnits(files, true, errorRecovery.getValue(), sourcePath, classPath, javaVersion, (loc, cu) -> {
checkInterrupted(eval);
m3s.insert(convertToM3(store, cache, loc, cu));
asts.insert(convertToAST(VF.bool(true), cache, loc, cu, store));
});
return VF.tuple(m3s.done(), asts.done());
} catch (IOException e) {
throw RuntimeExceptionFactory.io(VF.string(e.getMessage()), null, null);
}
}
public IValue createM3FromString(ISourceLocation loc, IString contents, IBool errorRecovery, IList sourcePath, IList classPath, IString javaVersion, IEvaluatorContext eval) {
try {
CompilationUnit cu = getCompilationUnit(loc.getPath(), contents.getValue().toCharArray(), true, errorRecovery.getValue(), javaVersion, translatePaths(sourcePath), translatePaths(classPath));
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::Core").getStore());
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
return convertToM3(store, new HashMap<>(), loc, cu);
} catch (IOException e) {
throw RuntimeExceptionFactory.io(VF.string(e.getMessage()), null, null);
}
}
public IValue createAstsFromFiles(ISet files, IBool collectBindings, IBool errorRecovery, IList sourcePath, IList classPath, IString javaVersion,
IEvaluatorContext eval) {
try {
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
Map<String, ISourceLocation> cache = new HashMap<>();
ISetWriter result = VF.setWriter();
buildCompilationUnits(files, collectBindings.getValue(), errorRecovery.getValue(), sourcePath, classPath, javaVersion, (loc, cu) -> {
checkInterrupted(eval);
result.insert(convertToAST(collectBindings, cache, loc, cu, store));
});
return result.done();
} catch (IOException e) {
throw RuntimeExceptionFactory.io(VF.string(e.getMessage()), null, null);
}
}
public IValue createAstFromString(ISourceLocation loc, IString contents, IBool collectBindings, IBool errorRecovery, IList sourcePath, IList classPath, IString javaVersion,
IEvaluatorContext eval) {
try {
CompilationUnit cu = getCompilationUnit(loc.getPath(), contents.getValue().toCharArray(), collectBindings.getValue(), errorRecovery.getValue(), javaVersion, translatePaths(sourcePath), translatePaths(classPath));
TypeStore store = new TypeStore();
store.extendStore(eval.getHeap().getModule("lang::java::m3::AST").getStore());
return convertToAST(collectBindings, new HashMap<>(), loc, cu, store);
} catch (IOException e) {
throw RuntimeExceptionFactory.io(VF.string(e.getMessage()), null, null);
}
}
protected IValue convertToM3(TypeStore store, Map<String, ISourceLocation> cache, ISourceLocation loc,
CompilationUnit cu) {
SourceConverter converter = new SourceConverter(store, cache);
converter.convert(cu, cu, loc);
for (Object cm: cu.getCommentList()) {
Comment comment = (Comment)cm;
// Issue 720: changed condition to only visit comments without a parent (includes line, block and misplaced javadoc comments).
if (comment.getParent() != null)
continue;
converter.convert(cu, comment, loc);
}
return converter.getModel(true);
}
protected void buildCompilationUnits(ISet files, boolean resolveBindings, boolean errorRecovery, IList sourcePath, IList classPath, IString javaVersion, BiConsumer<ISourceLocation, CompilationUnit> buildNotifier) throws IOException {
boolean fastPath = true;
for (IValue f : files) {
fastPath &= safeResolve((ISourceLocation)f).getScheme().equals("file");
}
if (fastPath) {
Map<String, ISourceLocation> reversePathLookup = new HashMap<>();
String[] absolutePaths = new String[files.size()];
String[] encodings = new String[absolutePaths.length];
int i = 0;
for (IValue p : files) {
ISourceLocation loc = (ISourceLocation)p;
if (!URIResolverRegistry.getInstance().isFile(loc)) {
throw RuntimeExceptionFactory.io(VF.string("" + loc + " is not a file"), null, null);
}
if (!URIResolverRegistry.getInstance().exists(loc)) {
throw RuntimeExceptionFactory.io(VF.string("" + loc + " doesn't exist"), null, null);
}
absolutePaths[i] = new File(safeResolve(loc).getPath()).getAbsolutePath();
reversePathLookup.put(absolutePaths[i], loc);
encodings[i] = guessEncoding(loc);
i++;
}
ASTParser parser = constructASTParser(resolveBindings, errorRecovery, javaVersion, translatePaths(sourcePath), translatePaths(classPath));
parser.createASTs(absolutePaths, encodings, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit ast) {
buildNotifier.accept(reversePathLookup.get(sourceFilePath), ast);
}
}, null);
}
else {
for (IValue file: files) {
ISourceLocation loc = (ISourceLocation) file;
CompilationUnit cu = getCompilationUnit(loc.getPath(), getFileContents(loc), resolveBindings, errorRecovery, javaVersion, translatePaths(sourcePath), translatePaths(classPath));
buildNotifier.accept(loc, cu);
}
}
}
protected String[] translatePaths(IList paths) {
String[] result = new String[paths.length()];
int i = 0;
for (IValue p : paths) {
ISourceLocation loc = safeResolve((ISourceLocation)p);
if (!loc.getScheme().equals("file")) {
throw RascalRuntimeException.io(VF.string("all path entries must have (or resolve to) the file:/// scheme: " + loc), null);
}
result[i++] = new File(loc.getPath()).getAbsolutePath();
}
return result;
}
private ISourceLocation safeResolve(ISourceLocation loc) {
try {
ISourceLocation result = URIResolverRegistry.getInstance().logicalToPhysical(loc);
if (result != null) {
return result;
}
return loc;
}
catch (IOException e) {
return loc;
}
}
protected String guessEncoding(ISourceLocation loc) {
try {
Charset result = URIResolverRegistry.getInstance().getCharset(loc);
if (result != null) {
return result.name();
}
try (InputStream file = URIResolverRegistry.getInstance().getInputStream(loc)) {
return UnicodeDetector.estimateCharset(file).name();
}
}
catch (Throwable x) {
return null;
}
}
protected CompilationUnit getCompilationUnit(String unitName, char[] contents, boolean resolveBindings, boolean errorRecovery, IString javaVersion, String[] sourcePath, String[] classPath)
throws IOException {
ASTParser parser = constructASTParser(resolveBindings, errorRecovery, javaVersion, sourcePath, classPath);
parser.setUnitName(unitName);
parser.setSource(contents);
return (CompilationUnit) parser.createAST(null);
}
protected IValue convertToAST(IBool collectBindings, Map<String, ISourceLocation> cache, ISourceLocation loc,
CompilationUnit cu, TypeStore store) {
ASTConverter converter = new ASTConverter(store, cache, collectBindings.getValue());
converter.convert(cu, cu, loc);
converter.insertCompilationUnitMessages(true, null);
return converter.getValue();
}
protected ASTParser constructASTParser(boolean resolveBindings, boolean errorRecovery, IString javaVersion, String[] sourcePath, String[] classPath) {
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setResolveBindings(resolveBindings);
parser.setBindingsRecovery(true);
parser.setStatementsRecovery(errorRecovery);
Hashtable<String, String> options = new Hashtable<String, String>();
options.put(JavaCore.COMPILER_SOURCE, javaVersion.getValue());
options.put(JavaCore.COMPILER_COMPLIANCE, javaVersion.getValue());
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, "enabled");
parser.setCompilerOptions(options);
parser.setEnvironment(classPath, sourcePath, null, true);
return parser;
}
protected char[] getFileContents(ISourceLocation loc) throws IOException {
try (Reader textStream = URIResolverRegistry.getInstance().getCharacterReader(loc)) {
return InputConverter.toChar(textStream);
}
}
}