/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.aurora.common.args;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
import org.apache.aurora.common.args.constraints.NotNullVerifier;
import org.apache.aurora.common.base.MorePreconditions;
/**
* Description of a command line {@link Arg} instance.
*/
public abstract class ArgumentInfo<T> {
static final ImmutableSet<String> HELP_ARGS = ImmutableSet.of("h", "help");
/**
* Extracts the {@code Arg} from the given field.
*
* @param field The field containing the {@code Arg}.
* @param instance An optional object instance containing the field.
* @return The extracted {@code} Arg.
* @throws IllegalArgumentException If the field does not contain an arg.
*/
protected static Arg<?> getArgForField(Field field, Optional<?> instance) {
Preconditions.checkArgument(field.getType() == Arg.class,
"Field is annotated for argument parsing but is not of Arg type: " + field);
Preconditions.checkArgument(Modifier.isStatic(field.getModifiers()) || instance.isPresent(),
"Non-static argument fields are not supported, found " + field);
field.setAccessible(true);
try {
return (Arg<?>) field.get(instance.orNull());
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot get arg value for " + field);
}
}
private final String name;
private final String help;
private final Arg<T> arg;
private final TypeToken<T> type;
private final List<Annotation> verifierAnnotations;
@Nullable private final Class<? extends Parser<? extends T>> parser;
/**
* Creates a new {@code ArgsInfo}.
*
* @param name The simple name for the argument.
* @param help Help string.
* @param arg Argument object.
* @param type Concrete argument type.
* @param verifierAnnotations {@link Verifier} annotations for this
* argument.
* @param parser Parser for the argument type.
*/
protected ArgumentInfo(
String name,
String help,
Arg<T> arg,
TypeToken<T> type,
List<Annotation> verifierAnnotations,
@Nullable Class<? extends Parser<? extends T>> parser) {
this.name = MorePreconditions.checkNotBlank(name);
this.help = MorePreconditions.checkNotBlank(help);
this.arg = Preconditions.checkNotNull(arg);
this.type = Preconditions.checkNotNull(type);
this.verifierAnnotations = ImmutableList.copyOf(verifierAnnotations);
this.parser = parser;
}
/**
* Return the name of the command line argument. In an optional argument, this is expressed on
* the command line by "-name=value"; whereas, for a positional argument, the name indicates
* the type/function.
*/
public final String getName() {
return name;
}
/**
* Returns the instructions for this command-line argument. This is typically used when the
* executable is passed the -help flag.
*/
public String getHelp() {
return help;
}
/**
* Returns the Arg associated with this command-line argument. The Arg<?> is a mutable container
* cell that holds the value passed-in on the command line, after parsing and validation.
*/
public Arg<T> getArg() {
return arg;
}
/**
* Sets the value of the {@link Arg} described by this {@code ArgumentInfo}.
*
* @param value The value to set.
*/
protected void setValue(@Nullable T value) {
arg.set(value);
}
/**
* Returns the TypeToken that represents the type of this command-line argument.
*/
public TypeToken<T> getType() {
return type;
}
@Override
public boolean equals(Object object) {
return (object instanceof ArgumentInfo) && arg.equals(((ArgumentInfo) object).arg);
}
@Override
public int hashCode() {
return arg.hashCode();
}
/**
* Finds an appropriate parser for this args underlying value type.
*
* @param parserOracle The registry of known parsers.
* @return A parser that can parse strings into the underlying argument type.
* @throws IllegalArgumentException If no parser was found for the underlying argument type.
*/
protected Parser<? extends T> getParser(ParserOracle parserOracle) {
Preconditions.checkNotNull(parserOracle);
if (parser == null || NoParser.class.equals(parser)) {
return parserOracle.get(type);
} else {
try {
return parser.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Failed to instantiate parser " + parser);
} catch (IllegalAccessException e) {
throw new RuntimeException("No access to instantiate parser " + parser);
}
}
}
static class ValueVerifier<T> {
private final Verifier<? super T> verifier;
private final Annotation annotation;
ValueVerifier(Verifier<? super T> verifier, Annotation annotation) {
this.verifier = verifier;
this.annotation = annotation;
}
void verify(@Nullable T value) {
if (value != null || verifier instanceof NotNullVerifier) {
verifier.verify(value, annotation);
}
}
String toString(Class<? extends T> rawType) {
return verifier.toString(rawType, annotation);
}
}
private Iterable<ValueVerifier<T>> getVerifiers(final Verifiers verifierOracle) {
Function<Annotation, Optional<ValueVerifier<T>>> toVerifier =
annotation -> {
@Nullable Verifier<? super T> verifier = verifierOracle.get(type, annotation);
if (verifier != null) {
return Optional.of(new ValueVerifier<T>(verifier, annotation));
} else {
return Optional.absent();
}
};
return Optional.presentInstances(Iterables.transform(verifierAnnotations, toVerifier));
}
void verify(Verifiers verifierOracle) {
@Nullable T value = getArg().uncheckedGet();
for (ValueVerifier<T> valueVerifier : getVerifiers(verifierOracle)) {
valueVerifier.verify(value);
}
}
ImmutableList<String> collectConstraints(Verifiers verifierOracle) {
@SuppressWarnings("unchecked") // type.getType() is T
final Class<? extends T> rawType = (Class<? extends T>) type.getRawType();
return FluentIterable.from(getVerifiers(verifierOracle)).transform(
verifier -> verifier.toString(rawType)).toList();
}
}