blob: b89d2c01adea457005b8cd7a3d1dc515a52ef7d0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020, 2021 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.aql.launcher;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.eclipse.acceleo.Module;
import org.eclipse.acceleo.aql.AcceleoUtil;
import org.eclipse.acceleo.aql.evaluation.AcceleoEvaluator;
import org.eclipse.acceleo.aql.evaluation.GenerationResult;
import org.eclipse.acceleo.aql.parser.AcceleoParser;
import org.eclipse.acceleo.aql.parser.ModuleLoader;
import org.eclipse.acceleo.query.ide.QueryPlugin;
import org.eclipse.acceleo.query.ide.runtime.impl.namespace.OSGiQualifiedNameResolver;
import org.eclipse.acceleo.query.runtime.Query;
import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameQueryEnvironment;
import org.eclipse.acceleo.query.runtime.namespace.IQualifiedNameResolver;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.StringArrayOptionHandler;
import org.kohsuke.args4j.spi.StringOptionHandler;
import org.osgi.framework.Bundle;
/**
* Application class for the Acceleo Launcher.
* <p>
* Parses the arguments and starts the specified Acceleo generation.
* </p>
*
* @author lgoubet
*/
public class AcceleoLauncher implements IApplication {
/** Return code for the application in case of error. */
private static final Integer APPLICATION_ERROR = Integer.valueOf(-1);
/**
* Input models for the generation. Cannot be empty.
*/
@Option(name = "-models", usage = "Specifies the input models for this generation.", metaVar = "INPUT", handler = StringArrayOptionHandler.class, required = true)
private String[] models = new String[0];
/**
* Identifier of the bundle containing the main module for this generation.
*/
@Option(name = "-bundle", usage = "The identifier of the bundle containing the main module of the generation to start.", metaVar = "BUNDLE", handler = StringOptionHandler.class, required = true)
private String bundleIdentifier;
/**
* Input module for the generation. This needs to the qualified name of the module containing an
* "@main"-annotated template.
*/
@Option(name = "-module", usage = "Specifies the module which main template will be executed.", metaVar = "MODULE", handler = StringOptionHandler.class, required = true)
private String moduleQualifiedName;
/**
* The destination URI for this generation. Generated files will use this folder as their root to resolve
* against.
*/
@Option(name = "-target", usage = "Specifies the destination for the generation.", metaVar = "TARGET", handler = StringOptionHandler.class, required = true)
private String target;
/**
* Workspace location. This argument is here only to mimic the OSGi applications common arguments so that
* they are displayed in usage.
*/
@Option(name = "-data", usage = "Specify the folder which will keep the workspace.", metaVar = "FOLDER")
private File dataFolder;
/**
* consoleLog. This argument is here only to mimic the OSGi applications common arguments so that they are
* displayed in usage.
*/
@Option(name = "-consoleLog", usage = "Log messages in the console.")
private boolean consoleLog;
/** List of URIs parsed from the {@link #models} argument. */
private List<URI> modelURIs;
/** Bundle containing our main module for this generation. */
private Bundle bundle;
@Override
public Object start(IApplicationContext context) throws Exception {
String[] args = (String[])context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
return new AcceleoLauncher().doMain(args);
}
/**
* Main logic for this launcher.
*
* @param args
* Application parameters passed by the Equinox framework.
* @return The return value of this application.
*/
public Object doMain(String[] args) {
CmdLineParser parser = new CmdLineParser(this);
Object applicationResult = IApplication.EXIT_OK;
try {
parser.parseArgument(args);
validateArguments(parser);
GenerationResult result = launchGeneration();
if (result.getDiagnostic().getSeverity() > Diagnostic.INFO) {
PrintStream stream;
switch (result.getDiagnostic().getSeverity()) {
case Diagnostic.WARNING:
stream = System.out;
stream.println("WARNING");
break;
case Diagnostic.ERROR:
// Fall-through
default:
// Shouldn't happen as we only show warnings and errors
stream = System.err;
stream.println("ERROR");
applicationResult = APPLICATION_ERROR;
break;
}
printDiagnostic(stream, result.getDiagnostic(), "");
}
Set<URI> generatedFiles = result.getGeneratedFiles();
System.out.println("Generated " + generatedFiles.size() + " in " + target);
if (result.getGeneratedFiles().size() <= 10) {
for (URI generated : result.getGeneratedFiles()) {
System.out.println(generated);
}
}
} catch (CmdLineException e) {
parser.printUsage(System.err);
System.err.println();
AcceleoLauncherPlugin.INSTANCE.log(e);
applicationResult = APPLICATION_ERROR;
}
return applicationResult;
}
private void validateArguments(CmdLineParser parser) throws CmdLineException {
if (models.length == 0 || models[0].length() == 0) {
throw new CmdLineException(parser, "The input model path cannot be empty.");
}
if (bundleIdentifier.length() == 0) {
throw new CmdLineException(parser,
"The main module's containing bundle identifier cannot be empty.");
}
if (moduleQualifiedName.length() == 0) {
throw new CmdLineException(parser, "The main module's qualified name cannot be empty.");
}
if (target.length() == 0) {
throw new CmdLineException(parser, "The target folder path cannot be empty.");
}
modelURIs = convertToURIs(parser, models);
bundle = Platform.getBundle(bundleIdentifier);
if (bundle == null || bundle.getState() == Bundle.UNINSTALLED) {
throw new CmdLineException(parser, "The Bundle " + bundleIdentifier
+ " must be available in the target platform.");
}
try {
URI.createURI(target);
} catch (IllegalArgumentException e) {
throw new CmdLineException(parser, e);
}
}
private List<URI> convertToURIs(CmdLineParser parser, String... paths) throws CmdLineException {
List<URI> uris = new ArrayList<>();
for (String path : paths) {
URI pathURI = null;
try {
pathURI = URI.createURI(path, true);
} catch (IllegalArgumentException e) {
throw new CmdLineException(parser, e);
}
if (pathURI != null && !pathURI.hasAbsolutePath()) {
pathURI = URI.createFileURI(path);
}
if (pathURI != null) {
uris.add(pathURI);
}
}
return uris;
}
private GenerationResult launchGeneration() {
// TODO allow users to either provide their own resource set or configure this one as we might need
// urimaps, resourcefactories, EPackages... to load the input models.
ResourceSet modelResourceSet = new ResourceSetImpl();
for (URI modelURI : modelURIs) {
modelResourceSet.getResource(modelURI, true);
}
IQualifiedNameResolver resolver = new OSGiQualifiedNameResolver(bundle,
AcceleoParser.QUALIFIER_SEPARATOR);
/* FIXME we need a cross reference provider, and we need to make it configurable */
final IQualifiedNameQueryEnvironment queryEnvironment = Query
.newQualifiedNameEnvironmentWithDefaultServices(resolver, null, null);
AcceleoEvaluator evaluator = new AcceleoEvaluator(queryEnvironment.getLookupEngine());
resolver.addLoader(new ModuleLoader(new AcceleoParser(), evaluator));
resolver.addLoader(QueryPlugin.getPlugin().createJavaLoader(AcceleoParser.QUALIFIER_SEPARATOR));
final Object resolved = resolver.resolve(moduleQualifiedName);
final Module mainModule;
if (resolved instanceof Module) {
mainModule = (Module)resolved;
} else {
mainModule = null;
}
evaluate(evaluator, queryEnvironment, mainModule, modelResourceSet);
return evaluator.getGenerationResult();
}
private void evaluate(AcceleoEvaluator evaluator, IQualifiedNameQueryEnvironment queryEnvironment,
Module mainModule, ResourceSet modelResourceSet) {
AcceleoUtil.generate(evaluator, queryEnvironment, mainModule, modelResourceSet, URI.createURI(
target));
}
private void printDiagnostic(PrintStream stream, Diagnostic diagnostic, String indentation) {
String nextIndentation = indentation;
if (diagnostic.getMessage() != null) {
stream.print(indentation);
stream.println(diagnostic.getMessage());
nextIndentation += "\t";
}
for (Diagnostic child : diagnostic.getChildren()) {
printDiagnostic(stream, child, nextIndentation);
}
}
@Override
public void stop() {
// Nothing to do on application stop
}
}