/*
* 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.
*/
/*
* StandaloneJarTool.java
* Created: May 17, 2007
* By: Joseph Wong
*/
package org.openquark.cal.services;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.Version;
import org.openquark.cal.internal.machine.lecc.StandaloneJarBuilder;
import org.openquark.cal.internal.machine.lecc.StandaloneJarBuilder.JavaScope;
import org.openquark.cal.internal.machine.lecc.StandaloneJarBuilder.LibraryClassSpec;
import org.openquark.cal.internal.machine.lecc.StandaloneJarBuilder.MainClassSpec;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;
import org.openquark.util.SimpleConsoleHandler;
/**
* This class implements a command-line utility for constructing a <em>standalone JAR</em>, which may contain
* application and library classes.
* <p>
* A standalone JAR may package up a CAL application by gathering together all the generated runtime
* classes necessary for running a specific CAL function, and a class containing a
* <code>public static void main(String[] args)</code> method which runs the CAL function directly
* (without having to first initialize a CAL workspace). This makes it possible to package up a CAL
* function into a command-line application.
* <p>
* To run the CAL function using the standalone JAR, simply include the standalone JAR, the CAL
* platform jars, and any required external jars on the classpath, and specify the name of the
* generated class as the one to run. Command line arguments will be passed into the CAL function.
* <p>
* A standalone JAR may also package up one or more library classes - a library class is a non-instatiatable class
* containing static methods corresponding to the functions and data constructors defined in a particular
* CAL module. This makes it possible to expose CAL libraries in Java, by defining API modules in CAL (whose
* functions contain appropriate marshalling from/unmarshalling to foreign types), from which library classes are
* generated.
* <p>
* This utility only works with the LECC machine.
* <p>
* In this version, we support generating standalone applications for CAL functions with the type
* <code>[String] -> ()</code>. In this case, the command line arguments array will be marshalled
* into a CAL list of Strings.
*
* @author Joseph Wong
*/
public final class StandaloneJarTool {
/** The launch command displayed in the usage help. */
private static final String LAUNCH_COMMAND = "quarkc";
/** The name of the non-interruptible property. */
private static final String NON_INTERRUPTIBLE_PROPERTY = "org.openquark.cal.machine.lecc.non_interruptible";
/**
* Encapsulates the command line argument list.
*
* @author Joseph Wong
*/
private static class ArgumentList {
/**
* Exception for representing that there are not enough arguments supplied.
*
* @author Joseph Wong
*/
private static class NotEnoughArgumentsException extends Exception {
private static final long serialVersionUID = -5402072553834237273L;
/** Constructs an instance of this exception. */
private NotEnoughArgumentsException() {}
}
/**
* Exception for representing that there are too many arguments supplied.
*
* @author Joseph Wong
*/
private static class TooManyArgumentsException extends Exception {
private static final long serialVersionUID = 1154558440221248070L;
/** Constructs an instance of this exception. */
private TooManyArgumentsException() {}
}
/** The arguments in a List. */
private final List<String> argsList;
/**
* Constructs an ArgumentList.
* @param args the arguments to be encapsulated.
*/
private ArgumentList(final String[] args) {
argsList = new ArrayList<String>();
// we drop all empty string command-line arguments
// (these can be supplied on unix platforms via quoted empty strings e.g. '')
for (final String element : args) {
if (element.length() > 0) {
argsList.add(element);
}
}
}
/**
* @return the current argument being examined.
* @throws NotEnoughArgumentsException
*/
private String getArgument() throws NotEnoughArgumentsException {
if (argsList.isEmpty()) {
throw new NotEnoughArgumentsException();
} else {
return argsList.get(0);
}
}
/**
* Removes the current argument from the list.
* @throws NotEnoughArgumentsException
*/
private void consumeArgument() throws NotEnoughArgumentsException {
if (argsList.isEmpty()) {
throw new NotEnoughArgumentsException();
} else {
argsList.remove(0);
}
}
/**
* Returns the current argument and removes it from the list.
* @return the current argument, which is removed.
* @throws NotEnoughArgumentsException
*/
private String getAndConsumeArgument() throws NotEnoughArgumentsException {
if (argsList.isEmpty()) {
throw new NotEnoughArgumentsException();
} else {
return argsList.remove(0);
}
}
/**
* Verifies that there are no more arguments in the list, or throws an exception otherwise.
*
* @throws TooManyArgumentsException
*/
private void noMoreArgumentsAllowed() throws TooManyArgumentsException {
if (!argsList.isEmpty()) {
throw new TooManyArgumentsException();
}
}
/**
* @return whether there are more arguments left.
*/
private boolean hasMoreArguments() {
return !argsList.isEmpty();
}
}
/** Private constructor. This class is not meant to be instantiated. */
private StandaloneJarTool() {}
/**
* The main method for the tool.
* @param args
*/
public static void main(final String[] args) {
////
/// Set up a logger for printing status messages to standard output
//
final Logger logger = Logger.getLogger(StandaloneJarTool.class.getName());
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
final SimpleConsoleHandler handler = new SimpleConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
logger.info(StandaloneJarToolMessages.getString("versionBanner", Version.CURRENT));
////
/// Parsing the command line arguments
//
try {
final ArgumentList arguments = new ArgumentList(args);
final String cwsName = arguments.getAndConsumeArgument();
final boolean verbose;
if (arguments.getArgument().equals("-verbose")) {
arguments.consumeArgument();
verbose = true;
} else {
verbose = false;
}
final List<MainClassSpec> mainClassSpecs = new ArrayList<MainClassSpec>();
final List<LibraryClassSpec> libClassSpecs = new ArrayList<LibraryClassSpec>();
// unsupported option for adding all modules as libraries via -XX:all
boolean addAllModulesAsLibraries = false;
while (true) {
final String command = arguments.getArgument();
if (command.equals("-main")) {
arguments.consumeArgument();
final String entryPointNameString = arguments.getAndConsumeArgument();
final String mainClassName = arguments.getAndConsumeArgument();
final QualifiedName entryPointName;
try {
entryPointName = QualifiedName.makeFromCompoundName(entryPointNameString);
} catch (final IllegalArgumentException e) {
logger.info(StandaloneJarToolMessages.getString("invalidEntryPointName", entryPointNameString));
return;
}
try {
mainClassSpecs.add(MainClassSpec.make(mainClassName, entryPointName));
} catch (final StandaloneJarBuilder.InvalidConfigurationException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("errorPrefix", e.getMessage()));
return;
}
} else if (command.equals("-lib")) {
arguments.consumeArgument();
final String moduleNameString = arguments.getAndConsumeArgument();
final String libClassScope = arguments.getAndConsumeArgument();
final String libClassName = arguments.getAndConsumeArgument();
final JavaScope scope;
try {
scope = JavaScope.valueOf(libClassScope.toUpperCase());
if (scope == JavaScope.PROTECTED || scope == JavaScope.PRIVATE) {
//package and private scopes are not allowed for top-level Java classes.
logger.info(StandaloneJarToolMessages.getString("invalidScope", libClassScope));
return;
}
} catch (final IllegalArgumentException e) {
logger.info(StandaloneJarToolMessages.getString("invalidScope", libClassScope));
return;
}
final ModuleName moduleName = ModuleName.maybeMake(moduleNameString);
if (moduleName == null) {
logger.info(StandaloneJarToolMessages.getString("invalidModuleName", moduleNameString));
return;
}
try {
libClassSpecs.add(LibraryClassSpec.make(scope, libClassName, moduleName));
} catch (final StandaloneJarBuilder.InvalidConfigurationException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("errorPrefix", e.getMessage()));
return;
}
} else if (command.equals("-XX:all")) { // unsupported internal use argument - adds all modules in the program
arguments.consumeArgument();
addAllModulesAsLibraries = true;
} else {
// not a command... so break out of the loop to process the remainder
break;
}
}
if (mainClassSpecs.isEmpty() && libClassSpecs.isEmpty() && !addAllModulesAsLibraries) {
// there must be at least one -main or -lib command specified (or -XX:all)
throw new ArgumentList.NotEnoughArgumentsException();
}
final File outputFile = new File(arguments.getAndConsumeArgument());
final File outputSrcZipFile;
if (arguments.hasMoreArguments()) {
final String command = arguments.getAndConsumeArgument();
if (command.equals("-src")) {
outputSrcZipFile = new File(arguments.getAndConsumeArgument());
} else {
showUsage(logger);
return;
}
} else {
outputSrcZipFile = null;
}
arguments.noMoreArgumentsAllowed();
if (!LECCMachineConfiguration.nonInterruptibleRuntime()) {
logger.info(StandaloneJarToolMessages.getString("nonInterruptiblePropertyNotSet", NON_INTERRUPTIBLE_PROPERTY));
}
////
/// Compile the CAL workspace
//
logger.info(StandaloneJarToolMessages.getString("initializingCalWorkspace"));
final MessageLogger msgLogger = new MessageLogger();
final BasicCALServices calServices = BasicCALServices.makeCompiled(cwsName, msgLogger);
if (calServices == null) {
logger.info(msgLogger.toString());
return;
}
logger.info(StandaloneJarToolMessages.getString("calWorkspaceInitialized"));
final WorkspaceManager workspaceManager = calServices.getWorkspaceManager();
// process the -XX:all argument
if (addAllModulesAsLibraries) {
for (final ModuleName moduleName : workspaceManager.getModuleNamesInProgram()) {
try {
libClassSpecs.add(LibraryClassSpec.make(JavaScope.PUBLIC, "cal.lib." + moduleName.toSourceText(), moduleName));
} catch (final StandaloneJarBuilder.InvalidConfigurationException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("errorPrefix", e.getMessage()));
return;
}
}
}
////
/// Set up the progress monitor for displaying status messages to the user.
//
final StandaloneJarBuilder.Monitor monitor = new StandaloneJarBuilder.Monitor() {
public void jarBuildingStarted(final String jarName) {
logger.info(StandaloneJarToolMessages.getString("buildingStandaloneJar", jarName));
}
public void addingFile(final String fileName) {
if (verbose) {
logger.info(StandaloneJarToolMessages.getString("addingFile", fileName));
}
}
public void skippingFunctionWithTypeClassConstraints(final QualifiedName name) {
// always display regardless of the verbose flag
logger.info(StandaloneJarToolMessages.getString("skippingFunctionWithTypeClassConstraints", name.toSourceText()));
}
public void skippingClassMethod(final QualifiedName name) {
// always display regardless of the verbose flag
logger.info(StandaloneJarToolMessages.getString("skippingClassMethod", name.toSourceText()));
}
public void addingSourceFile(final String fileName) {
if (verbose) {
logger.info(StandaloneJarToolMessages.getString("addingSourceFile", fileName));
}
}
public void jarBuildingDone(final String jarName, boolean success) {
if (success) {
logger.info(StandaloneJarToolMessages.getString("doneBuildingStandaloneJar", jarName));
} else {
logger.info(StandaloneJarToolMessages.getString("failedBuildingStandaloneJar", jarName));
}
}
};
////
/// Check the arguments and build the JAR
//
try {
StandaloneJarBuilder.buildStandaloneJar(outputFile, outputSrcZipFile, mainClassSpecs, libClassSpecs, workspaceManager, monitor);
} catch (final IOException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("ioError"), e);
} catch (final StandaloneJarBuilder.InvalidConfigurationException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("errorPrefix", e.getMessage()));
} catch (final UnableToResolveForeignEntityException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("foreignResolutionError"), e);
} catch (final StandaloneJarBuilder.InternalProblemException e) {
logger.log(Level.SEVERE, StandaloneJarToolMessages.getString("internalError"), e);
}
} catch (final ArgumentList.NotEnoughArgumentsException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("notEnoughArguments"));
showUsage(logger);
} catch (final ArgumentList.TooManyArgumentsException e) {
logger.log(Level.INFO, StandaloneJarToolMessages.getString("tooManyArguments"));
showUsage(logger);
}
}
/**
* Shows the usage help.
* @param logger the Logger to use for showing the usage help.
*/
private static void showUsage(final Logger logger) {
logger.info(StandaloneJarToolMessages.getString("usage", LAUNCH_COMMAND));
}
}