package sharpen.xobotos;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import sharpen.core.Configuration.NameMapping;
import sharpen.core.Sharpen;
import sharpen.core.csharp.CSharpPrinter;
import sharpen.core.csharp.ast.CSCompilationUnit;
import sharpen.core.framework.ASTResolver;
import sharpen.core.framework.ByRef;
import sharpen.core.framework.CompilationUnitPair;
import sharpen.core.framework.DefaultASTResolver;
import sharpen.core.framework.Environments;
import sharpen.core.framework.resources.WorkspaceUtilities;
import sharpen.xobotos.api.APIDefinition;
import sharpen.xobotos.api.bindings.BindingManager;
import sharpen.xobotos.config.CSProjectFile;
import sharpen.xobotos.config.ConfigFile;
import sharpen.xobotos.config.LocationFilter;
import sharpen.xobotos.config.LocationFilter.Match;
import sharpen.xobotos.config.SourceInfo;
import sharpen.xobotos.config.xstream.RootContext;
import sharpen.xobotos.generator.CompilationUnitBuilder;
import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
public class XobotBuilder {
private final ConfigFile _configFile;
private final XobotConfiguration _config;
private final XobotLogger _logger;
private final IProject _project;
private final Map<ICompilationUnit, Boolean> _sources;
private final Map<ICompilationUnit, Boolean> _mustParse = new HashMap<ICompilationUnit, Boolean>();
private final URI _root;
private final SourceInfo _sourceInfo;
private final IFolder _sourceFolder;
private final IFolder _outputFolder;
private final APIDefinition _api;
private boolean _foundErrors;
final static int PARSING_PRICE = 5;
final static int GENERATING_PRICE = 3;
final static int OUTPUT_PRICE = 2;
protected XobotBuilder(ConfigFile configFile, XobotLogger logger, IProject project,
Map<ICompilationUnit, Boolean> sources) {
this._configFile = configFile;
this._logger = logger;
this._project = project;
this._sources = sources;
this._config = new XobotConfiguration(_configFile);
addLogFile();
_root = _project.getLocationURI();
_sourceInfo = _configFile.getSourceInfo();
_sourceFolder = _project.getFolder(_sourceInfo.getSourceFolder());
_outputFolder = _project.getFolder(_sourceInfo.getOutputFolder());
String apiFileName = configFile.getAPIDefinitionFileName();
_api = RootContext.readConfigurationFile(_root, apiFileName, APIDefinition.class, _config);
}
private void addLogFile() {
ConfigFile.LogFileEntry logFile = _configFile.getLogFile();
if (logFile == null)
return;
try {
File file = getProjectFile(logFile.path);
_logger.setLogFile(file, logFile.append);
} catch (Exception e) {
Sharpen.Log(e, "Cannot create log file '%s'", logFile.path);
}
}
public File getProjectFile(String fileName) {
return _project.getLocation().append(fileName).toFile();
}
public XobotConfiguration getConfig() {
return _config;
}
public ConfigFile getConfigFile() {
return _configFile;
}
protected boolean run(final IProgressMonitor monitor) {
Sharpen.Log(Level.INFO, "Starting build");
try {
checkFileList();
} catch (Exception e) {
Sharpen.Log(e, "Cannot compute file list");
return false;
}
int countModified = 0;
for (final Entry<ICompilationUnit, Boolean> entry : _sources.entrySet()) {
if (!entry.getValue())
continue;
++countModified;
_mustParse.put(entry.getKey(), true);
}
if (countModified < 1) {
Sharpen.Log(Level.INFO, "Nothing to do.");
return true;
}
List<ICompilationUnit> parsingList = new ArrayList<ICompilationUnit>();
for (final Entry<ICompilationUnit, Boolean> entry : _mustParse.entrySet()) {
if (!entry.getValue())
continue;
parsingList.add(entry.getKey());
}
final int totalWork = parsingList.size() * PARSING_PRICE + countModified
* (GENERATING_PRICE + OUTPUT_PRICE);
Sharpen.Log(Level.INFO, "Converting %d files (must parse %d).", countModified, parsingList.size());
monitor.beginTask("Converting", totalWork);
final List<CompilationUnitPair> pairs = stage1_parse(parsingList, monitor);
if (monitor.isCanceled() || (pairs == null) || (pairs.size() == 0))
return false;
if (!stage1b_checkForErrors(pairs)) {
Sharpen.Log(Level.SEVERE, "Found errors while parsing compilation units; aborting!");
return false;
}
AST ast = pairs.get(0).ast.getAST();
final BindingManager bindings = new BindingManager(ast, _configFile.getNativeConfig());
final ByRef<Map<ICompilationUnit, CompilationUnitBuilder>> builders = new ByRef<Map<ICompilationUnit, CompilationUnitBuilder>>();
Environments.runWith(Environments.newClosedEnvironment(_api, bindings, _config), new Runnable() {
@Override
public void run() {
builders.value = stage2_preprocess(bindings, pairs);
}
});
if (monitor.isCanceled())
return false;
if (builders.value == null) {
Sharpen.Log(Level.SEVERE, "Found errors while pre-processing; aborting!");
return false;
}
final List<CompilationUnitPair> modifiedPairs = new ArrayList<CompilationUnitPair>();
final List<CompilationUnitBuilder> modified = new ArrayList<CompilationUnitBuilder>();
for (final CompilationUnitBuilder builder : builders.value.values()) {
if (_sources.get(builder.getPair().source)) {
modifiedPairs.add(builder.getPair());
modified.add(builder);
}
}
final ASTResolver resolver = new DefaultASTResolver(modifiedPairs);
final ByRef<Boolean> ok = new ByRef<Boolean>();
Environments.runWith(Environments.newClosedEnvironment(_api, bindings, _config, _configFile, resolver),
new Runnable() {
@Override
public void run() {
ok.value = stage3_generate(monitor, modified);
}
});
if (monitor.isCanceled() || !ok.value)
return false;
Environments.runWith(Environments.newClosedEnvironment(_api, bindings, _config, _configFile, resolver),
new Runnable() {
@Override
public void run() {
// ok.value =
// stage4_post_processs(monitor,
// modified);
ok.value = bindings.postProcess();
}
});
if (monitor.isCanceled() || !ok.value)
return false;
Environments.runWith(Environments.newClosedEnvironment(_api, bindings, _config, _configFile, resolver),
new Runnable() {
@Override
public void run() {
ok.value = stage4_save_output(monitor, modified);
}
});
if (monitor.isCanceled() || !ok.value)
return false;
return stage5_generate_csproj();
}
public static boolean run(ConfigFile configFile, IProject project, Map<ICompilationUnit, Boolean> sources,
IProgressMonitor monitor) {
final Date startTime = new Date();
final XobotLogger logger = new XobotLogger();
boolean foundErrors = false;
try {
XobotBuilder builder = new XobotBuilder(configFile, logger, project, sources);
if (!builder.run(monitor)) {
foundErrors = true;
return false;
}
foundErrors = builder._foundErrors;
return true;
} catch (Exception e) {
Sharpen.Log(e, "Sharpen builder caught unhandled exception");
foundErrors = true;
return false;
} finally {
if (monitor.isCanceled())
Sharpen.Log(Level.SEVERE, "Sharpening aborted at %s.", formatEndTime(startTime));
else if (foundErrors)
Sharpen.Log(Level.SEVERE, "Conversion finished with errors at %s.",
formatEndTime(startTime));
else
Sharpen.Log(Level.SEVERE, "Conversion finished successfully at %s.",
formatEndTime(startTime));
monitor.done();
logger.close();
}
}
private static String formatEndTime(Date startTime) {
Date endTime = new Date();
long timeDiff = endTime.getTime() - startTime.getTime();
return String.format("%s (%d seconds)", endTime, timeDiff / 1000);
}
private void collectAllFiles(ArrayList<String> files, URI root, File directory, String suffix) {
for (final File file : directory.listFiles()) {
if (file.isDirectory())
collectAllFiles(files, root, file, suffix);
else {
if ((suffix != null) && !file.getName().endsWith(suffix))
continue;
files.add(root.relativize(file.toURI()).getPath());
}
}
}
private void checkFileList() {
final IPath sourcePath = _sourceFolder.getLocation();
final IPath outputPath = _outputFolder.getLocation();
final URI outputURI = outputPath.toFile().toURI();
final SourceInfo sourceInfo = _configFile.getSourceInfo();
final List<LocationFilter> excludeFilters = sourceInfo.getLocationFilters();
ArrayList<String> extraCSharpSources = new ArrayList<String>();
for (final String dir : sourceInfo.getExtraCSharpSources()) {
File path = _project.getLocation().append(dir).toFile();
collectAllFiles(extraCSharpSources, path.toURI(), path, ".cs");
}
for (final Entry<ICompilationUnit, Boolean> entry : _sources.entrySet()) {
final ICompilationUnit unit = entry.getKey();
String unitName = getUnitName(unit);
String sourceName = unitName.replace('.', '/') + ".java";
File sourceFile = sourcePath.append(sourceName).toFile();
String outputName = unitName.replace('.', '/') + ".cs";
File outputFile = outputPath.append(outputName).toFile();
URI uri = outputURI.relativize(outputFile.toURI());
if (_api.compilationUnitDefinesBindings(unitName)) {
/*
* This CompilationUnit defines bindings. We
* must always parse it, but don't need to
* regenerate it if the output is up-to-date.
*/
_mustParse.put(unit, true);
}
if (extraCSharpSources.contains(uri.getPath())) {
entry.setValue(false);
continue;
}
if (outputFile.exists() && (outputFile.lastModified() >= sourceFile.lastModified())) {
entry.setValue(false);
continue;
}
/*
* Check location filters.
*/
Match match = Match.NO_MATCH;
for (final LocationFilter filter : excludeFilters) {
match = filter.matches(unitName);
if (match != Match.NO_MATCH)
break;
}
if (match != Match.NO_MATCH) {
entry.setValue(match == Match.POSITIVE);
continue;
}
/*
* Default to building.
*/
entry.setValue(true);
}
}
public String getUnitName(ICompilationUnit unit) {
String packageName = unit.getParent().getElementName();
String unitName = unit.getElementName().split("\\.")[0];
if (packageName.length() > 0)
unitName = _config.applyNamespaceMappings(packageName) + '.' + unitName;
return unitName;
}
private List<CompilationUnitPair> stage1_parse(final List<ICompilationUnit> sources, IProgressMonitor monitor) {
final int totalWork = PARSING_PRICE * sources.size();
final IProgressMonitor subMonitor = new SubProgressMonitor(monitor, totalWork);
final ArrayList<CompilationUnitPair> pairs = new ArrayList<CompilationUnitPair>(sources.size());
ASTRequestor requestor = new ASTRequestor() {
@Override
public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
pairs.add(new CompilationUnitPair(source, ast));
subMonitor.subTask(String.format("Parsing (%d/%d): %s", pairs.size(), sources.size(),
getUnitName(source)));
subMonitor.worked(PARSING_PRICE);
}
};
final ASTParser _parser = ASTParser.newParser(AST.JLS3);
_parser.setKind(ASTParser.K_COMPILATION_UNIT);
_parser.setProject(sources.get(0).getJavaProject());
_parser.setResolveBindings(true);
final ICompilationUnit[] sourceArray = sources.toArray(new ICompilationUnit[0]);
Sharpen.Log(Level.INFO, "Parsing %d compilation units.", sources.size());
try {
subMonitor.beginTask("parsing compile units", totalWork);
_parser.createASTs(sourceArray, new String[0], requestor, null);
} finally {
subMonitor.done();
}
return pairs;
}
private boolean stage1b_checkForErrors(List<CompilationUnitPair> sources) {
for (final CompilationUnitPair pair : sources) {
for (final IProblem problem : pair.ast.getProblems()) {
if (problem.isError())
return false;
}
for (final IProblem problem : pair.getProblems()) {
if (problem.isError())
return false;
}
}
return true;
}
private Map<ICompilationUnit, CompilationUnitBuilder> stage2_preprocess(BindingManager bindings,
List<CompilationUnitPair> sources) {
Map<ICompilationUnit, CompilationUnitBuilder> builders = new HashMap<ICompilationUnit, CompilationUnitBuilder>();
boolean foundErrors = false;
for (final CompilationUnitPair pair : sources) {
CompilationUnitBuilder builder = _api.preprocess(pair);
if (builder == null) {
Sharpen.Log(Level.SEVERE, "Preprocessor failed on %s", getUnitName(pair.source));
continue;
}
builders.put(pair.source, builder);
}
if (foundErrors)
return null;
if (!bindings.resolveTypes()) {
Sharpen.Log(Level.SEVERE, "Failed to resolve some types!");
}
return builders;
}
private Boolean stage3_generate(IProgressMonitor monitor, List<CompilationUnitBuilder> builders) {
final int totalWork = GENERATING_PRICE * builders.size();
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, totalWork);
subMonitor.beginTask("Converting", totalWork);
Sharpen.Log(Level.INFO, "Converting %d files", builders.size());
int i = 0;
int errorCount = 0;
for (final CompilationUnitBuilder builder : builders) {
if (monitor.isCanceled())
return null;
final String message = String.format("Generating (%d/%d): %s", ++i, builders.size(),
builder.getName());
subMonitor.subTask(message);
try {
if (!builder.build()) {
Sharpen.Log(Level.SEVERE, "Generator did not produce any output for %s",
builder.getName());
++errorCount;
}
} catch (Exception e) {
Sharpen.Log(e, "Exception while generating %s", builder.getName());
++errorCount;
} finally {
subMonitor.worked(GENERATING_PRICE);
}
}
if (errorCount > 0) {
Sharpen.Log(Level.SEVERE, "Converting finished, found %d errors.", errorCount);
_foundErrors = true;
}
subMonitor.done();
return !_foundErrors;
}
private boolean stage4_save_output(IProgressMonitor monitor, List<CompilationUnitBuilder> builders) {
final int count = builders.size();
final int totalWork = OUTPUT_PRICE * count;
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, totalWork);
subMonitor.beginTask("Writing output", totalWork);
Sharpen.Log(Level.INFO, "Writing output ...");
int i = 0;
boolean foundErrors = false;
for (final CompilationUnitBuilder builder : builders) {
final ICompilationUnit source = builder.getPair().source;
final CSCompilationUnit unit = builder.getCompilationUnit();
final String pathName = builder.getName();
final String message = String.format("Saving (%d/%d): %s", ++i, count, pathName);
subMonitor.subTask(message);
try {
if (!builder.writeOutput()) {
Sharpen.Log(Level.SEVERE, "Failed to save output file: %s", pathName);
foundErrors = true;
continue;
}
if (unit.ignore() || unit.types().isEmpty())
continue;
final StringWriter writer = new StringWriter();
writer.write(_config.header());
CSharpPrinter printer = new CSharpPrinter();
printer.setWriter(writer);
printer.print(unit);
if (writer.getBuffer().length() > 0)
saveConvertedFile(source, unit, writer);
} catch (CoreException e) {
Sharpen.Log(e, "Cannot save output file: %s", pathName);
foundErrors = true;
} finally {
subMonitor.worked(OUTPUT_PRICE);
}
}
if (foundErrors) {
Sharpen.Log(Level.SEVERE, "Errors while writing output!");
_foundErrors = true;
}
subMonitor.done();
return !foundErrors;
}
private void saveConvertedFile(ICompilationUnit cu, CSCompilationUnit csModule, StringWriter convertedContents)
throws CoreException {
String unitName = getUnitName(cu);
IFolder folder;
String fileName;
int pos = unitName.lastIndexOf('.');
if (pos > 0) {
folder = _outputFolder.getFolder(unitName.substring(0, pos).replace('.', '/'));
fileName = unitName.substring(pos + 1) + ".cs";
} else {
folder = _outputFolder;
fileName = unitName + ".cs";
}
WorkspaceUtilities.initializeTree(folder, null);
WorkspaceUtilities.writeText(folder.getFile(fileName), convertedContents.getBuffer().toString());
}
static boolean equalLists(SortedSet<String> a, SortedSet<String> b) {
if (a.size() != b.size())
return false;
final int count = a.size();
String[] array_a = a.toArray(new String[count]);
String[] array_b = b.toArray(new String[count]);
for (int i = 0; i < count; i++) {
if (!array_a[i].equals(array_b[i]))
return false;
}
return true;
}
boolean stage5_generate_csproj() {
List<CSProjectFile> csprojFiles = _sourceInfo.getCSProjectFiles();
if (csprojFiles == null)
return true;
for (final CSProjectFile file : csprojFiles) {
if (!generate_csproj(file))
return false;
}
return true;
}
boolean generate_csproj(CSProjectFile csprojFile) {
URI root = _project.getLocationURI();
File output = getProjectFile(csprojFile.getPath());
File fileList = getProjectFile(csprojFile.getFileListFile());
File template = getProjectFile(csprojFile.getTemplateFile());
try {
ArrayList<String> allFiles = new ArrayList<String>();
URI path = _outputFolder.getLocationURI();
collectAllFiles(allFiles, root, new File(path), ".cs");
for (final String extra : _sourceInfo.getExtraCSharpSources()) {
File extraPath = _project.getLocation().append(extra).toFile();
collectAllFiles(allFiles, root, extraPath, ".cs");
}
for (NameMapping mapping : csprojFile.getPathMappings()) {
for (int i = 0; i < allFiles.size(); i++) {
String file = allFiles.get(i);
allFiles.set(i, file.replaceAll(mapping.from, mapping.to));
}
}
String line;
SortedSet<String> sortedList = new TreeSet<String>();
sortedList.addAll(allFiles);
if (fileList.exists()) {
FileInputStream listStream = new FileInputStream(fileList);
BufferedReader listReader = new BufferedReader(new InputStreamReader(listStream));
SortedSet<String> savedFileList = new TreeSet<String>();
while ((line = listReader.readLine()) != null)
savedFileList.add(line);
listStream.close();
if (equalLists(sortedList, savedFileList))
return true;
}
FileOutputStream listOutputStream = new FileOutputStream(fileList);
PrintStream listWriter = new PrintStream(listOutputStream);
for (final String file : sortedList)
listWriter.println(file);
listWriter.close();
listOutputStream.close();
FileInputStream templateStream = new FileInputStream(template);
BufferedReader templateReader = new BufferedReader(new InputStreamReader(templateStream));
FileOutputStream outputStream = new FileOutputStream(output);
PrintStream writer = new PrintStream(outputStream);
while ((line = templateReader.readLine()) != null) {
if (!line.trim().equals("@FILELIST@")) {
writer.println(line);
continue;
}
writer.println(" <ItemGroup>");
for (final String file : allFiles) {
final String windowsPath = file.replace('/', '\\');
writer.printf(" <Compile Include=\"%s\" />", windowsPath);
writer.println();
}
writer.println(" </ItemGroup>");
}
templateStream.close();
writer.close();
outputStream.close();
return true;
} catch (Exception e) {
Sharpen.Log(e, "Cannot write csproj file '%s'", csprojFile);
return false;
}
}
}