io.airlift.command.model.MetadataLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of airline Show documentation
Show all versions of airline Show documentation
Airline is a Java annotation-based framework for parsing Git like command line structures.
The newest version!
package io.airlift.command.model;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import io.airlift.command.*;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.lang.reflect.Field;
import java.util.*;
import static com.google.common.base.Predicates.compose;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
public class MetadataLoader
{
public static GlobalMetadata loadGlobal(String name,
String description,
CommandMetadata defaultCommand,
Iterable defaultGroupCommands,
Iterable groups)
{
ImmutableList.Builder globalOptionsBuilder = ImmutableList.builder();
if (defaultCommand != null) {
globalOptionsBuilder.addAll(defaultCommand.getGlobalOptions());
}
for (CommandMetadata command : defaultGroupCommands) {
globalOptionsBuilder.addAll(command.getGlobalOptions());
}
for (CommandGroupMetadata group : groups) {
for (CommandMetadata command : group.getCommands()) {
globalOptionsBuilder.addAll(command.getGlobalOptions());
}
}
List globalOptions = mergeOptionSet(globalOptionsBuilder.build());
return new GlobalMetadata(name, description, globalOptions, defaultCommand, defaultGroupCommands, groups);
}
public static CommandGroupMetadata loadCommandGroup(String name, String description, CommandMetadata defaultCommand, Iterable commands)
{
ImmutableList.Builder groupOptionsBuilder = ImmutableList.builder();
if (defaultCommand != null) {
groupOptionsBuilder.addAll(defaultCommand.getGroupOptions());
}
for (CommandMetadata command : commands) {
groupOptionsBuilder.addAll(command.getGroupOptions());
}
List groupOptions = mergeOptionSet(groupOptionsBuilder.build());
return new CommandGroupMetadata(name, description, groupOptions, defaultCommand, commands);
}
public static ImmutableList loadCommands(Iterable> defaultCommands)
{
return ImmutableList.copyOf(Iterables.transform(defaultCommands, new Function, CommandMetadata>()
{
public CommandMetadata apply(Class> commandType)
{
return loadCommand(commandType);
}
}));
}
public static CommandMetadata loadCommand(Class> commandType)
{
Command command = null;
List groups = new ArrayList();
for (Class> cls = commandType; command == null && !Object.class.equals(cls); cls = cls.getSuperclass()) {
command = cls.getAnnotation(Command.class);
if(cls.isAnnotationPresent(Groups.class))
{
groups.addAll(Arrays.asList(cls.getAnnotation(Groups.class).value()));
}
if(cls.isAnnotationPresent(Group.class))
{
groups.add(cls.getAnnotation(Group.class));
}
}
Preconditions.checkArgument(command != null, "Command %s is not annotated with @Command", commandType.getName());
String name = command.name();
String description = command.description().isEmpty() ? null : command.description();
List groupNames = Arrays.asList(command.groupNames());
boolean hidden = command.hidden();
InjectionMetadata injectionMetadata = loadInjectionMetadata(commandType);
CommandMetadata commandMetadata = new CommandMetadata(
name,
description,
hidden, injectionMetadata.globalOptions,
injectionMetadata.groupOptions,
injectionMetadata.commandOptions,
Iterables.getFirst(injectionMetadata.arguments, null),
injectionMetadata.metadataInjections,
commandType,
groupNames,
groups);
return commandMetadata;
}
public static SuggesterMetadata loadSuggester(Class extends Suggester> suggesterClass)
{
InjectionMetadata injectionMetadata = loadInjectionMetadata(suggesterClass);
return new SuggesterMetadata(suggesterClass, injectionMetadata.metadataInjections);
}
public static InjectionMetadata loadInjectionMetadata(Class> type)
{
InjectionMetadata injectionMetadata = new InjectionMetadata();
loadInjectionMetadata(type, injectionMetadata, ImmutableList.of());
injectionMetadata.compact();
return injectionMetadata;
}
public static void loadInjectionMetadata(Class> type, InjectionMetadata injectionMetadata, List fields)
{
if(type.isInterface())
{
return;
}
for (Class> cls = type; !Object.class.equals(cls); cls = cls.getSuperclass()) {
for (Field field : cls.getDeclaredFields()) {
field.setAccessible(true);
ImmutableList path = concat(fields, field);
Inject injectAnnotation = field.getAnnotation(Inject.class);
if (injectAnnotation != null) {
if (field.getType().equals(GlobalMetadata.class) ||
field.getType().equals(CommandGroupMetadata.class) ||
field.getType().equals(CommandMetadata.class)) {
injectionMetadata.metadataInjections.add(new Accessor(path));
} else {
loadInjectionMetadata(field.getType(), injectionMetadata, path);
}
}
Option optionAnnotation = field.getAnnotation(Option.class);
if (optionAnnotation != null) {
OptionType optionType = optionAnnotation.type();
String name;
if (!optionAnnotation.title().isEmpty()) {
name = optionAnnotation.title();
}
else {
name = field.getName();
}
List options = ImmutableList.copyOf(optionAnnotation.name());
String description = optionAnnotation.description();
int arity = optionAnnotation.arity();
Preconditions.checkArgument(arity >= 0 || arity == Integer.MIN_VALUE, "Invalid arity for option %s", name);
if (optionAnnotation.arity() >= 0) {
arity = optionAnnotation.arity();
}
else {
Class> fieldType = field.getType();
if (Boolean.class.isAssignableFrom(fieldType) || boolean.class.isAssignableFrom(fieldType)) {
arity = 0;
}
else {
arity = 1;
}
}
boolean required = optionAnnotation.required();
boolean hidden = optionAnnotation.hidden();
List allowedValues = ImmutableList.copyOf(optionAnnotation.allowedValues());
if (allowedValues.isEmpty()) {
allowedValues = null;
}
OptionMetadata optionMetadata = new OptionMetadata(optionType, options, name, description, arity, required, hidden, allowedValues, path);
switch (optionType) {
case GLOBAL:
injectionMetadata.globalOptions.add(optionMetadata);
break;
case GROUP:
injectionMetadata.groupOptions.add(optionMetadata);
break;
case COMMAND:
injectionMetadata.commandOptions.add(optionMetadata);
break;
}
}
Arguments argumentsAnnotation = field.getAnnotation(Arguments.class);
if (field.isAnnotationPresent(Arguments.class)) {
String title;
if (!argumentsAnnotation.title().isEmpty()) {
title = argumentsAnnotation.title();
}
else {
title = field.getName();
}
String description = argumentsAnnotation.description();
String usage = argumentsAnnotation.usage();
boolean required = argumentsAnnotation.required();
injectionMetadata.arguments.add(new ArgumentsMetadata(title, description, usage, required, path));
}
}
}
}
private static List mergeOptionSet(List options)
{
ListMultimap metadataIndex = ArrayListMultimap.create();
for (OptionMetadata option : options) {
metadataIndex.put(option, option);
}
options = ImmutableList.copyOf(transform(metadataIndex.asMap().values(), new Function, OptionMetadata>()
{
@Override
public OptionMetadata apply(@Nullable Collection options)
{
return new OptionMetadata(options);
}
}));
Map optionIndex = newHashMap();
for (OptionMetadata option : options) {
for (String optionName : option.getOptions()) {
if (optionIndex.containsKey(optionName)) {
throw new IllegalArgumentException(String.format("Fields %s and %s have conflicting definitions of option %s",
optionIndex.get(optionName).getAccessors().iterator().next(),
option.getAccessors().iterator().next(),
optionName));
}
optionIndex.put(optionName, option);
}
}
return options;
}
private static ImmutableList concat(Iterable iterable, T item)
{
return ImmutableList.builder().addAll(iterable).add(item).build();
}
public static void loadCommandsIntoGroupsByAnnotation(List allCommands, List commandGroups, List defaultCommandGroup)
{
List newCommands = new ArrayList();
// first, create any groups explicitly annotated
createGroupsFromAnnotations(allCommands,newCommands,commandGroups,defaultCommandGroup);
for (CommandMetadata command : allCommands) {
boolean added = false;
//now add the command to any groupNames specified in the Command annotation
for(String groupName : command.getGroupNames())
{
CommandGroupMetadata group = find(commandGroups, compose(equalTo(groupName), CommandGroupMetadata.nameGetter()), null);
if (group != null) {
group.addCommand(command);
added = true;
}
else
{
ImmutableList.Builder groupOptionsBuilder = ImmutableList.builder();
groupOptionsBuilder.addAll(command.getGroupOptions());
CommandGroupMetadata newGroup = loadCommandGroup(groupName,"",null, Collections.singletonList(command));
commandGroups.add(newGroup);
added = true;
}
}
if(added && defaultCommandGroup.contains(command))
{
defaultCommandGroup.remove(command);
}
}
allCommands.addAll(newCommands);
}
private static void createGroupsFromAnnotations(List allCommands, List newCommands, List commandGroups, List defaultCommandGroup)
{
for (CommandMetadata command : allCommands) {
boolean added = false;
// first, create any groups explicitly annotated
for(Group groupAnno : command.getGroups())
{
Class defaultCommandClass = null;
CommandMetadata defaultCommand = null;
//load default command if needed
if(!groupAnno.defaultCommand().equals(Group.DEFAULT.class))
{
defaultCommandClass = groupAnno.defaultCommand();
defaultCommand = find(allCommands, compose(equalTo(defaultCommandClass), CommandMetadata.typeGetter()), null);
if(null == defaultCommand)
{
defaultCommand = loadCommand(defaultCommandClass);
newCommands.add(defaultCommand);
}
}
//load other commands if needed
List groupCommands = new ArrayList(groupAnno.commands().length);
CommandMetadata groupCommand = null;
for(Class commandClass : groupAnno.commands())
{
groupCommand = find(allCommands, compose(equalTo(commandClass), CommandMetadata.typeGetter()), null);
if(null == groupCommand)
{
groupCommand = loadCommand(commandClass);
newCommands.add(groupCommand);
groupCommands.add(groupCommand);
}
}
CommandGroupMetadata groupMetadata = find(commandGroups, compose(equalTo(groupAnno.name()), CommandGroupMetadata.nameGetter()), null);
if(null == groupMetadata)
{
groupMetadata = loadCommandGroup(groupAnno.name(),groupAnno.description(),defaultCommand, groupCommands);
commandGroups.add(groupMetadata);
}
groupMetadata.addCommand(command);
added = true;
}
if(added && defaultCommandGroup.contains(command))
{
defaultCommandGroup.remove(command);
}
}
}
private static class InjectionMetadata
{
private List globalOptions = newArrayList();
private List groupOptions = newArrayList();
private List commandOptions = newArrayList();
private List arguments = newArrayList();
private List metadataInjections = newArrayList();
private void compact()
{
globalOptions = mergeOptionSet(globalOptions);
groupOptions = mergeOptionSet(groupOptions);
commandOptions = mergeOptionSet(commandOptions);
if (arguments.size() > 1) {
arguments = ImmutableList.of(new ArgumentsMetadata(arguments));
}
}
}
}