blob: a98dc8d2d81c793b9db8af3585a6bb9cc81da2bf [file] [log] [blame]
/*
* Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.tools.jaotc;
import static org.graalvm.compiler.core.common.GraalOptions.GeneratePIC;
import static org.graalvm.compiler.core.common.GraalOptions.ImmutableCode;
import static org.graalvm.compiler.hotspot.meta.HotSpotAOTProfilingPlugin.Options.TieredAOT;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Stream;
import jdk.tools.jaotc.binformat.BinaryContainer;
import jdk.tools.jaotc.binformat.ByteContainer;
import jdk.tools.jaotc.collect.*;
import jdk.tools.jaotc.collect.classname.ClassNameSourceProvider;
import jdk.tools.jaotc.collect.directory.DirectorySourceProvider;
import jdk.tools.jaotc.collect.jar.JarSourceProvider;
import jdk.tools.jaotc.collect.module.ModuleSourceProvider;
import jdk.tools.jaotc.utils.Timer;
import org.graalvm.compiler.api.runtime.GraalJVMCICompiler;
import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig;
import org.graalvm.compiler.hotspot.HotSpotGraalRuntimeProvider;
import org.graalvm.compiler.hotspot.HotSpotHostBackend;
import org.graalvm.compiler.java.GraphBuilderPhase;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
import org.graalvm.compiler.phases.BasePhase;
import org.graalvm.compiler.phases.PhaseSuite;
import org.graalvm.compiler.phases.tiers.HighTierContext;
import org.graalvm.compiler.runtime.RuntimeProvider;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
import jdk.vm.ci.runtime.JVMCI;
public class Main implements LogPrinter {
static {
GeneratePIC.setValue(true);
ImmutableCode.setValue(true);
}
static class BadArgs extends Exception {
private static final long serialVersionUID = 1L;
final String key;
final Object[] args;
boolean showUsage;
BadArgs(String key, Object... args) {
super(MessageFormat.format(key, args));
this.key = key;
this.args = args;
}
BadArgs showUsage(boolean b) {
showUsage = b;
return this;
}
}
abstract static class Option {
final String help;
final boolean hasArg;
final String[] aliases;
Option(String help, boolean hasArg, String... aliases) {
this.help = help;
this.hasArg = hasArg;
this.aliases = aliases;
}
boolean isHidden() {
return false;
}
boolean matches(String opt) {
for (String a : aliases) {
if (a.equals(opt)) {
return true;
} else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) {
return true;
}
}
return false;
}
boolean ignoreRest() {
return false;
}
abstract void process(Main task, String opt, String arg) throws BadArgs;
}
static Option[] recognizedOptions = { new Option(" --output <file> Output file name", true, "--output") {
@Override
void process(Main task, String opt, String arg) {
String name = arg;
if (name.endsWith(".so")) {
name = name.substring(0, name.length() - ".so".length());
}
task.options.outputName = name;
}
}, new Option(" --class-name <class names> List of classes to compile", true, "--class-name", "--classname") {
@Override
void process(Main task, String opt, String arg) {
task.options.files.addAll(ClassSearch.makeList(ClassNameSourceProvider.TYPE, arg));
}
}, new Option(" --jar <jarfiles> List of jar files to compile", true, "--jar") {
@Override
void process(Main task, String opt, String arg) {
task.options.files.addAll(ClassSearch.makeList(JarSourceProvider.TYPE, arg));
}
}, new Option(" --module <modules> List of modules to compile", true, "--module") {
@Override
void process(Main task, String opt, String arg) {
task.options.files.addAll(ClassSearch.makeList(ModuleSourceProvider.TYPE, arg));
}
}, new Option(" --directory <dirs> List of directories where to search for files to compile", true, "--directory") {
@Override
void process(Main task, String opt, String arg) {
task.options.files.addAll(ClassSearch.makeList(DirectorySourceProvider.TYPE, arg));
}
}, new Option(" --search-path <dirs> List of directories where to search for specified files", true, "--search-path") {
@Override
void process(Main task, String opt, String arg) {
String[] elements = arg.split(":");
task.options.searchPath.add(elements);
}
}, new Option(" --compile-commands <file> Name of file with compile commands", true, "--compile-commands") {
@Override
void process(Main task, String opt, String arg) {
task.options.methodList = arg;
}
}, new Option(" --compile-for-tiered Generate profiling code for tiered compilation", false, "--compile-for-tiered") {
@Override
void process(Main task, String opt, String arg) {
TieredAOT.setValue(true);
}
}, new Option(" --compile-with-assertions Compile with java assertions", false, "--compile-with-assertions") {
@Override
void process(Main task, String opt, String arg) {
task.options.compileWithAssertions = true;
}
}, new Option(" --compile-threads <number> Number of compilation threads to be used", true, "--compile-threads", "--threads") {
@Override
void process(Main task, String opt, String arg) {
int threads = Integer.parseInt(arg);
final int available = Runtime.getRuntime().availableProcessors();
if (threads <= 0) {
task.warning("invalid number of threads specified: {0}, using: {1}", threads, available);
threads = available;
}
if (threads > available) {
task.warning("too many threads specified: {0}, limiting to: {1}", threads, available);
}
task.options.threads = Integer.min(threads, available);
}
}, new Option(" --ignore-errors Ignores all exceptions thrown during class loading", false, "--ignore-errors") {
@Override
void process(Main task, String opt, String arg) {
task.options.ignoreClassLoadingErrors = true;
}
}, new Option(" --exit-on-error Exit on compilation errors", false, "--exit-on-error") {
@Override
void process(Main task, String opt, String arg) {
task.options.exitOnError = true;
}
}, new Option(" --info Print information during compilation", false, "--info") {
@Override
void process(Main task, String opt, String arg) throws BadArgs {
task.options.info = true;
}
}, new Option(" --verbose Print verbose information", false, "--verbose") {
@Override
void process(Main task, String opt, String arg) throws BadArgs {
task.options.info = true;
task.options.verbose = true;
}
}, new Option(" --debug Print debug information", false, "--debug") {
@Override
void process(Main task, String opt, String arg) throws BadArgs {
task.options.info = true;
task.options.verbose = true;
task.options.debug = true;
}
}, new Option(" --help Print this usage message", false, "--help") {
@Override
void process(Main task, String opt, String arg) {
task.options.help = true;
}
}, new Option(" --version Version information", false, "--version") {
@Override
void process(Main task, String opt, String arg) {
task.options.version = true;
}
}, new Option(" -J<flag> Pass <flag> directly to the runtime system", false, "-J") {
@Override
void process(Main task, String opt, String arg) {
}
}};
public static class Options {
public List<SearchFor> files = new LinkedList<>();
public String outputName = "unnamed";
public String methodList;
public List<ClassSource> sources = new ArrayList<>();
public SearchPath searchPath = new SearchPath();
/**
* We don't see scaling beyond 16 threads.
*/
private static final int COMPILER_THREADS = 16;
public int threads = Integer.min(COMPILER_THREADS, Runtime.getRuntime().availableProcessors());
public boolean ignoreClassLoadingErrors;
public boolean exitOnError;
public boolean info;
public boolean verbose;
public boolean debug;
public boolean help;
public boolean version;
public boolean compileWithAssertions;
}
/* package */final Options options = new Options();
/**
* Logfile.
*/
private static FileWriter logFile = null;
private static final int EXIT_OK = 0; // No errors.
private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches.
private static final int EXIT_ABNORMAL = 4; // Terminated abnormally.
private static final String PROGNAME = "jaotc";
private static final String JVM_VERSION = System.getProperty("java.runtime.version");
public static void main(String[] args) throws Exception {
Main t = new Main();
final int exitCode = t.run(args);
System.exit(exitCode);
}
private int run(String[] args) {
if (log == null) {
log = new PrintWriter(System.out);
}
try {
handleOptions(args);
if (options.help) {
showHelp();
return EXIT_OK;
}
if (options.version) {
showVersion();
return EXIT_OK;
}
printlnInfo("Compiling " + options.outputName + "...");
final long start = System.currentTimeMillis();
if (!run()) {
return EXIT_ABNORMAL;
}
final long end = System.currentTimeMillis();
printlnInfo("Total time: " + (end - start) + " ms");
return EXIT_OK;
} catch (BadArgs e) {
reportError(e.key, e.args);
if (e.showUsage) {
showUsage();
}
return EXIT_CMDERR;
} catch (Exception e) {
e.printStackTrace();
return EXIT_ABNORMAL;
} finally {
log.flush();
}
}
private static String humanReadableByteCount(long bytes) {
int unit = 1024;
if (bytes < unit) {
return bytes + " B";
}
int exp = (int) (Math.log(bytes) / Math.log(unit));
char pre = "KMGTPE".charAt(exp - 1);
return String.format("%.1f %cB", bytes / Math.pow(unit, exp), pre);
}
void printMemoryUsage() {
if (options.verbose) {
MemoryUsage memusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
float freeratio = 1f - (float) memusage.getUsed() / memusage.getCommitted();
log.format(" [used: %-7s, comm: %-7s, freeRatio ~= %.1f%%]",
humanReadableByteCount(memusage.getUsed()),
humanReadableByteCount(memusage.getCommitted()),
freeratio * 100);
}
}
@SuppressWarnings("try")
private boolean run() throws Exception {
openLog();
try {
CompilationSpec compilationRestrictions = collectSpecifiedMethods();
Set<Class<?>> classesToCompile = new HashSet<>();
try (Timer t = new Timer(this, "")) {
FileSupport fileSupport = new FileSupport();
ClassSearch lookup = new ClassSearch();
lookup.addProvider(new ModuleSourceProvider());
lookup.addProvider(new ClassNameSourceProvider(fileSupport));
lookup.addProvider(new JarSourceProvider());
lookup.addProvider(new DirectorySourceProvider(fileSupport));
List<LoadedClass> found = null;
try {
found = lookup.search(options.files, options.searchPath);
} catch (InternalError e) {
reportError(e);
return false;
}
for (LoadedClass loadedClass : found) {
classesToCompile.add(loadedClass.getLoadedClass());
}
printInfo(classesToCompile.size() + " classes found");
}
GraalJVMCICompiler graalCompiler = (GraalJVMCICompiler) JVMCI.getRuntime().getCompiler();
HotSpotGraalRuntimeProvider runtime = (HotSpotGraalRuntimeProvider) graalCompiler.getGraalRuntime();
HotSpotHostBackend backend = (HotSpotHostBackend) runtime.getCapability(RuntimeProvider.class).getHostBackend();
MetaAccessProvider metaAccess = backend.getProviders().getMetaAccess();
GraalFilters filters = new GraalFilters(metaAccess);
List<AOTCompiledClass> classes;
try (Timer t = new Timer(this, "")) {
classes = collectMethodsToCompile(classesToCompile, compilationRestrictions, filters, metaAccess);
}
// Free memory!
try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) {
printMemoryUsage();
compilationRestrictions = null;
classesToCompile = null;
System.gc();
}
AOTBackend aotBackend = new AOTBackend(this, backend, filters);
AOTCompiler compiler = new AOTCompiler(this, aotBackend, options.threads);
classes = compiler.compileClasses(classes);
GraalHotSpotVMConfig graalHotSpotVMConfig = runtime.getVMConfig();
PhaseSuite<HighTierContext> graphBuilderSuite = aotBackend.getGraphBuilderSuite();
ListIterator<BasePhase<? super HighTierContext>> iterator = graphBuilderSuite.findPhase(GraphBuilderPhase.class);
GraphBuilderConfiguration graphBuilderConfig = ((GraphBuilderPhase) iterator.previous()).getGraphBuilderConfig();
// Free memory!
try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) {
printMemoryUsage();
aotBackend = null;
compiler = null;
System.gc();
}
BinaryContainer binaryContainer = new BinaryContainer(graalHotSpotVMConfig, graphBuilderConfig, JVM_VERSION);
DataBuilder dataBuilder = new DataBuilder(this, backend, classes, binaryContainer);
dataBuilder.prepareData();
// Print information about section sizes
printContainerInfo(binaryContainer.getHeaderContainer().getContainer());
printContainerInfo(binaryContainer.getConfigContainer());
printContainerInfo(binaryContainer.getKlassesOffsetsContainer());
printContainerInfo(binaryContainer.getMethodsOffsetsContainer());
printContainerInfo(binaryContainer.getKlassesDependenciesContainer());
printContainerInfo(binaryContainer.getStubsOffsetsContainer());
printContainerInfo(binaryContainer.getMethodMetadataContainer());
printContainerInfo(binaryContainer.getCodeContainer());
printContainerInfo(binaryContainer.getCodeSegmentsContainer());
printContainerInfo(binaryContainer.getConstantDataContainer());
printContainerInfo(binaryContainer.getMetaspaceGotContainer());
printContainerInfo(binaryContainer.getMetadataGotContainer());
printContainerInfo(binaryContainer.getMethodStateContainer());
printContainerInfo(binaryContainer.getOopGotContainer());
printContainerInfo(binaryContainer.getMetaspaceNamesContainer());
// Free memory!
try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) {
printMemoryUsage();
backend = null;
for (AOTCompiledClass aotCompClass : classes) {
aotCompClass.clear();
}
classes.clear();
classes = null;
dataBuilder = null;
binaryContainer.freeMemory();
System.gc();
}
String objectFileName = options.outputName + ".o";
String libraryFileName = options.outputName + ".so";
try (Timer t = new Timer(this, "Creating binary: " + objectFileName)) {
binaryContainer.createBinary(objectFileName, JVM_VERSION);
}
// Free memory!
try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) {
printMemoryUsage();
binaryContainer = null;
System.gc();
}
try (Timer t = new Timer(this, "Creating shared library: " + libraryFileName)) {
Process p = Runtime.getRuntime().exec("ld -shared -z noexecstack -o " + libraryFileName + " " + objectFileName);
final int exitCode = p.waitFor();
if (exitCode != 0) {
InputStream stderr = p.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
Stream<String> lines = br.lines();
StringBuilder sb = new StringBuilder();
lines.iterator().forEachRemaining(e -> sb.append(e));
throw new InternalError(sb.toString());
}
File objFile = new File(objectFileName);
if (objFile.exists()) {
if (!objFile.delete()) {
throw new InternalError("Failed to delete " + objectFileName + " file");
}
}
// Make non-executable for all.
File libFile = new File(libraryFileName);
if (libFile.exists()) {
if (!libFile.setExecutable(false, false)) {
throw new InternalError("Failed to change attribute for " + libraryFileName + " file");
}
}
}
printVerbose("Final memory ");
printMemoryUsage();
printlnVerbose("");
} finally {
closeLog();
}
return true;
}
private void addMethods(AOTCompiledClass aotClass, ResolvedJavaMethod[] methods, CompilationSpec compilationRestrictions, GraalFilters filters) {
for (ResolvedJavaMethod m : methods) {
addMethod(aotClass, m, compilationRestrictions, filters);
}
}
private void addMethod(AOTCompiledClass aotClass, ResolvedJavaMethod method, CompilationSpec compilationRestrictions, GraalFilters filters) {
// Don't compile native or abstract methods.
if (!method.hasBytecodes()) {
return;
}
if (!compilationRestrictions.shouldCompileMethod(method)) {
return;
}
if (!filters.shouldCompileMethod(method)) {
return;
}
aotClass.addMethod(method);
printlnVerbose(" added " + method.getName() + method.getSignature().toMethodDescriptor());
}
private void printContainerInfo(ByteContainer container) {
printlnVerbose(container.getContainerName() + ": " + container.getByteStreamSize() + " bytes");
}
PrintWriter log;
private void handleOptions(String[] args) throws BadArgs {
if (args.length == 0) {
options.help = true;
return;
}
// Make checkstyle happy.
int i = 0;
for (; i < args.length; i++) {
String arg = args[i];
if (arg.charAt(0) == '-') {
Option option = getOption(arg);
String param = null;
if (option.hasArg) {
if (arg.startsWith("--") && arg.indexOf('=') > 0) {
param = arg.substring(arg.indexOf('=') + 1, arg.length());
} else if (i + 1 < args.length) {
param = args[++i];
}
if (param == null || param.isEmpty() || param.charAt(0) == '-') {
throw new BadArgs("missing argument for option: {0}", arg).showUsage(true);
}
}
option.process(this, arg, param);
if (option.ignoreRest()) {
break;
}
} else {
options.files.add(new SearchFor(arg));
}
}
}
private static Option getOption(String name) throws BadArgs {
for (Option o : recognizedOptions) {
if (o.matches(name)) {
return o;
}
}
throw new BadArgs("unknown option: {0}", name).showUsage(true);
}
public void printInfo(String message) {
if (options.info) {
log.print(message);
log.flush();
}
}
public void printlnInfo(String message) {
if (options.info) {
log.println(message);
log.flush();
}
}
public void printVerbose(String message) {
if (options.verbose) {
log.print(message);
log.flush();
}
}
public void printlnVerbose(String message) {
if (options.verbose) {
log.println(message);
log.flush();
}
}
public void printDebug(String message) {
if (options.debug) {
log.print(message);
log.flush();
}
}
public void printlnDebug(String message) {
if (options.debug) {
log.println(message);
log.flush();
}
}
public void printError(String message) {
log.println("Error: " + message);
log.flush();
}
private void reportError(Throwable e) {
log.println("Error: " + e.getMessage());
e.printStackTrace(log);
log.flush();
}
private void reportError(String key, Object... args) {
printError(MessageFormat.format(key, args));
}
private void warning(String key, Object... args) {
log.println("Warning: " + MessageFormat.format(key, args));
log.flush();
}
private void showUsage() {
log.println("Usage: " + PROGNAME + " <options> list");
log.println("use --help for a list of possible options");
}
private void showHelp() {
log.println("Usage: " + PROGNAME + " <options> list");
log.println();
log.println(" list A : separated list of class names, modules, jar files");
log.println(" or directories which contain class files.");
log.println();
log.println("where options include:");
for (Option o : recognizedOptions) {
String name = o.aliases[0].substring(1); // there must always be at least one name
name = name.charAt(0) == '-' ? name.substring(1) : name;
if (o.isHidden() || name.equals("h")) {
continue;
}
log.println(o.help);
}
}
private void showVersion() {
log.println(PROGNAME + " " + JVM_VERSION);
}
/**
* Collect all method we should compile.
*
* @return array list of AOT classes which have compiled methods.
*/
private List<AOTCompiledClass> collectMethodsToCompile(Set<Class<?>> classesToCompile, CompilationSpec compilationRestrictions, GraalFilters filters, MetaAccessProvider metaAccess) {
int total = 0;
int count = 0;
List<AOTCompiledClass> classes = new ArrayList<>();
for (Class<?> c : classesToCompile) {
ResolvedJavaType resolvedJavaType = metaAccess.lookupJavaType(c);
if (filters.shouldCompileAnyMethodInClass(resolvedJavaType)) {
AOTCompiledClass aotClass = new AOTCompiledClass(resolvedJavaType);
printlnVerbose(" Scanning " + c.getName());
// Constructors
try {
ResolvedJavaMethod[] ctors = resolvedJavaType.getDeclaredConstructors();
addMethods(aotClass, ctors, compilationRestrictions, filters);
total += ctors.length;
} catch (Throwable e) {
// If we are running in JCK mode we ignore all exceptions.
if (options.ignoreClassLoadingErrors) {
printError(c.getName() + ": " + e);
} else {
throw new InternalError(e);
}
}
// Methods
try {
ResolvedJavaMethod[] methods = resolvedJavaType.getDeclaredMethods();
addMethods(aotClass, methods, compilationRestrictions, filters);
total += methods.length;
} catch (Throwable e) {
// If we are running in JCK mode we ignore all exceptions.
if (options.ignoreClassLoadingErrors) {
printError(c.getName() + ": " + e);
} else {
throw new InternalError(e);
}
}
// Class initializer
try {
ResolvedJavaMethod clinit = resolvedJavaType.getClassInitializer();
if (clinit != null) {
addMethod(aotClass, clinit, compilationRestrictions, filters);
total++;
}
} catch (Throwable e) {
// If we are running in JCK mode we ignore all exceptions.
if (options.ignoreClassLoadingErrors) {
printError(c.getName() + ": " + e);
} else {
throw new InternalError(e);
}
}
// Found any methods to compile? Add the class.
if (aotClass.hasMethods()) {
classes.add(aotClass);
count += aotClass.getMethodCount();
}
}
}
printInfo(total + " methods total, " + count + " methods to compile");
return classes;
}
/**
* If a file with compilation limitations is specified using the java property
* jdk.tools.jaotc.compile.method.list, read the file's contents and collect the restrictions.
*/
private CompilationSpec collectSpecifiedMethods() {
CompilationSpec compilationRestrictions = new CompilationSpec();
String methodListFileName = options.methodList;
if (methodListFileName != null && !methodListFileName.equals("")) {
try {
FileReader methListFile = new FileReader(methodListFileName);
BufferedReader readBuf = new BufferedReader(methListFile);
String line = null;
while ((line = readBuf.readLine()) != null) {
String trimmedLine = line.trim();
if (!trimmedLine.startsWith("#")) {
String[] components = trimmedLine.split(" ");
if (components.length == 2) {
String directive = components[0];
String pattern = components[1];
switch (directive) {
case "compileOnly":
compilationRestrictions.addCompileOnlyPattern(pattern);
break;
case "exclude":
compilationRestrictions.addExcludePattern(pattern);
break;
default:
System.out.println("Unrecognized command " + directive + ". Ignoring\n\t" + line + "\n encountered in " + methodListFileName);
}
} else {
if (!trimmedLine.equals("")) {
System.out.println("Ignoring malformed line:\n\t " + line + "\n");
}
}
}
}
readBuf.close();
} catch (FileNotFoundException e) {
throw new InternalError("Unable to open method list file: " + methodListFileName, e);
} catch (IOException e) {
throw new InternalError("Unable to read method list file: " + methodListFileName, e);
}
}
return compilationRestrictions;
}
private static void openLog() {
int v = Integer.getInteger("jdk.tools.jaotc.logCompilation", 0);
if (v == 0) {
logFile = null;
return;
}
// Create log file in current directory
String fileName = "aot_compilation" + new Date().getTime() + ".log";
Path logFilePath = Paths.get("./", fileName);
String logFileName = logFilePath.toString();
try {
// Create file to which we do not append
logFile = new FileWriter(logFileName, false);
} catch (IOException e) {
System.out.println("Unable to open logfile :" + logFileName + "\nNo logs will be created");
logFile = null;
}
}
public static void writeLog(String str) {
if (logFile != null) {
try {
logFile.write(str + "\n");
logFile.flush();
} catch (IOException e) {
// Print to console
System.out.println(str + "\n");
}
}
}
public static void closeLog() {
if (logFile != null) {
try {
logFile.close();
} catch (IOException e) {
// Do nothing
}
}
}
}