package cz.cvut.fel.adaptiverestfulapi.example; import cz.cvut.fel.adaptiverestfulapi.meta.ModelInspectionListener; import cz.cvut.fel.adaptiverestfulapi.meta.model.*; import cz.cvut.fel.adaptiverestfulapi.meta.model.Entity; import cz.cvut.fel.adaptiverestfulapi.meta.reflection.Triplet; import javax.persistence.*; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; import java.util.Set; /** * The default model inspection listener. */ public class ModelListener implements ModelInspectionListener { @Override public Entity entity(Class clazz) { return new Entity(this.entityName(clazz), clazz); } @Override public Property property(Triplet<Field, Method, Method> triplet, Entity entity, Set<Entity> entities) { Method getter = triplet.b; Method setter = triplet.c; String shortName = this.propertyShortName(triplet, entity); String name = this.propertyName(triplet, entity); Entity targetEntity = this.targetEntity(triplet, entity, entities); if (targetEntity != null) { RelationshipType relationshipType = this.relationshipType(triplet, entity, entities); return new Relationship(name, shortName, getter, setter, targetEntity.getName(), relationshipType); } Type attributeType = this.attributeType(triplet, entity); boolean primary = this.isPrimary(triplet, entity); return new Attribute(name, shortName, getter, setter, attributeType, primary); } /** * Returns entity name from class. * * It uses fully qualified name of the class, * eq.: `com.app.model.Project`. * * @param clazz * @return The entity name. */ protected String entityName(Class clazz) { return clazz.getName(); } /** * Returns property name from triplet of field, getter and setter. * * It uses entity name combined with short name, * eq.: `com.app.model.Project.startedAt`. * * @param triplet * @param entity The entity of the property. * @return The property name. */ protected String propertyName(Triplet<Field, Method, Method> triplet, Entity entity) { return entity.getName() + "." + this.propertyShortName(triplet, entity); } /** * Returns property short name from triplet of field, getter and setter. * * It uses entity name combined with name of the field, * or name of the getter without `get` (`is`) prefix, * or name of the setter without `set` prefix, * eq.: `startedAt`. * * @param triplet * @param entity The entity of the property. * @return The property short name. */ protected String propertyShortName(Triplet<Field, Method, Method> triplet, Entity entity) { Field field = triplet.a; Method getter = triplet.b; Method setter = triplet.c; if (field != null) { return this.toFirstLetterLowerCase(field.getName()); } else if (getter != null && getter.getName().startsWith("get")) { return this.toFirstLetterLowerCase(getter.getName().substring("get".length())); } else if (getter != null && getter.getName().startsWith("is")) { return this.toFirstLetterLowerCase(getter.getName().substring("is".length())); } else if (setter != null && setter.getName().startsWith("set")) { return this.toFirstLetterLowerCase(setter.getName().substring("set".length())); } else { return null; } } /** * Looks for relationship type from triplet of field, getter and setter. * @param triplet * @param entity The entity of the property. * @param entities The known entities in the model. * @return The relationship type or null if not found. */ protected RelationshipType relationshipType(Triplet<Field, Method, Method> triplet, Entity entity, Set<Entity> entities) { RelationshipType relationshipType = null; if (triplet.a != null) { relationshipType = this.relationshipTypeFromAnnotations(triplet.a.getAnnotations()); if (relationshipType != null) { return relationshipType; } } if (triplet.b != null) { relationshipType = this.relationshipTypeFromAnnotations(triplet.b.getAnnotations()); if (relationshipType != null) { return relationshipType; } } if (triplet.c != null) { relationshipType = this.relationshipTypeFromAnnotations(triplet.c.getAnnotations()); if (relationshipType != null) { return relationshipType; } } if (triplet.a != null) { relationshipType = this.relationshipTypeFromField(triplet.a); if (relationshipType != null) { return relationshipType; } } if (triplet.b != null) { relationshipType = this.relationshipTypeFromGetter(triplet.b); if (relationshipType != null) { return relationshipType; } } if (triplet.b != null) { relationshipType = this.relationshipTypeFromSetter(triplet.c); if (relationshipType != null) { return relationshipType; } } return null; } /** * Returns relationship type from field. * @param field * @return The relationship type. */ protected RelationshipType relationshipTypeFromField(Field field) { Class<?> target = field.getType(); if (Collection.class.isAssignableFrom(target)) { return RelationshipType.ToMany; } return RelationshipType.ToOne; } /** * Returns relationship type from getter method. * @param getter * @return The relationship type. */ protected RelationshipType relationshipTypeFromGetter(Method getter) { Class<?> target = getter.getReturnType(); if (Collection.class.isAssignableFrom(target)) { return RelationshipType.ToMany; } return RelationshipType.ToOne; } /** * Returns relationship type from setter method. * @param setter * @return The relationship type. */ protected RelationshipType relationshipTypeFromSetter(Method setter) { Class<?> target = setter.getParameterTypes()[0]; if (Collection.class.isAssignableFrom(target)) { return RelationshipType.ToMany; } return RelationshipType.ToOne; } /** * Looks for JPA relationship annotation to find relationship type. * @param annotations * @return The relationship type. */ protected RelationshipType relationshipTypeFromAnnotations(Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(OneToOne.class)) { OneToOne oneToOne = (OneToOne)annotation; return RelationshipType.ToOne; } else if (annotation.annotationType().equals(OneToMany.class)) { OneToMany oneToMany = (OneToMany)annotation; return RelationshipType.ToMany; } else if (annotation.annotationType().equals(ManyToOne.class)) { ManyToOne manyToOne = (ManyToOne)annotation; return RelationshipType.ToOne; } else if (annotation.annotationType().equals(ManyToMany.class)) { ManyToMany manyToMany = (ManyToMany)annotation; return RelationshipType.ToMany; } } return null; } /** * Looks for target entity from triplet of field, getter and setter. * @param triplet * @param entity The entity of the property. * @param entities The known entities in the model. * @return The target entity or null if not found. */ protected Entity targetEntity(Triplet<Field, Method, Method> triplet, Entity entity, Set<Entity> entities) { Class<?> target = this.targetClass(triplet); for (Entity e : entities) { if (e.getEntityClass().equals(target)) { return e; } } return null; } /** * Returns target class from triplet of field, getter and setter. * @param triplet * @return The target class. */ protected Class<?> targetClass(Triplet<Field, Method, Method> triplet) { Class<?> target = null; if (triplet.a != null) { target = this.targetClassFromField(triplet.a); if (target == null) { target = this.targetClassFromAnnotations(triplet.a.getAnnotations()); } } else if (triplet.b != null) { target = this.targetClassFromGetter(triplet.b); if (target == null) { target = this.targetClassFromAnnotations(triplet.b.getAnnotations()); } } else if (triplet.c != null) { target = this.targetClassFromSetter(triplet.c); if (target == null) { target = this.targetClassFromAnnotations(triplet.c.getAnnotations()); } } return target; } /** * Returns target class from field. * @param field * @return The target class. */ protected Class<?> targetClassFromField(Field field) { Class<?> target = field.getType(); if (Collection.class.isAssignableFrom(target)) { Type type = field.getGenericType(); if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)type; Type[] arr = pType.getActualTypeArguments(); target = (Class<?>)arr[0]; } } return target; } /** * Returns target class from getter method. * @param getter * @return The target class. */ protected Class<?> targetClassFromGetter(Method getter) { Class<?> target = getter.getReturnType(); if (Collection.class.isAssignableFrom(target)) { Type type = getter.getGenericReturnType(); if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)type; Type[] arr = pType.getActualTypeArguments(); target = (Class<?>)arr[0]; } } return target; } /** * Returns target class from setter method. * @param setter * @return The target class. */ protected Class<?> targetClassFromSetter(Method setter) { Class<?> target = setter.getParameterTypes()[0]; if (Collection.class.isAssignableFrom(target)) { Type type = setter.getGenericParameterTypes()[0]; if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)type; Type[] arr = pType.getActualTypeArguments(); target = (Class<?>)arr[0]; } } return target; } /** * Looks for JPA relationship annotation to find target class. * @param annotations * @return The target class. */ protected Class<?> targetClassFromAnnotations(Annotation[] annotations) { for (Annotation annotation : annotations) { if (annotation.annotationType().equals(OneToOne.class)) { OneToOne oneToOne = (OneToOne)annotation; return oneToOne.targetEntity(); } else if (annotation.annotationType().equals(OneToMany.class)) { OneToMany oneToMany = (OneToMany)annotation; return oneToMany.targetEntity(); } else if (annotation.annotationType().equals(ManyToOne.class)) { ManyToOne manyToOne = (ManyToOne)annotation; return manyToOne.targetEntity(); } else if (annotation.annotationType().equals(ManyToMany.class)) { ManyToMany manyToMany = (ManyToMany)annotation; return manyToMany.targetEntity(); } } return null; } protected Type attributeType(Triplet<Field, Method, Method> triplet, Entity entity) { Type type = null; if (triplet.a != null) { type = this.attributeTypeFromField(triplet.a, entity); if (type != null) { return type; } } else if (triplet.b != null) { type = this.attributeTypeFromGetter(triplet.b, entity); if (type != null) { return type; } } else if (triplet.c != null) { type = this.attributeTypeFromSetter(triplet.c, entity); if (type != null) { return type; } } return type; } protected Type attributeTypeFromField(Field field, Entity entity) { return field.getGenericType(); } protected Type attributeTypeFromGetter(Method getter, Entity entity) { return getter.getGenericReturnType(); } protected Type attributeTypeFromSetter(Method setter, Entity entity) { return setter.getGenericParameterTypes()[0]; } /** * Determines if the triplet of the entity is primary attribute. * * Looks up for the JPA's Id annotation or matches property name "id". * * @param triplet * @param entity The entity. * @return True if triplet is primary. */ protected boolean isPrimary(Triplet<Field, Method, Method> triplet, Entity entity) { Field field = triplet.a; Method getter = triplet.b; Method setter = triplet.c; Annotation annotation = (field != null) ? field.getAnnotation(Id.class) : null; if (annotation == null) { annotation = (getter != null) ? getter.getAnnotation(Id.class) : null; if (annotation == null) { annotation = (setter != null) ? setter.getAnnotation(Id.class) : null; } } if (annotation != null) { return true; } String name = this.propertyName(triplet, entity); return name.equalsIgnoreCase("id"); } /** * Makes first letter of the string lower cased. * @param string * @return The first letter lower cased string. */ protected String toFirstLetterLowerCase(String string) { return string.substring(0, 1).toLowerCase() + string.substring(1); } }