/*
* ******************************************************************************
* MontiCore Language Workbench
* Copyright (c) 2015, MontiCore, All rights reserved.
*
* This project is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this project. If not, see <http://www.gnu.org/licenses/>.
* ******************************************************************************
*/
package de.monticore.cli;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import de.monticore.MontiCoreConfiguration;
import de.monticore.MontiCoreScript;
import de.se_rwth.commons.cli.CLIArguments;
import de.se_rwth.commons.logging.Log;
import de.se_rwth.commons.logging.Slf4jLog;
/**
* Command line interface for MontiCore.
*
* @author (last commit) $Author$
* @version $Revision$, $Date$
*/
public final class MontiCoreCLI {
public static final String LOGBACK_USER_CONFIG = "user.logging.xml";
public static final String LOGBACK_DEVELOPER_CONFIG = "developer.logging.xml";
static final String MC_OUT = "MC_OUT";
static final String LOGBACK_CONFIGURATIONFILE = "logback.configurationFile";
/**
* Main method.
*
* @param args the CLI arguments
*/
public static void main(String[] args) {
if (args.length == 0) {
// the only required input are the grammar file(s)/directories
System.out
.println("MontiCore CLI Usage: java -jar monticore-cli.jar <grammar files> <options>");
return;
}
// check if the input model(s) are specified without option and add it for
// further processing
if (!args[0].startsWith("-")) {
ArrayList<String> fixedArgs = new ArrayList<String>(Arrays.asList(args));
fixedArgs.add(0, "-" + MontiCoreConfiguration.Options.GRAMMARS_SHORT.toString());
args = fixedArgs.toArray(new String[fixedArgs.size()]);
}
CLIArguments arguments = CLIArguments.forArguments(args);
MontiCoreCLIConfiguration configuration = MontiCoreCLIConfiguration.fromArguments(arguments);
// this will be CLI's default model path if none is specified
Iterable<String> mp = Arrays.asList("monticore-cli.jar");
Iterable<String> mpArg = arguments.asMap().get(
MontiCoreConfiguration.Options.MODELPATH.toString());
Iterable<String> mpShortArg = arguments.asMap().get(
MontiCoreConfiguration.Options.MODELPATH_SHORT.toString());
if ((mpArg == null || Iterables.isEmpty(mpArg))
&& (mpShortArg == null || Iterables.isEmpty(mpShortArg))) {
// prepare args which contain the fixed model path
Map<String, Iterable<String>> wrappedArgs = new HashMap<>();
wrappedArgs.put(MontiCoreConfiguration.Options.MODELPATH.toString(), mp);
wrappedArgs.putAll(arguments.asMap());
// use this fixed configuration
configuration = MontiCoreCLIConfiguration.fromMap(wrappedArgs);
}
// we store the requested output directory as a system variable such that we
// can inject it into the logback configuration
System.setProperty(MC_OUT, configuration.getInternal().getOut().getAbsolutePath());
// this should always happen first in order to use any custom configurations
if (System.getProperty(LOGBACK_CONFIGURATIONFILE) == null) {
initLogging(configuration);
}
// this needs to be called after the statement above; otherwise logback will
// ignore custom configurations supplied via system property
Slf4jLog.init();
if (System.getProperty(LOGBACK_CONFIGURATIONFILE) != null) {
Log.debug(
"Using system property logback configuration "
+ System.getProperty(LOGBACK_CONFIGURATIONFILE), MontiCoreCLI.class.getName());
}
// before we launch MontiCore we check if there are any ".mc4" files in the
// input argument (source path)
Iterator<Path> inputPaths = configuration.getInternal().getGrammars().getResolvedPaths();
if (!inputPaths.hasNext()) {
System.clearProperty(MC_OUT);
Log.error("0xA1000 There are no \".mc4\" files to parse. Please check the \"grammars\" option.");
return;
}
try {
// since this is the default we load the default script
ClassLoader l = MontiCoreScript.class.getClassLoader();
String script = Resources.asCharSource(l.getResource("de/monticore/monticore_noemf.groovy"),
Charset.forName("UTF-8")).read();
// BUT if the user specifies another script to use, we check if it is
// there and load its content
if (configuration.getScript().isPresent()) {
if (!configuration.getScript().get().exists()) {
System.clearProperty(MC_OUT);
Log.error("0xA1001 Custom script \"" + configuration.getScript().get().getPath()
+ "\" not found!");
return;
}
script = Files.toString(configuration.getScript().get(), Charset.forName("UTF-8"));
}
// execute the scripts (either default or custom)
new MontiCoreScript().run(script, configuration.getInternal());
}
catch (IOException e) {
System.clearProperty(MC_OUT);
Log.error("0xA1002 Failed to load Groovy script.", e);
}
}
/**
* Initializes the logging configuration based on the CLI arguments.
*
* @param configuration the MontiCore CLI configuration
*/
static void initLogging(MontiCoreCLIConfiguration configuration) {
// pick detailed developer logging if specified
if (configuration.getDev()) {
useDeveloperLoggingConfiguration();
return;
}
if (configuration.getCustomLog().isPresent()) {
String userFile = configuration.getCustomLog().get();
// instead of silently failing custom configuration (e.g. not existing
// configuration file) we test if it is present and fall back to default
File userLogFile = new File(userFile);
if (!userLogFile.exists() || !userLogFile.isFile()) {
// fall back to user configuration
useUserLoggingConfiguration();
Log.warn("0xA1030 Failed to load specified custom logback configuration: \"" + userFile
+ "\". Falling back to built-in user logging configuration.");
return;
}
else {
// apparently the requested file is there, let's try it
try {
useLogbackConfiguration(new FileInputStream(userLogFile));
}
catch (JoranException | FileNotFoundException e) {
// fall back to user configuration
useUserLoggingConfiguration();
Log.warn("0xA1031 Failed to load specified custom logback configuration: \"" + userFile
+ "\". Falling back to built-in user logging configuration.", e);
}
return;
}
}
// this is the default
useUserLoggingConfiguration();
}
public static void useUserLoggingConfiguration() {
try {
useLogbackConfiguration(MontiCoreScript.class.getClassLoader().getResourceAsStream(
LOGBACK_USER_CONFIG));
}
catch (JoranException e) {
// e.printStackTrace();
// this should not happen as we use this mechanism for the built-in
// configurations only (i.e. user.logging.xml and developer.logging.xml)
System.err.println("Failed to load default logback configuration for users.");
}
}
public static void useDeveloperLoggingConfiguration() {
try {
useLogbackConfiguration(MontiCoreScript.class.getClassLoader().getResourceAsStream(
LOGBACK_DEVELOPER_CONFIG));
}
catch (JoranException e) {
// e.printStackTrace();
// this should not happen as we use this mechanism for the built-in
// configurations only (i.e. user.logging.xml and developer.logging.xml)
System.err.println("Failed to load default logback configuration for developers.");
}
}
/**
* Programmatically load and configure logback with the given logback XML input stream.
*
* @param config
*/
protected static final void useLogbackConfiguration(InputStream config) throws JoranException {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
context.reset();
configurator.doConfigure(config);
}
private MontiCoreCLI() {
}
}