/*
* Copyright 2013 the original author or authors.
*
* 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.gradle.api.internal.tasks.options;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.gradle.internal.reflect.JavaMethod;
import org.gradle.internal.reflect.JavaReflectionUtil;
import org.gradle.util.CollectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OptionReader {
private final ListMultimap<Class<?>, OptionElement> cachedOptionElements = ArrayListMultimap.create();
private final Map<OptionElement, JavaMethod<Object, Collection>> cachedOptionValueMethods = new HashMap<OptionElement, JavaMethod<Object, Collection>>();
private final OptionValueNotationParserFactory optionValueNotationParserFactory = new OptionValueNotationParserFactory();
public List<OptionDescriptor> getOptions(Object target) {
final Class<?> targetClass = target.getClass();
Map<String, OptionDescriptor> options = new HashMap<String, OptionDescriptor>();
if (!cachedOptionElements.containsKey(targetClass)) {
loadClassDescriptorInCache(target);
}
for (OptionElement optionElement : cachedOptionElements.get(targetClass)) {
JavaMethod<Object, Collection> optionValueMethod = cachedOptionValueMethods.get(optionElement);
options.put(optionElement.getOptionName(), new InstanceOptionDescriptor(target, optionElement, optionValueMethod));
}
return CollectionUtils.sort(options.values());
}
private void loadClassDescriptorInCache(Object target) {
final Collection<OptionElement> optionElements = getOptionElements(target);
List<JavaMethod<Object, Collection>> optionValueMethods = loadValueMethodForOption(target.getClass());
Set<String> processedOptionElements = new HashSet<String>();
for (OptionElement optionElement : optionElements) {
if (processedOptionElements.contains(optionElement.getOptionName())) {
throw new OptionValidationException(String.format("@Option '%s' linked to multiple elements in class '%s'.",
optionElement.getOptionName(), target.getClass().getName()));
}
processedOptionElements.add(optionElement.getOptionName());
JavaMethod<Object, Collection> optionValueMethodForOption = getOptionValueMethodForOption(optionValueMethods, optionElement);
cachedOptionElements.put(target.getClass(), optionElement);
cachedOptionValueMethods.put(optionElement, optionValueMethodForOption);
}
}
private static JavaMethod<Object, Collection> getOptionValueMethodForOption(List<JavaMethod<Object, Collection>> optionValueMethods, OptionElement optionElement) {
JavaMethod<Object, Collection> valueMethod = null;
for (JavaMethod<Object, Collection> optionValueMethod : optionValueMethods) {
OptionValues optionValues = optionValueMethod.getMethod().getAnnotation(OptionValues.class);
if (CollectionUtils.toList(optionValues.value()).contains(optionElement.getOptionName())) {
if (valueMethod == null) {
valueMethod = optionValueMethod;
} else {
throw new OptionValidationException(
String.format("@OptionValues for '%s' cannot be attached to multiple methods in class '%s'.",
optionElement.getOptionName(),
optionValueMethod.getMethod().getDeclaringClass().getName()));
}
}
}
return valueMethod;
}
private Collection<OptionElement> getOptionElements(Object target) {
List<OptionElement> allOptionElements = new ArrayList<OptionElement>();
for (Class<?> type = target.getClass(); type != Object.class && type != null; type = type.getSuperclass()) {
allOptionElements.addAll(getMethodAnnotations(type));
allOptionElements.addAll(getFieldAnnotations(type));
}
return allOptionElements;
}
private List<OptionElement> getFieldAnnotations(Class<?> type) {
List<OptionElement> fieldOptionElements = new ArrayList<OptionElement>();
for (Field field : type.getDeclaredFields()) {
Option option = field.getAnnotation(Option.class);
if (option != null) {
if (Modifier.isStatic(field.getModifiers())) {
throw new OptionValidationException(String.format("@Option on static field '%s' not supported in class '%s'.",
field.getName(), field.getDeclaringClass().getName()));
}
fieldOptionElements.add(FieldOptionElement.create(option, field, optionValueNotationParserFactory));
}
}
return fieldOptionElements;
}
private List<OptionElement> getMethodAnnotations(Class<?> type) {
List<OptionElement> methodOptionElements = new ArrayList<OptionElement>();
for (Method method : type.getDeclaredMethods()) {
Option option = method.getAnnotation(Option.class);
if (option != null) {
if (Modifier.isStatic(method.getModifiers())) {
throw new OptionValidationException(String.format("@Option on static method '%s' not supported in class '%s'.",
method.getName(), method.getDeclaringClass().getName()));
}
final OptionElement methodOptionDescriptor = MethodOptionElement.create(option, method,
optionValueNotationParserFactory);
methodOptionElements.add(methodOptionDescriptor);
}
}
return methodOptionElements;
}
private static List<JavaMethod<Object, Collection>> loadValueMethodForOption(Class<?> declaredClass) {
List<JavaMethod<Object, Collection>> methods = new ArrayList<JavaMethod<Object, Collection>>();
for (Class<?> type = declaredClass; type != Object.class && type != null; type = type.getSuperclass()) {
for (Method method : type.getDeclaredMethods()) {
OptionValues optionValues = method.getAnnotation(OptionValues.class);
if (optionValues != null) {
if (Collection.class.isAssignableFrom(method.getReturnType())
&& method.getParameterTypes().length == 0
&& !Modifier.isStatic(method.getModifiers())) {
methods.add(JavaReflectionUtil.method(Collection.class, method));
} else {
throw new OptionValidationException(
String.format("@OptionValues annotation not supported on method '%s' in class '%s'. Supported method must be non-static, return a Collection<String> and take no parameters.",
method.getName(),
type.getName()));
}
}
}
}
return methods;
}
}