1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.devtools.common.options; 16 17 import com.google.common.base.Preconditions; 18 import com.google.common.collect.ImmutableList; 19 import java.util.function.Function; 20 import javax.annotation.Nullable; 21 22 /** 23 * The representation of a parsed option instance. 24 * 25 * <p>An option instance is distinct from the final value of an option, as multiple instances 26 * provide values may be overridden or combined in some way. 27 */ 28 public final class ParsedOptionDescription { 29 30 private final OptionDefinition optionDefinition; 31 @Nullable private final String commandLineForm; 32 @Nullable private final String unconvertedValue; 33 private final OptionInstanceOrigin origin; 34 35 private ParsedOptionDescription( 36 OptionDefinition optionDefinition, 37 @Nullable String commandLineForm, 38 @Nullable String unconvertedValue, 39 OptionInstanceOrigin origin) { 40 this.optionDefinition = Preconditions.checkNotNull(optionDefinition); 41 this.commandLineForm = commandLineForm; 42 this.unconvertedValue = unconvertedValue; 43 this.origin = Preconditions.checkNotNull(origin); 44 } 45 46 static ParsedOptionDescription newParsedOptionDescription( 47 OptionDefinition optionDefinition, 48 String commandLineForm, 49 @Nullable String unconvertedValue, 50 OptionInstanceOrigin origin) { 51 // An actual ParsedOptionDescription should always have a form in which it was parsed, but some 52 // options, such as expansion options, legitimately have no value. 53 return new ParsedOptionDescription( 54 optionDefinition, 55 Preconditions.checkNotNull(commandLineForm), 56 unconvertedValue, 57 origin); 58 } 59 60 /** 61 * This factory should be used when there is no actual parsed option, since in those cases we do 62 * not have an original value or form that the option took. 63 */ 64 static ParsedOptionDescription newDummyInstance( 65 OptionDefinition optionDefinition, OptionInstanceOrigin origin) { 66 return new ParsedOptionDescription(optionDefinition, null, null, origin); 67 } 68 69 public OptionDefinition getOptionDefinition() { 70 return optionDefinition; 71 } 72 73 @Nullable 74 public String getCommandLineForm() { 75 return commandLineForm; 76 } 77 78 public String getCanonicalForm() { 79 return getCanonicalFormWithValueEscaper(s -> s); 80 } 81 82 public String getCanonicalFormWithValueEscaper(Function<String, String> escapingFunction) { 83 // For boolean flags (note that here we do not check for TriState flags, only flags with actual 84 // boolean values, so that we know the return type of getConvertedValue), use the --[no]flag 85 // form for the canonical value. 86 if (optionDefinition.getType().equals(boolean.class)) { 87 try { 88 return ((boolean) getConvertedValue() ? "--" : "--no") + optionDefinition.getOptionName(); 89 } catch (OptionsParsingException e) { 90 throw new RuntimeException("Unexpected parsing exception", e); 91 } 92 } else { 93 String optionString = "--" + optionDefinition.getOptionName(); 94 if (unconvertedValue != null) { // Can be null for Void options. 95 optionString += "=" + escapingFunction.apply(unconvertedValue); 96 } 97 return optionString; 98 } 99 } 100 101 @Deprecated 102 // TODO(b/65646296) Once external dependencies are cleaned up, use getCanonicalForm() 103 String getDeprecatedCanonicalForm() { 104 String value = unconvertedValue; 105 // For boolean flags (note that here we do not check for TriState flags, only flags with actual 106 // boolean values, so that we know the return type of getConvertedValue), set them all to 1 or 107 // 0, instead of keeping the wide variety of values we accept in their original form. 108 if (optionDefinition.getType().equals(boolean.class)) { 109 try { 110 value = (boolean) getConvertedValue() ? "1" : "0"; 111 } catch (OptionsParsingException e) { 112 throw new RuntimeException("Unexpected parsing exception", e); 113 } 114 } 115 return String.format("--%s=%s", optionDefinition.getOptionName(), value); 116 } 117 118 public boolean isBooleanOption() { 119 return optionDefinition.getType().equals(boolean.class); 120 } 121 122 private OptionDocumentationCategory documentationCategory() { 123 return optionDefinition.getDocumentationCategory(); 124 } 125 126 private ImmutableList<OptionMetadataTag> metadataTags() { 127 return ImmutableList.copyOf(optionDefinition.getOptionMetadataTags()); 128 } 129 130 public boolean isDocumented() { 131 return documentationCategory() != OptionDocumentationCategory.UNDOCUMENTED && !isHidden(); 132 } 133 134 public boolean isHidden() { 135 ImmutableList<OptionMetadataTag> tags = metadataTags(); 136 return tags.contains(OptionMetadataTag.HIDDEN) || tags.contains(OptionMetadataTag.INTERNAL); 137 } 138 139 public String getUnconvertedValue() { 140 return unconvertedValue; 141 } 142 143 public OptionInstanceOrigin getOrigin() { 144 return origin; 145 } 146 147 public OptionPriority getPriority() { 148 return origin.getPriority(); 149 } 150 151 public String getSource() { 152 return origin.getSource(); 153 } 154 155 ParsedOptionDescription getImplicitDependent() { 156 return origin.getImplicitDependent(); 157 } 158 159 ParsedOptionDescription getExpandedFrom() { 160 return origin.getExpandedFrom(); 161 } 162 163 public boolean isExplicit() { 164 return origin.getExpandedFrom() == null && origin.getImplicitDependent() == null; 165 } 166 167 public Object getConvertedValue() throws OptionsParsingException { 168 Converter<?> converter = optionDefinition.getConverter(); 169 try { 170 return converter.convert(unconvertedValue); 171 } catch (OptionsParsingException e) { 172 // The converter doesn't know the option name, so we supply it here by re-throwing: 173 throw new OptionsParsingException( 174 String.format("While parsing option %s: %s", commandLineForm, e.getMessage()), e); 175 } 176 } 177 178 @Override 179 public String toString() { 180 // Check that a dummy value-less option instance does not output all the default information. 181 if (commandLineForm == null) { 182 return optionDefinition.toString(); 183 } 184 String source = origin.getSource(); 185 return String.format( 186 "option '%s'%s", 187 commandLineForm, source == null ? "" : String.format(" (source %s)", source)); 188 } 189 190 } 191