
com.oracle.svm.driver.APIOptionHandler Maven / Gradle / Ivy
/*
* Copyright (c) 2018, 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.oracle.svm.driver;
import java.lang.reflect.Field;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.ServiceLoader;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.graalvm.compiler.options.OptionDescriptor;
import org.graalvm.compiler.options.OptionDescriptors;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.hosted.Feature;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.option.APIOption;
import com.oracle.svm.core.option.APIOption.APIOptionKind;
import com.oracle.svm.core.option.APIOptionGroup;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.option.HostedOptionParser;
import com.oracle.svm.util.ReflectionUtil;
import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError;
class APIOptionHandler extends NativeImage.OptionHandler {
static final class OptionInfo {
final String[] variants;
final char valueSeparator;
final String builderOption;
final String defaultValue;
final String helpText;
final boolean hasPathArguments;
final boolean defaultFinal;
final String deprecationWarning;
final List> valueTransformers;
final APIOptionGroup group;
OptionInfo(String[] variants, char valueSeparator, String builderOption, String defaultValue, String helpText, boolean hasPathArguments, boolean defaultFinal, String deprecationWarning,
List> valueTransformers, APIOptionGroup group) {
this.variants = variants;
this.valueSeparator = valueSeparator;
this.builderOption = builderOption;
this.defaultValue = defaultValue;
this.helpText = helpText;
this.hasPathArguments = hasPathArguments;
this.defaultFinal = defaultFinal;
this.deprecationWarning = deprecationWarning;
this.valueTransformers = valueTransformers;
this.group = group;
}
boolean isDeprecated() {
return deprecationWarning.length() > 0;
}
}
private final SortedMap apiOptions;
APIOptionHandler(NativeImage nativeImage) {
super(nativeImage);
if (NativeImage.IS_AOT) {
apiOptions = ImageSingletons.lookup(APIOptionCollector.class).options;
} else {
List> optionDescriptorsList = new ArrayList<>();
ServiceLoader serviceLoader = ServiceLoader.load(OptionDescriptors.class, nativeImage.getClass().getClassLoader());
for (OptionDescriptors optionDescriptors : serviceLoader) {
optionDescriptorsList.add(optionDescriptors.getClass());
}
apiOptions = extractOptions(optionDescriptorsList);
}
}
static SortedMap extractOptions(List> optionsClasses) {
SortedMap hostedOptions = new TreeMap<>();
SortedMap runtimeOptions = new TreeMap<>();
HostedOptionParser.collectOptions(optionsClasses, hostedOptions, runtimeOptions);
SortedMap apiOptions = new TreeMap<>();
Map> groupDefaults = new HashMap<>();
hostedOptions.values().forEach(o -> extractOption(NativeImage.oH, o, apiOptions, groupDefaults));
runtimeOptions.values().forEach(o -> extractOption(NativeImage.oR, o, apiOptions, groupDefaults));
groupDefaults.forEach((groupName, defaults) -> {
if (defaults.size() > 1) {
VMError.shouldNotReachHere(String.format("APIOptionGroup %s must only have a single default (but has: %s)",
groupName, String.join(", ", defaults)));
}
});
return apiOptions;
}
private static void extractOption(String optionPrefix, OptionDescriptor optionDescriptor,
SortedMap apiOptions, Map> groupDefaults) {
try {
Field optionField = optionDescriptor.getDeclaringClass().getDeclaredField(optionDescriptor.getFieldName());
APIOption[] apiAnnotations = optionField.getAnnotationsByType(APIOption.class);
for (APIOption apiAnnotation : apiAnnotations) {
String builderOption = optionPrefix;
if (apiAnnotation.name().length <= 0) {
VMError.shouldNotReachHere(String.format("APIOption for %s does not provide a name entry", optionDescriptor.getLocation()));
}
String apiOptionName = APIOption.Utils.optionName(apiAnnotation.name()[0]);
String rawOptionName = optionDescriptor.getName();
APIOptionGroup group = null;
String defaultValue = null;
boolean booleanOption = false;
if (optionDescriptor.getOptionValueType().equals(Boolean.class)) {
if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) {
try {
Class extends APIOptionGroup> groupClass = apiAnnotation.group();
group = ReflectionUtil.newInstance(groupClass);
String groupName = APIOption.Utils.groupName(group);
if (group.helpText() == null || group.helpText().isEmpty()) {
VMError.shouldNotReachHere(String.format("APIOptionGroup %s(%s) needs to provide help text", groupClass.getName(), group.name()));
}
String groupMember = apiAnnotation.name()[0];
apiOptionName = groupName + groupMember;
Boolean isEnabled = (Boolean) optionDescriptor.getOptionKey().getDefaultValue();
if (isEnabled) {
groupDefaults.computeIfAbsent(groupName, cls -> new ArrayList<>()).add(groupMember);
/* Use OptionInfo.defaultValue to remember group default value */
defaultValue = groupMember;
}
} catch (ReflectionUtilError ex) {
throw VMError.shouldNotReachHere(
"Class specified as group for @APIOption " + apiOptionName + " cannot be loaded or instantiated: " + apiAnnotation.group().getTypeName(), ex.getCause());
}
}
if (apiAnnotation.kind().equals(APIOptionKind.Paths)) {
VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOptionKind.Paths", apiOptionName, rawOptionName));
}
if (apiAnnotation.defaultValue().length > 0) {
VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.defaultValue", apiOptionName, rawOptionName));
}
if (apiAnnotation.fixedValue().length > 0) {
VMError.shouldNotReachHere(String.format("Boolean APIOption %s(%s) cannot use APIOption.fixedValue", apiOptionName, rawOptionName));
}
builderOption += apiAnnotation.kind().equals(APIOptionKind.Negated) ? "-" : "+";
builderOption += rawOptionName;
booleanOption = true;
} else {
if (!apiAnnotation.group().equals(APIOption.NullGroup.class)) {
VMError.shouldNotReachHere(String.format("Using @APIOption.group not supported for non-boolean APIOption %s(%s)", apiOptionName, rawOptionName));
}
if (apiAnnotation.kind().equals(APIOptionKind.Negated)) {
VMError.shouldNotReachHere(String.format("Non-boolean APIOption %s(%s) cannot use APIOptionKind.Negated", apiOptionName, rawOptionName));
}
if (apiAnnotation.defaultValue().length > 1) {
VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.defaultValue", apiOptionName, rawOptionName));
}
if (apiAnnotation.fixedValue().length > 1) {
VMError.shouldNotReachHere(String.format("APIOption %s(%s) cannot have more than one APIOption.fixedValue", apiOptionName, rawOptionName));
}
if (apiAnnotation.fixedValue().length > 0 && apiAnnotation.defaultValue().length > 0) {
VMError.shouldNotReachHere(String.format("APIOption %s(%s) APIOption.defaultValue and APIOption.fixedValue cannot be combined", apiOptionName, rawOptionName));
}
if (apiAnnotation.defaultValue().length > 0) {
defaultValue = apiAnnotation.defaultValue()[0];
}
if (apiAnnotation.fixedValue().length > 0) {
defaultValue = apiAnnotation.fixedValue()[0];
}
builderOption += rawOptionName;
builderOption += "=";
}
String helpText = optionDescriptor.getHelp();
if (!apiAnnotation.customHelp().isEmpty()) {
helpText = apiAnnotation.customHelp();
}
if (helpText == null || helpText.isEmpty()) {
VMError.shouldNotReachHere(String.format("APIOption %s(%s) needs to provide help text", apiOptionName, rawOptionName));
}
if (group == null) {
/* Regular help text needs to start with lower-case letter */
helpText = startLowerCase(helpText);
}
List> valueTransformers = new ArrayList<>(apiAnnotation.valueTransformer().length);
for (Class extends Function
© 2015 - 2025 Weber Informatics LLC | Privacy Policy