/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * CAL.java * Creation date: Sep 12, 2006. * By: Edward Lam */ package org.openquark.cal; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.StreamHandler; import org.openquark.cal.compiler.CompilerMessage; import org.openquark.cal.compiler.CompilerMessageLogger; import org.openquark.cal.compiler.MessageLogger; import org.openquark.cal.compiler.ModuleName; import org.openquark.cal.compiler.SourceModel; import org.openquark.cal.compiler.SourceModelUtilities; import org.openquark.cal.services.ProgramModelManager; import org.openquark.cal.services.StandardVault; import org.openquark.cal.services.Status; import org.openquark.cal.services.VaultRegistry; import org.openquark.cal.services.WorkspaceDeclaration; import org.openquark.cal.services.WorkspaceManager; import org.openquark.util.TextEncodingUtilities; /** * A command-line application launcher for CAL. * * For now, this app behaves in the following way: * <ul> * <li>All modules in the standard vault are compiled to the workspace. * <li>The expression defined by the app's args is run in the Prelude module. * </ul> * * @author Edward Lam */ public class CAL extends ConsoleRunner { /** The boilerplate string which gets displayed as part of the usage string. */ private static final String toolBoilerPlate = "CAL Application Launcher Version 0.0.1 (c) 2006 Business Objects."; /** The default level at which logged messages will be output to the console. */ private static Level defaultConsoleLevel = Level.INFO; /** The namespace for CAL log messages. */ private static final String calLoggerNamespace = "org.openquark.cal.cal"; /** The workspace manager for the workspace in which the app will be launched. */ private final WorkspaceManager workspaceManager; /** The command line options passed to the application launcher. */ private final CommandLineOptions options; /** * A StreamHandler which simply outputs log records to the given output stream. * @author Edward Lam */ private static class OutputStreamStreamHandler extends StreamHandler { /** * Constructor for an OutputStreamStreamHandler */ public OutputStreamStreamHandler(OutputStream outputStream) { super(outputStream, new ConsoleFormatter()); } /** Override this to always flush the stream. */ @Override public void publish(LogRecord record) { super.publish(record); flush(); } /** Override to just flush the stream, we don't want to close System.out. */ @Override public void close() { flush(); } } /** * A log message formatter that simply outputs the message of the log record, * plus the text of any throwable. * Used to print messages to the console. */ private static class ConsoleFormatter extends Formatter { /** * {@inheritDoc} */ @Override public String format(LogRecord record) { StringBuilder sb = new StringBuilder(); // Append the log message sb.append(record.getMessage() + "\n"); // Append the throwable if there is one if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { sb.append("Failed to generate a stack trace for the throwable"); } } return sb.toString(); } } /** * A class to encapsulate the command-line options passed to the JFit tool. * * When this class is instantiated, the following are true: * moduleName is a valid module name. * workspaceName is provided * patterns are specified. * inputFile is non-null (for now) and exists. * outputFolder exists if non-null. * * @author Edward Lam */ private static class CommandLineOptions { private static final String[] usageString = { toolBoilerPlate, "Usage:", " java " + CAL.class.getName() + " [options] cal_expression", " - run a cal expression (must specify -m)", "", " java " + CAL.class.getName() + " [options] ModuleName.unqualifiedName", " - run a cal expression in module ModuleName with no arguments", "", "Options:", " -m moduleName specify the module in which to run the expression", " -quiet, -q be extra quiet", " -verbose, -v be extra verbose", " -h this help message" }; private final String calExpression; private final ModuleName moduleName; private final Level verbosity; /** * An internal exception which is thrown if there was a problem parsing the command line arguments. * @author Edward Lam */ private static class ParseException extends Exception { private static final long serialVersionUID = 6129252467685138327L; ParseException() { } } /** * Private constructor for an Options object. * To instantiate, call parseOptions() or makeOptions(). * * @param calExpression * @param moduleName * @param verbosity the specified verbosity of the logger */ private CommandLineOptions(String calExpression, ModuleName moduleName, Level verbosity) { this.calExpression = calExpression; this.moduleName = moduleName; this.verbosity = verbosity; } /** * Dump the usage string to the logger. */ private static void logUsageString(Logger logger) { for (final String element : usageString) { logger.info(element); } } /** * Parse command line arguments into options for this tool. * If there are problems parsing the options, null is returned and the usage string is logged. * * @param args the command line arguments for this tool. * @param logger the logger to which to log any error messages. * @return the corresponding options object. Null if there are problems with the command line options. */ static CommandLineOptions parseOptions(String[] args, Logger logger) { if (args.length < 1) { logUsageString(logger); return null; } List<String> argList = new ArrayList<String>(Arrays.asList(args)); // flag to indicate whether the usage string has already been logged via -h option. boolean loggedUsageStringForOption = false; ModuleName moduleName = null; Level verbosity = null; String calExpression = null; try { while (!argList.isEmpty()) { String nextArg = argList.get(0); // Handle non-dash argument. // The remaining args are the cal expression itself. if (!nextArg.startsWith("-")) { StringBuilder sb = new StringBuilder(); boolean firstIteration = true; for (final String arg : argList) { if (!firstIteration) { sb.append(' '); } sb.append(arg); firstIteration = false; } calExpression = sb.toString(); argList = Collections.emptyList(); } else { // Save the option text. String option = nextArg; // Shorten argList by one arg. argList = argList.subList(1, argList.size()); // // Zero-argument options. // if (option.equals("-h")) { if (!loggedUsageStringForOption) { loggedUsageStringForOption = true; logUsageString(logger); } } else if (option.equals("-v") || option.equals("-verbose")) { if (verbosity != null) { logger.severe("Error: Multiple verbosity options provided."); throw new ParseException(); } verbosity = Level.ALL; } else if (option.equals("-q") || option.equals("-quiet")) { if (verbosity != null) { logger.severe("Error: Multiple verbosity options provided."); throw new ParseException(); } verbosity = Level.SEVERE; // // One-argument options. // } else { // Make sure there's another argument if (argList.size() < 1) { throw new ParseException(); } // Get the argument to the option. String optionArg = argList.get(0); // Shorten argList by another arg. argList = argList.subList(1, argList.size()); if (option.equals("-m")) { // The name of the workspace. if (moduleName != null) { logger.severe("Error: Multiple module name options provided."); throw new ParseException(); } moduleName = ModuleName.maybeMake(optionArg); if (moduleName == null) { logger.severe("Error: Bad module name provided: " + optionArg); throw new ParseException(); } } else { logger.severe("Error: Unknown option: " + option); throw new ParseException(); } } } } } catch (ParseException pe) { // Some problem occurred parsing the arguments. // Log the usage string and return null. if (!loggedUsageStringForOption) { logUsageString(logger); } return null; } if (loggedUsageStringForOption && calExpression == null) { return null; } return makeOptions(calExpression, moduleName, verbosity, logger); } /** * Create a command-line options object given the arguments. * * @param calExpression * @param moduleName * @param verbosity * @param logger the logger to which to log any errors. * * @return the options object, or null if there were errors in the provided option arguments. * If null, errors will have been logged to the logger. */ public static CommandLineOptions makeOptions(String calExpression, ModuleName moduleName, Level verbosity, Logger logger) { if (calExpression == null) { logger.severe("Error: No cal expression provided."); return null; } if (moduleName == null) { // Attempt to catch invalid expressions early. SourceModel.Expr expr = SourceModelUtilities.TextParsing.parseExprIntoSourceModel(calExpression); if (expr == null) { logger.severe("Error: \"" + calExpression + "\"" + " is not a valid CAL expression."); return null; } // Pick out the module name if the remaining expression is a dc or var expression. if (expr instanceof SourceModel.Expr.DataCons) { SourceModel.Expr.DataCons dataCons = (SourceModel.Expr.DataCons)expr; moduleName = SourceModel.Name.Module.maybeToModuleName(dataCons.getDataConsName().getModuleName()); // may be null } else if (expr instanceof SourceModel.Expr.Var) { SourceModel.Expr.Var var = (SourceModel.Expr.Var)expr; moduleName = SourceModel.Name.Module.maybeToModuleName(var.getVarName().getModuleName()); // may be null. } // if the module name is still null, we can't continue. if (moduleName == null) { logger.severe("Error: No module name provided."); return null; } } return new CommandLineOptions(calExpression, moduleName, verbosity); } public String getCalExpression() { return this.calExpression; } public ModuleName getModuleName() { return this.moduleName; } /** * @return the specified verbosity (Logging) level. May be null if not specified. */ public Level getVerbosity() { return verbosity; } /** * Log the current command-line options to a logger. * @param logger the logger to which to log the current command-line options. */ public void logConfig(Logger logger) { logger.config("Configuration: "); logger.config(" CAL expression: " + this.calExpression); logger.config(" Module name: " + (this.moduleName == null ? "(null)" : moduleName.toSourceText())); logger.config(" Verbosity: " + (this.verbosity == null ? "(null)" : verbosity.toString())); } } /** * The main entry point. * @param args */ public static void main(String[] args) { Logger commandLineLogger = Logger.getLogger(CAL.class.getPackage().getName()); commandLineLogger.setLevel(Level.FINEST); commandLineLogger.setUseParentHandlers(false); StreamHandler optionsOutputStreamHandler = new OutputStreamStreamHandler(System.out); optionsOutputStreamHandler.setLevel(Level.INFO); commandLineLogger.addHandler(optionsOutputStreamHandler); CommandLineOptions options = CommandLineOptions.parseOptions(args, commandLineLogger); if (options == null) { System.exit(1); } CAL cal = new CAL(options); boolean success = cal.compileAndRunExpression(); if (!success) { System.exit(1); } } /** * Constructor for this class. * @param options */ private CAL(CommandLineOptions options) { super(Logger.getLogger(calLoggerNamespace)); this.options = options; this.workspaceManager = WorkspaceManager.getWorkspaceManager(null); calLogger.setLevel(Level.FINEST); calLogger.setUseParentHandlers(false); StreamHandler consoleHandler = new OutputStreamStreamHandler(System.out); // Note that we can add other handlers which log more about what's happening. Level verbosity = options.getVerbosity(); if (verbosity != null) { consoleHandler.setLevel(verbosity); } else { consoleHandler.setLevel(defaultConsoleLevel); } calLogger.addHandler(consoleHandler); // Log set configuration options to the logger. options.logConfig(calLogger); } /** * {@inheritDoc} */ public ProgramModelManager getProgramModelManager() { return workspaceManager; } /** * {@inheritDoc} */ public boolean hasModuleSource(ModuleName moduleName) { return Arrays.asList(workspaceManager.getWorkspace().getModuleNames()).contains(moduleName); } /** * Compile the workspace and run the given expression. * @return whether execution terminated normally. */ private boolean compileAndRunExpression() { // Init and compile the workspace. boolean compileSucceeded = compileWorkspace(); if (!compileSucceeded) { return false; } return runExpression(options.getModuleName(), options.getCalExpression(), false); } /** * Compile the workspace. * @return whether the workspace compiled successfully. */ private boolean compileWorkspace() { CompilerMessageLogger ml = new MessageLogger(); calLogger.fine("Compiling workspace.."); // Init and compile the workspace. Status initStatus = new Status("Init status."); workspaceManager.initWorkspace(getStreamProvider(), false, initStatus); if (initStatus.getSeverity() != Status.Severity.OK) { ml.logMessage(initStatus.asCompilerMessage()); } long startCompile = System.currentTimeMillis(); // If there are no errors go ahead and compile the workspace. if (ml.getMaxSeverity().compareTo(CompilerMessage.Severity.ERROR) < 0) { workspaceManager.compile(ml, true, null); } long compileTime = System.currentTimeMillis() - startCompile; // Write out compiler messages writeOutCompileResult(ml); int nModules = workspaceManager.getModuleNamesInProgram().length; calLogger.fine("CAL: Finished compiling " + nModules + " modules in " + compileTime + "ms\n"); return ml.getNErrors() == 0; } /** * @return a generated WorkspaceDeclaration.StreamProvider which encompasses all modules in the standard vault. */ private static WorkspaceDeclaration.StreamProvider getStreamProvider() { ModuleName[] availableModules = StandardVault.getInstance().getAvailableModules(new Status("Get Status")); final StringBuilder sb = new StringBuilder(); for (final ModuleName element : availableModules) { sb.append("StandardVault " + element + "\n"); } return new WorkspaceDeclaration.StreamProvider() { public String getName() { return "generatedWorkspace.cws"; } public String getLocation() { return "(temporary)"; } public String getDebugInfo(VaultRegistry vaultRegistry) { return "(generated)"; } public InputStream getInputStream(VaultRegistry vaultRegistry, Status status) { return new ByteArrayInputStream(TextEncodingUtilities.getUTF8Bytes(sb.toString())); } }; } }