package net.jangaroo.ide.idea.jps.exml;
import net.jangaroo.exml.api.Exmlc;
import net.jangaroo.exml.api.ExmlcException;
import net.jangaroo.exml.config.ExmlConfiguration;
import net.jangaroo.ide.idea.jps.JangarooBuilder;
import net.jangaroo.ide.idea.jps.JangarooModelSerializerExtension;
import net.jangaroo.ide.idea.jps.JoocConfigurationBean;
import net.jangaroo.ide.idea.jps.util.CompilerLoader;
import net.jangaroo.ide.idea.jps.util.JpsCompileLog;
import net.jangaroo.jooc.api.FilePosition;
import net.jangaroo.properties.api.Propc;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.ModuleChunk;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
import org.jetbrains.jps.incremental.BuilderCategory;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.FSOperations;
import org.jetbrains.jps.incremental.MessageHandler;
import org.jetbrains.jps.incremental.ModuleBuildTarget;
import org.jetbrains.jps.incremental.ModuleLevelBuilder;
import org.jetbrains.jps.incremental.ProjectBuildException;
import org.jetbrains.jps.incremental.fs.CompilationRound;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.model.module.JpsModule;
import org.xml.sax.SAXParseException;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static net.jangaroo.ide.idea.jps.util.IdeaFileUtils.toPath;
/**
* Jangaroo analog of {@link org.jetbrains.jps.incremental.java.JavaBuilder}.
*/
public class ExmlBuilder extends ModuleLevelBuilder {
private static final String EXML_BUILDER_NAME = "exmlc";
private static final FileFilter EXMLC_SOURCES_FILTER = JangarooBuilder.createSuffixFileFilter(Exmlc.EXML_SUFFIX);
private static final String PROPERTIES_BUILDER_NAME = "propc";
private static final String PROPERTIES_SUFFIX = ".properties";
private static final FileFilter PROPC_SOURCES_FILTER = JangarooBuilder.createSuffixFileFilter(PROPERTIES_SUFFIX);
public ExmlBuilder() {
super(BuilderCategory.SOURCE_GENERATOR);
}
public static void copyFromBeanToConfiguration(@NotNull ExmlcConfigurationBean exmlcConfigurationBean,
@NotNull ExmlConfiguration exmlConfiguration, boolean forTests) {
exmlConfiguration.setOutputDirectory(new File(toPath(forTests ? exmlcConfigurationBean.getGeneratedTestSourcesDirectory() : exmlcConfigurationBean.getGeneratedSourcesDirectory())));
exmlConfiguration.setResourceOutputDirectory(new File(toPath(exmlcConfigurationBean.getGeneratedResourcesDirectory())));
exmlConfiguration.setConfigClassPackage(exmlcConfigurationBean.getConfigClassPackage());
exmlConfiguration.setValidationMode(exmlcConfigurationBean.validationMode);
}
public static FilePosition extractFilePosition(@NotNull ExmlcException e) {
ExmlcException result = e;
for (Throwable current = e; current instanceof ExmlcException; current = current.getCause()) {
ExmlcException exmlcException = (ExmlcException)current;
if (exmlcException.getFile() != null && result.getFile() == null) {
result = exmlcException;
}
if (exmlcException.getLine() > 0) {
result = exmlcException;
break;
}
}
if (result.getLine() <= 0 && result.getCause() instanceof SAXParseException) {
// Exmlc forgot to transfer line and column from SAXParseException! :-(
SAXParseException saxParseException = (SAXParseException)result.getCause();
result.setLine(saxParseException.getLineNumber());
result.setColumn(saxParseException.getColumnNumber());
}
return result;
}
@Override
public List<String> getCompilableFileExtensions() {
return Arrays.asList(Exmlc.EXML_SUFFIX.substring(1), PROPERTIES_SUFFIX.substring(1));
}
@Override
public ExitCode build(CompileContext context, ModuleChunk chunk,
DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
Map<ModuleBuildTarget, List<File>> propertiesFilesToCompile = JangarooBuilder.getFilesToCompile(
PROPC_SOURCES_FILTER, dirtyFilesHolder);
Map<ModuleBuildTarget, List<File>> exmlFilesToCompile = JangarooBuilder.getFilesToCompile(
EXMLC_SOURCES_FILTER, dirtyFilesHolder);
if (!propertiesFilesToCompile.isEmpty() || !exmlFilesToCompile.isEmpty()) {
for (ModuleBuildTarget moduleBuildTarget : chunk.getTargets()) {
build(context, propertiesFilesToCompile.get(moduleBuildTarget), exmlFilesToCompile.get(moduleBuildTarget),
moduleBuildTarget, outputConsumer);
}
return ExitCode.OK;
}
return ExitCode.NOTHING_DONE;
}
/**
* Generate AS from properties and EXML for one module.
*/
private void build(CompileContext context, List<File> propertiesFilesToCompile,
List<File> exmlFilesToCompile, ModuleBuildTarget moduleBuildTarget,
OutputConsumer outputConsumer) throws IOException {
JpsModule module = moduleBuildTarget.getModule();
ExmlConfiguration exmlcConfiguration = getExmlcConfiguration(module, moduleBuildTarget.isTests());
if (exmlcConfiguration == null) {
return; // no EXML Facet in this module: skip silently!
}
JoocConfigurationBean joocConfigurationBean = JangarooModelSerializerExtension.getJoocSettings(module);
if (joocConfigurationBean == null) {
context.processMessage(new CompilerMessage(EXML_BUILDER_NAME, BuildMessage.Kind.ERROR,
String.format("EXML module %s does not have a Jangaroo Facet.", module.getName())));
return;
}
List<String> jarPaths = JangarooBuilder.getJangarooSdkJarPath(joocConfigurationBean, module);
if (jarPaths == null) {
return; // no Jangaroo SDK: JangarooBuilder must already have reported this problem, so skip silently!
}
JpsCompileLog log = new JpsCompileLog(EXML_BUILDER_NAME, context);
exmlcConfiguration.setLog(log);
// invoke properties compiler for module:
if (propertiesFilesToCompile != null) {
exmlcConfiguration.setSourceFiles(propertiesFilesToCompile);
Propc propc = loadPropc(context, jarPaths, exmlcConfiguration);
compileProperties(moduleBuildTarget, propc, context, outputConsumer);
}
// invoke EXML compiler for module:
if (exmlFilesToCompile != null) {
exmlcConfiguration.setSourceFiles(exmlFilesToCompile);
Exmlc exmlc = loadExmlc(context, jarPaths, exmlcConfiguration);
compileExml(moduleBuildTarget, exmlc, context, outputConsumer);
}
}
private void compileProperties(ModuleBuildTarget moduleBuildTarget, Propc propc, CompileContext context,
OutputConsumer outputConsumer) {
List<File> sourceFiles = propc.getConfig().getSourceFiles();
for (File sourceFile : sourceFiles) {
try {
File generatedPropertiesClass = propc.generate(sourceFile);
// getLog().info("properties->as: " + fileUrl + " -> " + generatedPropertiesClass.getPath());
registerOutputFile(outputConsumer, moduleBuildTarget, context, generatedPropertiesClass,
JangarooBuilder.toSingletonPath(sourceFile));
} catch (IOException e) {
context.processMessage(new CompilerMessage(PROPERTIES_BUILDER_NAME, e));
}
}
}
private void compileExml(ModuleBuildTarget moduleBuildTarget, Exmlc exmlc, CompileContext context,
OutputConsumer outputConsumer) throws IOException {
List<File> sourceFiles = exmlc.getConfig().getSourceFiles();
if (!sourceFiles.isEmpty()) {
List<String> allCompiledSourceFiles = new ArrayList<String>();
for (File sourceFile : sourceFiles) {
try {
File generatedConfigClass = exmlc.generateConfigClass(sourceFile);
registerOutputFile(outputConsumer, moduleBuildTarget, context, generatedConfigClass, JangarooBuilder.toSingletonPath(sourceFile));
File generatedTargetClass = exmlc.generateComponentClass(sourceFile);
// getLog().info("exml->as (config): " + fileUrl + " -> " + generatedConfigClass.getPath());
if (generatedTargetClass != null) {
// getLog().info("exml->as (target): " + fileUrl + " -> " + generatedTargetClass.getPath());
registerOutputFile(outputConsumer, moduleBuildTarget, context, generatedTargetClass, JangarooBuilder.toSingletonPath(sourceFile));
}
allCompiledSourceFiles.add(sourceFile.getPath());
} catch (ExmlcException e) {
processExmlcException(context, sourceFile, e);
}
}
if (!moduleBuildTarget.isTests()) {
try {
// The config class registry in the current ExmlConfiguration contains too many classes (also from other
// modules), and thus cannot be reused. We have to create a clean copy.
// TODO: remove this when bug is fixed in EXML compiler!
ExmlConfiguration exmlConfiguration = new ExmlConfiguration();
exmlConfiguration.setLog(exmlc.getConfig().getLog());
exmlConfiguration.setConfigClassPackage(exmlc.getConfig().getConfigClassPackage());
exmlConfiguration.setOutputDirectory(exmlc.getConfig().getOutputDirectory());
exmlConfiguration.setResourceOutputDirectory(exmlc.getConfig().getResourceOutputDirectory());
exmlConfiguration.setSourcePath(exmlc.getConfig().getSourcePath());
exmlConfiguration.setClassPath(exmlc.getConfig().getClassPath());
exmlc.setConfig(exmlConfiguration);
File xsdFile = exmlc.generateXsd();
if (xsdFile == null || !xsdFile.exists()) {
return;
}
registerOutputFile(outputConsumer, moduleBuildTarget, context, xsdFile, allCompiledSourceFiles);
} catch (ExmlcException e) {
processExmlcException(context, null, e);
}
}
}
}
private static void processExmlcException(MessageHandler messageHandler, File sourceFile, ExmlcException e) {
FilePosition filePosition = extractFilePosition(e);
CompilerMessage compilerMessage = filePosition.getFileName() == null
? sourceFile == null
? new CompilerMessage(EXML_BUILDER_NAME, e)
: new CompilerMessage(EXML_BUILDER_NAME, BuildMessage.Kind.ERROR, sourceFile.getPath())
: new CompilerMessage(EXML_BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage(),
filePosition.getFileName(), 0L, 0L, 0L, filePosition.getLine(), filePosition.getColumn());
messageHandler.processMessage(compilerMessage);
}
@Nullable
protected ExmlConfiguration getExmlcConfiguration(JpsModule module, boolean forTests) {
ExmlcConfigurationBean exmlcConfigurationBean = JangarooModelSerializerExtension.getExmlcSettings(module);
if (exmlcConfigurationBean == null) {
return null;
}
ExmlConfiguration exmlcConfig = new ExmlConfiguration();
JangarooBuilder.updateFileLocations(exmlcConfig, module, forTests, false);
copyFromBeanToConfiguration(exmlcConfigurationBean, exmlcConfig, forTests);
return exmlcConfig;
}
public static Propc loadPropc(MessageHandler messageHandler, List<String> jarPaths, ExmlConfiguration configuration) {
Propc propc;
try {
propc = CompilerLoader.loadPropc(jarPaths);
} catch (FileNotFoundException e) {
messageHandler.processMessage(new CompilerMessage(PROPERTIES_BUILDER_NAME, e));
return null;
} catch (Exception e) {
// Jangaroo SDK not correctly set up or not compatible with this Jangaroo IDEA plugin:
messageHandler.processMessage(new CompilerMessage(PROPERTIES_BUILDER_NAME, e));
return null;
}
propc.setConfig(configuration);
return propc;
}
public static Exmlc loadExmlc(MessageHandler messageHandler, List<String> jarPaths, ExmlConfiguration configuration) {
Exmlc exmlc;
try {
exmlc = CompilerLoader.loadExmlc(jarPaths);
} catch (FileNotFoundException e) {
messageHandler.processMessage(new CompilerMessage(EXML_BUILDER_NAME, e));
return null;
} catch (Exception e) {
// Jangaroo SDK not correctly set up or not compatible with this Jangaroo IDEA plugin:
messageHandler.processMessage(new CompilerMessage(EXML_BUILDER_NAME, e));
return null;
}
exmlc.setConfig(configuration);
return exmlc;
}
private static void registerOutputFile(OutputConsumer outputConsumer, ModuleBuildTarget moduleBuildTarget,
CompileContext context, File generatedFile, Collection<String> sourcePaths)
throws IOException {
outputConsumer.registerOutputFile(moduleBuildTarget, generatedFile, sourcePaths);
FSOperations.markDirty(context, CompilationRound.CURRENT, generatedFile);
JangarooBuilder.processFileInvalidationMessage(context, generatedFile);
}
@NotNull
@Override
public String getPresentableName() {
return "EXML Compiler";
}
}