/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.aries.blueprint.plugin.model;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Class to find uniquely-named fields declared in a class hierarchy with specified annotations.
*/
final class Introspector {
private final Class<?> originalClazz;
/**
* @param clazz the class to introspect (including those defined in parent classes).
*/
Introspector(Class<?> clazz) {
this.originalClazz = clazz;
}
/**
* @param requiredAnnotations annotations the fields must have
* @return fields in the given class (including parent classes) that match this finder's annotations requirements.
* @throws UnsupportedOperationException if any field matching the annotations requirement shares its name with a
* field declared elsewhere in the class hierarchy.
*/
@SafeVarargs
final List<Field> fieldsWith(Class<? extends Annotation>... requiredAnnotations) {
Multimap<String, Field> fieldsByName = HashMultimap.create();
Set<String> acceptedFieldNames = Sets.newHashSet();
Class<?> clazz = originalClazz;
// For each parent class of clazz...
while(clazz != null && clazz != Object.class) {
for (Field field : clazz.getDeclaredFields()) {
// ...add all declared fields
fieldsByName.put(field.getName(), field);
// ...and if it meets the annotation requirement, add the field name to the set of accepted field names
if (hasAnyRequiredAnnotation(field, requiredAnnotations)) {
acceptedFieldNames.add(field.getName());
}
}
clazz = clazz.getSuperclass();
}
// Add all accepted fields to acceptedFields
List<Field> acceptedFields = Lists.newArrayList();
for (String fieldName : acceptedFieldNames) {
Collection<Field> fields = fieldsByName.get(fieldName);
validateOnlyOneFieldWithName(fieldName, fields);
acceptedFields.addAll(fields);
}
return acceptedFields;
}
/**
* Check that each field name is defined no more than once
* @param acceptedFieldName
* @param acceptedFieldsWithSameName
*/
private void validateOnlyOneFieldWithName(String acceptedFieldName,
Collection<Field> acceptedFieldsWithSameName) {
if (acceptedFieldsWithSameName.size() > 1) {
String header = String.format("Field '%s' in bean class '%s' has been defined multiple times in:",
acceptedFieldName, originalClazz.getName());
StringBuilder msgBuilder = new StringBuilder(header);
for (Field field : acceptedFieldsWithSameName) {
msgBuilder.append("\n\t- ").append(field.getDeclaringClass().getName());
}
throw new UnsupportedOperationException(msgBuilder.toString());
}
}
@SafeVarargs
private final boolean hasAnyRequiredAnnotation(Field field, Class<? extends Annotation>... requiredAnnotations) {
if (requiredAnnotations.length == 0) {
throw new IllegalArgumentException("Must specify at least one annotation");
}
for (Class<? extends Annotation> requiredAnnotation : requiredAnnotations) {
if (field.getAnnotation(requiredAnnotation) != null) {
return true;
}
}
return false;
}
@SafeVarargs
final List<Method> methodsWith(Class<? extends Annotation>... annotationClasses) {
List<Method> methods = new ArrayList<>();
for (Method method : originalClazz.getMethods()) {
for(Class<? extends Annotation> annotationClass : annotationClasses) {
if (method.getAnnotation(annotationClass) != null) {
methods.add(method);
break;
}
}
}
return methods;
}
}