/*
* 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.
*/
/*
* CarTool.java
* Creation date: Jan 24, 2006.
* By: Joseph Wong
*/
package org.openquark.cal.services;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.Version;
import org.openquark.cal.machine.StatusListener;
import org.openquark.util.FileSystemHelper;
import org.openquark.util.SimpleConsoleHandler;
/**
* This class provides a command line interface to the CAL Archive (Car) building tool.
*
* This class is not meant to be instantiated or subclassed.
*
* @author Joseph Wong
*/
public final class CarTool {
/**
* 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(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);
}
}
/**
* @return an Iterator for the remaining arguments.
*/
private Iterator<String> getRemainingArgumentsIterator() {
return argsList.iterator();
}
/**
* @return a duplicate of this argument list.
*/
private ArgumentList duplicate() {
return new ArgumentList(argsList.toArray(new String[argsList.size()]));
}
/**
* Verifies that there are no more arguments in the list, or throws an exception otherwise.
* Don't use this if {@link #getRemainingArgumentsIterator} is used to fetch the trailing
* variadic argument list.
*
* @throws TooManyArgumentsException
*/
private void noMoreArgumentsAllowed() throws TooManyArgumentsException {
if (!argsList.isEmpty()) {
throw new TooManyArgumentsException();
}
}
}
/**
* Exception for representing that the operation of building Cars has failed.
*
* @author Joseph Wong
*/
private static class OperationFailedException extends Exception {
private static final long serialVersionUID = 1248202291878880653L;
/** Constructs an instance of this exception. */
private OperationFailedException() {}
}
/** Private constructor. */
private CarTool() {}
/**
* Runs the Car building tool with the given command line arguments.
* @param args the command line arguments.
* @return true if the operation succeeds; false otherwise.
*/
private boolean runWithCommandLine(String[] args) {
////
/// Set up a logger for printing status messages to standard output
//
Logger logger = Logger.getLogger(getClass().getName());
logger.setLevel(Level.FINEST);
logger.setUseParentHandlers(false);
SimpleConsoleHandler handler = new SimpleConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);
logger.info("Car Tool Version " + Version.CURRENT + " (c) 2006 Business Objects");
try {
return runWithCommandLineAndLogger(new ArgumentList(args), logger);
} catch (ArgumentList.NotEnoughArgumentsException e) {
logger.info("Not enough arguments.");
logger.info(getUsage(getClass()));
return false;
} catch (ArgumentList.TooManyArgumentsException e) {
logger.info("Too many arguments.");
logger.info(getUsage(getClass()));
return false;
}
}
/**
* Runs the Car building tool with the given command line arguments and logger.
* @param arguments the command line arguments.
* @param logger the logger to use for logging output.
* @return true if the operation succeeds; false otherwise.
*/
private boolean runWithCommandLineAndLogger(ArgumentList arguments, final Logger logger) throws ArgumentList.NotEnoughArgumentsException, ArgumentList.TooManyArgumentsException {
boolean success = true;
if (arguments.getArgument().equals("-unsource")) {
arguments.consumeArgument();
List<String> carOrCarJarsList = new ArrayList<String>();
if (arguments.getArgument().equals("-incars")) {
arguments.consumeArgument();
while (!arguments.getArgument().equals("--")) {
carOrCarJarsList.add(arguments.getAndConsumeArgument());
}
arguments.consumeArgument(); // consume the -- delimiter
} else {
carOrCarJarsList.add(arguments.getAndConsumeArgument());
}
if (arguments.getArgument().equals("-d")) {
arguments.consumeArgument();
String outputFolderString = arguments.getAndConsumeArgument();
arguments.noMoreArgumentsAllowed();
for (final String carOrCarJarPath : carOrCarJarsList) {
try {
unsourceCar(carOrCarJarPath, outputFolderString, logger);
} catch (OperationFailedException e) {
success = false;
}
}
} else {
success = false;
}
} else if (arguments.getArgument().equals("-multi")) {
arguments.consumeArgument();
List<String> cwsNames = new ArrayList<String>();
while (!arguments.getArgument().equals("--")) {
cwsNames.add(arguments.getAndConsumeArgument());
}
arguments.consumeArgument(); // consume the -- delimiter
ArgumentList origArgs = arguments;
Set<String> carsAlreadyBuilt = new HashSet<String>();
for (int i = 0, n = cwsNames.size(); i < n; i++) {
final String workspaceDeclarationName = cwsNames.get(i);
final BasicCALServices calServices = BasicCALServices.make(workspaceDeclarationName);
arguments = origArgs.duplicate();
try {
String[] carsBuilt = helpRun(arguments, logger, workspaceDeclarationName, calServices, carsAlreadyBuilt, true);
carsAlreadyBuilt.addAll(Arrays.asList(carsBuilt));
} catch (OperationFailedException e) {
success = false;
}
}
} else {
final String workspaceDeclarationName = arguments.getAndConsumeArgument();
final BasicCALServices calServices = BasicCALServices.make(workspaceDeclarationName);
try {
helpRun(arguments, logger, workspaceDeclarationName, calServices, Collections.<String>emptySet(), false);
} catch (OperationFailedException e) {
success = false;
}
}
return success;
}
/**
* Helper method for generating a car (or car jar) without source, given the car with source.
* @param carOrCarJarPath the path to the car or car jar.
* @param outputFolderString the path to the output folder
*/
private void unsourceCar(String carOrCarJarPath, String outputFolderString, Logger logger) throws OperationFailedException {
File outputFolder = new File(outputFolderString);
if (!outputFolder.isDirectory()) {
if (!FileSystemHelper.ensureDirectoryExists(outputFolder)) {
logger.info("Could not create folder: " + outputFolder);
throw new OperationFailedException();
}
}
try {
CarUnsourcer.unsourceCar(carOrCarJarPath, outputFolderString, logger);
} catch (IOException e) {
logger.info("Error occurred generating output file: " + e.getMessage());
throw new OperationFailedException();
}
}
/**
* Helper method for running the Car building tool.
*
* @param arguments the remaining command line arguments.
* @param logger the logger to use for logging output.
* @param workspaceDeclarationName the name of the workspace declaration to process.
* @param calServices the associated copy of BasicCALServices initialized with the named workspace.
* @param additionalCarsToExclude names of Car files to exclude from being generated.
* @param isMulti whether the -multi option specified.
* @return an array of the names of the Car files built.
* @throws ArgumentList.TooManyArgumentsException
*/
private String[] helpRun(ArgumentList arguments, final Logger logger, final String workspaceDeclarationName, final BasicCALServices calServices, Set<String> additionalCarsToExclude, boolean isMulti) throws ArgumentList.NotEnoughArgumentsException, ArgumentList.TooManyArgumentsException, OperationFailedException {
////
/// Create and compile a workspace through a BasicCALServices
//
WorkspaceManager workspaceManager = calServices.getWorkspaceManager();
MessageLogger msgLogger = new MessageLogger();
StatusListener.StatusListenerAdapter statusListener = new StatusListener.StatusListenerAdapter() {
/**
* {@inheritDoc}
*/
@Override
public void setModuleStatus(StatusListener.Status.Module moduleStatus, ModuleName moduleName) {
if (moduleStatus == StatusListener.SM_GENCODE) {
logger.fine("Compiling " + moduleName);
}
}
};
logger.info("Compiling CAL workspace...");
calServices.compileWorkspace(statusListener, msgLogger);
if (msgLogger.getNErrors() == 0) {
logger.info("Compilation successful");
////
/// We only run the CarBuilder if everything compiled properly without errors
//
CarBuilder.Monitor monitor = new CarBuilder.Monitor() {
public boolean isCanceled() {
return false;
}
public void operationStarted(int nCars, int nTotalModules) {}
public void showMessages(String[] messages) {
for (final String element : messages) {
logger.info(element);
}
}
public void carBuildingStarted(String carName, int nModules) {
logger.info("Creating the Car file " + carName);
}
public void processingModule(String carName, ModuleName moduleName) {
logger.info("Adding module " + moduleName + " to the Car...");
}
public void carBuildingDone(String carName) {
logger.info("Done creating the Car file " + carName);
}
public void operationDone() {
logger.info("All done");
}
};
///
// Command line syntax:
//
// [-notVerbose] [-keepsource] [-nocws | -nosuffix] [-s] [-excludeCarsInDirs ... --] [-excludeCarJarsInDirs ... --] [-jar] outputDirectory [carSpecName1 carSpecName2...]
// OR
// [-notVerbose] [-keepsource] [-nocws | -nosuffix] [-s] [-excludeCarsInDirs ... --] [-excludeCarJarsInDirs ... --] [-jar] -d outputDirectory
boolean verboseMode = true; // default: verbose
if (arguments.getArgument().equals("-notVerbose")) {
verboseMode = false;
arguments.consumeArgument();
}
boolean shouldBuildSourcelessModules = true; // default: generate sourceless modules
if (arguments.getArgument().equals("-keepsource")) {
shouldBuildSourcelessModules = false;
arguments.consumeArgument();
}
boolean shouldGenerateCorrespWorspaceDecl = true; // default: generated the cws files
boolean noCarSuffixInWorkspaceDeclName = false; // default: generated cws files end with .car.cws
if (arguments.getArgument().equals("-nocws")) {
shouldGenerateCorrespWorspaceDecl = false;
arguments.consumeArgument();
} else if (arguments.getArgument().equals("-nosuffix")) {
noCarSuffixInWorkspaceDeclName = true;
arguments.consumeArgument();
}
boolean shouldSkipModulesAlreadyInCars = false; // default: do not skip modules already in Cars
if (arguments.getArgument().equals("-s")) {
shouldSkipModulesAlreadyInCars = true;
arguments.consumeArgument();
}
Set<String> carsToExclude = new HashSet<String>(additionalCarsToExclude);
if (arguments.getArgument().equals("-excludeCarsInDirs")) {
arguments.consumeArgument();
while (!arguments.getArgument().equals("--")) {
File directory = new File(arguments.getAndConsumeArgument(), "Car");
File[] files = directory.listFiles();
if (files == null) {
logger.warning("Folder does not exist: " + directory);
} else {
for (final File element : files) {
carsToExclude.add(element.getName());
}
}
}
arguments.consumeArgument(); // consume the -- delimiter
}
if (arguments.getArgument().equals("-excludeCarJarsInDirs")) {
arguments.consumeArgument();
while (!arguments.getArgument().equals("--")) {
File directory = new File(arguments.getAndConsumeArgument());
File[] files = directory.listFiles();
if (files == null) {
logger.warning("Folder does not exist: " + directory);
} else {
for (final File element : files) {
String fileName = element.getName();
if (fileName.endsWith(CarBuilder.DOT_CAR_DOT_JAR)) {
String carName = fileName.substring(0, fileName.length() - 4); // chop ".jar" off the end
carsToExclude.add(carName);
}
}
}
}
arguments.consumeArgument(); // consume the -- delimiter
}
boolean shouldGenerateCarJarSuffix = false; // default: generate Cars and not Car-jars.
if (arguments.getArgument().equals("-jar")) {
shouldGenerateCarJarSuffix = true;
arguments.consumeArgument();
}
if (verboseMode) {
System.out.println();
System.out.println(workspaceManager.getDebugInfo());
System.out.println();
}
CarBuilder.BuilderOptions options = new CarBuilder.BuilderOptions(
shouldSkipModulesAlreadyInCars,
shouldGenerateCorrespWorspaceDecl,
noCarSuffixInWorkspaceDeclName,
shouldBuildSourcelessModules,
carsToExclude,
shouldGenerateCarJarSuffix);
if (arguments.getArgument().equals("-d")) {
// the user wants one Car per workspace declaration file
arguments.consumeArgument();
final String outputDir = arguments.getAndConsumeArgument();
arguments.noMoreArgumentsAllowed();
final File outputDirectory = new File(outputDir);
try {
String[] carsBuilt = CarBuilder.buildOneCarPerWorkspaceDeclaration(workspaceManager, monitor, outputDirectory, options);
return carsBuilt;
} catch (IOException e) {
logger.info("Error occurred generating the Car files: " + e.getMessage());
throw new OperationFailedException();
}
} else {
// the user wants just one Car for all modules in the workspace
if (!carsToExclude.equals(additionalCarsToExclude)) {
logger.info("The option -excludeCarsInDirs does not apply for building just one Car. Ignoring...");
}
final String carName = CarBuilder.makeCarNameFromSourceWorkspaceDeclName(workspaceDeclarationName);
final String outputDir = arguments.getAndConsumeArgument();
final File outputDirectory = new File(outputDir);
CarBuilder.Configuration config =
CarBuilder.Configuration.makeConfigOptionallySkippingModulesAlreadyInCars(workspaceManager, options);
for (Iterator<String> it = arguments.getRemainingArgumentsIterator(); it.hasNext(); ) {
String carSpecName = it.next();
File specFile = new File(carSpecName);
config.addFileBasedCarSpec(specFile.getName(), specFile);
}
config.setMonitor(monitor);
try {
boolean notCanceled = CarBuilder.buildCar(config, outputDirectory, carName, options);
if (notCanceled) {
return new String[] {carName};
}
} catch (IOException e) {
logger.info("Error occurred generating the Car file: " + e.getMessage());
throw new OperationFailedException();
}
}
} else {
logger.severe("Compilation failed:");
List<CompilerMessage> messages = msgLogger.getCompilerMessages();
for (int i = 0, n = messages.size(); i < n; i++) {
logger.info(" " + messages.get(i).toString());
}
throw new OperationFailedException();
}
return new String[0];
}
/** The main method */
public static void main(String[] args) {
boolean success = new CarTool().runWithCommandLine(args);
if (!success) {
System.err.println("Car Tool - Car building failed.");
System.exit(1);
}
}
/**
* @return the usage string.
*/
private static String getUsage(Class<? extends CarTool> mainClass) {
String usage =
"Usage 1 - to generate a single Car file per source workspace declaration specified:\n" +
"java " + mainClass.getName() + " [workspaceDeclaration | -multi workspaceDeclaration ... --] [-notVerbose] [-keepsource] [-nocws | -nosuffix] [-s] [-excludeCarsInDirs dirName ... --] [-excludeCarJarsInDirs dirName ... --] [-jar] outputDirectory [specFileName ...]\n" +
"\n" +
"Where:\n" +
" workspaceDeclaration - the name of the workspace declaration file, e.g. everything.default.cws\n" +
" -multi - if specified, then the generated Cars will be based on the set of workspace declaration files between -multi and --.\n" +
" -notVerbose - if specified, then additional diagnostic information will not be displayed.\n" +
" -keepsource - if specified, then the generated Car will contain the full source of the modules (versus having sourceless modules with empty CAL file stubs).\n" +
" -nocws - if specified, then a new workspace declaration file referencing the output Car will not be generated.\n" +
" -nosuffix - if specified, then the workspace declaration file generated will end simply with .cws rather than .car.cws.\n" +
" -s - if specified, then the modules that come from Cars will be skipped over in the output Car.\n" +
" -excludeCarsInDirs - if specified, then the Cars found in the 'Car' subdirectories of the listed directories will not be generated.\n" +
" -excludeCarJarsInDirs- if specified, then the Car-jars found in the listed directories will not be generated.\n" +
" -jar - if specified, then the output will be in the form of Car-jars.\n" +
" outputDirectory - the name of the output directory to which the single Car file will be generated (under a 'Car' subfolder).\n" +
" and also the corresponding workspace declaration if requested (under a 'Workspace Declarations' subfolder).\n" +
" specFileName - the name of an additional Car workspace spec file\n" +
"\n" +
"Usage 2 - to generate multiple Car files - one Car file per workspace declaration that is imported:\n" +
"java " + mainClass.getName() + " [workspaceDeclaration | -multi workspaceDeclaration ... --] [-notVerbose] [-keepsource] [-nocws | -nosuffix] [-s] [-excludeCarsInDirs dirName ... --] [-excludeCarJarsInDirs dirName ... --] [-jar] -d outputDirectory\n" +
"\n" +
"Where:\n" +
" workspaceDeclaration - the name of the workspace declaration file, e.g. everything.default.cws\n" +
" -multi - if specified, then the generated Cars will be based on the set of workspace declaration files between -multi and --.\n" +
" -notVerbose - if specified, then additional diagnostic information will not be displayed.\n" +
" -keepsource - if specified, then the generated Cars will contain the full source of the modules (versus having sourceless modules with empty CAL file stubs).\n" +
" -nocws - if specified, then a new workspace declaration file will not be generated for each output Car.\n" +
" -nosuffix - if specified, then the workspace declaration files generated will end simply with .cws rather than .car.cws.\n" +
" -s - if specified, then the modules that come from Cars will be skipped over in the output Cars.\n" +
" -excludeCarsInDirs - if specified, then the Cars found in the 'Car' subdirectories of the listed directories will not be generated.\n" +
" -excludeCarJarsInDirs- if specified, then the Car-jars found in the listed directories will not be generated.\n" +
" -jar - if specified, then the output will be in the form of Car-jars.\n" +
" outputDirectory - the name of the output directory to which one Car file per workspace declaration file will be generated (under a 'Car' subfolder)\n" +
" and also the corresponding workspace declarations if requested (under a 'Workspace Declarations' subfolder).\n" +
"\n" +
"Usage 3 - to remove source from an existing car or carjar file. \n" +
"java " + mainClass.getName() + " -unsource [carOrCarJar | -incars carOrCarJar ... --] -d outputDirectory\n" +
"\n" +
"Where:\n" +
" -incars - if specified, then the generated Cars (or car jars) will be based on the set of input files between -incars and --.\n" +
" carOrCarJar - the path to the car or carjar file, e.g. \"c:\\\\everything.default.car\"\n" +
" outputDirectory - the name of the output directory to the output Car files will be generated\n" +
"\n";
return usage;
}
}