1 // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
6 public abstract class AbstractMutableOptions
{
8 protected static final String KEY_VALUE_PAIR_SEPARATOR
= ";";
9 protected static final char KEY_VALUE_SEPARATOR
= '=';
10 static final String INT_ARRAY_INT_SEPARATOR
= ":";
12 protected final String
[] keys
;
13 private final String
[] values
;
16 * User must use builder pattern, or parser.
18 * @param keys the keys
19 * @param values the values
21 protected AbstractMutableOptions(final String
[] keys
, final String
[] values
) {
30 String
[] getValues() {
35 * Returns a string representation of MutableOptions which
36 * is suitable for consumption by {@code #parse(String)}.
38 * @return String representation of MutableOptions
41 public String
toString() {
42 final StringBuilder buffer
= new StringBuilder();
43 for(int i
= 0; i
< keys
.length
; i
++) {
46 .append(KEY_VALUE_SEPARATOR
)
49 if(i
+ 1 < keys
.length
) {
50 buffer
.append(KEY_VALUE_PAIR_SEPARATOR
);
53 return buffer
.toString();
56 public static abstract class AbstractMutableOptionsBuilder
<
57 T
extends AbstractMutableOptions
,
58 U
extends AbstractMutableOptionsBuilder
<T
, U
, K
>,
59 K
extends MutableOptionKey
> {
61 private final Map
<K
, MutableOptionValue
<?
>> options
= new LinkedHashMap
<>();
62 private final List
<OptionString
.Entry
> unknown
= new ArrayList
<>();
64 protected abstract U
self();
67 * Get all of the possible keys
69 * @return A map of all keys, indexed by name.
71 protected abstract Map
<String
, K
> allKeys();
74 * Construct a sub-class instance of {@link AbstractMutableOptions}.
76 * @param keys the keys
77 * @param values the values
79 * @return an instance of the options.
81 protected abstract T
build(final String
[] keys
, final String
[] values
);
84 final String
[] keys
= new String
[options
.size()];
85 final String
[] values
= new String
[options
.size()];
88 for (final Map
.Entry
<K
, MutableOptionValue
<?
>> option
: options
.entrySet()) {
89 keys
[i
] = option
.getKey().name();
90 values
[i
] = option
.getValue().asString();
94 return build(keys
, values
);
97 protected U
setDouble(
98 final K key
, final double value
) {
99 if (key
.getValueType() != MutableOptionKey
.ValueType
.DOUBLE
) {
100 throw new IllegalArgumentException(
101 key
+ " does not accept a double value");
103 options
.put(key
, MutableOptionValue
.fromDouble(value
));
107 protected double getDouble(final K key
)
108 throws NoSuchElementException
, NumberFormatException
{
109 final MutableOptionValue
<?
> value
= options
.get(key
);
111 throw new NoSuchElementException(key
.name() + " has not been set");
113 return value
.asDouble();
117 final K key
, final long value
) {
118 if(key
.getValueType() != MutableOptionKey
.ValueType
.LONG
) {
119 throw new IllegalArgumentException(
120 key
+ " does not accept a long value");
122 options
.put(key
, MutableOptionValue
.fromLong(value
));
126 protected long getLong(final K key
)
127 throws NoSuchElementException
, NumberFormatException
{
128 final MutableOptionValue
<?
> value
= options
.get(key
);
130 throw new NoSuchElementException(key
.name() + " has not been set");
132 return value
.asLong();
136 final K key
, final int value
) {
137 if(key
.getValueType() != MutableOptionKey
.ValueType
.INT
) {
138 throw new IllegalArgumentException(
139 key
+ " does not accept an integer value");
141 options
.put(key
, MutableOptionValue
.fromInt(value
));
145 protected int getInt(final K key
)
146 throws NoSuchElementException
, NumberFormatException
{
147 final MutableOptionValue
<?
> value
= options
.get(key
);
149 throw new NoSuchElementException(key
.name() + " has not been set");
151 return value
.asInt();
154 protected U
setBoolean(
155 final K key
, final boolean value
) {
156 if(key
.getValueType() != MutableOptionKey
.ValueType
.BOOLEAN
) {
157 throw new IllegalArgumentException(
158 key
+ " does not accept a boolean value");
160 options
.put(key
, MutableOptionValue
.fromBoolean(value
));
164 protected boolean getBoolean(final K key
)
165 throws NoSuchElementException
, NumberFormatException
{
166 final MutableOptionValue
<?
> value
= options
.get(key
);
168 throw new NoSuchElementException(key
.name() + " has not been set");
170 return value
.asBoolean();
173 protected U
setIntArray(
174 final K key
, final int[] value
) {
175 if(key
.getValueType() != MutableOptionKey
.ValueType
.INT_ARRAY
) {
176 throw new IllegalArgumentException(
177 key
+ " does not accept an int array value");
179 options
.put(key
, MutableOptionValue
.fromIntArray(value
));
183 protected int[] getIntArray(final K key
)
184 throws NoSuchElementException
, NumberFormatException
{
185 final MutableOptionValue
<?
> value
= options
.get(key
);
187 throw new NoSuchElementException(key
.name() + " has not been set");
189 return value
.asIntArray();
192 protected <N
extends Enum
<N
>> U
setEnum(
193 final K key
, final N value
) {
194 if(key
.getValueType() != MutableOptionKey
.ValueType
.ENUM
) {
195 throw new IllegalArgumentException(
196 key
+ " does not accept a Enum value");
198 options
.put(key
, MutableOptionValue
.fromEnum(value
));
202 @SuppressWarnings("unchecked")
203 protected <N
extends Enum
<N
>> N
getEnum(final K key
)
204 throws NoSuchElementException
, NumberFormatException
{
205 final MutableOptionValue
<?
> value
= options
.get(key
);
207 throw new NoSuchElementException(key
.name() + " has not been set");
210 if (!(value
instanceof MutableOptionValue
.MutableOptionEnumValue
)) {
211 throw new NoSuchElementException(key
.name() + " is not of Enum type");
214 return ((MutableOptionValue
.MutableOptionEnumValue
<N
>) value
).asObject();
218 * Parse a string into a long value, accepting values expressed as a double (such as 9.00) which
219 * are meant to be a long, not a double
221 * @param value the string containing a value which represents a long
222 * @return the long value of the parsed string
224 private long parseAsLong(final String value
) {
226 return Long
.parseLong(value
);
227 } catch (NumberFormatException nfe
) {
228 final double doubleValue
= Double
.parseDouble(value
);
229 if (doubleValue
!= Math
.round(doubleValue
))
230 throw new IllegalArgumentException("Unable to parse or round " + value
+ " to long");
231 return Math
.round(doubleValue
);
236 * Parse a string into an int value, accepting values expressed as a double (such as 9.00) which
237 * are meant to be an int, not a double
239 * @param value the string containing a value which represents an int
240 * @return the int value of the parsed string
242 private int parseAsInt(final String value
) {
244 return Integer
.parseInt(value
);
245 } catch (NumberFormatException nfe
) {
246 final double doubleValue
= Double
.parseDouble(value
);
247 if (doubleValue
!= Math
.round(doubleValue
))
248 throw new IllegalArgumentException("Unable to parse or round " + value
+ " to int");
249 return (int) Math
.round(doubleValue
);
254 * Constructs a builder for mutable column family options from a hierarchical parsed options
255 * string representation. The {@link OptionString.Parser} class output has been used to create a
256 * (name,value)-list; each value may be either a simple string or a (name, value)-list in turn.
258 * @param options a list of parsed option string objects
259 * @param ignoreUnknown what to do if the key is not one of the keys we expect
261 * @return a builder with the values from the parsed input set
263 * @throws IllegalArgumentException if an option value is of the wrong type, or a key is empty
265 protected U
fromParsed(final List
<OptionString
.Entry
> options
, final boolean ignoreUnknown
) {
266 Objects
.requireNonNull(options
);
268 for (final OptionString
.Entry option
: options
) {
270 if (option
.key
.isEmpty()) {
271 throw new IllegalArgumentException("options string is invalid: " + option
);
273 fromOptionString(option
, ignoreUnknown
);
274 } catch (NumberFormatException nfe
) {
275 throw new IllegalArgumentException(
276 "" + option
.key
+ "=" + option
.value
+ " - not a valid value for its type", nfe
);
284 * Set a value in the builder from the supplied option string
286 * @param option the option key/value to add to this builder
287 * @param ignoreUnknown if this is not set, throw an exception when a key is not in the known
289 * @return the same object, after adding options
290 * @throws IllegalArgumentException if the key is unkown, or a value has the wrong type/form
292 private U
fromOptionString(final OptionString
.Entry option
, final boolean ignoreUnknown
)
293 throws IllegalArgumentException
{
294 Objects
.requireNonNull(option
.key
);
295 Objects
.requireNonNull(option
.value
);
297 final K key
= allKeys().get(option
.key
);
298 if (key
== null && ignoreUnknown
) {
301 } else if (key
== null) {
302 throw new IllegalArgumentException("Key: " + key
+ " is not a known option key");
305 if (!option
.value
.isList()) {
306 throw new IllegalArgumentException(
307 "Option: " + key
+ " is not a simple value or list, don't know how to parse it");
310 // Check that simple values are the single item in the array
311 if (key
.getValueType() != MutableOptionKey
.ValueType
.INT_ARRAY
) {
313 if (option
.value
.list
.size() != 1) {
314 throw new IllegalArgumentException(
315 "Simple value does not have exactly 1 item: " + option
.value
.list
);
320 final List
<String
> valueStrs
= option
.value
.list
;
321 final String valueStr
= valueStrs
.get(0);
323 switch (key
.getValueType()) {
325 return setDouble(key
, Double
.parseDouble(valueStr
));
328 return setLong(key
, parseAsLong(valueStr
));
331 return setInt(key
, parseAsInt(valueStr
));
334 return setBoolean(key
, Boolean
.parseBoolean(valueStr
));
337 final int[] value
= new int[valueStrs
.size()];
338 for (int i
= 0; i
< valueStrs
.size(); i
++) {
339 value
[i
] = Integer
.parseInt(valueStrs
.get(i
));
341 return setIntArray(key
, value
);
344 String optionName
= key
.name();
345 if (optionName
.equals("prepopulate_blob_cache")) {
346 final PrepopulateBlobCache prepopulateBlobCache
=
347 PrepopulateBlobCache
.getFromInternal(valueStr
);
348 return setEnum(key
, prepopulateBlobCache
);
349 } else if (optionName
.equals("compression")
350 || optionName
.equals("blob_compression_type")) {
351 final CompressionType compressionType
= CompressionType
.getFromInternal(valueStr
);
352 return setEnum(key
, compressionType
);
354 throw new IllegalArgumentException("Unknown enum type: " + key
.name());
358 throw new IllegalStateException(key
+ " has unknown value type: " + key
.getValueType());
364 * @return the list of keys encountered which were not known to the type being generated
366 public List
<OptionString
.Entry
> getUnknown() {
367 return new ArrayList
<>(unknown
);