package com.twitter.common.args; import java.lang.reflect.Field; import java.util.logging.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.twitter.common.args.apt.Configuration; import com.twitter.common.args.apt.Configuration.ArgInfo; import static com.twitter.common.args.apt.Configuration.ConfigurationException; /** * A utility that can load {@literal @CmdLine} arg field info from a configuration database. */ final class Args { @VisibleForTesting static final Function<ArgInfo, Optional<Field>> TO_FIELD = new Function<ArgInfo, Optional<Field>>() { @Override public Optional<Field> apply(ArgInfo info) { try { return Optional.of(Class.forName(info.className).getDeclaredField(info.fieldName)); } catch (NoSuchFieldException e) { throw new ConfigurationException(e); } catch (ClassNotFoundException e) { throw new ConfigurationException(e); } catch (NoClassDefFoundError e) { // A compilation had this class available at the time the ArgInfo was deposited, but // the classes have been re-bundled with some subset including the class this ArgInfo // points to no longer available. If the re-bundling is correct, then the arg truly is // not needed. LOG.fine(String.format("Not on current classpath, skipping %s", info)); return Optional.absent(); } } }; private static final Logger LOG = Logger.getLogger(Args.class.getName()); private static final Function<Field, OptionInfo<?>> TO_OPTIONINFO = new Function<Field, OptionInfo<?>>() { @Override public OptionInfo<?> apply(Field field) { CmdLine cmdLine = field.getAnnotation(CmdLine.class); if (cmdLine == null) { throw new ConfigurationException("No @CmdLine Arg annotation for field " + field); } return OptionInfo.createFromField(field); } }; private static final Function<Field, PositionalInfo<?>> TO_POSITIONALINFO = new Function<Field, PositionalInfo<?>>() { @Override public PositionalInfo<?> apply(Field field) { Positional positional = field.getAnnotation(Positional.class); if (positional == null) { throw new ConfigurationException("No @Positional Arg annotation for field " + field); } return PositionalInfo.createFromField(field); } }; private Args() { // utility } static class ArgumentInfo { private final Optional<? extends PositionalInfo<?>> positionalInfo; private final Iterable<? extends OptionInfo<?>> optionInfos; ArgumentInfo(Optional<? extends PositionalInfo<?>> positionalInfo, Iterable<? extends OptionInfo<?>> cmdLineDescs) { this.positionalInfo = Preconditions.checkNotNull(positionalInfo); this.optionInfos = Preconditions.checkNotNull(cmdLineDescs); } Optional<? extends PositionalInfo<?>> getPositionalInfo() { return positionalInfo; } Iterable<? extends OptionInfo<?>> getOptionInfos() { return optionInfos; } } /** * Hydrates configured {@literal @CmdLine} arg fields and selects a desired set with the supplied * {@code filter}. * * @param configuration The configuration to find candidate {@literal @CmdLine} arg fields in. * @param filter A predicate to select fields with. * @return The desired hydrated {@literal @CmdLine} arg fields and optional {@literal @Positional} * arg field. */ static ArgumentInfo fromConfiguration(Configuration configuration, Predicate<Field> filter) { ImmutableSet<Field> positionalFields = ImmutableSet.copyOf(filterFields(configuration.positionalInfo(), filter)); if (positionalFields.size() > 1) { throw new IllegalArgumentException( String.format("Found %d fields marked for @Positional Args after applying filter - " + "only 1 is allowed:\n\t%s", positionalFields.size(), Joiner.on("\n\t").join(positionalFields))); } Iterable<PositionalInfo<?>> positionalInfos = Iterables.transform(positionalFields, TO_POSITIONALINFO); PositionalInfo<?> positionalInfoOrNull = Iterables.getOnlyElement(positionalInfos, null); Optional<? extends PositionalInfo<?>> positionalInfoOptional = Optional.fromNullable(positionalInfoOrNull); Iterable<? extends OptionInfo<?>> optionInfos = Iterables.transform( filterFields(configuration.optionInfo(), filter), TO_OPTIONINFO); return new ArgumentInfo(positionalInfoOptional, optionInfos); } private static Iterable<Field> filterFields(Iterable<ArgInfo> infos, Predicate<Field> filter) { return Iterables.filter( Optional.presentInstances(Iterables.transform(infos, TO_FIELD)), filter); } }