/*
* Copyright (c) 2007, 2012, 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 com.sun.max.vm;
import java.util.*;
import com.sun.max.*;
import com.sun.max.annotate.*;
import com.sun.max.memory.*;
import com.sun.max.program.*;
import com.sun.max.unsafe.*;
/**
* A class that represents an option to the virtual machine, including both the low-level,
* internal options (e.g. -Xmixed), and the standard options (e.g. -classpath). Several
* subclasses of this class provide integer values, strings, and sizes. Some options
* are parsed at different times than others; thus a VM option can have an associated
* phase in which it is parsed.
*
* A VM option must be registered to be enabled at runtime.
*/
public class VMOption {
/**
* Constants denoting the categories of VM options.
*/
public enum Category {
/**
* Constant denoting options that do not start with "-X".
*/
STANDARD(18, 72, "-"),
/**
* Constant denoting options that start with "-X" but not "-XX".
*/
NON_STANDARD(22, 92, "-X"),
/**
* Constant denoting options that start with "-XX".
*/
IMPLEMENTATION_SPECIFIC(42, 122, "-XX");
public final int helpIndent;
public final int helpLineMaxWidth;
public final String prefix;
Category(int helpIndent, int helpLineMaxWidth, String prefix) {
this.helpIndent = helpIndent;
this.helpLineMaxWidth = helpLineMaxWidth;
this.prefix = prefix;
}
public String optionName(VMOption option) {
String name = option.prefix.substring(prefix.length());
while (!Character.isJavaIdentifierStart(name.charAt(0))) {
name = name.substring(1);
}
for (int i = 0; i < name.length(); i++) {
if (!Character.isJavaIdentifierPart(name.charAt(i))) {
return name.substring(0, i);
}
}
return name;
}
static boolean isImplementationSpecificPrefixChar(char c) {
if (c >= 'A' && c <= 'Z') {
return true;
}
if (c >= '0' && c <= '9') {
return true;
}
return false;
}
public static Category from(String prefix) {
int colon = prefix.indexOf(':');
if (colon != -1) {
int i = 1;
while (i < colon) {
char c = prefix.charAt(i);
if (!isImplementationSpecificPrefixChar(c)) {
break;
}
i++;
}
if (i == colon) {
return Category.IMPLEMENTATION_SPECIFIC;
}
}
if (prefix.startsWith("-XX")) {
return Category.IMPLEMENTATION_SPECIFIC;
}
if (prefix.startsWith("-X")) {
return Category.NON_STANDARD;
}
return Category.STANDARD;
}
}
/**
* The name of the option. This is only unique within this option's {@linkplain #category() category}.
*/
protected final String name;
protected final String prefix;
protected final boolean exactPrefix;
protected final String help;
@RESET
protected Pointer optionStart = Pointer.zero();
/**
* Extends a given VM option help message with a suffix describing a given default value.
*
* @param help the original help message
* @param defaultValue the string version of a default value (ignored if {@code null})
* @return the help message extended to include {@code defaultValue} if the latter is not {@code null}
*/
@HOSTED_ONLY
protected static String appendDefaultValue(String help, String defaultValue) {
if (defaultValue == null) {
return help;
}
if (help == null || help.length() == 0) {
return "(default: " + defaultValue + ")";
}
return help + " (default: " + defaultValue + ")";
}
/**
* Creates a new VM option with the specified string prefix (which includes the '-') and the specified help text.
*
* <b>The caller is responsible for registering this option in the global registry or VM options.</b>
*
* @param prefix the name of the option, including the leading '-' character. If the name ends with a space, then it
* must be matched exactly against a VM argument, otherwise it matches if it is a prefix of a VM
* argument.
* @param help the help text to be printed for this option on the command line
*/
public VMOption(String prefix, String help) {
exactPrefix = prefix.endsWith(" ");
if (exactPrefix) {
this.prefix = prefix.substring(0, prefix.length() - 1);
} else {
this.prefix = prefix;
}
this.help = help;
this.name = category().optionName(this);
}
/**
* Determines if this option matches a given command line argument.
*
* @param arg a command line argument
* @return {@code true} if this option matches {@code arg}; otherwise {@code false}
*/
public boolean matches(Pointer arg) {
return exactPrefix ? CString.equals(arg, prefix) : CString.startsWith(arg, prefix);
}
/**
* Called when the prefix of this option is {@linkplain #matches(Pointer) matched} on the command line,
* this method should parse the option's value and return success or failure. The default
* behavior is to simply remove the prefix (i.e. the name of this option) and pass
* the result on to the {@code parseValue()} method, which is typically overridden
* in subclasses.
* @param start a pointer to a C-style string representing the entire option
* @return {@code true} if the option's value was parsed successfully, {@code false} otherwise
*/
public boolean parse(Pointer start) {
this.optionStart = start;
return parseValue(start.plus(prefix.length()));
}
/**
* Parses the value of this option, without the leading prefix.
*
* @param optionValue a pointer to the beginning of a C-style string containing the options value to be parsed
* @return {@code true} if the option was parsed successfully; {@code false} otherwise
*/
public boolean parseValue(Pointer optionValue) {
return true;
}
/**
* Prints out help onto the console.
*/
public void printHelp() {
VMOptions.printHelpForOption(category(), prefix, "", help);
}
/**
* Checks whether this option was present on the command line.
*
* @return {@code true} if this option was present on the command line; {@code false} otherwise
*/
public boolean isPresent() {
return !optionStart.isZero();
}
/**
* Returns whether this option expects to consume the next argument on the command line. Most options do not (i.e.
* their values are part of the same string as their prefix).
*
* @return {@code true} if this option consumes the next command line argument; {@code false} otherwise
*/
public boolean consumesNext() {
return false;
}
/**
* Returns whether this option should terminate the list of options. Most options can be put in any order, but some
* (currently only -jar) are required to be the last option, after which the arguments to the program begin.
*
* @return {@code true} if this option should terminate the list of options and begin the arguments to the program
*/
public boolean isLastOption() {
return false;
}
/**
* Determines if this option has an invalid value or a value that somehow conflicts with another option.
* This method must not performed any allocation.
*
* @return true if this option's value is valid, false otherwise
*/
public boolean check() {
return true;
}
/**
* Prints an error message with the {@link Log} facility describing why the call to {@link #check()} returned {@code false}.
* This method will not be called if {@link #check()} returned {@code true}.
*/
public void printErrorMessage() {
}
@Override
public String toString() {
return prefix;
}
/**
* Gets the category to which this option belongs.
*
* @return the category to which this option belongs
*/
public Category category() {
return Category.from(prefix);
}
@Override
public final boolean equals(Object obj) {
if (obj instanceof VMOption) {
VMOption option = (VMOption) obj;
return option.category() == category() && option.name.equals(name);
}
return false;
}
@Override
public final int hashCode() {
return name.hashCode();
}
/**
* Determines if this option halts the VM when it is parsed on the command line.
*/
protected boolean haltsVM() {
return false;
}
// Prototype-time support for setting VM options
@HOSTED_ONLY
static String[] vmArguments = null;
@HOSTED_ONLY
static Pointer vmArgumentPointers = null;
@HOSTED_ONLY
static String[] matchedVmArguments = null;
static {
// Simple way to set VM options via a system property when running in hosted mode
String value = System.getProperty("max.vmargs");
if (value != null) {
setVMArguments(value.split("\\s+"));
}
}
/**
* Sets the VM command line arguments that will be parsed for each {@link VMOption} created.
* This allows some functionality/state controlled by VM options to be exercised and/or pre-set while
* building the boot image.
*
* Note: Any option values set while bootstrapping are persisted in the boot image.
*
* @param vmArgs a set of command line arguments used to enable VM options while bootstrapping
*/
@HOSTED_ONLY
public static void setVMArguments(String[] vmArgs) {
vmArguments = vmArgs;
if (vmArgumentPointers != null) {
Memory.deallocate(vmArgumentPointers);
vmArgumentPointers = null;
}
matchedVmArguments = new String[vmArguments.length];
}
@HOSTED_ONLY
public static String[] extractVMArgs(String[] args) {
ArrayList<String> vmArgs = new ArrayList<String>();
ArrayList<String> otherArgs = new ArrayList<String>();
for (String arg : args) {
if (arg.startsWith("--")) {
vmArgs.add(arg.substring(1));
} else {
otherArgs.add(arg);
}
}
if (!vmArgs.isEmpty()) {
setVMArguments(vmArgs.toArray(new String[vmArgs.size()]));
args = otherArgs.toArray(new String[otherArgs.size()]);
}
return args;
}
/**
* Gets all of the VM arguments provided to {@link #setVMArguments(String[])} that have not been matched
* against a VM option.
* @return all of the VM arguments that have not been matched
*/
@HOSTED_ONLY
public static List<String> unmatchedVMArguments() {
if (vmArguments != null) {
final List<String> unmatched = new ArrayList<String>(vmArguments.length);
for (int i = 0; i < vmArguments.length; ++i) {
if (matchedVmArguments[i] == null) {
unmatched.add(vmArguments[i]);
}
}
return unmatched;
}
return Collections.emptyList();
}
@HOSTED_ONLY
private static ProgramError parseError(String message, int index) {
final StringBuilder sb = new StringBuilder(String.format("Error parsing VM option: %s:%n%s%n", message, Utils.toString(vmArguments, " ")));
for (int i = 0; i < index; ++i) {
sb.append(' ');
}
sb.append('^');
throw ProgramError.unexpected(sb.toString());
}
/**
* Searches for a {@linkplain #setVMArguments(String[]) registered} VM argument that this option matches and,
* if found, calls {@link #parse(Pointer)} or {@link #parseValue(Pointer)} on the argument.
*/
@HOSTED_ONLY
public void findMatchingArgumentAndParse() {
if (vmArguments != null) {
if (vmArgumentPointers == null) {
vmArgumentPointers = Pointer.fromLong(CString.utf8ArrayFromStringArray(vmArguments, false, false));
}
for (int i = 0; i < vmArguments.length; ++i) {
final String argument = vmArguments[i];
final Pointer argumentPointer = vmArgumentPointers.getWord(i).asPointer();
if (argument != null && (matches(argumentPointer))) {
matchedVmArguments[i] = argument;
if (consumesNext()) {
// this option expects a space and then its value (e.g. -classpath)
if (i + 1 >= vmArguments.length) {
parseError("Could not find argument for " + this, i);
}
final Pointer optionValue = vmArgumentPointers.getWord(i + 1).asPointer();
vmArguments[i + 1] = null;
final boolean ok = parseValue(optionValue);
if (!ok) {
parseError("Error parsing " + this, i);
}
} else {
final boolean ok = parse(argumentPointer);
if (!ok) {
parseError("Error parsing " + this, i);
}
}
}
}
}
}
/**
* Called once before the VM exits. This method exists primarily for VMOption subclasses to
* print out final statistics just before the VM exits. As such, all output it generates
* should be sent to {@link Log#out} and object allocation should be avoided.
*/
protected void beforeExit() {
}
}