/* * Copyright (c) 2010 Mysema Ltd. * All rights reserved. * */ package com.mysema.rdfbean.object; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; import java.lang.reflect.Member; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Nullable; import com.google.common.base.Objects; import com.mysema.commons.lang.Assert; import com.mysema.rdfbean.annotations.ComponentType; import com.mysema.rdfbean.annotations.Container; import com.mysema.rdfbean.annotations.ContainerType; import com.mysema.rdfbean.annotations.Default; import com.mysema.rdfbean.annotations.Defaults; import com.mysema.rdfbean.annotations.Id; import com.mysema.rdfbean.annotations.InjectService; import com.mysema.rdfbean.annotations.Localized; import com.mysema.rdfbean.annotations.MapElements; import com.mysema.rdfbean.annotations.Mixin; import com.mysema.rdfbean.annotations.Predicate; import com.mysema.rdfbean.annotations.Properties; import com.mysema.rdfbean.annotations.Required; import com.mysema.rdfbean.model.IDType; import com.mysema.rdfbean.model.UID; import com.mysema.util.BeanMap; /** * @author sasa * */ public abstract class MappedProperty<M extends Member & AnnotatedElement> implements Cloneable { @SuppressWarnings("unchecked") public static final List<Class<? extends Annotation>> MAPPING_ANNOTATIONS = Collections.unmodifiableList(Arrays.<Class<? extends Annotation>> asList( ComponentType.class, Container.class, Default.class, Defaults.class, Id.class, InjectService.class, Localized.class, MapElements.class, Mixin.class, Required.class )); @Nullable private final String name; @Nullable private Class<?> type; @Nullable private Class<?> componentType; @Nullable private UID context; private Class<?> keyType; private boolean collection; private MappedClass declaringClass; private TypeVariable<?>[] typeVariables = new TypeVariable<?>[4]; private Map<Class<? extends Annotation>, Annotation> annotations = new HashMap<Class<? extends Annotation>, Annotation>(); private boolean includeMapped; @SuppressWarnings("unchecked") MappedProperty(@Nullable String name, Annotation[] annotations, MappedClass declaringClass) { this.name = name; this.declaringClass = declaringClass; for (Annotation annotation : Assert.notNull(annotations, "annotations")) { Class<? extends Annotation> aclass = (Class<? extends Annotation>) annotation.getClass().getInterfaces()[0]; this.annotations.put(aclass, annotation); } } public MappedClass getDeclaringClass() { return declaringClass; } static Class<?> getUpper(@Nullable Class<?> clazz, Class<?> other) { if (clazz == null) { return other; } else if (other != null && !clazz.equals(other)) { if (clazz.isAssignableFrom(other)) { return other; } } return clazz; } @SuppressWarnings("unchecked") void resolve(@Nullable MappedClass owner) { if (this.type == null) { this.type = getTypeInternal(); } Type genericType = getGenericType(); if (genericType instanceof TypeVariable) { this.type = getUpper(this.type, getGenericClass(genericType, 0, owner, 0)); } this.collection = Collection.class.isAssignableFrom(type); ComponentType ctypeAnno = getAnnotation(ComponentType.class); if (ctypeAnno != null) { this.componentType = ctypeAnno.value(); } else if (collection || isClassReference()) { this.componentType = getUpper(this.componentType, getGenericClass(genericType, 0, owner, 1)); } else if (type.isArray()) { this.componentType = type.getComponentType(); } else if (isMap()) { MapElements mapKey = getAnnotation(MapElements.class); if (mapKey != null && !Void.class.equals(mapKey.keyType())) { keyType = mapKey.keyType(); } else { keyType = getUpper(keyType, getGenericClass(genericType, 0, owner, 2)); } this.componentType = getUpper(componentType, getGenericClass(genericType, 1, owner, 3)); } else { this.componentType = null; } Properties propertiesAnno = getAnnotation(Properties.class); if (propertiesAnno != null) { includeMapped = propertiesAnno.includeMapped(); if (!propertiesAnno.context().isEmpty()) { context = new UID(propertiesAnno.context()); } } } @SuppressWarnings("unchecked") @Nullable public Class<? extends Collection> getCollectionType() { if (isCollection()) { return getConcreteCollectionType(getType()); } else { return null; } } @Nullable @SuppressWarnings("unchecked") private Class<? extends Collection> getConcreteCollectionType(Class<?> collectionType) { if (collectionType.isInterface()) { if (List.class.isAssignableFrom(collectionType)) { return ArrayList.class; } else if (SortedSet.class.isAssignableFrom(collectionType)) { return TreeSet.class; } else if (Set.class.isAssignableFrom(collectionType)) { return LinkedHashSet.class; } else if (Collection.class.equals(collectionType)) { return HashSet.class; } else { throw new IllegalArgumentException("Unsupported Collection interface type: " + collectionType); } } else if (Collection.class.isAssignableFrom(collectionType)) { return (Class<? extends Collection>) collectionType; } return null; } @Nullable public Class<?> getComponentType() { return componentType; } public Class<?> getTargetType() { Class<?> clazz = getComponentType(); if (clazz == null) { clazz = getType(); } return clazz; } public List<UID> getDefaults() { Default[] defaults; Defaults defs = getAnnotation(Defaults.class); if (defs != null) { defaults = defs.value(); } else { Default def = getAnnotation(Default.class); if (def != null) { defaults = new Default[] { def }; } else { defaults = new Default[0]; } } List<UID> rs = new ArrayList<UID>(defaults.length); for (Default def : defaults) { // TODO: Use default ns and ln if there's only one default? rs.add(UID.create(null, def.ns(), def.ln(), name)); } return rs; } public boolean isAnnotationPresent(Class<? extends Annotation> atype) { return annotations.containsKey(atype); } @SuppressWarnings("unchecked") @Nullable Class getGenericClass(@Nullable final Type t, int index, MappedClass owner, int typeVariableIndex) { Type gtype = t; if (gtype != null) { if (gtype instanceof Class) { return (Class) gtype; } else if (gtype instanceof ParameterizedType && index >= 0) { gtype = ((ParameterizedType) gtype).getActualTypeArguments()[index]; } if (gtype instanceof Class) { return (Class) gtype; } else if (gtype instanceof WildcardType) { return getGenericClass((WildcardType) gtype); } else if (gtype instanceof TypeVariable) { return getGenericClass(owner, typeVariableIndex, (TypeVariable) gtype); } else if (gtype instanceof ParameterizedType) { return (Class) ((ParameterizedType) gtype).getRawType(); } else { throw new SessionException("Unable to get generic type [" + index + "] of " + t + " from " + owner); } } return null; } @SuppressWarnings("unchecked") private Class<?> getGenericClass(MappedClass owner, int typeVariableIndex, TypeVariable<?> type) { Type upperBound = null; if (owner == null || declaringClass.equals(owner)) { typeVariables[typeVariableIndex] = type; upperBound = typeVariables[typeVariableIndex].getBounds()[0]; } else if (typeVariables[typeVariableIndex] != null) { Type genericType = owner.resolveTypeVariable(typeVariables[typeVariableIndex].getName(), declaringClass); if (genericType instanceof TypeVariable) { // Nested TypeVariable in a sub class typeVariables[typeVariableIndex] = (TypeVariable<?>) genericType; upperBound = typeVariables[typeVariableIndex].getBounds()[0]; } else { typeVariables[typeVariableIndex] = null; upperBound = genericType; } declaringClass = owner; } return getGenericClass(upperBound, -1, owner, -1); } @SuppressWarnings("unchecked") private Class<?> getGenericClass(WildcardType wildcardType) { if (wildcardType.getUpperBounds()[0] instanceof ParameterizedType) { return (Class) ((ParameterizedType) wildcardType.getUpperBounds()[0]).getRawType(); } else if (wildcardType.getUpperBounds()[0] instanceof Class) { return (Class) wildcardType.getUpperBounds()[0]; } else { // System.err.println("Unable to find out actual type of " + gtype); return Object.class; } } public Map<Class<? extends Annotation>, Annotation> getAnnotations() { return Collections.unmodifiableMap(annotations); } @SuppressWarnings("unchecked") @Nullable public <T extends Annotation> T getAnnotation(Class<T> atype) { return (T) annotations.get(atype); } @Nullable public UID getKeyPredicate() { MapElements mapKey = getAnnotation(MapElements.class); if (mapKey != null) { Predicate predicate = mapKey.key(); // String parentNs = getParentNs(mapKey, getMember()); return UID.create(declaringClass.getClassNs(), predicate.ns(), predicate.ln(), null); } else { return null; } } public Class<?> getKeyType() { return keyType; } protected abstract M getMember(); public String getName() { return name; } public Class<?> getType() { return type; } protected abstract Class<?> getTypeInternal(); protected abstract Type getGenericType(); public abstract Object getValue(BeanMap instance); @Nullable public UID getValuePredicate() { MapElements mapKey = getAnnotation(MapElements.class); if (mapKey != null) { Predicate predicate = mapKey.value(); try { // String parentNs = getParentNs(mapKey, getMember()); return UID.create(declaringClass.getClassNs(), predicate.ns(), predicate.ln(), null); } catch (IllegalArgumentException e) { return null; } } else { return null; } } @Nullable public IDType getIDType() { Id annotation = getAnnotation(Id.class); return annotation != null ? annotation.value() : null; } @Nullable public String getIDNamespace() { Id annotation = getAnnotation(Id.class); return annotation != null ? annotation.ns() : null; } @Nullable public UID getContext() { return context; } public boolean isAnnotatedProperty() { if (!annotations.isEmpty()) { for (Class<? extends Annotation> anno : MAPPING_ANNOTATIONS) { if (annotations.containsKey(anno)) { return true; } } } return false; } public boolean isCollection() { return collection; } public boolean isIdReference() { return isAnnotationPresent(Id.class); } public boolean isList() { // refers to List RDF mapping, not the java.util.List type Container container = getAnnotation(Container.class); if (container != null) { return ContainerType.LIST == container.value(); } else { return List.class.isAssignableFrom(getType()) || type.isArray(); } } public boolean isArray() { return type.isArray(); } public boolean isLocalized() { return isAnnotationPresent(Localized.class); } public boolean isInjection() { return isAnnotationPresent(InjectService.class); } public boolean isMixin() { return isAnnotationPresent(Mixin.class); } public boolean isMap() { return Map.class.isAssignableFrom(getType()); } public boolean isDynamic() { return isAnnotationPresent(Properties.class); } public boolean isConstructorParameter() { return getMember() instanceof Constructor<?>; } public boolean isSet() { return Set.class.isAssignableFrom(getType()); } public boolean isSortedSet() { return SortedSet.class.isAssignableFrom(getType()); } public boolean isRequired() { return isAnnotationPresent(Required.class); } public abstract boolean isVirtual(); public abstract void setValue(BeanMap beanWrapper, @Nullable Object value); public void validate(MappedPath path) { if (isMixin()) { Member member = getMember(); if (getType().isAssignableFrom(member.getDeclaringClass())) { throw new IllegalArgumentException("Illegal mixin reference to oneself: " + toString()); } } } @Override public String toString() { return getMember().toString(); } public boolean isAssignableFrom(MappedProperty<?> other) { // Only methods may override... if (MethodProperty.class.isInstance(other) && Objects.equal(name, other.name)) { Class<?> domain = getMember().getDeclaringClass(); Class<?> otherDomain = other.getMember().getDeclaringClass(); return domain.isAssignableFrom(otherDomain); } else { return false; } } public void addAnnotations(MappedProperty<?> other) { this.annotations.putAll(other.annotations); } public boolean isAnyResource() { return UID.class == getType(); } public boolean isURI() { return UID.class.isAssignableFrom(getTargetType()); } public boolean isContainer() { Container container = this.getAnnotation(Container.class); return container != null && container.value() != ContainerType.LIST && container.value() != ContainerType.NONE; } @Nullable public ContainerType getContainerType() { Container container = this.getAnnotation(Container.class); return container != null ? container.value() : null; } public boolean isIndexed() { return isList() || ContainerType.SEQ.equals(getContainerType()); } @Override public Object clone() { try { MappedProperty<?> clone = (MappedProperty<?>) super.clone(); clone.annotations = new HashMap<Class<? extends Annotation>, Annotation>(annotations); clone.typeVariables = new TypeVariable<?>[4]; System.arraycopy(typeVariables, 0, clone.typeVariables, 0, 4); return clone; } catch (CloneNotSupportedException e) { throw new SessionException(e); } } public boolean isClassReference() { return Class.class.isAssignableFrom(type); } public boolean isIncludeMapped() { return includeMapped; } public boolean isDynamicCollection() { return Collection.class.isAssignableFrom(componentType); } @Nullable @SuppressWarnings("unchecked") public Class<? extends Collection> getDynamicCollectionType() { if (isDynamicCollection()) { return getConcreteCollectionType(componentType); } else { return null; } } @Nullable public Class<?> getDynamicCollectionComponentType() { Type genericType = getGenericType(); if (genericType instanceof ParameterizedType) { ParameterizedType parameterizedComponentType = (ParameterizedType) ((ParameterizedType) genericType).getActualTypeArguments()[1]; return (Class<?>) parameterizedComponentType.getActualTypeArguments()[0]; } else { return null; } } }