http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java b/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java deleted file mode 100644 index 0e793ef..0000000 --- a/commons-args/src/main/java/com/twitter/common/args/apt/CmdLineProcessor.java +++ /dev/null @@ -1,677 +0,0 @@ -/** - * 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 com.twitter.common.args.apt; - -import java.io.Closeable; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.lang.annotation.Annotation; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.annotation.Nullable; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.ProcessingEnvironment; -import javax.annotation.processing.RoundEnvironment; -import javax.annotation.processing.SupportedOptions; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleAnnotationValueVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic.Kind; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -import com.google.common.base.Function; -import com.google.common.base.Functions; -import com.google.common.base.Optional; -import com.google.common.base.Predicates; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; - -import com.twitter.common.args.Arg; -import com.twitter.common.args.ArgParser; -import com.twitter.common.args.CmdLine; -import com.twitter.common.args.Parser; -import com.twitter.common.args.Positional; -import com.twitter.common.args.Verifier; -import com.twitter.common.args.VerifierFor; -import com.twitter.common.args.apt.Configuration.ParserInfo; - -import static com.twitter.common.args.apt.Configuration.ArgInfo; -import static com.twitter.common.args.apt.Configuration.VerifierInfo; - -/** - * Processes {@literal @CmdLine} annotated fields and {@literal @ArgParser} and - * {@literal @VerifierFor} parser and verifier registrations and stores configuration data listing - * these fields, parsers and verifiers on the classpath for discovery via - * {@link com.twitter.common.args.apt.Configuration#load()}. - * - * <p>Supports an apt option useful for some build setups that create monolithic jars aggregating - * many library jars, one or more of which have embedded arg definitions themselves. By adding the - * following flag to a javac invocation: - * <code>-Acom.twitter.common.args.apt.CmdLineProcessor.main</code> - * you signal this apt processor that the compilation target is a leaf target that will comprise one - * or more executable mains (as opposed to a library jar). As a result, the embedded arg - * definitions generated will occupy a special resource that is always checked for first during - * runtime arg parsing. - */ -@SupportedOptions({ - CmdLineProcessor.MAIN_OPTION, - CmdLineProcessor.CHECK_LINKAGE_OPTION -}) -public class CmdLineProcessor extends AbstractProcessor { - static final String MAIN_OPTION = - "com.twitter.common.args.apt.CmdLineProcessor.main"; - static final String CHECK_LINKAGE_OPTION = - "com.twitter.common.args.apt.CmdLineProcessor.check_linkage"; - - private static final Function<Class<?>, String> GET_NAME = new Function<Class<?>, String>() { - @Override public String apply(Class<?> type) { - return type.getName(); - } - }; - - private final Supplier<Configuration> configSupplier = - Suppliers.memoize(new Supplier<Configuration>() { - @Override public Configuration get() { - try { - Configuration configuration = Configuration.load(); - for (ArgInfo argInfo : configuration.positionalInfo()) { - configBuilder.addPositionalInfo(argInfo); - } - for (ArgInfo argInfo : configuration.optionInfo()) { - configBuilder.addCmdLineArg(argInfo); - } - for (ParserInfo parserInfo : configuration.parserInfo()) { - configBuilder.addParser(parserInfo); - } - for (VerifierInfo verifierInfo : configuration.verifierInfo()) { - configBuilder.addVerifier(verifierInfo); - } - return configuration; - } catch (IOException e) { - error("Problem loading existing flags on compile time classpath: %s", - Throwables.getStackTraceAsString(e)); - return null; - } - } - }); - - private final Configuration.Builder configBuilder = new Configuration.Builder(); - private final ImmutableSet.Builder<String> contributingClassNamesBuilder = ImmutableSet.builder(); - - private Types typeUtils; - private Elements elementUtils; - private boolean isMain; - private boolean isCheckLinkage; - - private static boolean getBooleanOption(Map<String, String> options, String name, - boolean defaultValue) { - - if (!options.containsKey(name)) { - return defaultValue; - } - - // We want to map the presence of a boolean option without a value to indicate true, giving the - // following accepted boolean option formats: - // -Afoo -> true - // -Afoo=false -> false - // -Afoo=true -> true - - String isOption = options.get(name); - return (isOption == null) || Boolean.parseBoolean(isOption); - } - - @Override - public void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - - typeUtils = processingEnv.getTypeUtils(); - elementUtils = processingEnv.getElementUtils(); - - Map<String, String> options = processingEnv.getOptions(); - isMain = getBooleanOption(options, MAIN_OPTION, false); - isCheckLinkage = getBooleanOption(options, CHECK_LINKAGE_OPTION, true); - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latest(); - } - - @Override - public Set<String> getSupportedAnnotationTypes() { - return ImmutableSet.copyOf(Iterables.transform( - ImmutableList.of(Positional.class, CmdLine.class, ArgParser.class, VerifierFor.class), - GET_NAME)); - } - - @Override - public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { - try { - @Nullable Configuration classpathConfiguration = configSupplier.get(); - - Set<? extends Element> parsers = getAnnotatedElements(roundEnv, ArgParser.class); - contributingClassNamesBuilder.addAll(extractClassNames(parsers)); - @Nullable Set<String> parsedTypes = getParsedTypes(classpathConfiguration, parsers); - - Set<? extends Element> cmdlineArgs = getAnnotatedElements(roundEnv, CmdLine.class); - contributingClassNamesBuilder.addAll(extractEnclosingClassNames(cmdlineArgs)); - Set<? extends Element> positionalArgs = getAnnotatedElements(roundEnv, Positional.class); - contributingClassNamesBuilder.addAll(extractEnclosingClassNames(positionalArgs)); - - ImmutableSet<? extends Element> invalidArgs = - Sets.intersection(cmdlineArgs, positionalArgs).immutableCopy(); - if (!invalidArgs.isEmpty()) { - error("An Arg cannot be annotated with both @CmdLine and @Positional, found bad Arg " - + "fields: %s", invalidArgs); - } - - for (ArgInfo cmdLineInfo : processAnnotatedArgs(parsedTypes, cmdlineArgs, CmdLine.class)) { - configBuilder.addCmdLineArg(cmdLineInfo); - } - - for (ArgInfo positionalInfo - : processAnnotatedArgs(parsedTypes, positionalArgs, Positional.class)) { - - configBuilder.addPositionalInfo(positionalInfo); - } - checkPositionalArgsAreLists(roundEnv); - - processParsers(parsers); - - Set<? extends Element> verifiers = getAnnotatedElements(roundEnv, VerifierFor.class); - contributingClassNamesBuilder.addAll(extractClassNames(verifiers)); - processVerifiers(verifiers); - - if (roundEnv.processingOver()) { - if (classpathConfiguration != null - && (!classpathConfiguration.isEmpty() || !configBuilder.isEmpty())) { - - @Nullable Resource cmdLinePropertiesResource = - openCmdLinePropertiesResource(classpathConfiguration); - if (cmdLinePropertiesResource != null) { - Writer writer = cmdLinePropertiesResource.getWriter(); - try { - configBuilder.build(classpathConfiguration).store(writer, - "Generated via apt by " + getClass().getName()); - } finally { - closeQuietly(writer); - } - - writeResourceMapping(contributingClassNamesBuilder.build(), - cmdLinePropertiesResource.getResource()); - } - } - } - // TODO(John Sirois): Investigate narrowing this catch - its not clear there is any need to be - // so general. - // SUPPRESS CHECKSTYLE RegexpSinglelineJava - } catch (RuntimeException e) { - // Catch internal errors - when these bubble more useful queued error messages are lost in - // some javac implementations. - error("Unexpected error completing annotation processing:\n%s", - Throwables.getStackTraceAsString(e)); - } - return true; - } - - private void writeResourceMapping( - Set<String> contributingClassNames, - FileObject cmdLinePropertiesResourcePath) { - - // TODO(John Sirois): Lift the compiler resource-mappings writer to its own class/artifact to be - // re-used by other apt processors: https://github.com/twitter/commons/issues/319 - - // NB: javac rejects a package name with illegal package name characters like '-' so we just - // pass the empty package and the fully qualified resource file name. - @Nullable Resource resource = openResource("", - "META-INF/compiler/resource-mappings/" + getClass().getName()); - if (resource != null) { - PrintWriter writer = new PrintWriter(resource.getWriter()); - writer.printf("resources by class name:\n"); - writer.printf("%d items\n", contributingClassNames.size()); - try { - for (String className : contributingClassNames) { - writer.printf("%s -> %s\n", className, cmdLinePropertiesResourcePath.toUri().getPath()); - } - } finally { - closeQuietly(writer); - } - } - } - - private static final Function<Element, Element> EXTRACT_ENCLOSING_CLASS = - new Function<Element, Element>() { - @Override public Element apply(Element element) { - return element.getEnclosingElement(); - } - }; - - private final Function<Element, String> extractClassName = new Function<Element, String>() { - @Override public String apply(Element element) { - return getBinaryName((TypeElement) element); - } - }; - - private final Function<Element, String> extractEnclosingClassName = - Functions.compose(extractClassName, EXTRACT_ENCLOSING_CLASS); - - private Iterable<String> extractEnclosingClassNames(Iterable<? extends Element> elements) { - return Iterables.transform(elements, extractEnclosingClassName); - } - - private Iterable<String> extractClassNames(Iterable<? extends Element> elements) { - return Iterables.transform(elements, extractClassName); - } - - private void closeQuietly(Closeable closeable) { - try { - closeable.close(); - } catch (IOException e) { - log(Kind.MANDATORY_WARNING, "Failed to close %s: %s", closeable, e); - } - } - - private void checkPositionalArgsAreLists(RoundEnvironment roundEnv) { - for (Element positionalArg : getAnnotatedElements(roundEnv, Positional.class)) { - @Nullable TypeMirror typeArgument = - getTypeArgument(positionalArg.asType(), typeElement(Arg.class)); - if ((typeArgument == null) - || !typeUtils.isSubtype(typeElement(List.class).asType(), typeArgument)) { - error("Found @Positional %s %s.%s that is not a List", - positionalArg.asType(), positionalArg.getEnclosingElement(), positionalArg); - } - } - } - - @Nullable - private Set<String> getParsedTypes(@Nullable Configuration configuration, - Set<? extends Element> parsers) { - - if (!isCheckLinkage) { - return null; - } - - Iterable<String> parsersFor = Optional.presentInstances(Iterables.transform(parsers, - new Function<Element, Optional<String>>() { - @Override public Optional<String> apply(Element parser) { - TypeMirror parsedType = getTypeArgument(parser.asType(), typeElement(Parser.class)); - if (parsedType == null) { - error("failed to find a type argument for Parser: %s", parser); - return Optional.absent(); - } - // Equals on TypeMirrors doesn't work - so we compare string representations :/ - return Optional.of(typeUtils.erasure(parsedType).toString()); - } - })); - if (configuration != null) { - parsersFor = Iterables.concat(parsersFor, Iterables.filter( - Iterables.transform(configuration.parserInfo(), - new Function<ParserInfo, String>() { - @Override @Nullable public String apply(ParserInfo parserInfo) { - TypeElement typeElement = elementUtils.getTypeElement(parserInfo.parsedType); - // We may not have a type on the classpath for a previous round - this is fine as - // long as the no Args in this round that are of the type. - return (typeElement == null) - ? null : typeUtils.erasure(typeElement.asType()).toString(); - } - }), Predicates.notNull())); - } - return ImmutableSet.copyOf(parsersFor); - } - - private Iterable<ArgInfo> processAnnotatedArgs( - @Nullable final Set<String> parsedTypes, - Set<? extends Element> args, - final Class<? extends Annotation> argAnnotation) { - - return Optional.presentInstances(Iterables.transform(args, - new Function<Element, Optional<ArgInfo>>() { - @Override public Optional<ArgInfo> apply(Element arg) { - @Nullable TypeElement containingType = processArg(parsedTypes, arg, argAnnotation); - if (containingType == null) { - return Optional.absent(); - } else { - return Optional.of(new ArgInfo(getBinaryName(containingType), - arg.getSimpleName().toString())); - } - } - })); - } - - private Set<? extends Element> getAnnotatedElements(RoundEnvironment roundEnv, - Class<? extends Annotation> argAnnotation) { - return roundEnv.getElementsAnnotatedWith(typeElement(argAnnotation)); - } - - @Nullable - private TypeElement processArg(@Nullable Set<String> parsedTypes, Element annotationElement, - Class<? extends Annotation> annotationType) { - - TypeElement parserType = typeElement(Parser.class); - if (annotationElement.getKind() != ElementKind.FIELD) { - error("Found a @%s annotation on a non-field %s", - annotationType.getSimpleName(), annotationElement); - return null; - } else { - // Only types contain fields so this cast is safe. - TypeElement containingType = (TypeElement) annotationElement.getEnclosingElement(); - - if (!isAssignable(annotationElement.asType(), Arg.class)) { - error("Found a @%s annotation on a non-Arg %s.%s", - annotationType.getSimpleName(), containingType, annotationElement); - return null; - } - if (!annotationElement.getModifiers().contains(Modifier.STATIC)) { - return null; - } - - if (parsedTypes != null) { - // Check Parser<T> linkage for the Arg<T> type T. - TypeMirror typeArgument = - getTypeArgument(annotationElement.asType(), typeElement(Arg.class)); - @Nullable AnnotationMirror cmdLine = - getAnnotationMirror(annotationElement, typeElement(annotationType)); - if (cmdLine != null) { - TypeMirror customParserType = getClassType(cmdLine, "parser", parserType).asType(); - if (typeUtils.isSameType(parserType.asType(), customParserType)) { - if (!checkTypePresent(parsedTypes, typeArgument)) { - error("No parser registered for %s, %s.%s is un-parseable", - typeArgument, containingType, annotationElement); - } - } else { - TypeMirror customParsedType = getTypeArgument(customParserType, parserType); - if (!isAssignable(typeArgument, customParsedType)) { - error("Custom parser %s parses %s but registered for %s.%s with Arg type %s", - customParserType, customParsedType, containingType, annotationElement, - typeArgument); - } - } - } - } - - // TODO(John Sirois): Add additional compile-time @CmdLine verification for: - // 1.) for each @CmdLine Arg<T> annotated with @VerifierFor.annotation: T is a subtype of - // V where there is a Verifier<V> - // 2.) name checks, including dups - - return containingType; - } - } - - private boolean checkTypePresent(Set<String> types, TypeMirror type) { - Iterable<TypeMirror> allTypes = getAllTypes(type); - for (TypeMirror t : allTypes) { - if (types.contains(typeUtils.erasure(t).toString())) { - return true; - } - } - return false; - } - - private void processParsers(Set<? extends Element> elements) { - TypeElement parserType = typeElement(Parser.class); - for (Element element : elements) { - if (element.getKind() != ElementKind.CLASS) { - error("Found an @ArgParser annotation on a non-class %s", element); - } else { - TypeElement parser = (TypeElement) element; - if (!isAssignable(parser, Parser.class)) { - error("Found an @ArgParser annotation on a non-Parser %s", element); - return; - } - - @Nullable String parsedType = getTypeArgument(parser, parserType); - if (parsedType != null) { - configBuilder.addParser(parsedType, getBinaryName(parser)); - } - } - } - } - - private void processVerifiers(Set<? extends Element> elements) { - TypeElement verifierType = typeElement(Verifier.class); - TypeElement verifierForType = typeElement(VerifierFor.class); - for (Element element : elements) { - if (element.getKind() != ElementKind.CLASS) { - error("Found a @VerifierFor annotation on a non-class %s", element); - } else { - TypeElement verifier = (TypeElement) element; - if (!isAssignable(verifier, Verifier.class)) { - error("Found a @Verifier annotation on a non-Verifier %s", element); - return; - } - - @Nullable AnnotationMirror verifierFor = getAnnotationMirror(verifier, verifierForType); - if (verifierFor != null) { - @Nullable TypeElement verifyAnnotationType = getClassType(verifierFor, "value", null); - if (verifyAnnotationType != null) { - @Nullable String verifiedType = getTypeArgument(verifier, verifierType); - if (verifiedType != null) { - String verifyAnnotationClassName = - elementUtils.getBinaryName(verifyAnnotationType).toString(); - configBuilder.addVerifier(verifiedType, verifyAnnotationClassName, - getBinaryName(verifier)); - } - } - } - } - } - } - - @Nullable - private String getTypeArgument(TypeElement annotatedType, final TypeElement baseType) { - TypeMirror typeArgument = getTypeArgument(annotatedType.asType(), baseType); - return typeArgument == null - ? null - : getBinaryName((TypeElement) typeUtils.asElement(typeArgument)); - } - - private Iterable<TypeMirror> getAllTypes(TypeMirror type) { - return getAllTypes(new HashSet<String>(), Lists.<TypeMirror>newArrayList(), type); - } - - private Iterable<TypeMirror> getAllTypes(Set<String> visitedTypes, List<TypeMirror> types, - TypeMirror type) { - - String typeName = typeUtils.erasure(type).toString(); - if (!visitedTypes.contains(typeName)) { - types.add(type); - visitedTypes.add(typeName); - for (TypeMirror superType : typeUtils.directSupertypes(type)) { - getAllTypes(visitedTypes, types, superType); - } - } - return types; - } - - @Nullable - private TypeMirror getTypeArgument(TypeMirror annotatedType, final TypeElement baseType) { - for (TypeMirror type : getAllTypes(annotatedType)) { - TypeMirror typeArgument = type.accept(new SimpleTypeVisitor6<TypeMirror, Void>() { - @Override public TypeMirror visitDeclared(DeclaredType t, Void aVoid) { - if (isAssignable(t, baseType)) { - List<? extends TypeMirror> typeArguments = t.getTypeArguments(); - if (!typeArguments.isEmpty()) { - return typeUtils.erasure(typeArguments.get(0)); - } - } - return null; - } - }, null); - - if (typeArgument != null) { - return typeArgument; - } - } - error("Failed to find a type argument for %s in %s", baseType, annotatedType); - return null; - } - - @Nullable - private AnnotationMirror getAnnotationMirror(Element element, TypeElement annotationType) { - for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { - if (typeUtils.isSameType(annotationMirror.getAnnotationType(), annotationType.asType())) { - return annotationMirror; - } - } - error("Failed to find an annotation of type %s on %s", annotationType, element); - return null; - } - - @SuppressWarnings("unchecked") - private TypeElement getClassType(AnnotationMirror annotationMirror, String methodName, - TypeElement defaultClassType) { - - for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry - : annotationMirror.getElementValues().entrySet()) { - if (entry.getKey().getSimpleName().equals(elementUtils.getName(methodName))) { - TypeElement classType = entry.getValue().accept( - new SimpleAnnotationValueVisitor6<TypeElement, Void>() { - @Override public TypeElement visitType(TypeMirror t, Void unused) { - return (TypeElement) processingEnv.getTypeUtils().asElement(t); - } - }, null); - - if (classType != null) { - return classType; - } - } - } - if (defaultClassType == null) { - error("Could not find a class type for %s.%s", annotationMirror, methodName); - } - return defaultClassType; - } - - @Nullable - private FileObject createCommandLineDb(Configuration configuration) { - String name = isMain ? Configuration.mainResourceName() : configuration.nextResourceName(); - return createResource(Configuration.DEFAULT_RESOURCE_PACKAGE, name); - } - - @Nullable - private FileObject createResource(String packageName, String name) { - try { - return processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, - packageName, name); - } catch (IOException e) { - error("Failed to create resource file to store %s/%s: %s", - packageName, name, Throwables.getStackTraceAsString(e)); - return null; - } - } - - private static final class Resource { - private final FileObject resource; - private final Writer writer; - - Resource(FileObject resource, Writer writer) { - this.resource = resource; - this.writer = writer; - } - - FileObject getResource() { - return resource; - } - - Writer getWriter() { - return writer; - } - } - - @Nullable - private Resource openCmdLinePropertiesResource(Configuration configuration) { - @Nullable FileObject resource = createCommandLineDb(configuration); - return openResource(resource); - } - - @Nullable - private Resource openResource(String packageName, String name) { - @Nullable FileObject resource = createResource(packageName, name); - return openResource(resource); - } - - @Nullable - private Resource openResource(@Nullable FileObject resource) { - if (resource == null) { - return null; - } - try { - log(Kind.NOTE, "Writing %s", resource.toUri()); - return new Resource(resource, resource.openWriter()); - } catch (IOException e) { - if (!resource.delete()) { - log(Kind.WARNING, "Failed to clean up %s after a failing to open it for writing", - resource.toUri()); - } - error("Failed to open resource file to store %s: %s", resource.toUri(), - Throwables.getStackTraceAsString(e)); - return null; - } - } - - private TypeElement typeElement(Class<?> type) { - return elementUtils.getTypeElement(type.getName()); - } - - private String getBinaryName(TypeElement typeElement) { - return elementUtils.getBinaryName(typeElement).toString(); - } - - private boolean isAssignable(TypeElement subType, Class<?> baseType) { - return isAssignable(subType.asType(), baseType); - } - - private boolean isAssignable(TypeMirror subType, Class<?> baseType) { - return isAssignable(subType, typeElement(baseType)); - } - - private boolean isAssignable(TypeMirror subType, TypeElement baseType) { - return isAssignable(subType, baseType.asType()); - } - - private boolean isAssignable(TypeMirror subType, TypeMirror baseType) { - return typeUtils.isAssignable(typeUtils.erasure(subType), typeUtils.erasure(baseType)); - } - - private void error(String message, Object ... args) { - log(Kind.ERROR, message, args); - } - - private void log(Kind kind, String message, Object ... args) { - processingEnv.getMessager().printMessage(kind, String.format(message, args)); - } -}
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java b/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java deleted file mode 100644 index d19940a..0000000 --- a/commons-args/src/main/java/com/twitter/common/args/apt/Configuration.java +++ /dev/null @@ -1,527 +0,0 @@ -/** - * 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 com.twitter.common.args.apt; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.io.Reader; -import java.io.Writer; -import java.net.URL; -import java.util.Date; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.logging.Logger; - -import com.google.common.base.CharMatcher; -import com.google.common.base.Charsets; -import com.google.common.base.Function; -import com.google.common.base.Functions; -import com.google.common.base.Preconditions; -import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.io.CharStreams; -import com.google.common.io.InputSupplier; -import com.google.common.io.LineProcessor; - -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; - -/** - * Loads and stores {@literal @CmdLine} configuration data. By default, that data - * is contained in text files called cmdline.arg.info.txt.0, cmdline.arg.info.txt.1 - * etc. Every time a new Configuration object is created, it consumes all existing - * files with the above names. Saving this Configuration results in creation of a - * file with index increased by one, e.g. cmdline.arg.info.txt.2 in the above - * example. - * - * @author John Sirois - */ -public final class Configuration { - - /** - * Indicates a problem reading stored {@literal @CmdLine} arg configuration data. - */ - public static class ConfigurationException extends RuntimeException { - public ConfigurationException(String message, Object... args) { - super(String.format(message, args)); - } - public ConfigurationException(Throwable cause) { - super(cause); - } - } - - static final String DEFAULT_RESOURCE_PACKAGE = Configuration.class.getPackage().getName(); - - private static final Logger LOG = Logger.getLogger(Configuration.class.getName()); - - private static final CharMatcher IDENTIFIER_START = - CharMatcher.forPredicate(new Predicate<Character>() { - @Override public boolean apply(Character c) { - return Character.isJavaIdentifierStart(c); - } - }); - - private static final CharMatcher IDENTIFIER_REST = - CharMatcher.forPredicate(new Predicate<Character>() { - @Override public boolean apply(Character c) { - return Character.isJavaIdentifierPart(c); - } - }); - - private static final Function<URL, InputSupplier<? extends InputStream>> URL_TO_INPUT = - new Function<URL, InputSupplier<? extends InputStream>>() { - @Override public InputSupplier<? extends InputStream> apply(final URL resource) { - return new InputSupplier<InputStream>() { - @Override public InputStream getInput() throws IOException { - return resource.openStream(); - } - }; - } - }; - - private static final Function<InputSupplier<? extends InputStream>, - InputSupplier<? extends Reader>> INPUT_TO_READER = - new Function<InputSupplier<? extends InputStream>, InputSupplier<? extends Reader>>() { - @Override public InputSupplier<? extends Reader> apply( - final InputSupplier<? extends InputStream> input) { - return CharStreams.newReaderSupplier(input, Charsets.UTF_8); - } - }; - - private static final Function<URL, InputSupplier<? extends Reader>> URL_TO_READER = - Functions.compose(INPUT_TO_READER, URL_TO_INPUT); - - private static final String DEFAULT_RESOURCE_NAME = "cmdline.arg.info.txt"; - - private int nextResourceIndex; - private final ImmutableSet<ArgInfo> positionalInfos; - private final ImmutableSet<ArgInfo> cmdLineInfos; - private final ImmutableSet<ParserInfo> parserInfos; - private final ImmutableSet<VerifierInfo> verifierInfos; - - private Configuration(int nextResourceIndex, - Iterable<ArgInfo> positionalInfos, Iterable<ArgInfo> cmdLineInfos, - Iterable<ParserInfo> parserInfos, Iterable<VerifierInfo> verifierInfos) { - this.nextResourceIndex = nextResourceIndex; - this.positionalInfos = ImmutableSet.copyOf(positionalInfos); - this.cmdLineInfos = ImmutableSet.copyOf(cmdLineInfos); - this.parserInfos = ImmutableSet.copyOf(parserInfos); - this.verifierInfos = ImmutableSet.copyOf(verifierInfos); - } - - private static String checkValidIdentifier(String identifier, boolean compound) { - Preconditions.checkNotNull(identifier); - - String trimmed = identifier.trim(); - Preconditions.checkArgument(!trimmed.isEmpty(), "Invalid identifier: '%s'", identifier); - - String[] parts = compound ? trimmed.split("\\.") : new String[] {trimmed}; - for (String part : parts) { - Preconditions.checkArgument( - IDENTIFIER_REST.matchesAllOf(IDENTIFIER_START.trimLeadingFrom(part)), - "Invalid identifier: '%s'", identifier); - } - - return trimmed; - } - - public static final class ArgInfo { - public final String className; - public final String fieldName; - - public ArgInfo(String className, String fieldName) { - this.className = checkValidIdentifier(className, true); - this.fieldName = checkValidIdentifier(fieldName, false); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (!(obj instanceof ArgInfo)) { - return false; - } - - ArgInfo other = (ArgInfo) obj; - - return new EqualsBuilder() - .append(className, other.className) - .append(fieldName, other.fieldName) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(className) - .append(fieldName) - .toHashCode(); - } - - @Override public String toString() { - return new ToStringBuilder(this) - .append("className", className) - .append("fieldName", fieldName) - .toString(); - } - } - - public static final class ParserInfo { - public final String parsedType; - public final String parserClass; - - public ParserInfo(String parsedType, String parserClass) { - this.parsedType = checkValidIdentifier(parsedType, true); - this.parserClass = checkValidIdentifier(parserClass, true); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (!(obj instanceof ParserInfo)) { - return false; - } - - ParserInfo other = (ParserInfo) obj; - - return new EqualsBuilder() - .append(parsedType, other.parsedType) - .append(parserClass, other.parserClass) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(parsedType) - .append(parserClass) - .toHashCode(); - } - - @Override public String toString() { - return new ToStringBuilder(this) - .append("parsedType", parsedType) - .append("parserClass", parserClass) - .toString(); - } - } - - public static final class VerifierInfo { - public final String verifiedType; - public final String verifyingAnnotation; - public final String verifierClass; - - public VerifierInfo(String verifiedType, String verifyingAnnotation, String verifierClass) { - this.verifiedType = checkValidIdentifier(verifiedType, true); - this.verifyingAnnotation = checkValidIdentifier(verifyingAnnotation, true); - this.verifierClass = checkValidIdentifier(verifierClass, true); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (!(obj instanceof VerifierInfo)) { - return false; - } - - VerifierInfo other = (VerifierInfo) obj; - - return new EqualsBuilder() - .append(verifiedType, other.verifiedType) - .append(verifyingAnnotation, other.verifyingAnnotation) - .append(verifierClass, other.verifierClass) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder() - .append(verifiedType) - .append(verifyingAnnotation) - .append(verifierClass) - .toHashCode(); - } - - @Override public String toString() { - return new ToStringBuilder(this) - .append("verifiedType", verifiedType) - .append("verifyingAnnotation", verifyingAnnotation) - .append("verifierClass", verifierClass) - .toString(); - } - } - - static class Builder { - private final Set<ArgInfo> positionalInfos = Sets.newHashSet(); - private final Set<ArgInfo> argInfos = Sets.newHashSet(); - private final Set<ParserInfo> parserInfos = Sets.newHashSet(); - private final Set<VerifierInfo> verifierInfos = Sets.newHashSet(); - - public boolean isEmpty() { - return positionalInfos.isEmpty() - && argInfos.isEmpty() - && parserInfos.isEmpty() - && verifierInfos.isEmpty(); - } - - void addPositionalInfo(ArgInfo positionalInfo) { - positionalInfos.add(positionalInfo); - } - - void addCmdLineArg(ArgInfo argInfo) { - argInfos.add(argInfo); - } - - void addParser(ParserInfo parserInfo) { - parserInfos.add(parserInfo); - } - - public void addParser(String parserForType, String parserType) { - addParser(new ParserInfo(parserForType, parserType)); - } - - void addVerifier(VerifierInfo verifierInfo) { - verifierInfos.add(verifierInfo); - } - - public void addVerifier(String verifierForType, String annotationType, String verifierType) { - addVerifier(new VerifierInfo(verifierForType, annotationType, verifierType)); - } - - public Configuration build(Configuration configuration) { - return new Configuration(configuration.nextResourceIndex + 1, - positionalInfos, argInfos, parserInfos, verifierInfos); - } - } - - private static String getResourceName(int index) { - return String.format("%s.%s", DEFAULT_RESOURCE_NAME, index); - } - - private static String getResourcePath(int index) { - return String.format("%s/%s", DEFAULT_RESOURCE_PACKAGE.replace('.', '/'), - getResourceName(index)); - } - - static final class ConfigurationResources { - private final int nextResourceIndex; - private final Iterator<URL> resources; - - private ConfigurationResources(int nextResourceIndex, Iterator<URL> resources) { - this.nextResourceIndex = nextResourceIndex; - this.resources = resources; - } - } - - /** - * Loads the {@literal @CmdLine} argument configuration data stored in the classpath. - * - * @return The {@literal @CmdLine} argument configuration materialized from the classpath. - * @throws ConfigurationException if any configuration data is malformed. - * @throws IOException if the configuration data can not be read from the classpath. - */ - public static Configuration load() throws ConfigurationException, IOException { - ConfigurationResources allResources = getAllResources(); - List<URL> configs = ImmutableList.copyOf(allResources.resources); - if (configs.isEmpty()) { - LOG.info("No @CmdLine arg configs found on the classpath"); - } else { - LOG.info("Loading @CmdLine config from: " + configs); - } - return load(allResources.nextResourceIndex, configs); - } - - private static ConfigurationResources getAllResources() throws IOException { - int maxResourceIndex = 0; - Iterator<URL> allResources = getResources(0); // Try for a main - // Probe for resource files with index up to 10 (or more, while resources at the - // given index can be found) - for (int nextResourceIndex = 1; nextResourceIndex <= maxResourceIndex + 10; - nextResourceIndex++) { - Iterator<URL> resources = getResources(nextResourceIndex); - if (resources.hasNext()) { - allResources = Iterators.concat(allResources, resources); - maxResourceIndex = nextResourceIndex; - } - } - return new ConfigurationResources(maxResourceIndex + 1, allResources); - } - - private static Iterator<URL> getResources(int index) throws IOException { - return Iterators.forEnumeration( - Configuration.class.getClassLoader().getResources(getResourcePath(index))); - } - - private static final class ConfigurationParser implements LineProcessor<Configuration> { - private final int nextIndex; - private int lineNumber = 0; - - private final ImmutableList.Builder<ArgInfo> positionalInfo = ImmutableList.builder(); - private final ImmutableList.Builder<ArgInfo> fieldInfoBuilder = ImmutableList.builder(); - private final ImmutableList.Builder<ParserInfo> parserInfoBuilder = ImmutableList.builder(); - private final ImmutableList.Builder<VerifierInfo> verifierInfoBuilder = ImmutableList.builder(); - - private ConfigurationParser(int nextIndex) { - this.nextIndex = nextIndex; - } - - @Override - public boolean processLine(String line) throws IOException { - ++lineNumber; - String trimmed = line.trim(); - if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { - List<String> parts = Lists.newArrayList(trimmed.split(" ")); - if (parts.size() < 1) { - throw new ConfigurationException("Invalid line: %s @%d", trimmed, lineNumber); - } - - String type = parts.remove(0); - if ("positional".equals(type)) { - if (parts.size() != 2) { - throw new ConfigurationException( - "Invalid positional line: %s @%d", trimmed, lineNumber); - } - positionalInfo.add(new ArgInfo(parts.get(0), parts.get(1))); - } else if ("field".equals(type)) { - if (parts.size() != 2) { - throw new ConfigurationException("Invalid field line: %s @%d", trimmed, lineNumber); - } - fieldInfoBuilder.add(new ArgInfo(parts.get(0), parts.get(1))); - } else if ("parser".equals(type)) { - if (parts.size() != 2) { - throw new ConfigurationException("Invalid parser line: %s @%d", trimmed, lineNumber); - } - parserInfoBuilder.add(new ParserInfo(parts.get(0), parts.get(1))); - } else if ("verifier".equals(type)) { - if (parts.size() != 3) { - throw new ConfigurationException("Invalid verifier line: %s @%d", trimmed, lineNumber); - } - verifierInfoBuilder.add(new VerifierInfo(parts.get(0), parts.get(1), parts.get(2))); - } else { - LOG.warning(String.format("Did not recognize entry type %s for line: %s @%d", - type, trimmed, lineNumber)); - } - } - return true; - } - - @Override - public Configuration getResult() { - return new Configuration(nextIndex, positionalInfo.build(), - fieldInfoBuilder.build(), parserInfoBuilder.build(), verifierInfoBuilder.build()); - } - } - - private static Configuration load(int nextIndex, List<URL> configs) - throws ConfigurationException, IOException { - InputSupplier<Reader> input = CharStreams.join(Iterables.transform(configs, URL_TO_READER)); - return CharStreams.readLines(input, new ConfigurationParser(nextIndex)); - } - - public boolean isEmpty() { - return positionalInfos.isEmpty() - && cmdLineInfos.isEmpty() - && parserInfos.isEmpty() - && verifierInfos.isEmpty(); - } - - /** - * Returns the field info for the sole {@literal @Positional} annotated field on the classpath, - * if any. - * - * @return The field info for the {@literal @Positional} annotated field if any. - */ - public Iterable<ArgInfo> positionalInfo() { - return positionalInfos; - } - - /** - * Returns the field info for all the {@literal @CmdLine} annotated fields on the classpath. - * - * @return The field info for all the {@literal @CmdLine} annotated fields. - */ - public Iterable<ArgInfo> optionInfo() { - return cmdLineInfos; - } - - /** - * Returns the parser info for all the {@literal @ArgParser} annotated parsers on the classpath. - * - * @return The parser info for all the {@literal @ArgParser} annotated parsers. - */ - public Iterable<ParserInfo> parserInfo() { - return parserInfos; - } - - /** - * Returns the verifier info for all the {@literal @VerifierFor} annotated verifiers on the - * classpath. - * - * @return The verifier info for all the {@literal @VerifierFor} annotated verifiers. - */ - public Iterable<VerifierInfo> verifierInfo() { - return verifierInfos; - } - - static String mainResourceName() { - return getResourceName(0); - } - - String nextResourceName() { - return getResourceName(nextResourceIndex); - } - - void store(Writer output, String message) { - PrintWriter writer = new PrintWriter(output); - writer.printf("# %s\n", new Date()); - writer.printf("# %s\n ", message); - - writer.println(); - for (ArgInfo info : positionalInfos) { - writer.printf("positional %s %s\n", info.className, info.fieldName); - } - - writer.println(); - for (ArgInfo info : cmdLineInfos) { - writer.printf("field %s %s\n", info.className, info.fieldName); - } - - writer.println(); - for (ParserInfo info : parserInfos) { - writer.printf("parser %s %s\n", info.parsedType, info.parserClass); - } - - writer.println(); - for (VerifierInfo info : verifierInfos) { - writer.printf("verifier %s %s %s\n", - info.verifiedType, info.verifyingAnnotation, info.verifierClass); - } - } -} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/Arg.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/Arg.java b/commons-args/src/main/java/org/apache/aurora/common/args/Arg.java new file mode 100644 index 0000000..8e915c6 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/Arg.java @@ -0,0 +1,126 @@ +/** + * 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 javax.annotation.Nullable; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; + +/** + * Wrapper class for the value of an argument. For proper behavior, an {@code Arg} should always + * be annotated with {@link CmdLine}, which will define the command line interface to the argument. + */ +public class Arg<T> { + + @Nullable private final T defaultValue; + @Nullable private T value; + private boolean hasDefault = true; + private boolean valueApplied = false; + private boolean valueObserved = false; + + /** + * Creates an arg that has no default value, meaning that its value can only ever be retrieved + * if it has been externally set. + */ + public Arg() { + this(null); + hasDefault = false; + } + + /** + * Creates an arg that has a default value, and may optionally be set. + * + * @param defaultValue The default value for the arg. + */ + public Arg(@Nullable T defaultValue) { + this.defaultValue = defaultValue; + value = defaultValue; + } + + synchronized void set(@Nullable T appliedValue) { + Preconditions.checkState(!valueApplied, "A value cannot be applied twice to an argument."); + Preconditions.checkState(!valueObserved, "A value cannot be changed after it was read."); + valueApplied = true; + value = appliedValue; + } + + @VisibleForTesting + synchronized void reset() { + valueApplied = false; + valueObserved = false; + value = hasDefault ? defaultValue : null; + } + + public boolean hasDefault() { + return hasDefault; + } + + /** + * Gets the value of the argument. If a value has not yet been applied to the argument, or the + * argument did not provide a default value, {@link IllegalStateException} will be thrown. + * + * @return The argument value. + */ + @Nullable + public synchronized T get() { + // TODO(William Farner): This has a tendency to break bad-arg reporting by ArgScanner. Fix. + Preconditions.checkState(valueApplied || hasDefault, + "A value may only be retrieved from a variable that has a default or has been set."); + valueObserved = true; + return uncheckedGet(); + } + + /** + * Checks whether a value has been applied to the argument (i.e., apart from the default). + * + * @return {@code true} if a value from the command line has been applied to this arg, + * {@code false} otherwise. + */ + public synchronized boolean hasAppliedValue() { + return valueApplied; + } + + /** + * Gets the value of the argument, without checking whether a default was available or if a + * value was applied. + * + * @return The argument value. + */ + @Nullable + synchronized T uncheckedGet() { + return value; + } + + /** + * Convenience factory method to create an arg that has no default value. + * + * @param <T> Type of arg value. + * @return A new arg. + */ + public static <T> Arg<T> create() { + return new Arg<T>(); + } + + /** + * Convenience factory method to create an arg with a default value. + * + * @param value Default argument value. + * @param <T> Type of arg value. + * @return A new arg. + */ + public static <T> Arg<T> create(@Nullable T value) { + return new Arg<T>(value); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/ArgParser.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/ArgParser.java b/commons-args/src/main/java/org/apache/aurora/common/args/ArgParser.java new file mode 100644 index 0000000..25ed250 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/ArgParser.java @@ -0,0 +1,28 @@ +/** + * 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.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Annotation to register a command line argument parser globally. + */ +@Target(TYPE) +@Retention(SOURCE) +public @interface ArgParser { +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/CmdLine.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/CmdLine.java b/commons-args/src/main/java/org/apache/aurora/common/args/CmdLine.java new file mode 100644 index 0000000..a72ea7a --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/CmdLine.java @@ -0,0 +1,54 @@ +/** + * 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.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for a command line argument. + */ +@Target(FIELD) +@Retention(RUNTIME) +public @interface CmdLine { + /** + * The short name of the argument, as supplied on the command line. The argument can also be + * accessed by the canonical name, which is {@code com.foo.bar.MyArgClass.arg_name}. + * If the global scope contains more than one argument with the same name, all colliding arguments + * must be disambiguated with the canonical form. + * + * The argument name must match the format {@code [\w\-\.]+}. + */ + String name(); + + /** + * The help string to display on the command line in a usage message. + */ + String help(); + + /** + * The parser class to use for parsing this argument. The parser must return the same type as + * the field being annotated. + */ + Class<? extends Parser<?>> parser() default NoParser.class; + + /** + * The flag to indicate whether an argument file is accepted for this argument. + * + */ + boolean argFile() default false; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/NoParser.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/NoParser.java b/commons-args/src/main/java/org/apache/aurora/common/args/NoParser.java new file mode 100644 index 0000000..3366531 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/NoParser.java @@ -0,0 +1,23 @@ +/** + * 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; + +/** + * A sentinel parser type for internal use indicating no parser has been selected yet. + */ +abstract class NoParser implements Parser<Object> { + private NoParser() { + throw new UnsupportedOperationException("Not intended for construction."); + } +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/Parser.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/Parser.java b/commons-args/src/main/java/org/apache/aurora/common/args/Parser.java new file mode 100644 index 0000000..93c2323 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/Parser.java @@ -0,0 +1,35 @@ +/** + * 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.reflect.Type; + +/** + * An interface to a command line argument parser. + * + * @param <T> The base class this parser can parse all subtypes of. + */ +public interface Parser<T> { + /** + * Parses strings as arguments of a given subtype of {@code T}. + * + * @param parserOracle The registered parserOracle for delegation. + * @param type The target type of the parsed argument. + * @param raw The raw value of the argument. + * @return A value of the given type parsed from the raw value. + * @throws IllegalArgumentException if the raw value could not be parsed into a value of the + * given type. + */ + T parse(ParserOracle parserOracle, Type type, String raw) throws IllegalArgumentException; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/ParserOracle.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/ParserOracle.java b/commons-args/src/main/java/org/apache/aurora/common/args/ParserOracle.java new file mode 100644 index 0000000..e84d302 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/ParserOracle.java @@ -0,0 +1,31 @@ +/** + * 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 com.google.common.reflect.TypeToken; + +/** + * A registry of Parsers for different supported argument types. + */ +public interface ParserOracle { + + /** + * Gets the parser associated with a class. + * + * @param type Type to get the parser for. + * @return The parser for {@code cls}. + * @throws IllegalArgumentException If no parser was found for {@code cls}. + */ + <T> Parser<T> get(TypeToken<T> type) throws IllegalArgumentException; +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/Positional.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/Positional.java b/commons-args/src/main/java/org/apache/aurora/common/args/Positional.java new file mode 100644 index 0000000..1c6f86f --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/Positional.java @@ -0,0 +1,40 @@ +/** + * 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.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation to mark an {@link Arg} for gathering the positional command line arguments. + */ +@Retention(RUNTIME) +@Target(FIELD) +public @interface Positional { + /** + * The help string to display on the command line in a usage message. + */ + String help(); + + /** + * The parser class to use for parsing the positional arguments. The parser must return the same + * type as the field being annotated. + */ + Class<? extends Parser<?>> parser() default NoParser.class; + + // TODO: https://github.com/twitter/commons/issues/353, Consider to add argFile for positional. +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/Verifier.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/Verifier.java b/commons-args/src/main/java/org/apache/aurora/common/args/Verifier.java new file mode 100644 index 0000000..8f09a98 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/Verifier.java @@ -0,0 +1,37 @@ +/** + * 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; + +/** + * Typedef for a constraint verifier. + */ +public interface Verifier<T> { + /** + * Verifies the value against the annotation. + * + * @param value Value that is being applied. + * @throws IllegalArgumentException if the value is invalid. + */ + void verify(T value, Annotation annotation) throws IllegalArgumentException; + + /** + * Returns a representation of the constraint this verifier checks. + * + * @param argType The type of the {@link Arg} this annotation applies to. + * @return A representation of the constraint this verifier checks. + */ + String toString(Class<? extends T> argType, Annotation annotation); +} http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons-args/src/main/java/org/apache/aurora/common/args/VerifierFor.java ---------------------------------------------------------------------- diff --git a/commons-args/src/main/java/org/apache/aurora/common/args/VerifierFor.java b/commons-args/src/main/java/org/apache/aurora/common/args/VerifierFor.java new file mode 100644 index 0000000..aad8807 --- /dev/null +++ b/commons-args/src/main/java/org/apache/aurora/common/args/VerifierFor.java @@ -0,0 +1,34 @@ +/** + * 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.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +/** + * Annotation to register a command line argument verifier. + */ +@Target(TYPE) +@Retention(SOURCE) +public @interface VerifierFor { + /** + * Returns the annotation that marks a field for verification by the annotated + * {@link Verifier} class. + */ + Class<? extends Annotation> value(); +}
