package org.openflexo.builders;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.openflexo.FlexoProperties;
import org.openflexo.GeneralPreferences;
import org.openflexo.builders.exception.MissingArgumentException;
import org.openflexo.builders.utils.FlexoBuilderListener;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.foundation.FlexoObject;
import org.openflexo.foundation.action.FlexoAction;
import org.openflexo.foundation.resource.FlexoResourceCenterService;
import org.openflexo.logging.FlexoLogger;
import org.openflexo.logging.FlexoLoggingManager;
import org.openflexo.logging.FlexoLoggingManager.LoggingManagerDelegate;
import org.openflexo.module.Module;
import org.openflexo.module.UserType;
import org.openflexo.toolbox.FileResource;
import org.openflexo.toolbox.ResourceLocator;
public abstract class FlexoExternalMain implements Runnable {
public static final String CONSOLE_OUTPUT_ENCODING = "UTF-8";
private static final Logger logger = FlexoLogger.getLogger(FlexoExternalMain.class.getPackage().getName());
public static final String DEV_ARGUMENT = "Dev";
public static final String RESOURCE_PATH_ARGUMENT_PREFIX = "-ResourcePath=";
public static final String WORKING_DIR = "-WorkingDir=";
private static final int MISSING_ARGUMENT = -2;
public static final int FLEXO_ACTION_FAILED = -3;
private static final int CLEANUP_EXCEPTION = -4;
public static final int LOCAL_IO_EXCEPTION = -5;
public static final int PROJECT_NOT_FOUND = -6;
public static final int CORRUPTED_PROJECT_EXCEPTION = -7;
public static final int CLASS_NOT_FOUND = -8;
public static final int UNEXPECTED_EXCEPTION = -100;
public static final int UNKNOWN_EXIT = -120;
public static final int TIMEOUT = -126;
private volatile int exitCode = 0;
protected boolean isDev = false;
private PrintStream consoleStream;
protected static byte[] mem = new byte[500 * 1024]; // Let's grab 500kb to free in case of an error (especially OutOfMemoryError)
private boolean notifyOnExit;
private boolean done;
protected File workingDir = null;
private FlexoResourceCenterService resourceCenterService;
public FlexoExternalMain() {
try {
consoleStream = new PrintStream(System.err, true, CONSOLE_OUTPUT_ENCODING);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
consoleStream = new PrintStream(System.err, true);
}
}
protected void init(String[] args) throws MissingArgumentException {
String resourcePath = null;
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith(RESOURCE_PATH_ARGUMENT_PREFIX)) {
resourcePath = args[i].substring(RESOURCE_PATH_ARGUMENT_PREFIX.length());
if (resourcePath.startsWith("\"")) {
resourcePath = resourcePath.substring(1);
}
if (resourcePath.endsWith("\"")) {
resourcePath = resourcePath.substring(0, resourcePath.length() - 1);
}
} else if (args[i].equals(DEV_ARGUMENT)) {
isDev = true;
} else if (args[i].startsWith(WORKING_DIR)) {
String wd = args[i].substring(WORKING_DIR.length());
if (wd.startsWith("\"")) {
wd = wd.substring(1);
}
if (wd.endsWith("\"")) {
wd = wd.substring(0, wd.length() - 1);
}
workingDir = new File(wd);
workingDir.mkdirs();
}
}
}
if (resourcePath == null && !isDev) {
throw new MissingArgumentException(RESOURCE_PATH_ARGUMENT_PREFIX);
}
if (!isDev) {
ResourceLocator.resetFlexoResourceLocation(new File(resourcePath));
}
UserType.setCurrentUserType(UserType.DEVELOPER);
FlexoProperties.load();
initializeLoggingManager();
if (!isDev) {
if (logger.isLoggable(Level.INFO)) {
logger.info("PreferredResourcePath is set to " + ResourceLocator.getPreferredResourcePath().getAbsolutePath());
}
}
FlexoObject.initialize(false);
if (logger.isLoggable(Level.INFO)) {
logger.info("Launching " + getName() + "...");
}
GeneralPreferences.setFavoriteModuleName(Module.WKF_MODULE.getName());
}
public File getWorkingDir() {
return workingDir;
}
@Override
public final void run() {
try {
doRun();
} catch (Throwable t) {
mem = null;
System.gc();
setExitCodePrintStackTraceCleanUpAndExit(t);
}
}
protected abstract void doRun();
protected abstract String getName();
protected void cleanUp() {
}
public static FlexoLoggingManager initializeLoggingManager() {
try {
FlexoProperties properties = FlexoProperties.load();
return FlexoLoggingManager.initialize(properties.getMaxLogCount(), properties.getIsLoggingTrace(),
properties.getCustomLoggingFile(), properties.getDefaultLoggingLevel(), new LoggingManagerDelegate() {
@Override
public void setMaxLogCount(Integer maxLogCount) {
FlexoProperties.instance().setMaxLogCount(maxLogCount);
}
@Override
public void setKeepLogTrace(boolean logTrace) {
FlexoProperties.instance().setIsLoggingTrace(logTrace);
}
@Override
public void setDefaultLoggingLevel(Level lev) {
String fileName = "SEVERE";
if (lev == Level.SEVERE) {
fileName = "SEVERE";
}
if (lev == Level.WARNING) {
fileName = "WARNING";
}
if (lev == Level.INFO) {
fileName = "INFO";
}
if (lev == Level.FINE) {
fileName = "FINE";
}
if (lev == Level.FINER) {
fileName = "FINER";
}
if (lev == Level.FINEST) {
fileName = "FINEST";
}
reloadLoggingFile(new FileResource("Config/logging_" + fileName + ".properties").getAbsolutePath());
FlexoProperties.instance().setLoggingFileName(null);
}
@Override
public void setConfigurationFileName(String configurationFile) {
reloadLoggingFile(configurationFile);
FlexoProperties.instance().setLoggingFileName(configurationFile);
}
});
} catch (SecurityException e) {
logger.severe("cannot read logging configuration file : " + System.getProperty("java.util.logging.config.file")
+ "\nIt seems the file has read access protection.");
e.printStackTrace();
return null;
} catch (IOException e) {
logger.severe("cannot read logging configuration file : " + System.getProperty("java.util.logging.config.file"));
e.printStackTrace();
return null;
}
}
private static boolean reloadLoggingFile(String filePath) {
logger.info("reloadLoggingFile with " + filePath);
System.setProperty("java.util.logging.config.file", filePath);
try {
LogManager.getLogManager().readConfiguration();
} catch (SecurityException e) {
logger.warning("The specified logging configuration file can't be read (not enough privileges).");
e.printStackTrace();
return false;
} catch (IOException e) {
logger.warning("The specified logging configuration file cannot be read.");
e.printStackTrace();
return false;
}
return true;
}
protected void handleActionFailed(FlexoAction<?, ? extends FlexoModelObject, ? extends FlexoModelObject> action, File fileToOpen) {
if (fileToOpen != null) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Action " + action.getLocalizedName() + " could not be performed on project at "
+ fileToOpen.getAbsolutePath());
}
} else {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Action " + action.getLocalizedName() + " could not be performed");
}
}
reportMessage("Action " + action.getLocalizedName() + " could not be performed");
if (action.getThrownException() != null) {
action.getThrownException().printStackTrace();
}
setExitCodeCleanUpAndExit(getExitCode() == 0 ? FLEXO_ACTION_FAILED : getExitCode());
}
public FlexoResourceCenterService getResourceCenterService() {
return resourceCenterService;
}
public void setResourceCenterService(FlexoResourceCenterService resourceCenter) {
this.resourceCenterService = resourceCenter;
}
protected void handleActionFailed(FlexoAction<?, ? extends FlexoModelObject, ? extends FlexoModelObject> action) {
handleActionFailed(action, null);
}
void build(String[] args) {
try {
init(args);
} catch (MissingArgumentException e) {
e.printStackTrace();
cleanUp();
if (getExitCode() == 0) {
System.exit(MISSING_ARGUMENT);
} else {
System.exit(getExitCode());
}
}
try {
SwingUtilities.invokeAndWait(this);
// Don't perform any code after this
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
dumpThreadStackAndExit();
}
protected static void dumpThreadStackAndExit() {
try {
int size = Thread.activeCount();
Thread[] t;
int count;
do {
t = new Thread[size];
count = Thread.enumerate(t);
size += Thread.activeCount();
} while (t.length < count);
for (Thread thread : t) {
StackTraceElement[] stackTrace = thread.getStackTrace();
System.err.println(thread.toString());
for (StackTraceElement e : stackTrace) {
System.err.println("\tat " + e.getClassName() + "." + e.getMethodName() + "(" + e.getFileName() + ":"
+ (e.getLineNumber() > 0 ? e.getLineNumber() : "Unknown") + ")");
}
}
} finally {
System.err.println("Time out reached! Exiting now...");
System.exit(TIMEOUT);
}
}
public static <A extends FlexoExternalMain> void launch(Class<A> builderClass, String[] args) {
// The next line is very important for performance purposes. External mains can always be run with the smallest priority because
// they are not immediate
final Thread currentThread = Thread.currentThread();
currentThread.setPriority(Thread.MIN_PRIORITY);
Thread timeout = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50 * 60 * 1000);
// Time-out reached!
dumpThreadStackAndExit();
} catch (InterruptedException e) {
return;// Normal
}
}
}, "Timeout thread");
timeout.start();
try {
A main = builderClass.newInstance();
try {
main.build(args);
} catch (Exception e) {
mem = null;
e.printStackTrace();
if (logger.isLoggable(Level.SEVERE)) {
logger.severe("An unexpected exception occured in " + main.getName() + ". Returning now.");
}
try {
main.cleanUp();
} catch (Exception e1) {
e1.printStackTrace();
System.exit(CLEANUP_EXCEPTION);
}
System.exit(-1);
} catch (Throwable e) {
mem = null;
e.printStackTrace();
if (logger.isLoggable(Level.SEVERE)) {
logger.severe("An unexpected ERROR occured in " + main.getName() + ". Returning now.");
}
try {
main.cleanUp();
} catch (Exception e1) {
e1.printStackTrace();
System.exit(CLEANUP_EXCEPTION);
}
System.exit(-100);
}
} catch (InstantiationException e) {
e.printStackTrace(); // Should not happen!
System.exit(-125);
} catch (IllegalAccessException e) {
e.printStackTrace(); // Should not happen!
System.exit(-127);
} finally {
timeout.interrupt();
}
}
/**
* Search recursively and return a project directory in the <code>root</code> directory. The strategy is the following, it first looks
* for a file ending with .rmxml in the current directory, if non can be found, it searches within the directory of the current level
* and so on.
*
* @param root
* - the root directory in which to search.
* @return the project directory if a project can be found within the <code>root</code> directory. If several projects are located in
* that directory, the returned value is uncertain (depending on how the method listFiles() returns the sub-directories of
* <code>root</code>).
*/
public static File searchProjectDirectory(File root) {
if (root == null || !root.exists() || !root.isDirectory()) {
return null;
}
Vector<File> founds = new Vector<File>();
File[] f = root.listFiles();
for (int i = 0; i < f.length; i++) {
File file = f[i];
if (file.isFile() && file.getName().toLowerCase().endsWith(".rmxml")) {
founds.add(file.getParentFile());
}
}
for (int i = 0; i < f.length; i++) {
File file = f[i];
if (file.isDirectory()) {
File found = searchProjectDirectory(file);
if (found != null) {
founds.add(found);
}
}
}
if (founds.size() == 0) {
return null;
} else if (founds.size() == 1) {
return founds.firstElement();
} else {
File ret = founds.firstElement();
for (File file : founds) {
if (file.getAbsolutePath().length() < ret.getAbsolutePath().length()) {
ret = file;
}
}
return ret;
}
}
public void reportMessage(String message) {
writeToConsole(FlexoBuilderListener.REPORT_START_TAG);
writeToConsole(message);
writeToConsole(FlexoBuilderListener.REPORT_END_TAG);
}
public void writeToConsole(String s) {
consoleStream.println(s);
}
public int getExitCode() {
return exitCode;
}
private void setExitCode(int exitCode) {
this.exitCode = exitCode;
this.done = true;
}
public boolean isDone() {
return done;
}
public synchronized void setExitCodePrintStackTraceCleanUpAndExit(Throwable t) {
t.printStackTrace();
setExitCodeCleanUpAndExit(UNEXPECTED_EXCEPTION);
}
public synchronized void setExitCodeCleanUpAndExit(int exitCode) {
setExitCode(exitCode);
try {
cleanUp();
} catch (Throwable e) {
e.printStackTrace();
}
if (isDev) {
if (notifyOnExit) {
synchronized (this) {
notifyAll();
}
}
return;
}
System.exit(getExitCode());
}
public boolean notifyOnExit() {
return notifyOnExit;
}
public void setNotifyOnExit(boolean notifyOnExit) {
this.notifyOnExit = notifyOnExit;
}
}