Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.osgl.inject.BeanSpec Maven / Gradle / Ivy
package org.osgl.inject;
/*-
* #%L
* OSGL Genie
* %%
* Copyright (C) 2017 OSGL (Open Source General Library)
* %%
* 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.
* #L%
*/
import org.osgl.$;
import org.osgl.Lang;
import org.osgl.inject.annotation.*;
import org.osgl.inject.util.AnnotationUtil;
import org.osgl.inject.util.ParameterizedTypeImpl;
import org.osgl.util.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import javax.enterprise.inject.spi.Bean;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
/**
* Specification of a bean to be injected.
*/
public class BeanSpec implements BeanInfo {
/**
* The dependency injector.
*
* The injector can be used to do some check including
* the following:
*
* * {@link Injector#isQualifier(Class)}
* * {@link Injector#scopeByAlias(Class)}
*/
private final Injector injector;
/**
* Pre-calculated {@link Object#hashCode()} of this bean spec.
*/
private final int hc;
/**
* The {@link Type} of the bean.
*/
private final Type type;
/**
* The raw type of {@link #type()}
*/
private transient volatile Class> rawType;
/**
* The origin of this BeanSpec
*/
private Field field;
/**
* Is the bean an array type or not.
*/
private final boolean isArray;
private final Set elementLoaders = new HashSet<>();
private final Set filters = new HashSet<>();
private final Set transformers = new HashSet<>();
private final Set qualifiers = new HashSet<>();
private final Set postProcessors = new HashSet<>();
// only applied when bean spec constructed from Field
private final int modifiers;
/**
* The annotations will be used for calculating the hashCode and do
* equality test. The following annotations will added into
* the list:
* * {@link #elementLoaders}
* * {@link #filters}
* * {@link #qualifiers}
* * {@link #valueLoader}
* * {@link #postProcessors}
*/
private final Map, Annotation> annotations = new HashMap<>();
/**
* Stores all annotations including the ones that participating
* hashCode calculating and those who don't equality test.
*
* @see #annotations
*/
private final Map, Annotation> allAnnotations = new HashMap<>();
/**
* Stores all annotations that are tagged with another annotation mapped to the tag annotation class.
* E.g. for all annotation that are marked with `@Qualifier` will be stored in a set mapped to
* `Qualifier.class`
*/
private final Map, Set> tagAnnotations = new HashMap<>();
private final Set annoData = new HashSet<>();
private final Set injectTags = new HashSet<>();
/**
* Store the name value of Named annotation if presented.
*/
private String originalName;
private String name;
private MapKey mapKey;
private Class extends Annotation> scope;
private List componentSpecs;
private volatile boolean componentSpecsSet;
private boolean stopInheritedScope;
private Annotation valueLoader;
private List typeParams;
private Map fieldTypeImplLookup;
// simple type without injection tag
private volatile Map fields;
/**
* Construct the `BeanSpec` with bean type and field or parameter
* annotations.
*
* @param type
* the type of the bean to be instantiated
* @param annotations
* the annotation tagged on field or parameter,
* or `null` if this is a direct API injection
* request
* @param name
* optional, the name coming from the Named qualifier
* @param injector
* the injector instance
* @param modifiers
* the modifiers
*/
private BeanSpec(Type type, Annotation[] annotations, String name, Injector injector, int modifiers) {
this(type, annotations, name, injector, modifiers, C.Map());
}
private BeanSpec(Type type, Annotation[] annotations, String name, Injector injector, int modifiers, Map typeParamImplLookup) {
this.injector = injector;
this.type = type;
this.originalName = name;
this.name = name;
this.rawType = rawTypeOf(type, typeParamImplLookup);
this.typeParams(typeParamImplLookup);
if (null != typeParamImplLookup && type instanceof TypeVariable && rawType.getTypeParameters().length > 0 && !typeParamImplLookup.isEmpty()) {
this.fieldTypeImplLookup = Generics.subLookup(typeParamImplLookup, ((TypeVariable) type).getName());
}
this.isArray = rawType.isArray();
this.resolveTypeAnnotations(injector);
this.resolveAnnotations(annotations, injector, typeParamImplLookup);
this.hc = calcHashCode();
this.modifiers = modifiers;
}
private BeanSpec(BeanSpec source, Type convertTo) {
this.originalName = source.name;
this.name = source.name;
this.injector = source.injector;
this.type = convertTo;
if (convertTo == ArrayList.class) {
this.rawType = ArrayList.class;
this.typeParams = (List) C.list(this.rawType);
}
this.isArray = rawType().isArray();
this.qualifiers.addAll(source.qualifiers);
this.elementLoaders.addAll(source.elementLoaders);
this.filters.addAll(source.filters);
this.transformers.addAll(source.transformers);
this.valueLoader = source.valueLoader;
this.annotations.putAll(source.annotations);
this.annoData.addAll(source.annoData);
this.allAnnotations.putAll(source.allAnnotations);
this.tagAnnotations.putAll(source.tagAnnotations);
this.hc = calcHashCode();
this.modifiers = source.modifiers;
this.fieldTypeImplLookup = source.fieldTypeImplLookup;
}
private BeanSpec(BeanSpec source, String name) {
this.originalName = source.name;
this.name = name;
this.injector = source.injector;
this.type = source.type;
this.rawType = source.rawType;
this.isArray = source.isArray;
this.qualifiers.addAll(source.qualifiers);
this.elementLoaders.addAll(source.elementLoaders);
this.filters.addAll(source.filters);
this.transformers.addAll(source.transformers);
this.valueLoader = source.valueLoader;
this.annotations.putAll(source.annotations);
this.annoData.addAll(source.annoData);
this.allAnnotations.putAll(source.allAnnotations);
this.tagAnnotations.putAll(source.tagAnnotations);
this.hc = calcHashCode();
this.modifiers = source.modifiers;
this.rawType = source.rawType;
this.typeParams = source.typeParams;
}
@Override
public int hashCode() {
return hc;
}
/**
* A bean spec equals to another bean spec if all of the following conditions are met:
* * the {@link #type} of the two bean spec equals to each other
* * the {@link #annotations} of the two bean spec equals to each other
*
* Specifically, {@link #scope} does not participate comparison because
* 1. Scope annotations shall be put onto {@link java.lang.annotation.ElementType#TYPE type},
* or the factory method with {@link Provides} annotation, which is equivalent to `Type`.
* So it is safe to ignore scope annotation because one type cannot be annotated with different
* scope
* 2. If we count scope annotation in equality test, we will never be able to get the correct
* provider stem from the factory methods.
*
* @param obj
* the object to compare with this object
* @return `true` if the two objects equals as per described above
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof BeanSpec) {
BeanSpec that = (BeanSpec) obj;
return that.hc == hc
&& $.eq(type, that.type)
&& $.eq(rawType, that.rawType)
&& $.eq(name, that.name)
&& $.eq(annoData, that.annoData);
}
return false;
}
@Override
public String toString() {
StringBuilder sb = S.builder(type());
if (S.notBlank(name)) {
sb.append("(").append(name).append(")");
}
C.List list = C.newList();
if (null != valueLoader) {
list.append(valueLoader);
} else {
list.append(qualifiers).append(elementLoaders).append(filters);
if (null != mapKey) {
list.append(mapKey);
}
}
if (null != scope) {
list.append(scope.getSimpleName());
}
if (!list.isEmpty()) {
sb.append("@[").append(S.join(", ", list)).append("]");
}
return sb.toString();
}
public Injector injector() {
return injector;
}
@Override
public Type type() {
return type;
}
@Override
public Class rawType() {
if (null == rawType) {
synchronized (this) {
if (null == rawType) {
rawType = rawTypeOf(type);
}
}
}
return rawType;
}
@Override
public String name() {
return name;
}
@Override
public boolean isArray() {
return isArray;
}
@Override
public Annotation[] allAnnotations() {
return allAnnotations.values().toArray(new Annotation[allAnnotations.size()]);
}
public Annotation[] taggedAnnotations(Class extends Annotation> tagType) {
Set tagged = tagAnnotations.get(tagType);
return null == tagged ? new Annotation[0] : tagged.toArray(new Annotation[tagged.size()]);
}
/**
* Convert an array bean spec to a list bean spec.
*
* @return the array bean spec with component type derived from this bean spec
*/
public BeanSpec toList() {
return new BeanSpec(this, ArrayList.class);
}
@Override
public T getAnnotation(Class annoClass) {
return (T) allAnnotations.get(annoClass);
}
@Override
public boolean hasAnnotation(Class extends Annotation> annoClass) {
return allAnnotations.containsKey(annoClass);
}
public boolean hasAnnotation() {
return !allAnnotations.isEmpty();
}
@Override
public int getModifiers() {
return modifiers;
}
@Override
public boolean isTransient() {
return Modifier.isTransient(modifiers);
}
@Override
public boolean isStatic() {
return Modifier.isStatic(modifiers);
}
@Override
public boolean isPrivate() {
return Modifier.isPrivate(modifiers);
}
@Override
public boolean isPublic() {
return Modifier.isPublic(modifiers);
}
@Override
public boolean isProtected() {
return Modifier.isProtected(modifiers);
}
@Override
public boolean isFinal() {
return Modifier.isFinal(modifiers);
}
@Override
public boolean isInterface() {
return rawType().isInterface();
}
public boolean isMap() {
return Map.class.isAssignableFrom(rawType());
}
public boolean isCollection() {
return Collection.class.isAssignableFrom(rawType());
}
public boolean isList() {
return List.class.isAssignableFrom(rawType());
}
public boolean isSet() {
return Set.class.isAssignableFrom(rawType());
}
BeanSpec rawTypeSpec() {
return BeanSpec.of(rawType(), injector);
}
MapKey mapKey() {
return mapKey;
}
boolean isProvider() {
return Provider.class.isAssignableFrom(rawType());
}
BeanSpec toProvidee() {
return new BeanSpec(this, ((ParameterizedType) type).getActualTypeArguments()[0]);
}
public BeanSpec withoutName() {
return new BeanSpec(this, (String) null);
}
BeanSpec withoutQualifiers() {
if (qualifiers.isEmpty()) {
return this;
}
BeanSpec spec = withoutName();
spec.qualifiers.clear();
return spec;
}
/**
* Returns a list of type parameters if the
* {@link #type()} of the bean is instance of
* {@link ParameterizedType}.
*
* For example the bean spec of a field declared
* as
*
* ```java
* private Map scores;
* ```
*
* The `typeParams()` method will return a list
* of `String` and `Integer`
*
* @return type parameter list
*/
@Override
public List typeParams() {
if (null == typeParams) {
if (type instanceof ParameterizedType) {
ParameterizedType ptype = $.cast(type);
Type[] ta = ptype.getActualTypeArguments();
typeParams = C.listOf(ta);
} else {
typeParams = C.list();
}
}
return typeParams;
}
public List componentSpecs() {
if (!componentSpecsSet) {
synchronized (this) {
if (!componentSpecsSet) {
componentSpecsSet = true;
if (isArray()) {
BeanSpec componentSpec = BeanSpec.of(rawType.getComponentType(), injector);
componentSpecs = C.list(componentSpec);
} else {
componentSpecs = C.list(typeParams).map(new Lang.Transformer() {
@Override
public BeanSpec transform(Type type) {
return BeanSpec.of(type, injector);
}
});
}
}
}
}
return componentSpecs;
}
private void typeParams(Map typeParamImplLookup) {
if (type instanceof ParameterizedType) {
ParameterizedType ptype = $.cast(type);
Type[] ta = ptype.getActualTypeArguments();
for (int i = ta.length - 1; i >= 0; --i) {
Type t = ta[i];
if (t instanceof TypeVariable) {
TypeVariable v = $.cast(t);
String name = v.getName();
Class c = typeParamImplLookup.get(name);
if (null != c) {
ta[i] = c;
}
}
}
typeParams = C.listOf(ta);
} else {
typeParams = C.list();
}
}
public BeanSpec componentSpec() {
List componentSpecs = componentSpecs();
return componentSpecs.isEmpty() ? null : componentSpecs.get(0);
}
/**
* Return if the bean is instance of give class.
*
* @param c
* the class
* @return `true` if the underline bean is an instance of `c`
*/
public boolean isInstanceOf(Class c) {
return c.isAssignableFrom(rawType());
}
/**
* Check if a given object is instance of the type of this bean spec.
*
* @param o
* the object instance
* @return `true` if the object is an instance of type of this bean spec
*/
public boolean isInstance(Object o) {
Class c = rawType();
if (c.isInstance(o)) {
return true;
}
Class p = $.primitiveTypeOf(c);
if (null != p && p.isInstance(o)) {
return true;
}
Class w = $.wrapperClassOf(c);
if (null != w && w.isInstance(o)) {
return true;
}
return false;
}
/**
* Check if the bean spec has injector decorators. The following annotations
* are considered to be inject decorators:
* * qualifiers
* * post construct annotation
* * scope annotation
* * {@link Inject} or any annotation has {@link InjectTag} presented
*
* @return `true` if the beanSpec has inject decorators or `false` otherwise
*/
public boolean hasInjectDecorator() {
return !annoData.isEmpty() || !injectTags.isEmpty();
}
/**
* Returns all qualifier annotation of this bean spec.
*
* @return all qualifier annotations of this bean spec
*/
public Set qualifiers() {
return new HashSet<>(qualifiers);
}
public boolean isSimpleType() {
return $.isSimpleType(rawType);
}
public BeanSpec parent() {
Class> rawType = rawType();
Type type = rawType.getGenericSuperclass();
type = null == type ? rawType.getSuperclass() : type;
return null == type ? null : BeanSpec.of(type, injector());
}
public boolean isObject() {
return Object.class == rawType();
}
public boolean isNotObject() {
return Object.class != rawType();
}
Constructor getDeclaredConstructor() {
try {
return rawType().getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new InjectException(e, "cannot instantiate %s", rawType);
}
}
Method[] getDeclaredMethods() {
return rawType().getDeclaredMethods();
}
public BeanSpec field(String name) {
return fields().get(name);
}
/**
* Returns a list of `BeanSpec` for all fields including super class fields.
*
* @return the fields spec list
*/
public Map fields() {
if (null != fields) {
return fields;
}
synchronized (this) {
if (null == fields) {
Map map = new HashMap<>();
for (BeanSpec spec : fields($.F.yes())) {
map.put(spec.name, spec);
// need to treat the alias of field name
if (spec.originalName != spec.name) {
map.put(spec.originalName, spec);
}
}
fields = Collections.unmodifiableMap(map);
}
}
return fields;
}
public List nonStaticFields() {
return fields(NON_STATIC_FIELD);
}
/**
* Returns a list of `BeanSpec` for all fields including super class fields.
*
* @param filter
* a function to filter out fields that are not needed.
* @return the fields spec list
*/
public List fields($.Predicate filter) {
List retVal = new ArrayList<>();
BeanSpec current = this;
Class> rawType = rawType();
if ($.isSimpleType(rawType)) {
return C.list();
}
Type[] typeDeclarations = rawType.getTypeParameters();
Map typeImplLookup = null;
while (null != current && current.isNotObject()) {
Type[] fieldTypeParams = null;
Type[] classTypeParams;
Map typeParamsMapping = new HashMap<>();
for (Field field : current.rawType().getDeclaredFields()) {
if (!filter.test(field)) {
continue;
}
Type fieldGenericType = field.getGenericType();
if (fieldGenericType instanceof ParameterizedType) {
if (null == fieldTypeParams) {
fieldTypeParams = ((ParameterizedType) fieldGenericType).getActualTypeArguments();
if (fieldTypeParams != null && fieldTypeParams.length > 0) {
classTypeParams = current.rawType().getTypeParameters();
if (classTypeParams != null && classTypeParams.length > 0) {
List classTypeImpls = current.typeParams();
for (int i = 0; i < classTypeParams.length; ++i) {
if (i >= classTypeImpls.size()) {
break;
}
Type classTypeParam = classTypeParams[i];
if (classTypeParam instanceof TypeVariable) {
typeParamsMapping.put(((TypeVariable) classTypeParam).getName(), classTypeImpls.get(i));
}
}
}
}
}
boolean updated = false;
if (!typeParamsMapping.isEmpty()) {
for (int i = 0; i < fieldTypeParams.length; ++i) {
Type typeArg = fieldTypeParams[i];
if (typeArg instanceof TypeVariable) {
String name = ((TypeVariable) typeArg).getName();
Type typeImpl = typeParamsMapping.get(name);
if (null != typeImpl && $.ne(typeImpl, typeArg)) {
updated = true;
fieldTypeParams[i] = typeImpl;
}
}
}
}
if (updated) {
fieldGenericType = new ParameterizedTypeImpl(fieldTypeParams, ((ParameterizedType) fieldGenericType).getOwnerType(), ((ParameterizedType) fieldGenericType).getRawType());
}
retVal.add(beanSpecOf(field, fieldGenericType));
} else if (fieldGenericType instanceof TypeVariable) {
if (null == typeImplLookup) {
typeImplLookup = Generics.buildTypeParamImplLookup(this.rawType);
if (null != fieldTypeImplLookup) {
typeImplLookup.putAll(fieldTypeImplLookup);
}
}
TypeVariable fieldType = (TypeVariable) fieldGenericType;
Class clazz = typeImplLookup.get(fieldType.getName());
if (null != clazz) {
retVal.add(of(field, fieldGenericType, injector, typeImplLookup));
} else {
boolean added = false;
int typeParamCount = current.typeParams().size();
for (int i = typeDeclarations.length - 1; i >= 0; --i) {
if (typeDeclarations[i].equals(fieldGenericType)) {
fieldGenericType = i < typeParamCount ? current.typeParams().get(i) : Object.class;
retVal.add(of(field, fieldGenericType, injector, typeImplLookup));
added = true;
break;
}
}
if (!added) {
throw new InjectException("Cannot infer field type: " + field);
}
}
} else if (fieldGenericType instanceof GenericArrayType) {
if (null == typeImplLookup) {
typeImplLookup = Generics.buildTypeParamImplLookup(this.rawType);
}
retVal.add(of(field, injector(), typeImplLookup));
} else {
retVal.add(of(field, injector()));
}
}
current = current.parent();
if (null != current) {
typeDeclarations = current.rawType.getTypeParameters();
}
}
return retVal;
}
// find the beanspec of a field with generic type
private BeanSpec beanSpecOf(Field field, Type genericType) {
return of(field, genericType, injector);
// int len = typeArgs.size();
// for (int i = 0; i < len; ++i) {
// if (genericType.equals(typeArgs.get(i))) {
// return of(field, typeImpls.get(i), injector());
// }
// }
// return of(field, injector);
}
boolean hasElementLoader() {
return !elementLoaders.isEmpty();
}
boolean hasValueLoader() {
return null != valueLoader;
}
Set loaders() {
return elementLoaders;
}
Set filters() {
return filters;
}
Set transformers() {
return transformers;
}
Set postProcessors() {
return postProcessors;
}
Annotation valueLoader() {
return valueLoader;
}
Class extends Annotation> scope() {
return scope;
}
BeanSpec scope(Class extends Annotation> scopeAnno) {
scope = scopeAnno;
return this;
}
void makeFieldAccessible() {
E.illegalStateIf(null == field);
field.setAccessible(true);
}
void setField(Object bean, Object value) {
E.illegalStateIf(null == field);
try {
field.set(bean, value);
} catch (Exception e) {
throw new InjectException(e, "Unable to inject field value on %s", bean.getClass());
}
}
boolean notConstructable() {
Class> c = rawType();
return c.isInterface() || c.isArray() || Modifier.isAbstract(c.getModifiers());
}
private void resolveTypeAnnotations(Injector injector) {
for (Annotation annotation : rawType().getAnnotations()) {
resolveScope(annotation, injector);
Class extends Annotation> annoType = annotation.annotationType();
if (annoType == Named.class) {
name = ((Named) annotation).value();
} else if (injector.isQualifier(annoType)) {
qualifiers.add(annotation);
}
allAnnotations.put(annotation.annotationType(), annotation);
storeTagAnnotation(annotation);
}
}
private void resolveAnnotations(Annotation[] aa, Injector injector, Map typeParamImplLookup) {
if (null == aa || aa.length == 0) {
return;
}
Class> rawType = rawType();
boolean isMap = Map.class.isAssignableFrom(rawType);
boolean isContainer = isMap || Collection.class.isAssignableFrom(rawType) || rawType.isArray();
MapKey mapKey = null;
List loadValueIncompatibles = new ArrayList();
// Note only qualifiers and bean loaders annotation are considered
// effective annotation. Scope annotations is not effective here
// because they are tagged on target type, not the field or method
// parameter
for (Annotation anno : aa) {
Class extends Annotation> cls = anno.annotationType();
Annotation prev = allAnnotations.put(cls, anno);
if (null == prev || anno != prev) {
storeTagAnnotation(anno);
}
if (Inject.class == cls || Provides.class == cls) {
injectTags.add(new AnnoData(anno));
continue;
}
boolean isInjectTag = tagAnnotations.containsKey(InjectTag.class);
if (isInjectTag) {
injectTags.add(new AnnoData(anno));
}
if (cls == MapKey.class) {
if (null != mapKey) {
throw new InjectException("MapKey annotation already presented");
}
if (!isMap) {
Genie.logger.warn("MapKey annotation ignored on target that is not of Map type");
} else {
mapKey = $.cast(anno);
}
}
if (LoadValue.class == cls || cls.isAnnotationPresent(LoadValue.class)) {
valueLoader = anno;
} else if (LoadCollection.class == cls || cls.isAnnotationPresent(LoadCollection.class)) {
if (isContainer) {
elementLoaders.add(anno);
loadValueIncompatibles.add(anno);
} else {
Genie.logger.warn("LoadCollection annotation[%s] ignored as target type is neither Collection nor Map", cls.getSimpleName());
}
} else if (Named.class == cls) {
name = ((Named) anno).value();
} else if (injector.isQualifier(cls)) {
qualifiers.add(anno);
annotations.put(anno.annotationType(), anno);
annoData.add(new AnnoData(anno));
} else if (Filter.class == cls || cls.isAnnotationPresent(Filter.class)) {
if (isContainer) {
filters.add(anno);
loadValueIncompatibles.add(anno);
} else {
Genie.logger.warn("Filter annotation[%s] ignored as target type is neither Collection nor Map", cls.getSimpleName());
}
} else if (Transform.class == cls || cls.isAnnotationPresent(Transform.class)) {
transformers.add(anno);
} else {
if (injector.isPostConstructProcessor(cls)) {
postProcessors.add(anno);
annotations.put(cls, anno);
//annoData.add(new AnnoData(anno));
} else {
resolveScope(anno, injector);
}
}
}
if (isContainer && null == valueLoader && !hasElementLoader()) {
if (isMap) {
List typeParams = typeParams();
if (typeParams.size() > 1) {
Type type = typeParams.get(1);
if (type instanceof Class) {
elementLoaders.add(AnnotationUtil.createAnnotation(TypeOf.class));
}
}
} else {
// Collection
List typeParams = typeParams();
if (typeParams.size() > 0) {
Type type = typeParams.get(0);
if (type instanceof Class) {
elementLoaders.add(AnnotationUtil.createAnnotation(TypeOf.class));
}
}
}
}
if (isMap && hasElementLoader() && null == mapKey) {
List typeParams = typeParams();
if (typeParams.size() > 1) {
Type type = typeParams.get(1);
if (type instanceof Class) {
Class clazz = (Class) type;
if (clazz.isEnum()) {
mapKey = MapKey.Factory.create("name");
}
}
}
}
if (isContainer && null == valueLoader && elementLoaders.isEmpty()) {
// assume we want to inject typed elements
Class> rawType0;
if (rawType.isArray()) {
rawType0 = rawType.getComponentType();
} else {
List typeParams = typeParams();
if (typeParams.isEmpty()) {
rawType0 = Object.class;
} else {
Type theType = typeParams.get(isMap ? 1 : 0);
if (TypeVariable.class.isInstance(theType)) {
String typeVarName = ((TypeVariable) theType).getName();
theType = typeParamImplLookup.get(typeVarName);
if (null == theType) {
throw new InjectException("Cannot determine implementation type of type variable [%s]", typeVarName);
}
}
rawType0 = rawTypeOf(theType);
}
}
if (!$.isSimpleType(rawType0)) {
TypeOf typeOfAnno = AnnotationUtil.createAnnotation(TypeOf.class);
elementLoaders.add(typeOfAnno);
loadValueIncompatibles.add(typeOfAnno);
}
}
if (null != valueLoader) {
if (!loadValueIncompatibles.isEmpty()) {
throw new InjectException("ValueLoader annotation cannot be used with ElementLoader and Filter annotations: %s", annotations);
}
annotations.put(valueLoader.annotationType(), valueLoader);
annoData.add(new AnnoData(valueLoader));
} else {
for (Annotation anno : loadValueIncompatibles) {
annotations.put(anno.annotationType(), anno);
annoData.add(new AnnoData(anno));
}
if (null != mapKey) {
if (hasElementLoader()) {
this.mapKey = mapKey;
annotations.put(mapKey.annotationType(), mapKey);
annoData.add(new AnnoData(mapKey));
} else {
Genie.logger.warn("MapKey annotation ignored on target without ElementLoader annotation presented");
}
}
}
}
private static Set> WAIVE_TAG_TYPES = C.set(
Documented.class, Retention.class, Target.class, Inherited.class
);
/**
* Walk through anno's tag annotations
*
* @param anno
* the annotation
*/
private void storeTagAnnotation(Annotation anno) {
Class extends Annotation> annoType = anno.annotationType();
Annotation[] tags = annoType.getAnnotations();
for (Annotation tag : tags) {
Class extends Annotation> tagType = tag.annotationType();
if (WAIVE_TAG_TYPES.contains(tagType)) {
continue;
}
Set tagged = tagAnnotations.get(tagType);
if (null == tagged) {
tagged = new HashSet<>();
tagAnnotations.put(tagType, tagged);
}
tagged.add(anno);
}
}
private void resolveScope(Annotation annotation, Injector injector) {
if (stopInheritedScope) {
return;
}
Class extends Annotation> annoClass = annotation.annotationType();
if (injector.isInheritedScopeStopper(annoClass)) {
stopInheritedScope = true;
scope = null;
} else if (injector.isScope(annoClass)) {
if (null != scope) {
Class extends Annotation> newScope = injector.scopeByAlias(annoClass);
if (newScope != scope) {
throw new InjectException("Incompatible scope annotation found: %s", this);
}
} else {
scope = injector.scopeByAlias(annoClass);
// scope annotaton is a decorator for inject library usage, it
// can't add scope annotation into annoData and make it
// enter the equals and hashCode calculation,
//annoData.add(new AnnoData(annotation));
}
}
}
/**
* Return hash code based on {@link #type} and {@link #annotations}.
*
* @return {@link Object#hashCode()} of this bean
* @see #equals(Object)
*/
private int calcHashCode() {
return $.hc(type, name, annoData, rawType());
}
private BeanSpec setField(Field field) {
this.field = field;
return this;
}
private Class rawTypeOf(Type type, Map typeParamImplLookup) {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
return (Class) ((ParameterizedType) type).getRawType();
} else if (type instanceof TypeVariable) {
TypeVariable tv = (TypeVariable) type;
Class impl = typeParamImplLookup.get(tv.getName());
if (null != impl) {
return impl;
}
} else if (type instanceof GenericArrayType) {
GenericArrayType gat = (GenericArrayType) type;
Type componentType = gat.getGenericComponentType();
return rawTypeOf(componentType, typeParamImplLookup);
}
throw E.unexpected("type not recognized: %s", type);
}
public static BeanSpec of(Class> clazz, Injector injector) {
return new BeanSpec(clazz, null, null, injector, 0);
}
public static BeanSpec of(Type type, Injector injector) {
return new BeanSpec(type, null, null, injector, 0);
}
public static BeanSpec of(Type type, Injector injector, Map typeParamLookup) {
return new BeanSpec(type, null, null, injector, 0, typeParamLookup);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, Injector injector) {
return new BeanSpec(type, paramAnnotations, null, injector, 0);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, Injector injector, Map typeParamLookup) {
return new BeanSpec(type, paramAnnotations, null, injector, 0, typeParamLookup);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, Injector injector, int modifiers) {
return new BeanSpec(type, paramAnnotations, null, injector, modifiers);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, Injector injector, int modifiers, Map typeParamLookup) {
return new BeanSpec(type, paramAnnotations, null, injector, modifiers, typeParamLookup);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, String name, Injector injector) {
return new BeanSpec(type, paramAnnotations, name, injector, 0);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, String name, Injector injector, Map typeParamLookup) {
return new BeanSpec(type, paramAnnotations, name, injector, 0, typeParamLookup);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, String name, Injector injector, int modifiers) {
return new BeanSpec(type, paramAnnotations, name, injector, modifiers);
}
public static BeanSpec of(Type type, Annotation[] paramAnnotations, String name, Injector injector, int modifiers, Map typeParamLookup) {
return new BeanSpec(type, paramAnnotations, name, injector, modifiers, typeParamLookup);
}
public static BeanSpec of(Field field, Injector injector) {
return of(field, injector, C.Map());
}
public static BeanSpec of(Field field, Injector injector, Map typeParamLookup) {
Annotation[] annotations = field.getDeclaredAnnotations();
Type fieldType = field.getGenericType();
if (fieldType instanceof TypeVariable) {
fieldType = field.getType();
}
return BeanSpec.of(fieldType, annotations, field.getName(), injector, field.getModifiers(), typeParamLookup).setField(field);
}
private static BeanSpec of(Field field, Type realType, Injector injector) {
Annotation[] annotations = field.getDeclaredAnnotations();
return of(realType, annotations, field.getName(), injector, field.getModifiers()).setField(field);
}
private static BeanSpec of(Field field, Type realType, Injector injector, Map typeParamLookup) {
Annotation[] annotations = field.getDeclaredAnnotations();
return of(realType, annotations, field.getName(), injector, field.getModifiers(), typeParamLookup).setField(field);
}
/**
* A utility method to return raw type (the class) of a given
* type.
*
* @param type
* a {@link Type}
* @return a {@link Class} of the type
* @throws org.osgl.exception.UnexpectedException
* if class cannot be determined
*/
public static Class> rawTypeOf(Type type) {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
return (Class) ((ParameterizedType) type).getRawType();
} else {
throw E.unexpected("type not recognized: %s", type);
}
}
public static boolean isGetter(Method method) {
return BeanInfo.Util.isGetter(method);
}
public static boolean isSetter(Method method) {
return BeanInfo.Util.isSetter(method);
}
// keep the effective annotation data
// - the property annotated with NonBinding is ignored
private static class AnnoData {
private Class extends Annotation> annoClass;
private Map data;
AnnoData(Annotation annotation) {
annoClass = annotation.annotationType();
data = evaluate(annotation);
}
@Override
public int hashCode() {
return $.hc(annoClass, data);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof AnnoData) {
AnnoData that = (AnnoData) obj;
return $.eq(annoClass, that.annoClass) && $.eq(data, that.data);
}
return false;
}
private static Map evaluate(Annotation anno) {
Map properties = new HashMap<>();
Class extends Annotation> annoClass = anno.annotationType();
Method[] ma = annoClass.getMethods();
for (Method m : ma) {
if (isStandardAnnotationMethod(m) || shouldIgnore(m)) {
continue;
}
properties.put(m.getName(), $.invokeVirtual(anno, m));
}
return properties;
}
private static Set standardsAnnotationMethods = C.Set(C.list("equals", "hashCode", "toString", "annotationType", "getClass"));
private static boolean isStandardAnnotationMethod(Method m) {
return standardsAnnotationMethods.contains(m.getName());
}
private static boolean shouldIgnore(Method method) {
Annotation[] aa = method.getDeclaredAnnotations();
for (Annotation a : aa) {
if (shouldIgnore(a)) {
return true;
}
}
return false;
}
private static boolean shouldIgnore(Annotation annotation) {
return annotation.annotationType().getName().endsWith("Nonbinding");
}
}
private static $.Predicate NON_STATIC_FIELD = new $.Predicate() {
@Override
public boolean test(Field field) {
return !Modifier.isStatic(field.getModifiers());
}
};
}