package org.javers.core.metamodel.type; import java.util.Optional; import org.javers.common.reflection.ReflectionUtil; import org.javers.common.string.PrettyPrintBuilder; import org.javers.common.string.ToStringBuilder; import org.javers.common.validation.Validate; import org.javers.core.metamodel.annotation.TypeName; import java.lang.reflect.Constructor; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static org.javers.common.reflection.ReflectionUtil.extractClass; import static org.javers.common.validation.Validate.argumentIsNotNull; /** * Managed property type * <br/><br/> * * This is a convenient abstraction layer over raw and awkward * java.lang.reflect.Type and java.lang.Class * * @author bartosz walacik */ public abstract class JaversType { public static final Class DEFAULT_TYPE_PARAMETER = Object.class; private final Type baseJavaType; private final Class baseJavaClass; private final List<Type> concreteTypeArguments; private final String name; /** * @param baseJavaType Class or ParametrizedType */ JaversType(Type baseJavaType) { this(baseJavaType, Optional.<String>empty()); } JaversType(Type baseJavaType, Optional<String> name) { this(baseJavaType, name, 0); } JaversType(Type baseJavaType, Optional<String> name, int expectedArgs) { Validate.argumentIsNotNull(baseJavaType); Validate.argumentIsNotNull(name); this.baseJavaType = baseJavaType; this.baseJavaClass = extractClass(baseJavaType); this.concreteTypeArguments = Collections.unmodifiableList( buildListOfConcreteTypeArguments(baseJavaType, expectedArgs)); if (name.isPresent()) { this.name = name.get(); }else { this.name = extractClass(baseJavaType).getName(); } } /** * Factory method, delegates to self constructor */ JaversType spawn(Type baseJavaType) { try { Constructor c = this.getClass().getConstructor(new Class<?>[]{Type.class}); return (JaversType)c.newInstance(new Object[]{baseJavaType}); } catch (ReflectiveOperationException exception) { throw new RuntimeException("error calling Constructor for " + this.getClass().getName(), exception); } } public boolean isGenericType() { return (baseJavaType instanceof ParameterizedType); } public Type getBaseJavaType() { return baseJavaType; } public Class getBaseJavaClass() { return baseJavaClass; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof JaversType)) return false; JaversType that = (JaversType) o; return baseJavaType.equals(that.baseJavaType); } @Override public String toString() { return ToStringBuilder.toString(this, "baseType", baseJavaType); } @Override public int hashCode() { return baseJavaType.hashCode(); } /** * For generic types, returns a list of actual Class arguments. * For example, for Set<String>, returns String. * Non-concrete (like ?) or missing type arguments like are defaulted to Object. * <br/><br/> * * For array, returns List with {@link Class#getComponentType()} */ public List<Type> getConcreteClassTypeArguments() { return concreteTypeArguments; } private static List<Type> buildListOfConcreteTypeArguments(Type baseJavaType, int expectedSize) { List<Type> allTypeArguments = ReflectionUtil.getAllTypeArguments(baseJavaType); List<Type> concreteTypeArguments = new ArrayList<>(expectedSize); for (int i=0; i<expectedSize; i++) { Type existingArgument = null; if (!allTypeArguments.isEmpty() && i<allTypeArguments.size()){ existingArgument = allTypeArguments.get(i); } concreteTypeArguments.add(getActualClassTypeArgument(existingArgument)); } return concreteTypeArguments; } private static Type getActualClassTypeArgument(Type existingArgument) { if (existingArgument == null) { return DEFAULT_TYPE_PARAMETER; } Optional<Type> concreteType = ReflectionUtil.isConcreteType(existingArgument); if (concreteType.isPresent()) { return concreteType.get(); } else { return DEFAULT_TYPE_PARAMETER; } } /** * Type for JSON representation. * * For Values it's simply baseJavaType. * * For ManagedTypes (references to Entities and ValueObjects) it's GlobalId * because JaVers serializes references in the 'dehydrated' form. */ Type getRawDehydratedType() { return getBaseJavaClass(); } /** * Prints this object to String */ public final String prettyPrint(){ return prettyPrintBuilder().build(); } /** * JaversType name, clientsClass.name by default * or value of {@link TypeName} annotation. */ public String getName() { return name; } public boolean isInstance(Object cdo) { argumentIsNotNull(cdo); return baseJavaClass.isAssignableFrom(cdo.getClass()); } protected PrettyPrintBuilder prettyPrintBuilder(){ return new PrettyPrintBuilder(this) .addField("baseType", baseJavaType) .addField("typeName", name); } }