| /* |
| * Copyright 2012-present Facebook, Inc. |
| * |
| * 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.facebook.buck.cli; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.Sets; |
| |
| import org.kohsuke.args4j.Argument; |
| import org.kohsuke.args4j.ClassParser; |
| import org.kohsuke.args4j.CmdLineParser; |
| import org.kohsuke.args4j.IllegalAnnotationError; |
| import org.kohsuke.args4j.Option; |
| import org.kohsuke.args4j.spi.Setters; |
| |
| import java.lang.reflect.Field; |
| import java.util.Set; |
| |
| /** |
| * {@link CmdLineParser} with nested options via the {@link AdditionalOptions} annotation. |
| * <p> |
| * Fields annotated with it should be of classes which have a default constructor, and will be |
| * automatically instantiated. |
| */ |
| public class CmdLineParserAdditionalOptions extends CmdLineParser { |
| |
| /** |
| * Creates a new command line owner that parses arguments/options and set them into |
| * the given object. |
| * |
| * @param bean |
| * instance of a class annotated by {@link Option}, {@link Argument} |
| * and {@link AdditionalOptions}. |
| * This object will receive values. If this is null, the processing will |
| * be skipped, which is useful if you'd like to feed metadata from other sources. |
| * |
| * @throws IllegalAnnotationError |
| * if the option bean class is using args4j annotations incorrectly. |
| * @see CmdLineParser#CmdLineParser(Object) |
| */ |
| public CmdLineParserAdditionalOptions(Object bean) { |
| super(bean); |
| if (bean != null) { |
| Set<Class<?>> visited = Sets.newHashSet(); |
| parseAdditionalOptions(new ClassParser(), bean, visited); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void parseAdditionalOptions(ClassParser classParser, Object bean, Set<Class<?>> visited) { |
| // The top-level bean was already parsed by the superclass constructor, |
| // so an empty visited set means we're parsing the top-level bean. |
| if (!visited.isEmpty()) { |
| classParser.parse(bean, this); // 'Parse' the class of the bean looking for annotations. |
| } |
| Class<?> beanClass = bean.getClass(); |
| if (visited.contains(beanClass)) { |
| throw new IllegalAnnotationError(beanClass.getCanonicalName() + " used more than once."); |
| } else { |
| visited.add(beanClass); |
| } |
| |
| for (Field f : beanClass.getDeclaredFields()) { |
| if (f.isAnnotationPresent(AdditionalOptions.class)) { |
| try { |
| // TODO(user): nicer to do this lazily in parseArgument() |
| Object fieldValue = f.getType().newInstance(); |
| Setters.create(f, bean).addValue(fieldValue); |
| parseAdditionalOptions(classParser, fieldValue, visited); |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| } |
| } |
| |
| } |