diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java
new file mode 100644
index 000000000..73db9e4a0
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptor.java
@@ -0,0 +1,115 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class AnnotationDescriptor {
+
+ private final Annotation annotation;
+
+ private final Class extends Annotation> annotationType;
+
+ private final ElementType[] elementTypes;
+
+ private final RetentionPolicy policy;
+
+ private final boolean isDocumented;
+
+ private final boolean isInherited;
+
+ public AnnotationDescriptor(A annotation) {
+ this.annotation = annotation;
+ annotationType = annotation.annotationType();
+
+ Target target = annotationType.getAnnotation(Target.class);
+ elementTypes = (target == null) ? ElementType.values() : target.value();
+
+ Retention retention = annotationType.getAnnotation(Retention.class);
+ policy = (retention == null) ? RetentionPolicy.CLASS : retention.value();
+
+ Documented documented = annotationType.getAnnotation(Documented.class);
+ isDocumented = (documented != null);
+
+ Inherited inherited = annotationType.getAnnotation(Inherited.class);
+ isInherited = (inherited != null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public A getAnnotation() {
+ return (A) annotation;
+ }
+
+ public Class extends Annotation> getAnnotationType() {
+ return annotationType;
+ }
+
+ public ElementType[] getElementTypes() {
+ return elementTypes;
+ }
+
+ public RetentionPolicy getPolicy() {
+ return policy;
+ }
+
+ public boolean isDocumented() {
+ return isDocumented;
+ }
+
+ public boolean isInherited() {
+ return isInherited;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("AnnotationDescriptor [annotation=")
+ .append(annotation)
+ .append(", annotationType=")
+ .append(annotationType)
+ .append(", elementTypes=")
+ .append(Arrays.toString(elementTypes))
+ .append(", policy=")
+ .append(policy)
+ .append(", isDocumented=")
+ .append(isDocumented)
+ .append(", isInherited=")
+ .append(isInherited)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(elementTypes);
+ result = prime * result + Objects.hash(annotation, annotationType, isDocumented, isInherited, policy);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AnnotationDescriptor other = (AnnotationDescriptor) obj;
+ return Objects.equals(annotation, other.annotation)
+ && Objects.equals(annotationType, other.annotationType)
+ && Arrays.equals(elementTypes, other.elementTypes)
+ && isDocumented == other.isDocumented
+ && isInherited == other.isInherited
+ && policy == other.policy;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java
new file mode 100644
index 000000000..6c68267f8
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Annotations.java
@@ -0,0 +1,100 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class Annotations {
+
+ protected final AnnotatedElement annotatedElement;
+
+ protected final Map, AnnotationDescriptor> annotationsMap;
+
+ // cache
+ private AnnotationDescriptor[] allAnnotations;
+
+ public Annotations(AnnotatedElement annotatedElement) {
+ this.annotatedElement = annotatedElement;
+ this.annotationsMap = inspectAnnotations();
+ }
+
+ private Map, AnnotationDescriptor> inspectAnnotations() {
+
+ Annotation[] annotations = ReflectionUtil.getAnnotation(annotatedElement);
+ if (ArrayUtil.isEmpty(annotations)) {
+ return null;
+ }
+
+ Map, AnnotationDescriptor> map = new LinkedHashMap<>(annotations.length);
+
+ for (Annotation annotation : annotations) {
+ map.put(annotation.annotationType(), new AnnotationDescriptor(annotation));
+ }
+
+ return map;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ if (annotationsMap == null) {
+ return null;
+ }
+
+ return annotationsMap.get(clazz);
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ if (annotationsMap == null) {
+ return null;
+ }
+
+ if (allAnnotations == null) {
+ AnnotationDescriptor[] allAnnotations = new AnnotationDescriptor[annotationsMap.size()];
+
+ int index = 0;
+ for (AnnotationDescriptor annotationDescriptor : annotationsMap.values()) {
+ allAnnotations[index] = annotationDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allAnnotations, new Comparator() {
+ @Override
+ public int compare(AnnotationDescriptor ad1, AnnotationDescriptor ad2) {
+ return ad1.getClass().getName().compareTo(ad2.getClass().getName());
+ }
+ });
+
+ this.allAnnotations = allAnnotations;
+ }
+
+ return allAnnotations;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Annotations [annotatedElement=").append(annotatedElement).append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(annotatedElement);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Annotations other = (Annotations) obj;
+ return Objects.equals(annotatedElement, other.annotatedElement);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java
new file mode 100644
index 000000000..7c5a70d33
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtil.java
@@ -0,0 +1,93 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.Array;
+
+public class ArrayUtil {
+
+ private static final int INDEX_NOT_FOUND = -1;
+
+ public static boolean isEmpty(Object array) {
+ if(array == null) {
+ return true;
+ }
+
+ // not an array
+ if(!array.getClass().isArray()) {
+ return false;
+ }
+
+ // check array length
+ return Array.getLength(array) == 0;
+ }
+
+ public static boolean isNotEmpty(Object array) {
+ return !isEmpty(array);
+ }
+
+ public static int indexOf(Object[] array, Object objectToFind) {
+ return indexOf(array, objectToFind, 0);
+ }
+
+ public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
+ if (array == null) {
+ return INDEX_NOT_FOUND;
+ }
+
+ if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ if (objectToFind == null) {
+ for (int i = startIndex; i < array.length; i++) {
+ if (array[i] == null) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = startIndex; i < array.length; i++) {
+ if (objectToFind.equals(array[i])) {
+ return i;
+ }
+ }
+ }
+
+ return INDEX_NOT_FOUND;
+ }
+
+ public static T[] addAll(T[] array1, T[] array2) {
+ if (array1 == null) {
+ return (T[]) clone(array2);
+ } else if (array2 == null) {
+ return (T[]) clone(array1);
+ }
+ @SuppressWarnings("unchecked")
+ T[] joinedArray = (T[]) Array.newInstance(array1.getClass().getComponentType(), array1.length + array2.length);
+ System.arraycopy(array1, 0, joinedArray, 0, array1.length);
+ try {
+ System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
+ } catch (ArrayStoreException ase) {
+ // Check if problem was due to incompatible types
+ /*
+ * We do this here, rather than before the copy because: - it would be a wasted check most of the time -
+ * safer, in case check turns out to be too strict
+ */
+ final Class> type1 = array1.getClass().getComponentType();
+ final Class> type2 = array2.getClass().getComponentType();
+ if (!type1.isAssignableFrom(type2)) {
+ throw new IllegalArgumentException("Cannot store " + type2.getName() + " in an array of "
+ + type1.getName());
+ }
+ throw ase; // No, so rethrow original
+ }
+ return joinedArray;
+ }
+
+ public static T[] clone(T[] array) {
+ if (array == null) {
+ return null;
+ }
+
+ return (T[]) array.clone();
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java
new file mode 100644
index 000000000..b76246a02
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/BeanUtil.java
@@ -0,0 +1,107 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.beans.Introspector;
+import java.lang.reflect.Method;
+
+public abstract class BeanUtil {
+
+ public static final String METHOD_GET_PREFIX = "get";
+ public static final String METHOD_IS_PREFIX = "is";
+ public static final String METHOD_SET_PREFIX = "set";
+
+ public static String getBeanGetterName(Method method) {
+ if (method == null) {
+ return null;
+ }
+
+ int prefixLength = getBeanGetterPrefixLength(method);
+ if (prefixLength == 0) {
+ return null;
+ }
+
+ String methodName = method.getName().substring(prefixLength);
+ return Introspector.decapitalize(methodName);
+ }
+
+ private static int getBeanGetterPrefixLength(Method method) {
+ if (isObjectMethod(method)) {
+ return 0;
+ }
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_GET_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return 3;
+ }
+
+ if (methodName.startsWith(METHOD_IS_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return 2;
+ }
+
+ return 0;
+ }
+
+ public static boolean isBeanProperty(Method method) {
+ if (method == null || isObjectMethod(method)) {
+ return false;
+ }
+ String methodName = method.getName();
+ Class> returnType = method.getReturnType();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_GET_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return true;
+ }
+ if (methodName.startsWith(METHOD_IS_PREFIX) && ((returnType != null) && (paramTypes.length == 0))) {
+ return true;
+ }
+ if (methodName.startsWith(METHOD_SET_PREFIX) && paramTypes.length == 1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static boolean isBeanSetter(Method method) {
+ return getBeanSetterPrefixLength(method) != 0;
+ }
+
+ private static int getBeanSetterPrefixLength(Method method) {
+ if (isObjectMethod(method)) {
+ return 0;
+ }
+ String methodName = method.getName();
+ Class>[] paramTypes = method.getParameterTypes();
+ if (methodName.startsWith(METHOD_SET_PREFIX)) {
+ if (paramTypes.length == 1) {
+ return 3;
+ }
+ }
+ return 0;
+ }
+
+ public static String getBeanSetterName(Method method) {
+ if (method == null) {
+ return null;
+ }
+
+ int prefixLength = getBeanSetterPrefixLength(method);
+ if (prefixLength == 0) {
+ return null;
+ }
+
+ String methodName = method.getName().substring(prefixLength);
+ return Introspector.decapitalize(methodName);
+ }
+
+ public static boolean isBeanGetter(Method method) {
+ if (method == null) {
+ return false;
+ }
+
+ return getBeanGetterPrefixLength(method) != 0;
+ }
+
+ private static boolean isObjectMethod(Method method) {
+ return method.getDeclaringClass() == Object.class;
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java
new file mode 100644
index 000000000..252debdf6
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassDescriptor.java
@@ -0,0 +1,273 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public class ClassDescriptor {
+
+ protected final Class> type;
+ protected final boolean scanAccessible;
+ protected final boolean scanStatics;
+ protected final boolean extendedProperties;
+ protected final boolean includeFieldsAsProperties;
+ protected final String propertyFieldPrefix;
+ protected final Class>[] interfaces;
+ protected final Class>[] superclasses;
+ protected int usageCount;
+
+ private final boolean isArray;
+ private final boolean isMap;
+ private final boolean isList;
+ private final boolean isSet;
+ private final boolean isCollection;
+ private final Fields fields;
+ private final Methods methods;
+ private final Properties properties;
+ private final Constructors constructors;
+
+ private final Annotations annotations;
+
+ public ClassDescriptor(Class> type, boolean scanAccessible, boolean extendedProperties,
+ boolean includeFieldsAsProperties, boolean scanStatics, String propertyFieldPrefix) {
+ this.type = type;
+ this.scanAccessible = scanAccessible;
+ this.extendedProperties = extendedProperties;
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ this.scanStatics = scanStatics;
+
+ isArray = type.isArray();
+ isMap = Map.class.isAssignableFrom(type);
+ isList = List.class.isAssignableFrom(type);
+ isSet = Set.class.isAssignableFrom(type);
+ isCollection = Collection.class.isAssignableFrom(type);
+
+ interfaces = ClassUtil.getAllInterfacesAsArray(type);
+ superclasses = ClassUtil.getAllSuperclassesAsArray(type);
+
+ fields = new Fields(this);
+ methods = new Methods(this);
+ properties = new Properties(this);
+ constructors = new Constructors(this);
+
+ annotations = new Annotations(type);
+ }
+
+ public Class> getType() {
+ return type;
+ }
+
+ public boolean isScanAccessible() {
+ return scanAccessible;
+ }
+
+ public boolean isExtendedProperties() {
+ return extendedProperties;
+ }
+
+ public boolean isIncludeFieldsAsProperties() {
+ return includeFieldsAsProperties;
+ }
+
+ public String getPropertyFieldPrefix() {
+ return propertyFieldPrefix;
+ }
+
+ protected void increaseUsageCount() {
+ usageCount++;
+ }
+
+ public int getUsageCount() {
+ return usageCount;
+ }
+
+ public boolean isArray() {
+ return isArray;
+ }
+
+ public boolean isMap() {
+ return isMap;
+ }
+
+ public boolean isList() {
+ return isList;
+ }
+
+ public boolean isSet() {
+ return isSet;
+ }
+
+ public boolean isCollection() {
+ return isCollection;
+ }
+
+ protected Fields getFields() {
+ return fields;
+ }
+
+ public FieldDescriptor getFieldDescriptor(String name, boolean declared) {
+ FieldDescriptor fieldDescriptor = getFields().getFieldDescriptor(name);
+
+ if (fieldDescriptor != null) {
+ if (!fieldDescriptor.matchDeclared(declared)) {
+ return null;
+ }
+ }
+
+ return fieldDescriptor;
+ }
+
+ public FieldDescriptor[] getAllFieldDescriptors() {
+ return getFields().getAllFieldDescriptors();
+ }
+
+ protected Methods getMethods() {
+ return methods;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, boolean declared) {
+ MethodDescriptor methodDescriptor = getMethods().getMethodDescriptor(name);
+
+ if ((methodDescriptor != null) && methodDescriptor.matchDeclared(declared)) {
+ return methodDescriptor;
+ }
+
+ return methodDescriptor;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, Class>[] params, boolean declared) {
+ MethodDescriptor methodDescriptor = getMethods().getMethodDescriptor(name, params);
+
+ if ((methodDescriptor != null) && methodDescriptor.matchDeclared(declared)) {
+ return methodDescriptor;
+ }
+
+ return null;
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors(String name) {
+ return getMethods().getAllMethodDescriptors(name);
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors() {
+ return getMethods().getAllMethodDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // properties
+
+ protected Properties getProperties() {
+ return properties;
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(String name, boolean declared) {
+ PropertyDescriptor propertyDescriptor = getProperties().getPropertyDescriptor(name);
+
+ if ((propertyDescriptor != null) && propertyDescriptor.matchDeclared(declared)) {
+ return propertyDescriptor;
+ }
+
+ return null;
+ }
+
+ public PropertyDescriptor[] getAllPropertyDescriptors() {
+ return getProperties().getAllPropertyDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // constructors
+
+ protected Constructors getConstructors() {
+ return constructors;
+ }
+
+ public ConstructorDescriptor getDefaultCtorDescriptor(boolean declared) {
+ ConstructorDescriptor defaultConstructor = getConstructors().getDefaultCtor();
+
+ if ((defaultConstructor != null) && defaultConstructor.matchDeclared(declared)) {
+ return defaultConstructor;
+ }
+ return null;
+ }
+
+ public ConstructorDescriptor getConstructorDescriptor(Class>[] args, boolean declared) {
+ ConstructorDescriptor constructorDescriptor = getConstructors().getCtorDescriptor(args);
+
+ if ((constructorDescriptor != null) && constructorDescriptor.matchDeclared(declared)) {
+ return constructorDescriptor;
+ }
+ return null;
+ }
+
+ public ConstructorDescriptor[] getAllConstructorDescriptors() {
+ return getConstructors().getAllCtorDescriptors();
+ }
+
+ // ----------------------------------------------------------------
+ // annotations
+
+ protected Annotations getAnnotations() {
+ return annotations;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ return annotations.getAnnotationDescriptor(clazz);
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ return annotations.getAllAnnotationDescriptors();
+ }
+
+ public Class>[] getAllInterfaces() {
+ return interfaces;
+ }
+
+ public Class>[] getAllSuperclasses() {
+ return superclasses;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ClassDescriptor [type=")
+ .append(type)
+ .append(", scanAccessible=")
+ .append(scanAccessible)
+ .append(", extendedProperties=")
+ .append(extendedProperties)
+ .append(", includeFieldsAsProperties=")
+ .append(includeFieldsAsProperties)
+ .append(", propertyFieldPrefix=")
+ .append(propertyFieldPrefix)
+ .append(", usageCount=")
+ .append(usageCount)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ClassDescriptor other = (ClassDescriptor) obj;
+ return Objects.equals(type, other.type);
+ }
+
+
+ public boolean isScanStatics() {
+ return scanStatics;
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java
new file mode 100644
index 000000000..c832b6293
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospector.java
@@ -0,0 +1,107 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ClassIntrospector {
+
+ protected final Map, ClassDescriptor> cache = new LinkedHashMap<>();
+ protected final boolean scanAccessible;
+ protected final boolean enhancedProperties;
+ protected final boolean includeFieldsAsProperties;
+ protected final boolean scanStatics;
+ protected final String propertyFieldPrefix;
+
+ private ClassIntrospector(Builder builder) {
+ this.scanAccessible = builder.scanAccessible;
+ this.enhancedProperties = builder.enhancedProperties;
+ this.includeFieldsAsProperties = builder.includeFieldsAsProperties;
+ this.propertyFieldPrefix = builder.propertyFieldPrefix;
+ this.scanStatics = builder.scanStatics ;
+ }
+
+ public ClassIntrospector() {
+ this(true, true, true, true, null);
+ }
+
+ public ClassIntrospector(boolean scanAccessible,
+ boolean enhancedProperties,
+ boolean includeFieldsAsProperties,
+ boolean scanStatics,
+ String propertyFieldPrefix) {
+ this.scanAccessible = scanAccessible;
+ this.enhancedProperties = enhancedProperties;
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ this.scanStatics = scanStatics;
+ }
+
+ public ClassDescriptor introspect(Class> type) {
+ ClassDescriptor cd = cache.computeIfAbsent(type, this::getClassDescriptor);
+
+ cd.increaseUsageCount();
+
+ return cd;
+ }
+
+ private ClassDescriptor getClassDescriptor(Class> type) {
+ return new ClassDescriptor(type,
+ scanAccessible,
+ enhancedProperties,
+ includeFieldsAsProperties,
+ scanStatics,
+ propertyFieldPrefix);
+ }
+
+ /**
+ * Creates builder to build {@link ClassIntrospector}.
+ * @return created builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder to build {@link ClassIntrospector}.
+ */
+ public static final class Builder {
+
+ public boolean scanStatics = false;
+ private boolean scanAccessible = true;
+ private boolean enhancedProperties = true;
+ private boolean includeFieldsAsProperties = true;
+ private String propertyFieldPrefix = null;
+
+ private Builder() {
+ }
+
+ public Builder withScanAccessible(boolean scanAccessible) {
+ this.scanAccessible = scanAccessible;
+ return this;
+ }
+
+ public Builder withEnhancedProperties(boolean enhancedProperties) {
+ this.enhancedProperties = enhancedProperties;
+ return this;
+ }
+
+ public Builder withIncludeFieldsAsProperties(boolean includeFieldsAsProperties) {
+ this.includeFieldsAsProperties = includeFieldsAsProperties;
+ return this;
+ }
+
+ public Builder withPropertyFieldPrefix(String propertyFieldPrefix) {
+ this.propertyFieldPrefix = propertyFieldPrefix;
+ return this;
+ }
+
+ public Builder withScanStatics(boolean includeStatics) {
+ this.scanStatics = includeStatics;
+ return this;
+ }
+
+ public ClassIntrospector build() {
+ return new ClassIntrospector(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java
new file mode 100644
index 000000000..6ce510694
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ClassUtil.java
@@ -0,0 +1,70 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ClassUtil {
+
+ public static Class>[] getAllInterfacesAsArray(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ List> interfacesFound = new ArrayList<>();
+ getAllInterfaces(clazz, interfacesFound);
+
+ return interfacesFound.toArray(new Class>[0]);
+ }
+
+ private static void getAllInterfaces(Class> clazz, List> interfacesFound) {
+ while (clazz != null) {
+ Class>[] interfaces = clazz.getInterfaces();
+
+ for (int i = 0; i < interfaces.length; i++) {
+ if (!interfacesFound.contains(interfaces[i])) {
+ interfacesFound.add(interfaces[i]);
+ getAllInterfaces(interfaces[i], interfacesFound);
+ }
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ public static List> getAllInterfaces(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ List> interfacesFound = new ArrayList<>();
+ getAllInterfaces(clazz, interfacesFound);
+
+ return interfacesFound;
+ }
+ public static List> getAllSuperclasses(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ List> classes = new ArrayList<>();
+ Class> superclass = clazz.getSuperclass();
+ while (superclass != null && superclass != Object.class) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes;
+ }
+
+ public static Class>[] getAllSuperclassesAsArray(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ List> classes = new ArrayList<>();
+ Class> superclass = clazz.getSuperclass();
+ while (superclass != null && superclass != Object.class) {
+ classes.add(superclass);
+ superclass = superclass.getSuperclass();
+ }
+ return classes.toArray(new Class>[0]);
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java
new file mode 100644
index 000000000..6481396b0
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptor.java
@@ -0,0 +1,77 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class ConstructorDescriptor extends Descriptor {
+
+ protected final Constructor> constructor;
+ protected final Class>[] parameters;
+
+ public ConstructorDescriptor(ClassDescriptor classDescriptor, Constructor> constructor) {
+ super(classDescriptor, ReflectionUtil.isPublic(constructor));
+ this.constructor = constructor;
+ this.parameters = constructor.getParameterTypes();
+
+ annotations = new Annotations(constructor);
+
+ ReflectionUtil.forceAccess(constructor);
+ }
+
+ @Override
+ public String getName() {
+ return constructor.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return constructor.getDeclaringClass();
+ }
+
+ public Constructor> getConstructor() {
+ return constructor;
+ }
+
+ public Class>[] getParameters() {
+ return parameters;
+ }
+
+ public boolean isDefault() {
+ return parameters.length == 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ConstructorDescriptor [constructor=")
+ .append(constructor)
+ .append(", parameters=")
+ .append(Arrays.toString(parameters))
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(parameters);
+ result = prime * result + Objects.hash(constructor);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ConstructorDescriptor other = (ConstructorDescriptor) obj;
+ return Objects.equals(constructor, other.constructor)
+ && Arrays.equals(parameters, other.parameters);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java
new file mode 100644
index 000000000..7bc5f558a
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Constructors.java
@@ -0,0 +1,106 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Constructors {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final ConstructorDescriptor[] allConstructors;
+ protected ConstructorDescriptor defaultConstructor;
+
+ public Constructors(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.allConstructors = inspectConstructors();
+ }
+
+ protected ConstructorDescriptor[] inspectConstructors() {
+ Class> type = classDescriptor.getType();
+ Constructor>[] ctors = type.getDeclaredConstructors();
+
+ ConstructorDescriptor[] allConstructors = new ConstructorDescriptor[ctors.length];
+
+ for (int i = 0; i < ctors.length; i++) {
+ Constructor> ctor = ctors[i];
+
+ ConstructorDescriptor ctorDescriptor = createCtorDescriptor(ctor);
+ allConstructors[i] = ctorDescriptor;
+
+ if (ctorDescriptor.isDefault()) {
+ defaultConstructor = ctorDescriptor;
+ }
+ }
+
+ return allConstructors;
+ }
+
+ protected ConstructorDescriptor createCtorDescriptor(Constructor> constructor) {
+ return new ConstructorDescriptor(classDescriptor, constructor);
+ }
+
+ public ConstructorDescriptor getDefaultCtor() {
+ return defaultConstructor;
+ }
+
+ public ConstructorDescriptor getCtorDescriptor(Class>...args) {
+ ctors: for (ConstructorDescriptor ctorDescriptor : allConstructors) {
+ Class>[] arg = ctorDescriptor.getParameters();
+
+ if (arg.length != args.length) {
+ continue;
+ }
+
+ for (int j = 0; j < arg.length; j++) {
+ if (arg[j] != args[j]) {
+ continue ctors;
+ }
+ }
+
+ return ctorDescriptor;
+ }
+ return null;
+ }
+
+ ConstructorDescriptor[] getAllCtorDescriptors() {
+ return allConstructors;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Constructors [classDescriptor=")
+ .append(classDescriptor)
+ .append(", allConstructors=")
+ .append(Arrays.toString(allConstructors))
+ .append(", defaultConstructor=")
+ .append(defaultConstructor)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(allConstructors);
+ result = prime * result + Objects.hash(classDescriptor, defaultConstructor);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Constructors other = (Constructors) obj;
+ return Arrays.equals(allConstructors, other.allConstructors)
+ && Objects.equals(classDescriptor, other.classDescriptor)
+ && Objects.equals(defaultConstructor, other.defaultConstructor);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java
new file mode 100644
index 000000000..f1a050858
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Descriptor.java
@@ -0,0 +1,77 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.annotation.Annotation;
+import java.util.Objects;
+
+public abstract class Descriptor {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final boolean isPublic;
+
+ protected Annotations annotations;
+
+ protected Descriptor(ClassDescriptor classDescriptor, boolean isPublic) {
+ this.classDescriptor = classDescriptor;
+ this.isPublic = isPublic;
+ }
+
+ public ClassDescriptor getClassDescriptor() {
+ return classDescriptor;
+ }
+
+ public boolean isPublic() {
+ return isPublic;
+ }
+
+ public boolean matchDeclared(boolean declared) {
+ if (!declared) {
+ return isPublic;
+ }
+
+ return true;
+ }
+
+ protected Annotations getAnnotations() {
+ return annotations;
+ }
+
+ public AnnotationDescriptor getAnnotationDescriptor(Class extends Annotation> clazz) {
+ return annotations.getAnnotationDescriptor(clazz);
+ }
+
+ public A getAnnotation(Class clazz) {
+ AnnotationDescriptor annotationDescriptor = annotations.getAnnotationDescriptor(clazz);
+ if (annotationDescriptor == null) {
+ return null;
+ }
+
+ return annotationDescriptor.getAnnotation();
+ }
+
+ public AnnotationDescriptor[] getAllAnnotationDescriptors() {
+ return annotations.getAllAnnotationDescriptors();
+ }
+
+ public abstract String getName();
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(annotations, classDescriptor, isPublic);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Descriptor other = (Descriptor) obj;
+ return Objects.equals(annotations, other.annotations)
+ && Objects.equals(classDescriptor, other.classDescriptor)
+ && isPublic == other.isPublic;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java
new file mode 100644
index 000000000..9a5504d6b
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptor.java
@@ -0,0 +1,126 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Type;
+import java.util.Objects;
+
+public class FieldDescriptor extends Descriptor implements Getter, Setter {
+
+ protected final Field field;
+ protected final Type type;
+ protected final Class> rawType;
+ protected final Class> rawComponentType;
+ protected final Class> rawKeyComponentType;
+
+ public FieldDescriptor(ClassDescriptor classDescriptor, Field field) {
+ super(classDescriptor, ReflectionUtil.isPublic(field));
+ this.field = field;
+ this.type = field.getGenericType();
+ this.rawType = ReflectionUtil.getRawType(type, classDescriptor.getType());
+
+ Class>[] componentTypes = ReflectionUtil.getComponentTypes(type, classDescriptor.getType());
+ if (componentTypes != null) {
+ this.rawComponentType = componentTypes[componentTypes.length - 1];
+ this.rawKeyComponentType = componentTypes[0];
+ } else {
+ this.rawComponentType = null;
+ this.rawKeyComponentType = null;
+ }
+
+ annotations = new Annotations(field);
+
+ ReflectionUtil.forceAccess(field);
+ }
+
+ @Override
+ public String getName() {
+ return field.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return field.getDeclaringClass();
+ }
+
+ public Field getField() {
+ return field;
+ }
+
+ public Class> getRawType() {
+ return rawType;
+ }
+
+ public Class> getRawComponentType() {
+ return rawComponentType;
+ }
+
+ public Class> getRawKeyComponentType() {
+ return rawKeyComponentType;
+ }
+
+ public Class>[] resolveRawComponentTypes() {
+ return ReflectionUtil.getComponentTypes(type, classDescriptor.getType());
+ }
+
+ @Override
+ public Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException {
+ return field.get(target);
+ }
+
+ @Override
+ public Class> getGetterRawType() {
+ return getRawType();
+ }
+
+ @Override
+ public Class> getGetterRawComponentType() {
+ return getRawComponentType();
+ }
+
+ @Override
+ public Class> getGetterRawKeyComponentType() {
+ return getRawKeyComponentType();
+ }
+
+ @Override
+ public void invokeSetter(Object target, Object argument) throws IllegalAccessException {
+ field.set(target, argument);
+ }
+
+ @Override
+ public Class> getSetterRawType() {
+ return getRawType();
+ }
+
+ @Override
+ public Class> getSetterRawComponentType() {
+ return getRawComponentType();
+ }
+
+ @Override
+ public String toString() {
+ return classDescriptor.getType().getSimpleName() + '#' + field.getName();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(field, type);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ FieldDescriptor other = (FieldDescriptor) obj;
+ return Objects.equals(field, other.field) && Objects.equals(type, other.type);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java
new file mode 100644
index 000000000..d022667ed
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Fields.java
@@ -0,0 +1,84 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Fields {
+
+ public static final String SERIAL_VERSION_UID = "serialVersionUID";
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map fieldsMap;
+
+ // cache
+ private FieldDescriptor[] allFields;
+
+ public Fields(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.fieldsMap = inspectFields();
+ }
+
+ protected Map inspectFields() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ boolean scanStatics = classDescriptor.isScanStatics();
+ Class> type = classDescriptor.getType();
+
+ Field[] fields =
+ scanAccessible ? ReflectionUtil.getAccessibleFields(type) : ReflectionUtil.getAllFieldsOfClass(type);
+
+ Map map = new LinkedHashMap<>(fields.length);
+
+ for (Field field : fields) {
+ String fieldName = field.getName();
+
+ if (fieldName.equals(SERIAL_VERSION_UID)) {
+ continue;
+ }
+
+ if (!scanStatics && Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ map.put(fieldName, createFieldDescriptor(field));
+ }
+
+ return map;
+ }
+
+ protected FieldDescriptor createFieldDescriptor(Field field) {
+ return new FieldDescriptor(classDescriptor, field);
+ }
+
+ public FieldDescriptor getFieldDescriptor(String name) {
+ return fieldsMap.get(name);
+ }
+
+ public FieldDescriptor[] getAllFieldDescriptors() {
+ if (allFields == null) {
+ FieldDescriptor[] allFields = new FieldDescriptor[fieldsMap.size()];
+
+ int index = 0;
+ for (FieldDescriptor fieldDescriptor : fieldsMap.values()) {
+ allFields[index] = fieldDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allFields, new Comparator() {
+ @Override
+ public int compare(FieldDescriptor fd1, FieldDescriptor fd2) {
+ return fd1.getField().getName().compareTo(fd2.getField().getName());
+ }
+ });
+
+ this.allFields = allFields;
+ }
+
+ return allFields;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java
new file mode 100644
index 000000000..37da6e6f5
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Getter.java
@@ -0,0 +1,13 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+public interface Getter {
+ Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException;
+
+ Class> getGetterRawType();
+
+ Class> getGetterRawComponentType();
+
+ Class> getGetterRawKeyComponentType();
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java
new file mode 100644
index 000000000..64a5dc515
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptor.java
@@ -0,0 +1,158 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class MethodDescriptor extends Descriptor implements Getter, Setter {
+
+ protected final Method method;
+ protected final Type returnType;
+ protected final Class> rawReturnType;
+ protected final Class> rawReturnComponentType;
+ protected final Class> rawReturnKeyComponentType;
+ protected final Class>[] rawParameterTypes;
+ protected final Class>[] rawParameterComponentTypes;
+
+ public MethodDescriptor(ClassDescriptor classDescriptor, Method method) {
+ super(classDescriptor, ReflectionUtil.isPublic(method));
+ this.method = method;
+ this.returnType = method.getGenericReturnType();
+ this.rawReturnType = ReflectionUtil.getRawType(returnType, classDescriptor.getType());
+
+ Class>[] componentTypes = ReflectionUtil.getComponentTypes(returnType, classDescriptor.getType());
+ if (componentTypes != null) {
+ this.rawReturnComponentType = componentTypes[componentTypes.length - 1];
+ this.rawReturnKeyComponentType = componentTypes[0];
+ } else {
+ this.rawReturnComponentType = null;
+ this.rawReturnKeyComponentType = null;
+ }
+
+ annotations = new Annotations(method);
+
+ ReflectionUtil.forceAccess(method);
+
+ Type[] params = method.getGenericParameterTypes();
+ Type[] genericParams = method.getGenericParameterTypes();
+
+ rawParameterTypes = new Class[params.length];
+ rawParameterComponentTypes = genericParams.length == 0 ? null : new Class[params.length];
+
+ for (int i = 0; i < params.length; i++) {
+ Type type = params[i];
+ rawParameterTypes[i] = ReflectionUtil.getRawType(type, classDescriptor.getType());
+ if (rawParameterComponentTypes != null) {
+ rawParameterComponentTypes[i] =
+ ReflectionUtil.getComponentType(genericParams[i], classDescriptor.getType());
+ }
+ }
+ }
+
+ @Override
+ public String getName() {
+ return method.getName();
+ }
+
+ public Class> getDeclaringClass() {
+ return method.getDeclaringClass();
+ }
+
+ public Method getMethod() {
+ return method;
+ }
+
+ public Class> getRawReturnType() {
+ return rawReturnType;
+ }
+
+ public Class> getRawReturnComponentType() {
+ return rawReturnComponentType;
+ }
+
+ public Class> getRawReturnKeyComponentType() {
+ return rawReturnKeyComponentType;
+ }
+
+ public Class>[] resolveRawReturnComponentTypes() {
+ return ReflectionUtil.getComponentTypes(returnType, classDescriptor.getType());
+ }
+
+ public Class>[] getRawParameterTypes() {
+ return rawParameterTypes;
+ }
+
+ public Class>[] getRawParameterComponentTypes() {
+ return rawParameterComponentTypes;
+ }
+
+ @Override
+ public Object invokeGetter(Object target) throws InvocationTargetException, IllegalAccessException {
+ return method.invoke(target);
+ }
+
+ @Override
+ public Class> getGetterRawType() {
+ return getRawReturnType();
+ }
+
+ @Override
+ public Class> getGetterRawComponentType() {
+ return getRawReturnComponentType();
+ }
+
+ @Override
+ public Class> getGetterRawKeyComponentType() {
+ return getRawReturnKeyComponentType();
+ }
+
+ @Override
+ public void invokeSetter(Object target, Object argument) throws IllegalAccessException, InvocationTargetException {
+ method.invoke(target, argument);
+ }
+
+ @Override
+ public Class> getSetterRawType() {
+ return getRawParameterTypes()[0];
+ }
+
+ @Override
+ public Class> getSetterRawComponentType() {
+ Class>[] ts = getRawParameterComponentTypes();
+ if (ts == null) {
+ return null;
+ }
+ return ts[0];
+ }
+
+ @Override
+ public String toString() {
+ return classDescriptor.getType().getSimpleName() + '#' + method.getName() + "()";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(rawParameterTypes);
+ result = prime * result + Objects.hash(method, returnType);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MethodDescriptor other = (MethodDescriptor) obj;
+ return Objects.equals(method, other.method) && Arrays.equals(rawParameterTypes,
+ other.rawParameterTypes) && Objects.equals(returnType,
+ other.returnType);
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java
new file mode 100644
index 000000000..820a560e9
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Methods.java
@@ -0,0 +1,116 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Methods {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map methodsMap;
+
+ // cached
+ private MethodDescriptor[] allMethods;
+
+ public Methods(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.methodsMap = inspectMethods();
+ }
+
+ protected Map inspectMethods() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ boolean scanStatics = classDescriptor.isScanStatics();
+ Class> type = classDescriptor.getType();
+
+ Method[] methods =
+ scanAccessible ? ReflectionUtil.getAccessibleMethods(type) : ReflectionUtil.getAllMethodsOfClass(type);
+
+ Map map = new LinkedHashMap<>(methods.length);
+
+ for (Method method : methods) {
+
+ if(!scanStatics && Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+
+ String methodName = method.getName();
+
+ MethodDescriptor[] mds = map.get(methodName);
+
+ if (mds == null) {
+ mds = new MethodDescriptor[1];
+ } else {
+ mds = Arrays.copyOf(mds, mds.length + 1);
+ }
+
+ map.put(methodName, mds);
+
+ mds[mds.length - 1] = createMethodDescriptor(method);
+ }
+
+ return map;
+ }
+
+ protected MethodDescriptor createMethodDescriptor(Method method) {
+ return new MethodDescriptor(classDescriptor, method);
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name, Class>[] paramTypes) {
+ MethodDescriptor[] methodDescriptors = methodsMap.get(name);
+ if (methodDescriptors == null) {
+ return null;
+ }
+ for (int i = 0; i < methodDescriptors.length; i++) {
+ Method method = methodDescriptors[i].getMethod();
+ if (ObjectUtil.isEquals(method.getParameterTypes(), paramTypes)) {
+ return methodDescriptors[i];
+ }
+ }
+ return null;
+ }
+
+ public MethodDescriptor getMethodDescriptor(String name) {
+ MethodDescriptor[] methodDescriptors = methodsMap.get(name);
+ if (methodDescriptors == null) {
+ return null;
+ }
+ if (methodDescriptors.length != 1) {
+ throw new IllegalArgumentException("Method name not unique: " + name);
+ }
+ return methodDescriptors[0];
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors(String name) {
+ return methodsMap.get(name);
+ }
+
+ public MethodDescriptor[] getAllMethodDescriptors() {
+ if (allMethods == null) {
+ List allMethodsList = new ArrayList<>();
+
+ for (MethodDescriptor[] methodDescriptors : methodsMap.values()) {
+ Collections.addAll(allMethodsList, methodDescriptors);
+ }
+
+ MethodDescriptor[] allMethods = allMethodsList.toArray(new MethodDescriptor[allMethodsList.size()]);
+
+ Arrays.sort(allMethods, new Comparator() {
+ @Override
+ public int compare(MethodDescriptor md1, MethodDescriptor md2) {
+ return md1.getMethod().getName().compareTo(md2.getMethod().getName());
+ }
+ });
+
+ this.allMethods = allMethods;
+ }
+ return allMethods;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java
new file mode 100644
index 000000000..615109381
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtil.java
@@ -0,0 +1,60 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public class ObjectUtil {
+
+ public static boolean isAnyNull(Object... objects) {
+ if(objects == null) {
+ return true;
+ }
+
+ return Stream.of(objects).anyMatch(Objects::isNull);
+ }
+
+ public static boolean isEquals(Object object1, Object object2) {
+ if (object1 == object2) {
+ return true;
+ }
+
+ if (object1 == null || object2 == null) {
+ return false;
+ }
+
+ if (!object1.getClass().equals(object2.getClass())) {
+ return false;
+ }
+
+ if (object1 instanceof Object[]) {
+ return Arrays.deepEquals((Object[]) object1, (Object[]) object2);
+ }
+ if (object1 instanceof int[]) {
+ return Arrays.equals((int[]) object1, (int[]) object2);
+ }
+ if (object1 instanceof long[]) {
+ return Arrays.equals((long[]) object1, (long[]) object2);
+ }
+ if (object1 instanceof short[]) {
+ return Arrays.equals((short[]) object1, (short[]) object2);
+ }
+ if (object1 instanceof byte[]) {
+ return Arrays.equals((byte[]) object1, (byte[]) object2);
+ }
+ if (object1 instanceof double[]) {
+ return Arrays.equals((double[]) object1, (double[]) object2);
+ }
+ if (object1 instanceof float[]) {
+ return Arrays.equals((float[]) object1, (float[]) object2);
+ }
+ if (object1 instanceof char[]) {
+ return Arrays.equals((char[]) object1, (char[]) object2);
+ }
+ if (object1 instanceof boolean[]) {
+ return Arrays.equals((boolean[]) object1, (boolean[]) object2);
+ }
+ return object1.equals(object2);
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java
new file mode 100644
index 000000000..9a827f810
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Properties.java
@@ -0,0 +1,175 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class Properties {
+
+ protected final ClassDescriptor classDescriptor;
+ protected final Map propertyDescriptors;
+
+ // cache
+ private PropertyDescriptor[] allProperties;
+
+ public Properties(ClassDescriptor classDescriptor) {
+ this.classDescriptor = classDescriptor;
+ this.propertyDescriptors = inspectProperties();
+ }
+
+ protected Map inspectProperties() {
+ boolean scanAccessible = classDescriptor.isScanAccessible();
+ Class> type = classDescriptor.getType();
+
+ Map map = new LinkedHashMap<>();
+
+ Method[] methods =
+ scanAccessible ? ReflectionUtil.getAccessibleMethods(type) : ReflectionUtil.getAllMethodsOfClass(type);
+
+ for (int iteration = 0; iteration < 2; iteration++) {
+ // first find the getters, and then the setters!
+ for (Method method : methods) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ continue; // ignore static methods
+ }
+
+ boolean add = false;
+ boolean issetter = false;
+
+ String propertyName;
+
+ if (iteration == 0) {
+ propertyName = BeanUtil.getBeanGetterName(method);
+ if (propertyName != null) {
+ add = true;
+ issetter = false;
+ }
+ } else {
+ propertyName = BeanUtil.getBeanSetterName(method);
+ if (propertyName != null) {
+ add = true;
+ issetter = true;
+ }
+ }
+
+ if (add == true) {
+ MethodDescriptor methodDescriptor =
+ classDescriptor.getMethodDescriptor(method.getName(), method.getParameterTypes(), true);
+ addProperty(map, propertyName, methodDescriptor, issetter);
+ }
+ }
+ }
+
+ if (classDescriptor.isIncludeFieldsAsProperties()) {
+ FieldDescriptor[] fieldDescriptors = classDescriptor.getAllFieldDescriptors();
+ String prefix = classDescriptor.getPropertyFieldPrefix();
+
+ for (FieldDescriptor fieldDescriptor : fieldDescriptors) {
+ String name = fieldDescriptor.getField().getName();
+
+ if (prefix != null) {
+ if (!name.startsWith(prefix)) {
+ continue;
+ }
+ name = name.substring(prefix.length());
+ }
+
+ if (!map.containsKey(name)) {
+ // add missing field as a potential property
+ map.put(name, createPropertyDescriptor(name, fieldDescriptor));
+ }
+ }
+
+ }
+
+ return map;
+ }
+
+ protected void addProperty(Map map, String name, MethodDescriptor methodDescriptor,
+ boolean isSetter) {
+ MethodDescriptor setterMethod = isSetter ? methodDescriptor : null;
+ MethodDescriptor getterMethod = isSetter ? null : methodDescriptor;
+
+ PropertyDescriptor existing = map.get(name);
+
+ if (existing == null) {
+ // new property, just add it
+ PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod);
+
+ map.put(name, propertyDescriptor);
+ return;
+ }
+
+ if (!isSetter) {
+ // use existing setter
+ setterMethod = existing.getWriteMethodDescriptor();
+ // check existing
+ MethodDescriptor existingMethodDescriptor = existing.getReadMethodDescriptor();
+ if (existingMethodDescriptor != null) {
+ // check for special case of double get/is
+
+ // getter with the same name already exist
+ String methodName = methodDescriptor.getMethod().getName();
+ String existingMethodName = existingMethodDescriptor.getMethod().getName();
+
+ if (existingMethodName.startsWith(BeanUtil.METHOD_IS_PREFIX)
+ && methodName.startsWith(BeanUtil.METHOD_GET_PREFIX)) {
+ return;
+ }
+ }
+ } else {
+ // setter
+ // use existing getter
+ getterMethod = existing.getReadMethodDescriptor();
+
+ if (getterMethod.getMethod().getReturnType() != setterMethod.getMethod().getParameterTypes()[0]) {
+ return;
+ }
+ }
+
+ PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod);
+
+ map.put(name, propertyDescriptor);
+ }
+
+ protected PropertyDescriptor createPropertyDescriptor(String name, MethodDescriptor getterMethod,
+ MethodDescriptor setterMethod) {
+ return new PropertyDescriptor(classDescriptor, name, getterMethod, setterMethod);
+ }
+
+ protected PropertyDescriptor createPropertyDescriptor(String name, FieldDescriptor fieldDescriptor) {
+ return new PropertyDescriptor(classDescriptor, name, fieldDescriptor);
+ }
+
+ public PropertyDescriptor getPropertyDescriptor(String name) {
+ return propertyDescriptors.get(name);
+ }
+
+ public PropertyDescriptor[] getAllPropertyDescriptors() {
+ if (allProperties == null) {
+ PropertyDescriptor[] allProperties = new PropertyDescriptor[propertyDescriptors.size()];
+
+ int index = 0;
+ for (PropertyDescriptor propertyDescriptor : propertyDescriptors.values()) {
+ allProperties[index] = propertyDescriptor;
+ index++;
+ }
+
+ Arrays.sort(allProperties, new Comparator() {
+ @Override
+ public int compare(PropertyDescriptor pd1, PropertyDescriptor pd2) {
+ return pd1.getName().compareTo(pd2.getName());
+ }
+ });
+
+ this.allProperties = allProperties;
+ }
+
+ return allProperties;
+ }
+
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java
new file mode 100644
index 000000000..c8c380ec8
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptor.java
@@ -0,0 +1,237 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class PropertyDescriptor extends Descriptor {
+
+ protected final String name;
+ protected final MethodDescriptor readMethodDescriptor;
+ protected final MethodDescriptor writeMethodDescriptor;
+ protected final FieldDescriptor fieldDescriptor;
+
+ protected Class> type;
+ protected Getter[] getters;
+ protected Setter[] setters;
+
+ public PropertyDescriptor(ClassDescriptor classDescriptor, String propertyName, FieldDescriptor fieldDescriptor) {
+ super(classDescriptor, false);
+ this.name = propertyName;
+ this.readMethodDescriptor = null;
+ this.writeMethodDescriptor = null;
+ this.fieldDescriptor = fieldDescriptor;
+ this.annotations = new Annotations(fieldDescriptor.getField());
+ }
+
+ public PropertyDescriptor(ClassDescriptor classDescriptor, String propertyName, MethodDescriptor readMethod,
+ MethodDescriptor writeMethod) {
+ super(classDescriptor, ((readMethod == null) || readMethod.isPublic())
+ & (writeMethod == null || writeMethod.isPublic()));
+ this.name = propertyName;
+ this.readMethodDescriptor = readMethod;
+ this.writeMethodDescriptor = writeMethod;
+
+ if (classDescriptor.isExtendedProperties()) {
+ this.fieldDescriptor = findField(propertyName);
+ if(fieldDescriptor != null) {
+ this.annotations = new Annotations(fieldDescriptor.getField());
+ }
+ } else {
+ this.fieldDescriptor = null;
+ if(readMethod != null) {
+ this.annotations = new Annotations(readMethod.getMethod());
+ }
+ else if(writeMethod != null) {
+ this.annotations = new Annotations(writeMethod.getMethod());
+ }
+ }
+
+
+ }
+
+ protected FieldDescriptor findField(String fieldName) {
+ String prefix = classDescriptor.getPropertyFieldPrefix();
+
+ if (prefix != null) {
+ fieldName = prefix + fieldName;
+ }
+
+ return classDescriptor.getFieldDescriptor(fieldName, true);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public MethodDescriptor getReadMethodDescriptor() {
+ return readMethodDescriptor;
+ }
+
+ public MethodDescriptor getWriteMethodDescriptor() {
+ return writeMethodDescriptor;
+ }
+
+ public FieldDescriptor getFieldDescriptor() {
+ return fieldDescriptor;
+ }
+
+ public boolean isFieldOnlyDescriptor() {
+ return (readMethodDescriptor == null) && (writeMethodDescriptor == null);
+ }
+
+ public Class> getType() {
+ if (type == null) {
+ if (readMethodDescriptor != null) {
+ type = readMethodDescriptor.getMethod().getReturnType();
+ } else if (writeMethodDescriptor != null) {
+ type = writeMethodDescriptor.getMethod().getParameterTypes()[0];
+ } else if (fieldDescriptor != null) {
+ type = fieldDescriptor.getField().getType();
+ }
+ }
+
+ return type;
+ }
+
+ public Getter getGetter(boolean declared) {
+ if (getters == null) {
+ getters = new Getter[] { createGetter(false), createGetter(true), };
+ }
+
+ return getters[declared ? 1 : 0];
+ }
+
+ protected Getter createGetter(boolean declared) {
+ if (readMethodDescriptor != null) {
+ if (readMethodDescriptor.matchDeclared(declared)) {
+ return readMethodDescriptor;
+ }
+ }
+ if (fieldDescriptor != null) {
+ if (fieldDescriptor.matchDeclared(declared)) {
+ return fieldDescriptor;
+ }
+ }
+
+ return null;
+ }
+
+ public Setter getSetter(boolean declared) {
+ if (setters == null) {
+ setters = new Setter[] { createSetter(false), createSetter(true), };
+ }
+
+ return setters[declared ? 1 : 0];
+ }
+
+ protected Setter createSetter(boolean declared) {
+ if (writeMethodDescriptor != null) {
+ if (writeMethodDescriptor.matchDeclared(declared)) {
+ return writeMethodDescriptor;
+ }
+ }
+ if (fieldDescriptor != null) {
+ if (fieldDescriptor.matchDeclared(declared)) {
+ return fieldDescriptor;
+ }
+ }
+
+ return null;
+ }
+
+ public Class> resolveKeyType(boolean declared) {
+ Class> keyType = null;
+
+ Getter getter = getGetter(declared);
+
+ if (getter != null) {
+ keyType = getter.getGetterRawKeyComponentType();
+ }
+
+ if (keyType == null) {
+ FieldDescriptor fieldDescriptor = getFieldDescriptor();
+
+ if (fieldDescriptor != null) {
+ keyType = fieldDescriptor.getRawKeyComponentType();
+ }
+ }
+
+ return keyType;
+ }
+
+ public Class> resolveComponentType(boolean declared) {
+ Class> componentType = null;
+
+ Getter getter = getGetter(declared);
+
+ if (getter != null) {
+ componentType = getter.getGetterRawComponentType();
+ }
+
+ if (componentType == null) {
+ FieldDescriptor fieldDescriptor = getFieldDescriptor();
+
+ if (fieldDescriptor != null) {
+ componentType = fieldDescriptor.getRawComponentType();
+ }
+ }
+
+ return componentType;
+ }
+
+ // add
+ public Field getField() {
+ Class> clazz = this.getClassDescriptor().getType();
+
+ return ReflectionUtil.getField(clazz, this.getName());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PropertyDescriptor [name=")
+ .append(name)
+ .append(", readMethodDescriptor=")
+ .append(readMethodDescriptor)
+ .append(", writeMethodDescriptor=")
+ .append(writeMethodDescriptor)
+ .append(", fieldDescriptor=")
+ .append(fieldDescriptor)
+ .append(", type=")
+ .append(type)
+ .append(", getters=")
+ .append(Arrays.toString(getters))
+ .append(", setters=")
+ .append(Arrays.toString(setters))
+ .append(", classDescriptor=")
+ .append(classDescriptor)
+ .append(", isPublic=")
+ .append(isPublic)
+ .append(", annotations=")
+ .append(annotations)
+ .append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(name, type);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PropertyDescriptor other = (PropertyDescriptor) obj;
+ return Objects.equals(name, other.name) && Objects.equals(type, other.type);
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java
new file mode 100644
index 000000000..70aa1f361
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtil.java
@@ -0,0 +1,635 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+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.List;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class ReflectionUtil {
+
+ private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);
+
+ public static Field[] getAllFieldsOfClass(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ return getAllFieldsOfClass0(clazz);
+ }
+
+ public static Method[] getAccessibleMethods(Class> clazz) {
+ return getAccessibleMethods(clazz, Object.class);
+ }
+
+ public static Method[] getAllMethodsOfClass(final Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+ Method[] methods = null;
+ Class> itr = clazz;
+ while (itr != null && !itr.equals(Object.class)) {
+ methods = ArrayUtil.addAll(itr.getDeclaredMethods(), methods);
+ itr = itr.getSuperclass();
+ }
+ return methods;
+ }
+
+ public static Method[] getAccessibleMethods(Class> clazz, Class> limit) {
+ Package topPackage = clazz.getPackage();
+ List methodList = new ArrayList<>();
+ int topPackageHash = (topPackage == null) ? 0 : topPackage.hashCode();
+ boolean top = true;
+ do {
+ if (clazz == null) {
+ break;
+ }
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ if (Modifier.isVolatile(method.getModifiers())) {
+ continue;
+ }
+ if (top) {
+ methodList.add(method);
+ continue;
+ }
+ int modifier = method.getModifiers();
+ if (Modifier.isPrivate(modifier) || Modifier.isAbstract(modifier)) {
+ continue;
+ }
+
+ if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
+ addMethodIfNotExist(methodList, method);
+ continue;
+ }
+ // add super default methods from the same package
+ Package pckg = method.getDeclaringClass().getPackage();
+ int pckgHash = (pckg == null) ? 0 : pckg.hashCode();
+ if (pckgHash == topPackageHash) {
+ addMethodIfNotExist(methodList, method);
+ }
+ }
+ top = false;
+ } while ((clazz = clazz.getSuperclass()) != limit);
+
+ Method[] methods = new Method[methodList.size()];
+ for (int i = 0; i < methods.length; i++) {
+ methods[i] = methodList.get(i);
+ }
+ return methods;
+ }
+
+ private static void addMethodIfNotExist(List allMethods, Method newMethod) {
+ for (Method method : allMethods) {
+ if (ObjectUtil.isEquals(method, newMethod)) {
+ return;
+ }
+ }
+
+ allMethods.add(newMethod);
+ }
+
+ public static Field getField(Class> clazz, String fieldName) {
+ if (ObjectUtil.isAnyNull(clazz, fieldName)) {
+ return null;
+ }
+
+ return getField0(clazz, fieldName);
+ }
+
+ static Field getField0(Class> clazz, String fieldName) {
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ Field[] fields = itr.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getName().equals(fieldName)) {
+ return field;
+ }
+ }
+
+ itr = itr.getSuperclass();
+ }
+
+ return null;
+ }
+ public static Class> getComponentType(Type type, Class> implClass) {
+ Class>[] componentTypes = getComponentTypes(type, implClass);
+ if (componentTypes == null) {
+ return null;
+ }
+ return componentTypes[componentTypes.length - 1];
+ }
+
+ public static Class> getComponentType(Type type) {
+ return getComponentType(type, null);
+ }
+
+ static Field[] getAllFieldsOfClass0(Class> clazz) {
+ Field[] fields = null;
+
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ fields = ArrayUtil.addAll(itr.getDeclaredFields(), fields);
+ itr = itr.getSuperclass();
+ }
+
+ return fields;
+ }
+
+ public static boolean hasSuperClass(Class> clazz) {
+ return (clazz != null) && !clazz.equals(Object.class);
+ }
+
+ public static Annotation[] getAnnotation(AnnotatedElement annotatedElement) {
+ if (Objects.isNull(annotatedElement)) {
+ return null;
+ }
+
+ return annotatedElement.getAnnotations();
+ }
+
+ public static boolean isPublic(Member m) {
+ return m != null && Modifier.isPublic(m.getModifiers());
+ }
+
+ public static boolean isAccessible(Member m) {
+ return m != null && Modifier.isPublic(m.getModifiers());
+ }
+
+ public static void forceAccess(AccessibleObject object) {
+ if (object == null || object.isAccessible()) {
+ return;
+ }
+ try {
+ object.setAccessible(true);
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Class> getRawType(Type type) {
+ return getRawType(type, null);
+ }
+
+ public static Class> getRawType(Type type, Class> implClass) {
+ if (type == null) {
+ return null;
+ }
+
+ GenericType gt = GenericType.find(type);
+ if (gt != null) {
+ return gt.toRawType(type, implClass);
+ }
+
+ return null;
+
+ }
+
+ public static Class>[] getComponentTypes(Type type, Class> implClass) {
+ if (type == null) {
+ return null;
+ }
+
+ GenericType gt = GenericType.find(type);
+ if (gt != null) {
+ return gt.getComponentTypes(type, implClass);
+ }
+
+ return null;
+
+ }
+
+ public static Field[] getAccessibleFields(Class> clazz) {
+ return getAccessibleFields(clazz, Object.class);
+ }
+
+ public static Field[] getAccessibleFields(Class> clazz, Class> limit) {
+ if (clazz == null) {
+ return null;
+ }
+
+ Package topPackage = clazz.getPackage();
+ List fieldList = new ArrayList<>();
+ int topPackageHash = (topPackage == null) ? 0 : topPackage.hashCode();
+ boolean top = true;
+ do {
+ if (clazz == null) {
+ break;
+ }
+ Field[] declaredFields = clazz.getDeclaredFields();
+ for (Field field : declaredFields) {
+ if (top == true) { // add all top declared fields
+ fieldList.add(field);
+ continue;
+ }
+ int modifier = field.getModifiers();
+ if (Modifier.isPrivate(modifier)) {
+ continue;
+ }
+ if (Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
+ addFieldIfNotExist(fieldList, field);
+ continue;
+ }
+
+ // add super default methods from the same package
+ Package pckg = field.getDeclaringClass().getPackage();
+ int pckgHash = (pckg == null) ? 0 : pckg.hashCode();
+ if (pckgHash == topPackageHash) {
+ addFieldIfNotExist(fieldList, field);
+ }
+ }
+ top = false;
+ } while ((clazz = clazz.getSuperclass()) != limit);
+
+ Field[] fields = new Field[fieldList.size()];
+ for (int i = 0; i < fields.length; i++) {
+ fields[i] = fieldList.get(i);
+ }
+
+ return fields;
+ }
+
+ private static void addFieldIfNotExist(List allFields, Field newField) {
+ for (Field field : allFields) {
+ if (ObjectUtil.isEquals(field, newField)) {
+ return;
+ }
+ }
+
+ allFields.add(newField);
+ }
+
+
+ enum GenericType {
+
+ CLASS_TYPE {
+
+ @Override
+ Class> type() {
+ return Class.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ return (Class>) type;
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ Class> clazz = (Class>) type;
+ if (clazz.isArray()) {
+ return new Class[] { clazz.getComponentType() };
+ }
+ return null;
+ }
+ },
+ PARAMETERIZED_TYPE {
+
+ @Override
+ Class> type() {
+ return ParameterizedType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ ParameterizedType pType = (ParameterizedType) type;
+ return getRawType(pType.getRawType(), implClass);
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ ParameterizedType pt = (ParameterizedType) type;
+
+ Type[] generics = pt.getActualTypeArguments();
+
+ if (generics.length == 0) {
+ return null;
+ }
+
+ Class>[] types = new Class[generics.length];
+
+ for (int i = 0; i < generics.length; i++) {
+ types[i] = getRawType(generics[i], implClass);
+ }
+ return types;
+ }
+ },
+ WILDCARD_TYPE {
+
+ @Override
+ Class> type() {
+ return WildcardType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ WildcardType wType = (WildcardType) type;
+
+ Type[] lowerTypes = wType.getLowerBounds();
+ if (lowerTypes.length > 0) {
+ return getRawType(lowerTypes[0], implClass);
+ }
+
+ Type[] upperTypes = wType.getUpperBounds();
+ if (upperTypes.length != 0) {
+ return getRawType(upperTypes[0], implClass);
+ }
+
+ return Object.class;
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ return null;
+ }
+ },
+ GENERIC_ARRAY_TYPE {
+
+ @Override
+ Class> type() {
+ return GenericArrayType.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ Type genericComponentType = ((GenericArrayType) type).getGenericComponentType();
+ Class> rawType = getRawType(genericComponentType, implClass);
+ // FIXME
+ return Array.newInstance(rawType, 0).getClass();
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ GenericArrayType gat = (GenericArrayType) type;
+
+ Class> rawType = getRawType(gat.getGenericComponentType(), implClass);
+ if (rawType == null) {
+ return null;
+ }
+
+ return new Class[] { rawType };
+ }
+ },
+ TYPE_VARIABLE {
+
+ @Override
+ Class> type() {
+ return TypeVariable.class;
+ }
+
+ @Override
+ Class> toRawType(Type type, Class> implClass) {
+ TypeVariable> varType = (TypeVariable>) type;
+ if (implClass != null) {
+ Type resolvedType = resolveVariable(varType, implClass);
+ if (resolvedType != null) {
+ return getRawType(resolvedType, null);
+ }
+ }
+ Type[] boundsTypes = varType.getBounds();
+ if (boundsTypes.length == 0) {
+ return Object.class;
+ }
+ return getRawType(boundsTypes[0], implClass);
+ }
+
+ @Override
+ Class>[] getComponentTypes(Type type, Class> implClass) {
+ return null;
+ }
+ };
+
+ abstract Class> toRawType(Type type, Class> implClass);
+
+ abstract Class> type();
+
+ abstract Class>[] getComponentTypes(Type type, Class> implClass);
+
+ static GenericType find(Type type) {
+ for (GenericType gt : GenericType.values()) {
+ if (gt.type().isInstance(type)) {
+ return gt;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ public static Type resolveVariable(TypeVariable> variable, final Class> implClass) {
+ final Class> rawType = getRawType(implClass, null);
+
+ int index = ArrayUtil.indexOf(rawType.getTypeParameters(), variable);
+ if (index >= 0) {
+ return variable;
+ }
+
+ final Class>[] interfaces = rawType.getInterfaces();
+ final Type[] genericInterfaces = rawType.getGenericInterfaces();
+
+ for (int i = 0; i <= interfaces.length; i++) {
+ Class> rawInterface;
+
+ if (i < interfaces.length) {
+ rawInterface = interfaces[i];
+ } else {
+ rawInterface = rawType.getSuperclass();
+ if (rawInterface == null) {
+ continue;
+ }
+ }
+
+ final Type resolved = resolveVariable(variable, rawInterface);
+ if (resolved instanceof Class || resolved instanceof ParameterizedType) {
+ return resolved;
+ }
+
+ if (resolved instanceof TypeVariable) {
+ final TypeVariable> typeVariable = (TypeVariable>) resolved;
+ index = ArrayUtil.indexOf(rawInterface.getTypeParameters(), typeVariable);
+
+ if (index < 0) {
+ throw new IllegalArgumentException("Invalid type variable:" + typeVariable);
+ }
+
+ final Type type = i < genericInterfaces.length ? genericInterfaces[i] : rawType.getGenericSuperclass();
+
+ if (type instanceof Class) {
+ return Object.class;
+ }
+
+ if (type instanceof ParameterizedType) {
+ return ((ParameterizedType) type).getActualTypeArguments()[index];
+ }
+
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ }
+ return null;
+ }
+
+ public static Method getMethod(Class> clazz, String methodName, Class>...parameterTypes) {
+ if (clazz == null || methodName == null) {
+ return null;
+ }
+
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ Method[] methods = itr.getDeclaredMethods();
+
+ for (Method method : methods) {
+ if (method.getName().equals(methodName) && Arrays.equals(method.getParameterTypes(), parameterTypes)) {
+ return method;
+ }
+ }
+
+ itr = itr.getSuperclass();
+ }
+
+ return null;
+
+ }
+
+ public static Field[] getAllInstanceFields(Class> clazz) {
+ if (clazz == null) {
+ return null;
+ }
+
+ return getAllInstanceFields0(clazz);
+ }
+
+ static Field[] getAllInstanceFields0(Class> clazz) {
+ List fields = new ArrayList<>();
+ for (Class> itr = clazz; hasSuperClass(itr);) {
+ for (Field field : itr.getDeclaredFields()) {
+ if (!Modifier.isStatic(field.getModifiers())) {
+ fields.add(field);
+ }
+ }
+ itr = itr.getSuperclass();
+ }
+
+ return fields.toArray(new Field[fields.size()]);
+ }
+
+ public static List getAnnotationMethods(Class clazz, Class annotationType) {
+ if (clazz == null || annotationType == null) {
+ return null;
+ }
+ List list = new ArrayList<>();
+
+ for (Method method : getAllMethodsOfClass(clazz)) {
+ A type = method.getAnnotation(annotationType);
+ if (type != null) {
+ list.add(method);
+ }
+ }
+
+ return list;
+ }
+
+ public static Field[] getAnnotationFields(Class> clazz, Class extends Annotation> annotationClass) {
+ if (clazz == null || annotationClass == null) {
+ return null;
+ }
+
+ Field[] fields = getAllFieldsOfClass0(clazz);
+ if (ArrayUtil.isEmpty(fields)) {
+ return null;
+ }
+
+ List list = new ArrayList<>();
+ for (Field field : fields) {
+ if (null != field.getAnnotation(annotationClass)) {
+ list.add(field);
+ field.setAccessible(true);
+ }
+ }
+
+ return list.toArray(new Field[0]);
+ }
+
+ public static Class>[] getGenericSuperTypes(Class> type) {
+ if (type == null) {
+ return null;
+ }
+
+ return getComponentTypes(type.getGenericSuperclass());
+ }
+
+ public static Class>[] getComponentTypes(Type type) {
+ return getComponentTypes(type, null);
+ }
+
+ public static T invokeMethod(Method method, Object target, Object...args) {
+ if (method == null) {
+ return null;
+ }
+
+ method.setAccessible(true);
+ try {
+ @SuppressWarnings("unchecked")
+ T result = (T) method.invoke(target, args);
+
+ return result;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ }
+
+ public static Object invokeMethod(Object object, String methodName, Class>[] parameterTypes, Object...args) {
+ if (object == null || methodName == null) {
+ return null;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = new Class[0];
+ }
+ if (args == null) {
+ args = new Object[0];
+ }
+ Method method;
+ try {
+ method = object.getClass().getDeclaredMethod(methodName, parameterTypes);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ if (method == null) {
+ return null;
+ }
+
+ return invokeMethod(method, object, args);
+
+ }
+
+ public static Object invokeMethod(Object object, String methodName, Object...args) {
+ if (object == null || methodName == null) {
+ return null;
+ }
+ if (args == null) {
+ args = new Object[0];
+ }
+
+ int arguments = args.length;
+ Class>[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+
+ return invokeMethod(object, methodName, parameterTypes, args);
+ }
+}
\ No newline at end of file
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java
new file mode 100644
index 000000000..2b14ba947
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/introspection/Setter.java
@@ -0,0 +1,12 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import java.lang.reflect.InvocationTargetException;
+
+public interface Setter {
+
+ void invokeSetter(Object target, Object argument) throws IllegalAccessException, InvocationTargetException;
+
+ Class> getSetterRawType();
+
+ Class> getSetterRawComponentType();
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/EntityIntrospector.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/EntityIntrospector.java
new file mode 100644
index 000000000..6229997b4
--- /dev/null
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/EntityIntrospector.java
@@ -0,0 +1,426 @@
+package com.introproventures.graphql.jpa.query.schema.impl;
+
+import static java.util.Locale.ENGLISH;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import javax.persistence.metamodel.Attribute;
+import javax.persistence.metamodel.ManagedType;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
+import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
+import com.introproventures.graphql.jpa.query.introspection.ClassDescriptor;
+import com.introproventures.graphql.jpa.query.introspection.ClassIntrospector;
+import com.introproventures.graphql.jpa.query.introspection.FieldDescriptor;
+import com.introproventures.graphql.jpa.query.introspection.MethodDescriptor;
+import com.introproventures.graphql.jpa.query.introspection.PropertyDescriptor;
+
+public class EntityIntrospector {
+ private static final Logger LOGGER = LoggerFactory.getLogger(EntityIntrospector.class);
+
+ private static final Map, EntityIntrospectionResult> map = new LinkedHashMap<>();
+
+ private static ClassIntrospector introspector = ClassIntrospector.builder()
+ .withScanAccessible(false)
+ .withEnhancedProperties(true)
+ .withIncludeFieldsAsProperties(false)
+ .withScanStatics(false)
+ .build();
+ /**
+ * Get existing EntityIntrospectionResult for Java type
+ *
+ * @param entity Java type of the entity
+ * @return EntityIntrospectionResult result
+ * @throws NoSuchElementException if not found
+ */
+ public static EntityIntrospectionResult resultOf(Class> entity) {
+ return Optional.ofNullable(map.get(entity))
+ .orElseThrow(() -> new NoSuchElementException(entity.getName()));
+ }
+
+ /**
+ * Introspect entity type represented by ManagedType instance
+ *
+ * @param entityType ManagedType representing persistent entity
+ * @return EntityIntrospectionResult result
+ */
+ public static EntityIntrospectionResult introspect(ManagedType> entityType) {
+ return map.computeIfAbsent(entityType.getJavaType(),
+ cls -> new EntityIntrospectionResult(entityType));
+ }
+
+ public static class EntityIntrospectionResult {
+
+ private final Map descriptors;
+ private final Class> entity;
+ private final ClassDescriptor classDescriptor;
+ private final ManagedType> managedType;
+ private final Map> attributes;
+
+ public EntityIntrospectionResult(ManagedType> managedType) {
+ this.managedType = managedType;
+
+ this.attributes = managedType.getAttributes()
+ .stream()
+ .collect(Collectors.toMap(Attribute::getName,
+ Function.identity()));
+
+ this.entity = managedType.getJavaType();
+
+ this.classDescriptor = introspector.introspect(entity);
+
+ this.descriptors = Stream.of(classDescriptor.getAllPropertyDescriptors())
+ .filter(it -> !"class".equals(it.getName()))
+ .map(AttributePropertyDescriptor::new)
+ .collect(Collectors.toMap(AttributePropertyDescriptor::getName,
+ Function.identity()));
+ }
+
+ public Collection getTransientPropertyDescriptors() {
+ return descriptors.values()
+ .stream()
+ .filter(AttributePropertyDescriptor::isTransient)
+ .collect(Collectors.toList());
+ }
+
+ public Collection getPersistentPropertyDescriptors() {
+ return descriptors.values()
+ .stream()
+ .filter(AttributePropertyDescriptor::isPersistent)
+ .collect(Collectors.toList());
+ }
+
+ public Collection getIgnoredPropertyDescriptors() {
+ return descriptors.values()
+ .stream()
+ .filter(AttributePropertyDescriptor::isIgnored)
+ .collect(Collectors.toList());
+ }
+
+ public Map> getAttributes() {
+ return attributes;
+ }
+
+ /**
+ * Test if entity property is annotated with GraphQLIgnore
+ *
+ * @param entity a Java entity class to introspect
+ * @param propertyName the name of the property
+ * @return true if property has GraphQLIgnore annotation
+ * @throws NoSuchElementException if property does not exists
+ */
+ public Boolean isIgnored(String propertyName) {
+ return getPropertyDescriptor(propertyName).map(AttributePropertyDescriptor::isIgnored)
+ .orElseThrow(() -> noSuchElementException(entity, propertyName));
+ }
+
+ /**
+ * Test if entity property is not ignored
+ *
+ * @param entity a Java entity class to introspect
+ * @param propertyName the name of the property
+ * @return true if property has no GraphQLIgnore annotation
+ * @throws NoSuchElementException if property does not exists
+ */
+ public Boolean isNotIgnored(String propertyName) {
+ return getPropertyDescriptor(propertyName).map(AttributePropertyDescriptor::isNotIgnored)
+ .orElseThrow(() -> noSuchElementException(entity, propertyName));
+ }
+
+ public Collection getPropertyDescriptors() {
+ return descriptors.values();
+ }
+
+ public Optional getPropertyDescriptor(String fieldName) {
+ return Optional.ofNullable(descriptors.get(fieldName));
+ }
+
+ public Optional getPropertyDescriptor(Attribute,?> attribute) {
+ return getPropertyDescriptor(attribute.getName());
+ }
+
+ public boolean hasPropertyDescriptor(String fieldName) {
+ return descriptors.containsKey(fieldName);
+ }
+
+ /**
+ * Test if Java bean property is transient according to JPA specification
+ *
+ * @param entity a Java entity class to introspect
+ * @param propertyName the name of the property
+ * @return true if property has Transient annotation or transient field modifier
+ * @throws NoSuchElementException if property does not exists
+ */
+ public Boolean isTransient(String propertyName) {
+ return getPropertyDescriptor(propertyName).map(AttributePropertyDescriptor::isTransient)
+ .orElseThrow(() -> noSuchElementException(entity, propertyName));
+ }
+
+ /**
+ * Test if Java bean property is persistent according to JPA specification
+ *
+ * @param entity a Java entity class to introspect
+ * @param propertyName the name of the property
+ * @return true if property is persitent
+ * @throws NoSuchElementException if property does not exists
+ */
+ public Boolean isPersistent(String propertyName) {
+ return !isTransient(propertyName);
+ }
+
+ public Class> getEntity() {
+ return entity;
+ }
+
+ public ManagedType> getManagedType() {
+ return managedType;
+ }
+
+ public ClassDescriptor getClassDescriptor() {
+ return classDescriptor;
+ }
+
+ public Optional getSchemaDescription() {
+ return getClasses().filter(cls -> !Object.class.equals(cls))
+ .map(cls -> Optional.ofNullable(cls.getAnnotation(GraphQLDescription.class))
+ .map(GraphQLDescription::value))
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .findFirst();
+ }
+
+ public boolean hasSchemaDescription() {
+ return getSchemaDescription().isPresent();
+ }
+
+ public Optional getSchemaDescription(String propertyName) {
+ return getPropertyDescriptor(propertyName).flatMap(AttributePropertyDescriptor::getSchemaDescription);
+ }
+
+ public Stream> getClasses() {
+ return iterate(entity, k -> Optional.ofNullable(k.getSuperclass()));
+ }
+
+ public class AttributePropertyDescriptor {
+
+ private final PropertyDescriptor delegate;
+ private final Optional> attribute;
+ private final Optional field;
+ private final Optional readMethod;
+
+ public AttributePropertyDescriptor(PropertyDescriptor delegate) {
+ this.delegate = delegate;
+
+ String name = delegate.getName();
+
+ this.readMethod = Optional.ofNullable(delegate.getReadMethodDescriptor())
+ .map(MethodDescriptor::getMethod)
+ .filter(m -> !Modifier.isPrivate(m.getModifiers()));
+
+ this.attribute = Optional.ofNullable(attributes.getOrDefault(name,
+ attributes.get(capitalize(name))));
+ this.field = attribute.map(Attribute::getJavaMember)
+ .filter(Field.class::isInstance)
+ .map(Field.class::cast)
+ .map(Optional::of)
+ .orElseGet(() -> Optional.ofNullable(delegate.getFieldDescriptor())
+ .map(FieldDescriptor::getField));
+ }
+
+ public ManagedType> getManagedType() {
+ return managedType;
+ }
+
+ public PropertyDescriptor getDelegate() {
+ return delegate;
+ }
+
+ public Class> getPropertyType() {
+ return delegate.getType();
+ }
+
+ public String getName() {
+ return attribute.map(Attribute::getName)
+ .orElseGet(() -> delegate.getName());
+ }
+
+ public Optional getReadMethod() {
+ return readMethod;
+ }
+
+ public Optional getAnnotation(Class annotationClass) {
+ return getReadMethod().map(m -> m.getAnnotation(annotationClass))
+ .map(Optional::of).orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass)));
+ }
+
+ public Optional> getAttribute() {
+ return attribute;
+ }
+
+ public Optional getField() {
+ return field;
+ }
+
+ public Optional getSchemaDescription() {
+ return getAnnotation(GraphQLDescription.class).map(GraphQLDescription::value);
+ }
+
+ public boolean hasSchemaDescription() {
+ return getSchemaDescription().isPresent();
+ }
+
+ public boolean isTransient() {
+ return !attribute.isPresent();
+ }
+
+ public boolean isPersistent() {
+ return attribute.isPresent();
+ }
+
+ public boolean isIgnored() {
+ return isAnnotationPresent(GraphQLIgnore.class);
+ }
+
+ public boolean isNotIgnored() {
+ return !isIgnored();
+ }
+
+ public boolean hasReadMethod() {
+ return getReadMethod().isPresent();
+ }
+
+ public boolean isAnnotationPresent(Class extends Annotation> annotation) {
+ return getAnnotation(annotation).isPresent();
+ }
+
+ @Override
+ public String toString() {
+ return "AttributePropertyDescriptor [delegate=" + delegate + "]";
+ }
+
+ private EntityIntrospectionResult getEnclosingInstance() {
+ return EntityIntrospectionResult.this;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getEnclosingInstance().hashCode();
+ result = prime * result + Objects.hash(delegate);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AttributePropertyDescriptor other = (AttributePropertyDescriptor) obj;
+ if (!getEnclosingInstance().equals(other.getEnclosingInstance()))
+ return false;
+ return Objects.equals(delegate, other.delegate);
+ }
+ }
+
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(classDescriptor, entity);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ EntityIntrospectionResult other = (EntityIntrospectionResult) obj;
+ return Objects.equals(classDescriptor, other.classDescriptor) && Objects.equals(entity, other.entity);
+ }
+
+ @Override
+ public String toString() {
+ return "EntityIntrospectionResult [beanInfo=" + classDescriptor + "]";
+ }
+ }
+
+ /**
+ * The following method is borrowed from Streams.iterate,
+ * however Streams.iterate is designed to create infinite streams.
+ *
+ * This version has been modified to end when Optional.empty()
+ * is returned from the fetchNextFunction.
+ */
+ public static Stream iterate(T seed, Function> fetchNextFunction) {
+ Objects.requireNonNull(fetchNextFunction);
+
+ Iterator iterator = new Iterator() {
+ private Optional t = Optional.ofNullable(seed);
+
+ @Override
+ public boolean hasNext() {
+ return t.isPresent();
+ }
+
+ @Override
+ public T next() {
+ T v = t.get();
+
+ t = fetchNextFunction.apply(v);
+
+ return v;
+ }
+ };
+
+ return StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE),
+ false
+ );
+ }
+
+ private static NoSuchElementException noSuchElementException(Class> containerClass,
+ String propertyName) {
+ return new NoSuchElementException(String.format(Locale.ROOT,
+ "Could not locate field name [%s] on class [%s]",
+ propertyName,
+ containerClass.getName()));
+
+ }
+
+ /**
+ * Returns a String which capitalizes the first letter of the string.
+ */
+ private static String capitalize(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+ return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
index 9f103766b..44733d32d 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java
@@ -47,7 +47,7 @@
import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
import com.introproventures.graphql.jpa.query.schema.JavaScalars;
import com.introproventures.graphql.jpa.query.schema.NamingStrategy;
-import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor;
+import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult.AttributePropertyDescriptor;
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;
import graphql.Assert;
@@ -651,26 +651,26 @@ private GraphQLObjectType computeObjectType(EntityType> entityType) {
.name(entityType.getName())
.description(getSchemaDescription(entityType))
.fields(getEntityAttributesFields(entityType))
- .fields(getTransientFields(entityType.getJavaType()))
+ .fields(getTransientFields(entityType))
.build();
}
private List getEntityAttributesFields(EntityType> entityType) {
- return entityType
- .getAttributes().stream()
- .filter(this::isNotIgnored)
- .filter(attribute -> !IntrospectionUtils.isIgnored(entityType.getJavaType(), attribute.getName()))
- .map(it -> getObjectField(it, entityType))
- .collect(Collectors.toList());
- }
-
- private List getTransientFields(Class> clazz) {
- return IntrospectionUtils.introspect(clazz)
- .getPropertyDescriptors().stream()
- .filter(AttributePropertyDescriptor::isTransient)
- .filter(AttributePropertyDescriptor::isNotIgnored)
- .map(this::getJavaFieldDefinition)
- .collect(Collectors.toList());
+ return entityType.getAttributes()
+ .stream()
+ .filter(attribute -> EntityIntrospector.introspect(entityType)
+ .isNotIgnored(attribute.getName()))
+ .map(it -> getObjectField(it, entityType))
+ .collect(Collectors.toList());
+ }
+
+ private List getTransientFields(ManagedType> managedType) {
+ return EntityIntrospector.introspect(managedType)
+ .getTransientPropertyDescriptors()
+ .stream()
+ .filter(AttributePropertyDescriptor::isNotIgnored)
+ .map(this::getJavaFieldDefinition)
+ .collect(Collectors.toList());
}
@SuppressWarnings( { "rawtypes" } )
@@ -678,8 +678,7 @@ private GraphQLFieldDefinition getJavaFieldDefinition(AttributePropertyDescripto
GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName());
- String description = propertyDescriptor.getSchemaDescription()
- .orElse(null);
+ String description = propertyDescriptor.getSchemaDescription().orElse(null);
return GraphQLFieldDefinition.newFieldDefinition()
.name(propertyDescriptor.getName())
@@ -876,21 +875,19 @@ protected final boolean isValidAssociation(Attribute,?> attribute) {
private String getSchemaDescription(Attribute,?> attribute) {
- return IntrospectionUtils.introspect(attribute.getDeclaringType()
- .getJavaType())
- .getPropertyDescriptor(attribute)
- .flatMap(AttributePropertyDescriptor::getSchemaDescription)
+ return EntityIntrospector.introspect(attribute.getDeclaringType())
+ .getSchemaDescription(attribute.getName())
.orElse(null);
}
private String getSchemaDescription(EntityType> entityType) {
- return IntrospectionUtils.introspect(entityType.getJavaType())
+ return EntityIntrospector.introspect(entityType)
.getSchemaDescription()
.orElse(null);
}
private String getSchemaDescription(EmbeddableType> embeddableType) {
- return IntrospectionUtils.introspect(embeddableType.getJavaType())
+ return EntityIntrospector.introspect(embeddableType)
.getSchemaDescription()
.orElse(null);
}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java
deleted file mode 100644
index 42b96af83..000000000
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java
+++ /dev/null
@@ -1,341 +0,0 @@
-package com.introproventures.graphql.jpa.query.schema.impl;
-
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import javax.persistence.Transient;
-import javax.persistence.metamodel.Attribute;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
-import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
-import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor;
-
-public class IntrospectionUtils {
- private static final Logger log = LoggerFactory.getLogger(IntrospectionUtils.class);
-
- private static final Map, EntityIntrospectionResult> map = new LinkedHashMap<>();
-
- public static EntityIntrospectionResult introspect(Class> entity) {
- return map.computeIfAbsent(entity, EntityIntrospectionResult::new);
- }
-
- /**
- * Test if Java bean property is transient according to JPA specification
- *
- * @param entity a Java entity class to introspect
- * @param propertyName the name of the property
- * @return true if property has Transient annotation or transient field modifier
- * @throws RuntimeException if property does not exists
- */
- public static boolean isTransient(Class> entity, String propertyName) {
- return introspect(entity).getPropertyDescriptor(propertyName)
- .map(AttributePropertyDescriptor::isTransient)
- .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName)));
- }
-
- /**
- * Test if Java bean property is persistent according to JPA specification
- *
- * @param entity a Java entity class to introspect
- * @param propertyName the name of the property
- * @return true if property is persitent
- * @throws RuntimeException if property does not exists
- */
- public static boolean isPesistent(Class> entity, String propertyName) {
- return !isTransient(entity, propertyName);
- }
-
- /**
- * Test if entity property is annotated with GraphQLIgnore
- *
- * @param entity a Java entity class to introspect
- * @param propertyName the name of the property
- * @return true if property has GraphQLIgnore annotation
- * @throws RuntimeException if property does not exists
- */
- public static boolean isIgnored(Class> entity, String propertyName) {
- return introspect(entity).getPropertyDescriptor(propertyName)
- .map(AttributePropertyDescriptor::isIgnored)
- .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName)));
- }
-
- /**
- * Test if entity property is not ignored
- *
- * @param entity a Java entity class to introspect
- * @param propertyName the name of the property
- * @return true if property has no GraphQLIgnore annotation
- * @throws RuntimeException if property does not exists
- */
- public static boolean isNotIgnored(Class> entity, String propertyName) {
- return !isIgnored(entity, propertyName);
- }
-
- public static class EntityIntrospectionResult {
-
- private final Map descriptors;
- private final Class> entity;
- private final BeanInfo beanInfo;
- private final Map fields;
-
- @SuppressWarnings("rawtypes")
- public EntityIntrospectionResult(Class> entity) {
- try {
- this.beanInfo = Introspector.getBeanInfo(entity);
- } catch (IntrospectionException cause) {
- throw new RuntimeException(cause);
- }
-
- this.entity = entity;
- this.descriptors = Stream.of(beanInfo.getPropertyDescriptors())
- .map(AttributePropertyDescriptor::new)
- .collect(Collectors.toMap(AttributePropertyDescriptor::getName, it -> it));
-
- this.fields = getClasses().flatMap(k -> Arrays.stream(k.getDeclaredFields()))
- .filter(f -> descriptors.containsKey(f.getName()))
- .collect(Collectors.toMap(Field::getName, it -> it));
- }
-
- public Collection getPropertyDescriptors() {
- return descriptors.values();
- }
-
- public Collection getFields() {
- return fields.values();
- }
-
- public Optional getPropertyDescriptor(String fieldName) {
- return Optional.ofNullable(descriptors.getOrDefault(fieldName, null));
- }
-
- public Optional getPropertyDescriptor(Attribute,?> attribute) {
- return getPropertyDescriptor(attribute.getName());
- }
-
- public boolean hasPropertyDescriptor(String fieldName) {
- return descriptors.containsKey(fieldName);
- }
-
- public Optional getField(String fieldName) {
- return Optional.ofNullable(fields.get(fieldName));
- }
-
- public Class> getEntity() {
- return entity;
- }
-
- public BeanInfo getBeanInfo() {
- return beanInfo;
- }
-
- public Optional getSchemaDescription() {
- return getClasses().map(cls -> Optional.ofNullable(cls.getAnnotation(GraphQLDescription.class))
- .map(GraphQLDescription::value))
- .filter(Optional::isPresent)
- .map(Optional::get)
- .findFirst();
- }
-
- public boolean hasSchemaDescription() {
- return getSchemaDescription().isPresent();
- }
-
- public Stream> getClasses() {
- return iterate(entity, k -> Optional.ofNullable(k.getSuperclass()));
- }
-
- public class AttributePropertyDescriptor {
-
- private final PropertyDescriptor delegate;
-
- public AttributePropertyDescriptor(PropertyDescriptor delegate) {
- this.delegate = delegate;
- }
-
- public PropertyDescriptor getDelegate() {
- return delegate;
- }
-
- public Class> getPropertyType() {
- return delegate.getPropertyType();
- }
-
- public String getName() {
- return delegate.getName();
- }
-
- public Optional getField() {
- return Optional.ofNullable(fields.get(getName()));
- }
-
- public boolean hasField() {
- return getField().isPresent();
- }
-
- public Optional getReadMethod() {
- return Optional.ofNullable(delegate.getReadMethod());
- }
-
- public Optional getAnnotation(Class annotationClass) {
- return getReadMethod().map(m -> m.getAnnotation(annotationClass))
- .map(Optional::of) // Java 8 support
- .orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass)));
- }
-
- public Optional getSchemaDescription() {
- return getAnnotation(GraphQLDescription.class).map(GraphQLDescription::value);
- }
-
- public boolean hasSchemaDescription() {
- return getSchemaDescription().isPresent();
- }
-
- public boolean isTransient() {
- return isAnnotationPresent(Transient.class)
- || hasFieldModifier(Modifier::isTransient);
- }
-
- public boolean isIgnored() {
- return isAnnotationPresent(GraphQLIgnore.class);
- }
-
- public boolean isNotIgnored() {
- return !isIgnored();
- }
-
- public boolean hasReadMethod() {
- return getReadMethod().isPresent();
- }
-
- public boolean hasFieldModifier(Function test) {
- return getField().map(it -> test.apply(it.getModifiers()))
- .orElse(false);
- }
-
- public boolean isAnnotationPresent(Class extends Annotation> annotation) {
- return isAnnotationPresentOnField(annotation) || isAnnotationPresentOnReadMethod(annotation);
- }
-
- private boolean isAnnotationPresentOnField(Class extends Annotation> annotation) {
- return getField().map(f -> f.isAnnotationPresent(annotation))
- .orElse(false);
- }
-
- private boolean isAnnotationPresentOnReadMethod(Class extends Annotation> annotation) {
- return getReadMethod().map(m -> m.isAnnotationPresent(annotation))
- .orElse(false);
- }
-
- @Override
- public String toString() {
- return "EntityPropertyDescriptor [delegate=" + delegate + "]";
- }
-
- private EntityIntrospectionResult getEnclosingInstance() {
- return EntityIntrospectionResult.this;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + getEnclosingInstance().hashCode();
- result = prime * result + Objects.hash(delegate);
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- AttributePropertyDescriptor other = (AttributePropertyDescriptor) obj;
- if (!getEnclosingInstance().equals(other.getEnclosingInstance()))
- return false;
- return Objects.equals(delegate, other.delegate);
- }
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(beanInfo, entity);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- EntityIntrospectionResult other = (EntityIntrospectionResult) obj;
- return Objects.equals(beanInfo, other.beanInfo) && Objects.equals(entity, other.entity);
- }
-
- @Override
- public String toString() {
- return "EntityIntrospectionResult [beanInfo=" + beanInfo + "]";
- }
-
- }
-
- /**
- * The following method is borrowed from Streams.iterate,
- * however Streams.iterate is designed to create infinite streams.
- *
- * This version has been modified to end when Optional.empty()
- * is returned from the fetchNextFunction.
- */
- public static Stream iterate(T seed, Function> fetchNextFunction) {
- Objects.requireNonNull(fetchNextFunction);
-
- Iterator iterator = new Iterator() {
- private Optional t = Optional.ofNullable(seed);
-
- @Override
- public boolean hasNext() {
- return t.isPresent();
- }
-
- @Override
- public T next() {
- T v = t.get();
-
- t = fetchNextFunction.apply(v);
-
- return v;
- }
- };
-
- return StreamSupport.stream(
- Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE),
- false
- );
- }
-}
diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java
index 44d476878..05790b16f 100644
--- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java
+++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java
@@ -22,6 +22,7 @@
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
@@ -158,7 +159,7 @@ protected TypedQuery> getQuery(DataFetchingEnvironment environment, Field fiel
return entityManager.createQuery(query.distinct(isDistinct));
}
-
+
protected final List getFieldPredicates(Field field, CriteriaQuery> query, CriteriaBuilder cb, Root> root, From,?> from, DataFetchingEnvironment environment) {
List arguments = new ArrayList<>();
@@ -168,9 +169,9 @@ protected final List getFieldPredicates(Field field, CriteriaQuery>
field.getSelectionSet().getSelections().forEach(selection -> {
if (selection instanceof Field) {
Field selectedField = (Field) selection;
-
+
// "__typename" is part of the graphql introspection spec and has to be ignored by jpa
- if(!TYPENAME.equals(selectedField.getName()) && !IntrospectionUtils.isTransient(from.getJavaType(), selectedField.getName())) {
+ if(isPersistent(environment, selectedField.getName())) {
Path> fieldPath = from.get(selectedField.getName());
From,?> fetch = null;
@@ -975,32 +976,35 @@ else if (value instanceof EnumValue) {
return ((FloatValue) value).getValue();
} else if (value instanceof ObjectValue) {
Class javaType = getJavaType(environment, argument);
+ Map values = environment.getArgument(argument.getName());
try {
- Object beanValue = javaType.getConstructor()
- .newInstance();
-
- Map objectValue = environment.getArgument(argument.getName());
-
- objectValue.entrySet()
- .stream()
- .forEach(e -> {
- setPropertyValue(beanValue,
- e.getKey(),
- e.getValue());
- });
-
- return beanValue;
-
- } catch (Exception e1) {
- // TODO
+ return getJavaBeanValue(javaType, values);
+ } catch (Exception cause) {
+ throw new RuntimeException(cause);
}
-
}
return value;
}
+ private Object getJavaBeanValue(Class> javaType, Map values) throws Exception {
+ Constructor> constructor = javaType.getConstructor();
+ constructor.setAccessible(true);
+
+ Object javaBean = constructor.newInstance();
+
+ values.entrySet()
+ .stream()
+ .forEach(entry -> {
+ setPropertyValue(javaBean,
+ entry.getKey(),
+ entry.getValue());
+ });
+
+ return javaBean;
+ }
+
private void setPropertyValue(Object javaBean, String propertyName, Object propertyValue) {
try {
BeanInfo bi = Introspector.getBeanInfo(javaBean.getClass());
@@ -1016,7 +1020,7 @@ private void setPropertyValue(Object javaBean, String propertyName, Object prope
}
}
} catch (Exception ignored) {
- // TODO
+ // ignore
}
}
@@ -1161,7 +1165,7 @@ && isManagedType(entityType.getAttribute(it.getName()))
Subgraph> sg = entityGraph.addSubgraph(it.getName());
buildSubgraph(it, sg);
} else {
- if(!TYPENAME.equals(it.getName()) && !IntrospectionUtils.isTransient(entityType.getJavaType(), it.getName()))
+ if(isPersistent(entityType, it.getName()))
entityGraph.addAttributeNodes(it.getName());
}
});
@@ -1260,6 +1264,34 @@ protected final Optional getSelectionField(Field field, String fieldName)
.filter(it -> fieldName.equals(it.getName()))
.findFirst();
}
+
+ protected boolean isPersistent(DataFetchingEnvironment environment,
+ String attributeName) {
+ GraphQLObjectType objectType = getObjectType(environment);
+ EntityType> entityType = getEntityType(objectType);
+
+ return isPersistent(entityType, attributeName);
+ }
+
+ protected boolean isPersistent(EntityType> entityType,
+ String attributeName) {
+ try {
+ return entityType.getAttribute(attributeName) != null;
+ } catch (Exception ignored) { }
+
+ return false;
+ }
+
+ protected boolean isTransient(DataFetchingEnvironment environment,
+ String attributeName) {
+ return !isPersistent(environment, attributeName);
+ }
+
+ protected boolean isTransient(EntityType> entityType,
+ String attributeName) {
+ return !isPersistent(entityType, attributeName);
+ }
+
@SuppressWarnings("rawtypes")
class NullValue implements Value {
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptorTest.java
new file mode 100644
index 000000000..182707b07
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationDescriptorTest.java
@@ -0,0 +1,39 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import javax.persistence.Entity;
+
+import org.junit.Test;
+
+
+public class AnnotationDescriptorTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(AnnotationSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ AnnotationDescriptor subject = classDescriptor.getAnnotationDescriptor(Entity.class);
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+
+ }
+
+ @Entity
+ static class AnnotationSampeBean {
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationsTest.java
new file mode 100644
index 000000000..7a3c75086
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/AnnotationsTest.java
@@ -0,0 +1,45 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+public class AnnotationsTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(AnnotationsSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ Annotations subject = classDescriptor.getAnnotations();
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class AnnotationsSampeBean {
+
+ private String foo;
+ private String bar;
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtilTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtilTest.java
new file mode 100644
index 000000000..1ccf91f88
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ArrayUtilTest.java
@@ -0,0 +1,66 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+
+public class ArrayUtilTest {
+
+ @Test
+ public void testIsEmpty() {
+ assertThat(ArrayUtil.isEmpty(null)).isTrue();
+
+ assertThat(ArrayUtil.isEmpty(new String[0])).isTrue();;
+ assertThat(ArrayUtil.isEmpty(new String[10])).isFalse();
+
+ assertThat(ArrayUtil.isEmpty(new int[0])).isTrue();;
+ assertThat(ArrayUtil.isEmpty(new int[10])).isFalse();
+
+ assertThat(ArrayUtil.isEmpty(new Object())).isFalse(); // not an array
+ }
+
+ @Test
+ public void testIsNotEmpty() {
+ assertThat(ArrayUtil.isNotEmpty(null)).isFalse();
+
+ assertThat(ArrayUtil.isNotEmpty(new String[0])).isFalse();;
+ assertThat(ArrayUtil.isNotEmpty(new String[10])).isTrue();
+
+ assertThat(ArrayUtil.isNotEmpty(new int[0])).isFalse();;
+ assertThat(ArrayUtil.isNotEmpty(new int[10])).isTrue();
+
+ assertThat(ArrayUtil.isNotEmpty(new Object())).isTrue(); // not an array
+ }
+
+ @Test
+ public void testIndexOfObject() {
+ assertThat(ArrayUtil.indexOf(null, "a")).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", null, "c" }, (String) null)).isEqualTo(1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "b", "c" }, (String) null)).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[0], "a")).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "a")).isEqualTo(0);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "b")).isEqualTo(2);
+
+ assertThat(ArrayUtil.indexOf(null, "a", 0)).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", null, "c" }, (String) null, 0)).isEqualTo(1);
+ assertThat(ArrayUtil.indexOf(new String[0], "a", 0)).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "b", 0)).isEqualTo(2);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "b", 3)).isEqualTo(5);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "b", 9)).isEqualTo(-1);
+ assertThat(ArrayUtil.indexOf(new String[] { "a", "a", "b", "a", "a", "b", "a", "a" }, "b", -1)).isEqualTo(2);
+ }
+
+ @Test
+ public void testAddAll() {
+ assertThat(ArrayUtil.addAll(null, null)).isNull();
+ assertThat(ArrayUtil.addAll(null, new String[] {})).isEqualTo(new String[] {});
+ assertThat(ArrayUtil.addAll(new String[] {}, null)).isEqualTo(new String[] {});
+ assertThat(ArrayUtil.addAll(new String[] {}, new String[] {})).isEqualTo(new String[] {});
+ assertThat(ArrayUtil.addAll(null, new String[] {"a"})).isEqualTo(new String[] {"a"});
+ assertThat(ArrayUtil.addAll(new String[] {"a"}, null)).isEqualTo(new String[] {"a"});
+ assertThat(ArrayUtil.addAll(new String[] {"a"}, new String[] {"b"})).isEqualTo(new String[] {"a", "b"});
+ }
+
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/BeanUtilTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/BeanUtilTest.java
new file mode 100644
index 000000000..9ccc3d5e5
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/BeanUtilTest.java
@@ -0,0 +1,128 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.junit.Test;
+
+
+public class BeanUtilTest {
+
+ @Test
+ public void testGetBeanGetterNameNull() {
+ assertThat(BeanUtil.getBeanGetterName(null)).isNull();
+ }
+
+ @Test
+ public void testGetBeanSetterNameNull() {
+ assertThat(BeanUtil.getBeanSetterName(null)).isNull();
+ }
+
+ @Test
+ public void testIsBeanSetterNameNull() {
+ assertThat(BeanUtil.isBeanGetter(null)).isFalse();
+ }
+
+ @Test
+ public void testIsBeanGetterGetMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("getFoo", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanGetter(subject)).isTrue();
+ }
+
+ @Test
+ public void testIsBeanGetterIsMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("isBar", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanGetter(subject)).isTrue();
+ }
+
+ @Test
+ public void testIsBeanGetterToString() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("toString", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanGetter(subject)).isFalse();
+ }
+
+ @Test
+ public void testGetBeanGetterNameObjectMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = Object.class.getDeclaredMethod("hashCode", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.getBeanGetterName(subject)).isNull();
+ }
+
+ @Test
+ public void testIsBeanPropertyObjectMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = Object.class.getDeclaredMethod("hashCode", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanProperty(subject)).isFalse();
+ }
+
+ @Test
+ public void testIsBeanPropertyGetMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("getFoo", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanProperty(subject)).isTrue();
+ }
+
+ @Test
+ public void testIsBeanPropertyIsMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("isBar", new Class[] {});
+
+ // then
+ assertThat(BeanUtil.isBeanProperty(subject)).isTrue();
+ }
+
+ @Test
+ public void testIsBeanPropertySetMethod() throws NoSuchMethodException, SecurityException {
+ // given
+ Method subject = TestBean.class.getDeclaredMethod("setBar", new Class[] { boolean.class });
+
+ // then
+ assertThat(BeanUtil.isBeanProperty(subject)).isTrue();
+ }
+
+
+ static class TestBean {
+ private String foo;
+ private boolean bar;
+
+ public String getFoo() {
+ return foo;
+ }
+
+ public void setFoo(String foo) {
+ this.foo = foo;
+ }
+
+ public boolean isBar() {
+ return bar;
+ }
+
+ public void setBar(boolean bar) {
+ this.bar = bar;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TestBean [foo=").append(foo).append(", bar=").append(bar).append("]");
+ return builder.toString();
+ }
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospectorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospectorTest.java
new file mode 100644
index 000000000..1b6aebc34
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassIntrospectorTest.java
@@ -0,0 +1,725 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.Entity;
+
+import org.junit.Test;
+
+import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
+import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
+
+import lombok.Data;
+
+
+public class ClassIntrospectorTest {
+
+ private static ClassIntrospector introspector = ClassIntrospector.builder()
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withIncludeFieldsAsProperties(true)
+ .withScanStatics(false)
+ .build();
+
+ @Test
+ public void testBasic() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleA.class);
+ assertNotNull(cd);
+ PropertyDescriptor[] properties = cd.getAllPropertyDescriptors();
+ int c = 0;
+ for (PropertyDescriptor property : properties) {
+ if (property.isFieldOnlyDescriptor())
+ continue;
+ if (property.isPublic())
+ c++;
+ }
+ assertEquals(2, c);
+
+ Arrays.sort(properties, new Comparator() {
+ @Override
+ public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+
+ PropertyDescriptor pd = properties[0];
+ assertEquals("fooProp", pd.getName());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNotNull(pd.getWriteMethodDescriptor());
+ assertNotNull(pd.getFieldDescriptor());
+
+ pd = properties[1];
+ assertEquals("shared", pd.getName());
+ assertNull(pd.getReadMethodDescriptor());
+ assertNull(pd.getWriteMethodDescriptor());
+ assertNotNull(pd.getFieldDescriptor());
+
+ pd = properties[2];
+ assertEquals("something", pd.getName());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNull(pd.getWriteMethodDescriptor());
+ assertNull(pd.getFieldDescriptor());
+
+ assertNotNull(cd.getPropertyDescriptor("fooProp", false));
+ assertNotNull(cd.getPropertyDescriptor("something", false));
+ assertNull(cd.getPropertyDescriptor("FooProp", false));
+ assertNull(cd.getPropertyDescriptor("Something", false));
+ assertNull(cd.getPropertyDescriptor("notExisting", false));
+ }
+
+ @Test
+ public void testExtends() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleB.class);
+ assertNotNull(cd);
+
+ PropertyDescriptor[] properties = cd.getAllPropertyDescriptors();
+ int c = 0;
+ for (PropertyDescriptor property : properties) {
+ if (property.isFieldOnlyDescriptor())
+ continue;
+ if (property.isPublic())
+ c++;
+ }
+ assertEquals(2, c);
+
+ c = 0;
+ for (PropertyDescriptor property : properties) {
+ if (property.isFieldOnlyDescriptor())
+ continue;
+ c++;
+ }
+ assertEquals(3, c);
+ assertEquals(4, properties.length);
+
+ Arrays.sort(properties, new Comparator() {
+ @Override
+ public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+
+ PropertyDescriptor pd = properties[0];
+ assertEquals("boo", pd.getName());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNotNull(pd.getWriteMethodDescriptor());
+ assertNotNull(pd.getFieldDescriptor());
+ assertFalse(pd.isFieldOnlyDescriptor());
+
+ pd = properties[1];
+ assertEquals("fooProp", pd.getName());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNotNull(pd.getWriteMethodDescriptor());
+ assertNull(pd.getFieldDescriptor()); // null since field is not visible
+ assertFalse(pd.isFieldOnlyDescriptor());
+
+ pd = properties[2];
+ assertEquals("shared", pd.getName());
+ assertNull(pd.getReadMethodDescriptor());
+ assertNull(pd.getWriteMethodDescriptor());
+ assertNotNull(pd.getFieldDescriptor());
+ assertTrue(pd.isFieldOnlyDescriptor());
+
+ pd = properties[3];
+ assertEquals("something", pd.getName());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNull(pd.getWriteMethodDescriptor());
+ assertNull(pd.getFieldDescriptor());
+ assertFalse(pd.isFieldOnlyDescriptor());
+
+ assertNotNull(cd.getPropertyDescriptor("fooProp", false));
+ assertNotNull(cd.getPropertyDescriptor("something", false));
+ assertNull(cd.getPropertyDescriptor("FooProp", false));
+ assertNull(cd.getPropertyDescriptor("Something", false));
+ assertNull(cd.getPropertyDescriptor("notExisting", false));
+
+ assertNotNull(cd.getPropertyDescriptor("boo", true));
+ assertNull(cd.getPropertyDescriptor("boo", false));
+ }
+
+ @Test
+ public void testCtors() {
+ ClassDescriptor cd = introspector.introspect(Parent.class);
+ ConstructorDescriptor[] ctors = cd.getAllConstructorDescriptors();
+ int c = 0;
+ for (ConstructorDescriptor ctor : ctors) {
+ if (ctor.isPublic())
+ c++;
+ }
+ assertEquals(1, c);
+ ctors = cd.getAllConstructorDescriptors();
+ assertEquals(2, ctors.length);
+ assertNotNull(cd.getDefaultCtorDescriptor(true));
+ assertNull(cd.getDefaultCtorDescriptor(false));
+
+ Constructor> ctor = cd.getConstructorDescriptor(new Class[] { Integer.class }, true).getConstructor();
+ assertNotNull(ctor);
+
+ cd = introspector.introspect(Child.class);
+ ctors = cd.getAllConstructorDescriptors();
+ c = 0;
+ for (ConstructorDescriptor ccc : ctors) {
+ if (ccc.isPublic())
+ c++;
+ }
+ assertEquals(1, c);
+
+ ctors = cd.getAllConstructorDescriptors();
+ assertEquals(1, ctors.length);
+ assertNull(cd.getDefaultCtorDescriptor(false));
+ assertNull(cd.getDefaultCtorDescriptor(true));
+
+ ConstructorDescriptor ctorDescriptor = cd.getConstructorDescriptor(new Class[] { Integer.class }, true);
+ assertNull(ctorDescriptor);
+ ctor = cd.getConstructorDescriptor(new Class[] { String.class }, true).getConstructor();
+ assertNotNull(ctor);
+ }
+
+ @Test
+ public void testSameFieldDifferentClass() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleA.class);
+
+ FieldDescriptor fd = cd.getFieldDescriptor("shared", false);
+ assertNull(fd);
+
+ fd = cd.getFieldDescriptor("shared", true);
+ assertNotNull(fd);
+
+ ClassDescriptor cd2 = introspector.introspect(BeanSampleB.class);
+ FieldDescriptor fd2 = cd2.getFieldDescriptor("shared", true);
+
+ assertNotEquals(fd, fd2);
+ assertEquals(fd.getField(), fd2.getField());
+ }
+
+ @Test
+ public void testPropertyMatches() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleC.class);
+
+ PropertyDescriptor pd;
+
+ pd = cd.getPropertyDescriptor("s1", false);
+ assertNull(pd);
+
+ pd = cd.getPropertyDescriptor("s1", true);
+ assertFalse(pd.isPublic());
+ assertTrue(pd.getReadMethodDescriptor().isPublic());
+ assertFalse(pd.getWriteMethodDescriptor().isPublic());
+
+ assertNotNull(getPropertyGetterDescriptor(cd, "s1", false));
+ assertNull(getPropertySetterDescriptor(cd, "s1", false));
+
+ pd = cd.getPropertyDescriptor("s2", false);
+ assertNull(pd);
+
+ pd = cd.getPropertyDescriptor("s2", true);
+ assertFalse(pd.isPublic());
+ assertFalse(pd.getReadMethodDescriptor().isPublic());
+ assertTrue(pd.getWriteMethodDescriptor().isPublic());
+
+ assertNull(getPropertyGetterDescriptor(cd, "s2", false));
+ assertNotNull(getPropertySetterDescriptor(cd, "s2", false));
+
+ pd = cd.getPropertyDescriptor("s3", false);
+ assertNotNull(pd);
+
+ pd = cd.getPropertyDescriptor("s3", true);
+ assertTrue(pd.isPublic());
+ assertTrue(pd.getReadMethodDescriptor().isPublic());
+ assertTrue(pd.getWriteMethodDescriptor().isPublic());
+
+ assertNotNull(getPropertyGetterDescriptor(cd, "s3", false));
+ assertNotNull(getPropertySetterDescriptor(cd, "s3", false));
+ }
+
+ @Test
+ public void testOverload() {
+ ClassDescriptor cd = introspector.introspect(Overload.class);
+
+ PropertyDescriptor[] pds = cd.getAllPropertyDescriptors();
+
+ assertEquals(1, pds.length);
+
+ PropertyDescriptor pd = pds[0];
+
+ assertNotNull(pd.getFieldDescriptor());
+ assertNotNull(pd.getReadMethodDescriptor());
+ assertNull(pd.getWriteMethodDescriptor());
+ }
+
+ @Test
+ public void testSerialUid() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleB.class);
+
+ assertNull(cd.getFieldDescriptor("serialVersionUID", true));
+ }
+
+ @Test
+ public void testStaticField() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleA.class);
+
+ assertNull(cd.getFieldDescriptor("staticField", true));
+ }
+
+ @Test
+ public void testStaticMethod() {
+ ClassDescriptor cd = introspector.introspect(BeanSampleB.class);
+
+ assertNull(cd.getMethodDescriptor("staticMethod", true));
+ }
+
+ @Test
+ public void testFields() throws NoSuchFieldException {
+ ClassDescriptor cd = introspector.introspect(MethodParameterType.class);
+
+ assertEquals(MethodParameterType.class, cd.getType());
+ assertEquals(4, cd.getAllFieldDescriptors().length);
+
+ FieldDescriptor[] fs = cd.getAllFieldDescriptors();
+ int p = 0;
+ for (FieldDescriptor f : fs) {
+ if (f.isPublic()) {
+ p++;
+ }
+ }
+ assertEquals(0, p);
+
+ FieldDescriptor fd = cd.getFieldDescriptor("f", true);
+ FieldDescriptor fd2 = cd.getFieldDescriptor("f2", true);
+ FieldDescriptor fd3 = cd.getFieldDescriptor("f3", true);
+ FieldDescriptor fd4 = cd.getFieldDescriptor("f4", true);
+
+ assertEquals(List.class, fd.getRawType());
+ assertEquals(Object.class, fd.getRawComponentType());
+
+ assertEquals(List.class, fd2.getRawType());
+ assertEquals(Object.class, fd2.getRawComponentType());
+
+ assertEquals(Map.class, fd3.getRawType());
+ assertEquals(Object.class, fd3.getRawComponentType());
+
+ assertEquals(List.class, fd4.getRawType());
+ assertEquals(Long.class, fd4.getRawComponentType());
+
+ // impl
+ cd = introspector.introspect(Foo.class);
+
+ fd = cd.getFieldDescriptor("f", true);
+ fd2 = cd.getFieldDescriptor("f2", true);
+ fd3 = cd.getFieldDescriptor("f3", true);
+
+ assertEquals(List.class, fd.getRawType());
+ assertEquals(Integer.class, fd.getRawComponentType());
+
+ assertEquals(List.class, fd2.getRawType());
+ assertEquals(Object.class, fd2.getRawComponentType());
+
+ assertEquals(Map.class, fd3.getRawType());
+ assertEquals(Integer.class, fd3.getRawComponentType());
+ assertEquals(String.class, ReflectionUtil.getComponentTypes(fd3.getField().getGenericType(), cd.getType())[0]);
+ }
+
+ @Test
+ public void testMethods() throws NoSuchMethodException {
+ ClassDescriptor cd = introspector.introspect(MethodParameterType.class);
+
+ assertEquals(MethodParameterType.class, cd.getType());
+ assertEquals(5, cd.getAllMethodDescriptors().length);
+
+ MethodDescriptor[] mds = cd.getAllMethodDescriptors();
+ int mc = 0;
+ for (MethodDescriptor md : mds) {
+ if (md.isPublic())
+ mc++;
+ }
+ assertEquals(0, mc);
+
+ Class>[] params = new Class[] { Object.class, String.class, List.class, List.class, List.class };
+
+ Method m = MethodParameterType.class.getDeclaredMethod("m", params);
+ assertNotNull(m);
+
+ Method m2 = cd.getMethodDescriptor("m", params, true).getMethod();
+ assertNotNull(m2);
+ assertEquals(m, m2);
+
+ MethodDescriptor md1 = cd.getMethodDescriptor("m", params, true);
+ assertNotNull(md1);
+ assertEquals(m, md1.getMethod());
+ assertArrayEquals(params, md1.getRawParameterTypes());
+ assertEquals(void.class, md1.getRawReturnType());
+ assertNull(md1.getRawReturnComponentType());
+
+ MethodDescriptor md2 = cd.getMethodDescriptor("m2", params, true);
+ assertNotNull(md2);
+ assertArrayEquals(params, md2.getRawParameterTypes());
+ assertEquals(List.class, md2.getRawReturnType());
+ assertEquals(List.class, md2.getRawReturnComponentType());
+
+ MethodDescriptor md3 = cd.getMethodDescriptor("m3", params, true);
+ assertNotNull(md3);
+ assertArrayEquals(params, md3.getRawParameterTypes());
+ assertEquals(List.class, md3.getRawReturnType());
+ assertEquals(Object.class, md3.getRawReturnComponentType());
+
+ MethodDescriptor md4 = cd.getMethodDescriptor("m4", new Class[] { List.class }, true);
+ assertNotNull(md4);
+ assertArrayEquals(new Class[] { List.class }, md4.getRawParameterTypes());
+ assertEquals(List.class, md4.getRawReturnType());
+ assertEquals(Byte.class, md4.getRawReturnComponentType());
+ assertEquals(List.class, md4.getSetterRawType());
+ assertEquals(Long.class, md4.getSetterRawComponentType());
+
+ MethodDescriptor md5 = cd.getMethodDescriptor("m5", new Class[] { List.class }, true);
+ assertNotNull(md5);
+ assertArrayEquals(new Class[] { List.class }, md5.getRawParameterTypes());
+ assertEquals(List.class, md5.getRawReturnType());
+ assertEquals(Object.class, md5.getRawReturnComponentType());
+ assertEquals(List.class, md5.getSetterRawType());
+ assertEquals(Object.class, md5.getSetterRawComponentType());
+
+ Class>[] params2 = new Class[] { Integer.class, String.class, List.class, List.class, List.class };
+
+ ClassDescriptor cd1 = introspector.introspect(Foo.class);
+
+ MethodDescriptor[] allm = cd1.getAllMethodDescriptors();
+
+ assertEquals(5, allm.length);
+
+ md3 = cd1.getMethodDescriptor("m", params, true);
+ assertNotNull(md3);
+
+ assertArrayEquals(params2, md3.getRawParameterTypes());
+
+ md3 = cd1.getMethodDescriptor("m3", params, true);
+ assertNotNull(md3);
+ assertArrayEquals(params2, md3.getRawParameterTypes());
+ assertEquals(List.class, md3.getRawReturnType());
+ assertEquals(Integer.class, md3.getRawReturnComponentType());
+
+ md5 = cd1.getMethodDescriptor("m5", new Class[] { List.class }, true);
+ assertNotNull(md5);
+ assertArrayEquals(new Class[] { List.class }, md5.getRawParameterTypes());
+ assertEquals(List.class, md5.getRawReturnType());
+ assertEquals(Integer.class, md5.getRawReturnComponentType());
+ assertEquals(List.class, md5.getSetterRawType());
+ assertEquals(Integer.class, md5.getSetterRawComponentType());
+ }
+
+ @Test
+ public void testClassAnnotations() throws NoSuchFieldException, SecurityException {
+ // given
+ Annotation classAnnotation = BeanSampleD.class.getAnnotation(Entity.class);
+ AnnotationDescriptor expected = new AnnotationDescriptor(classAnnotation);
+
+ // when
+ ClassDescriptor classDescriptor = introspector.introspect(BeanSampleD.class);
+
+ // then
+ assertThat(classDescriptor.getAllAnnotationDescriptors())
+ .containsOnly(expected)
+ .extracting(AnnotationDescriptor::getAnnotation,
+ AnnotationDescriptor::getAnnotationType,
+ AnnotationDescriptor::getElementTypes,
+ AnnotationDescriptor::getPolicy,
+ AnnotationDescriptor::isDocumented,
+ AnnotationDescriptor::isInherited)
+ .contains(tuple(classAnnotation,
+ Entity.class,
+ new ElementType[] {ElementType.TYPE},
+ expected.getPolicy(),
+ true,
+ false));
+ }
+
+ @Test
+ public void testFieldAnnotations() throws NoSuchFieldException, SecurityException {
+ // given
+ Annotation fieldAnnotation = BeanSampleD.class.getDeclaredField("foo")
+ .getAnnotation(GraphQLIgnore.class);
+ AnnotationDescriptor expected = new AnnotationDescriptor(fieldAnnotation);
+
+ // when
+ FieldDescriptor subject = introspector.introspect(BeanSampleD.class)
+ .getFieldDescriptor("foo", true);
+ // then
+ assertThat(subject.getAllAnnotationDescriptors())
+ .containsOnly(expected)
+ .extracting(AnnotationDescriptor::getAnnotation,
+ AnnotationDescriptor::getAnnotationType,
+ AnnotationDescriptor::getElementTypes,
+ AnnotationDescriptor::getPolicy,
+ AnnotationDescriptor::isDocumented,
+ AnnotationDescriptor::isInherited)
+ .contains(tuple(fieldAnnotation,
+ GraphQLIgnore.class,
+ new ElementType[] {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD},
+ expected.getPolicy(),
+ false,
+ false));
+
+ }
+
+ @Test
+ public void testMethodAnnotations() throws NoSuchFieldException, SecurityException, NoSuchMethodException {
+ // given
+ Annotation fieldAnnotation = BeanSampleD.class.getDeclaredMethod("getBar")
+ .getAnnotation(GraphQLDescription.class);
+ AnnotationDescriptor expected = new AnnotationDescriptor(fieldAnnotation);
+
+ // when
+ MethodDescriptor subject = introspector.introspect(BeanSampleD.class)
+ .getMethodDescriptor("getBar", new Class[] {}, true);
+ // then
+ assertThat(subject.getAllAnnotationDescriptors())
+ .containsOnly(expected)
+ .extracting(AnnotationDescriptor::getAnnotation,
+ AnnotationDescriptor::getAnnotationType,
+ AnnotationDescriptor::getElementTypes,
+ AnnotationDescriptor::getPolicy,
+ AnnotationDescriptor::isDocumented,
+ AnnotationDescriptor::isInherited)
+ .contains(tuple(fieldAnnotation,
+ GraphQLDescription.class,
+ new ElementType[] {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD},
+ expected.getPolicy(),
+ false,
+ false));
+ }
+
+ @Test
+ public void testConstructorDescriptors() throws NoSuchFieldException, SecurityException, NoSuchMethodException {
+ // given
+ ClassDescriptor classDescriptor = introspector.introspect(BeanSampleD.class);
+ Constructor constructor = BeanSampleD.class.getConstructor(new Class[] {});
+
+ // when
+ ConstructorDescriptor subject = introspector.introspect(BeanSampleD.class)
+ .getConstructorDescriptor(new Class[] {}, true);
+ // then
+ assertThat(classDescriptor.getAllConstructorDescriptors())
+ .containsOnly(subject)
+ .extracting(ConstructorDescriptor::getConstructor,
+ ConstructorDescriptor::getClassDescriptor,
+ ConstructorDescriptor::getName,
+ ConstructorDescriptor::getParameters,
+ ConstructorDescriptor::isDefault,
+ ConstructorDescriptor::isPublic)
+ .contains(tuple(constructor,
+ classDescriptor,
+ "com.introproventures.graphql.jpa.query.introspection.ClassIntrospectorTest$BeanSampleD",
+ new Class[] {},
+ true,
+ true));
+ }
+
+
+ @Test
+ public void testPropertyDescriptors() throws NoSuchFieldException, SecurityException, NoSuchMethodException {
+ // given
+ ClassDescriptor classDescriptor = introspector.introspect(BeanSampleD.class);
+
+ // when
+ PropertyDescriptor[] subject = introspector.introspect(BeanSampleD.class)
+ .getAllPropertyDescriptors();
+ // then
+ assertThat(subject).hasSize(2)
+ .extracting(PropertyDescriptor::getName,
+ PropertyDescriptor::getType,
+ PropertyDescriptor::getClassDescriptor,
+ PropertyDescriptor::isFieldOnlyDescriptor,
+ PropertyDescriptor::isPublic)
+ .contains(tuple("bar", String.class, classDescriptor, false, false),
+ tuple("foo", Integer.class, classDescriptor, false, true));
+ }
+
+
+
+ @Data
+ @Entity
+ static class BeanSampleD {
+
+ @GraphQLIgnore
+ protected Integer foo;
+
+ private String bar;
+
+ @GraphQLDescription("getBar")
+ protected String getBar() {
+ return bar;
+ }
+ }
+
+ static class BeanSampleA {
+
+ protected static String staticField;
+
+ protected Integer shared;
+
+ private String fooProp = "abean_value";
+
+ public void setFooProp(String v) {
+ fooProp = v;
+ }
+
+ public String getFooProp() {
+ return fooProp;
+ }
+
+ public boolean isSomething() {
+ return true;
+ }
+ }
+
+ static class BeanSampleB extends BeanSampleA {
+
+ public static final long serialVersionUID = 42L;
+
+ public static void staticMethod() { };
+
+ private Long boo;
+
+ Long getBoo() {
+ return boo;
+ }
+
+ void setBoo(Long boo) {
+ this.boo = boo;
+ }
+ }
+
+ public class BeanSampleC {
+
+ private String s1;
+ private String s2;
+ private String s3;
+
+ public String getS1() {
+ return s1;
+ }
+
+ protected void setS1(String s1) {
+ this.s1 = s1;
+ }
+
+ protected String getS2() {
+ return s2;
+ }
+
+ public void setS2(String s2) {
+ this.s2 = s2;
+ }
+
+ public String getS3() {
+ return s3;
+ }
+
+ public void setS3(String s3) {
+ this.s3 = s3;
+ }
+ }
+
+ static class Parent {
+
+ protected Parent() {
+
+ }
+
+ public Parent(Integer i) {
+
+ }
+
+ }
+
+ static class Child extends Parent {
+
+ public Child(String a) {
+ super();
+ }
+ }
+
+ static class Overload {
+
+ String company;
+
+ // not a property setter
+ public void setCompany(StringBuilder sb) {
+ this.company = sb.toString();
+ }
+
+ public String getCompany() {
+ return company;
+ }
+ }
+
+ static class MethodParameterType {
+ List f;
+ List> f2;
+ Map f3;
+ List f4;
+
+ > void m(A a, String p1, T p2, List> p3, List p4) {
+ }
+
+ > List m2(A a, String p1, T p2, List> p3, List p4) {
+ return null;
+ }
+
+ > List m3(A a, String p1, T p2, List> p3, List p4) {
+ return null;
+ }
+
+ List m4(List list) {
+ return null;
+ }
+
+ List m5(List list) {
+ return null;
+ }
+ }
+
+ static class Foo extends MethodParameterType {
+ }
+
+ MethodDescriptor getPropertySetterDescriptor(ClassDescriptor cd, String name, boolean declared) {
+ PropertyDescriptor propertyDescriptor = cd.getPropertyDescriptor(name, true);
+
+ if (propertyDescriptor != null) {
+ MethodDescriptor setter = propertyDescriptor.getWriteMethodDescriptor();
+
+ if ((setter != null) && setter.matchDeclared(declared)) {
+ return setter;
+ }
+ }
+ return null;
+ }
+
+ MethodDescriptor getPropertyGetterDescriptor(ClassDescriptor cd, String name, boolean declared) {
+ PropertyDescriptor propertyDescriptor = cd.getPropertyDescriptor(name, true);
+
+ if (propertyDescriptor != null) {
+ MethodDescriptor getter = propertyDescriptor.getReadMethodDescriptor();
+
+ if ((getter != null) && getter.matchDeclared(declared)) {
+ return getter;
+ }
+ }
+ return null;
+ }
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassUtilTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassUtilTest.java
new file mode 100644
index 000000000..c9798bf8b
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ClassUtilTest.java
@@ -0,0 +1,69 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.Serializable;
+import java.util.AbstractCollection;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.RandomAccess;
+
+import org.junit.Test;
+
+
+public class ClassUtilTest {
+
+ @Test
+ public void getAllInterfaces() {
+ assertEquals(null, ClassUtil.getAllInterfaces(null));
+ assertTrue(ClassUtil.getAllInterfaces(Object.class).isEmpty());
+
+ assertFalse(ClassUtil.getAllInterfaces(String.class).isEmpty());
+ assertFalse(ClassUtil.getAllInterfaces(Class.class).isEmpty());
+
+ List> supers = ClassUtil.getAllInterfaces(ArrayList.class);
+ assertFalse(supers.contains(AbstractList.class));
+ assertFalse(supers.contains(AbstractCollection.class));
+
+ assertTrue(supers.contains(List.class));
+ assertTrue(supers.contains(RandomAccess.class));
+ assertTrue(supers.contains(Iterable.class));
+ assertFalse(supers.contains(Object.class));
+
+ assertTrue(ClassUtil.getAllInterfaces(int.class).isEmpty());
+ // assertTrue(ClassUtil.getAllInterfaces(int[].class).isEmpty());
+ assertFalse(ClassUtil.getAllInterfaces(Integer.class).contains(Number.class));
+ assertTrue(ClassUtil.getAllInterfaces(Integer.class).contains(Comparable.class));
+
+ List> list = ClassUtil.getAllInterfaces(Integer[].class);
+ assertTrue(list.contains(Cloneable.class));
+ assertTrue(list.contains(Serializable.class));
+
+ }
+
+ @Test
+ public void getAllSuperclasses() {
+ assertEquals(null, ClassUtil.getAllSuperclasses(null));
+ assertTrue(ClassUtil.getAllSuperclasses(Object.class).isEmpty());
+
+ assertTrue(ClassUtil.getAllSuperclasses(String.class).isEmpty());
+
+ List> supers = ClassUtil.getAllSuperclasses(ArrayList.class);
+ assertTrue(supers.contains(AbstractList.class));
+ assertTrue(supers.contains(AbstractCollection.class));
+
+ assertFalse(supers.contains(List.class));
+ assertFalse(supers.contains(Object.class));
+
+ assertTrue(ClassUtil.getAllSuperclasses(int.class).isEmpty());
+ assertTrue(ClassUtil.getAllSuperclasses(int[].class).isEmpty());
+ assertTrue(ClassUtil.getAllSuperclasses(Integer.class).contains(Number.class));
+
+ assertTrue(ClassUtil.getAllSuperclasses(Integer[].class).isEmpty());
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptorTest.java
new file mode 100644
index 000000000..cc1bc064e
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorDescriptorTest.java
@@ -0,0 +1,61 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+public class ConstructorDescriptorTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(SampleBean.class);
+
+ @Test
+ public void testToStringEqualsHashCode() {
+ ConstructorDescriptor subject = classDescriptor.getConstructorDescriptor(new Class[] {}, true);
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testGetDeclaringClass() {
+ ConstructorDescriptor subject = classDescriptor.getConstructorDescriptor(new Class[] {}, true);
+
+ // then
+ assertThat(subject.getDeclaringClass()).isEqualTo(SampleBean.class);
+ }
+
+ @Test
+ public void testParameters() {
+ ConstructorDescriptor subject = classDescriptor.getConstructorDescriptor(new Class[] {}, true);
+
+ // then
+ assertThat(subject.getParameters()).isEqualTo(new Class[] {});
+ }
+
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class SampleBean {
+ private String foo;
+ private String bar;
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorsTest.java
new file mode 100644
index 000000000..ad6e300db
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ConstructorsTest.java
@@ -0,0 +1,44 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+public class ConstructorsTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(ConstructorsSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ Constructors subject = classDescriptor.getConstructors();
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class ConstructorsSampeBean {
+ private String foo;
+ private String bar;
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptorTest.java
new file mode 100644
index 000000000..41d0874c5
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldDescriptorTest.java
@@ -0,0 +1,99 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Optional;
+
+import org.junit.Test;
+
+import lombok.Data;
+
+
+public class FieldDescriptorTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(FieldsSampleBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ FieldDescriptor subject = classDescriptor.getFieldDescriptor("id", true);
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+
+ }
+
+ @Test
+ public void testFieldDescriptor() {
+ FieldDescriptor subject = classDescriptor.getFieldDescriptor("nickName", true);
+
+ // then
+ assertThat(subject).isNotNull()
+ .extracting(FieldDescriptor::getName,
+ FieldDescriptor::getDeclaringClass,
+ FieldDescriptor::getRawType,
+ FieldDescriptor::getRawComponentType,
+ FieldDescriptor::getRawKeyComponentType,
+ FieldDescriptor::getGetterRawComponentType,
+ FieldDescriptor::getGetterRawKeyComponentType,
+ FieldDescriptor::getSetterRawType,
+ FieldDescriptor::getSetterRawComponentType)
+ .contains("nickName",
+ FieldsSampleBean.class,
+ Optional.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class,
+ String.class);
+ }
+
+
+ @Test
+ public void testInvokeGetter() throws InvocationTargetException, IllegalAccessException {
+ FieldDescriptor subject = classDescriptor.getFieldDescriptor("id", true);
+ FieldsSampleBean target = new FieldsSampleBean("id");
+
+
+ // when
+ Object result = subject.invokeGetter(target);
+
+ // then
+ assertThat(result).isEqualTo("id");
+ }
+
+ @Test
+ public void testInvokeSetter() throws InvocationTargetException, IllegalAccessException {
+ FieldDescriptor subject = classDescriptor.getFieldDescriptor("name", true);
+ FieldsSampleBean target = new FieldsSampleBean("id");
+
+ // when
+ subject.invokeSetter(target, "name");
+
+ // then
+ assertThat(target.getName()).isEqualTo("name");
+ }
+
+
+ @Data
+ static class FieldsSampleBean {
+ private final String id;
+ private String name;
+ private Optional nickName = Optional.empty();
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldsTest.java
new file mode 100644
index 000000000..28afce115
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/FieldsTest.java
@@ -0,0 +1,44 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+public class FieldsTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(FieldsSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ Fields subject = classDescriptor.getFields();
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class FieldsSampeBean {
+
+ private String foo;
+ private String bar;
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptorTest.java
new file mode 100644
index 000000000..e71a5c6ee
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodDescriptorTest.java
@@ -0,0 +1,122 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Optional;
+
+import org.junit.Test;
+
+import lombok.Data;
+
+public class MethodDescriptorTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(MethodsSampleBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ MethodDescriptor subject = classDescriptor.getMethodDescriptor("getId", true);
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+
+ }
+
+ @Test
+ public void testGetMethodDescriptor() {
+ MethodDescriptor subject = classDescriptor.getMethodDescriptor("getNickName", true);
+
+ // then
+ assertThat(subject).isNotNull()
+ .extracting(MethodDescriptor::getName,
+ MethodDescriptor::getDeclaringClass,
+ MethodDescriptor::getGetterRawType,
+ MethodDescriptor::getGetterRawComponentType,
+ MethodDescriptor::getGetterRawKeyComponentType,
+ MethodDescriptor::getRawParameterTypes,
+ MethodDescriptor::getRawReturnType)
+ .contains("getNickName",
+ MethodsSampleBean.class,
+ Optional.class,
+ String.class,
+ String.class,
+ Optional.class,
+ String.class,
+ String.class);
+ }
+
+ @Test
+ public void testSetMethodDescriptor() throws NoSuchMethodException, SecurityException {
+ MethodDescriptor subject = classDescriptor.getMethodDescriptor("setNickName", true);
+ Method method = MethodsSampleBean.class.getDeclaredMethod("setNickName", new Class[] {Optional.class});
+
+ // then
+ assertThat(subject).isNotNull()
+ .extracting(MethodDescriptor::getName,
+ MethodDescriptor::getMethod,
+ MethodDescriptor::isPublic,
+ MethodDescriptor::getDeclaringClass,
+ MethodDescriptor::getGetterRawType,
+ MethodDescriptor::getGetterRawComponentType,
+ MethodDescriptor::getGetterRawKeyComponentType,
+ MethodDescriptor::getRawParameterTypes,
+ MethodDescriptor::getRawReturnType)
+ .contains("setNickName",
+ method,
+ true,
+ MethodsSampleBean.class,
+ void.class,
+ null,
+ null,
+ new Class[] {Optional.class},
+ void.class);
+ }
+
+
+ @Test
+ public void testInvokeGetter() throws InvocationTargetException, IllegalAccessException {
+ MethodDescriptor subject = classDescriptor.getMethodDescriptor("getId", true);
+ MethodsSampleBean target = new MethodsSampleBean("id");
+
+ // when
+ Object result = subject.invokeGetter(target);
+
+ // then
+ assertThat(result).isEqualTo("id");
+ }
+
+ @Test
+ public void testInvokeSetter() throws InvocationTargetException, IllegalAccessException {
+ MethodDescriptor subject = classDescriptor.getMethodDescriptor("setName", true);
+ MethodsSampleBean target = new MethodsSampleBean("id");
+
+ // when
+ subject.invokeSetter(target, "name");
+
+ // then
+ assertThat(target.getName()).isEqualTo("name");
+ }
+
+ @Data
+ static class MethodsSampleBean {
+
+ private final String id;
+ private String name;
+ private Optional nickName = Optional.empty();
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodsTest.java
new file mode 100644
index 000000000..7e521a534
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/MethodsTest.java
@@ -0,0 +1,45 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+public class MethodsTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(MethodsSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ Methods subject = classDescriptor.getMethods();
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class MethodsSampeBean {
+
+ private String foo;
+ private String bar;
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtilTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtilTest.java
new file mode 100644
index 000000000..ff7efe1b3
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ObjectUtilTest.java
@@ -0,0 +1,82 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+
+public class ObjectUtilTest {
+
+ @Test
+ public void testNull() {
+ // all null
+ Object[] NULL = null;
+
+ // any null
+ assertTrue(ObjectUtil.isAnyNull(NULL));
+ assertTrue(ObjectUtil.isAnyNull(new Object[] { null, null, null, null, null }));
+ assertTrue(ObjectUtil.isAnyNull(new Object[] { null, null, 0, null, null }));
+ assertTrue(ObjectUtil.isAnyNull(new Object[] { null, "null", 0, null, null }));
+ assertFalse(ObjectUtil
+ .isAnyNull(new Object[] { "", "null", 0, new int[] {}, new ArrayList<>() }));
+ }
+
+ @Test
+ public void isEquals() throws Exception {
+ assertTrue(ObjectUtil.isEquals(null, null));
+ assertFalse(ObjectUtil.isEquals(null, ""));
+ assertFalse(ObjectUtil.isEquals("", null));
+ assertTrue(ObjectUtil.isEquals("", ""));
+ assertFalse(ObjectUtil.isEquals(Boolean.TRUE, null));
+ assertFalse(ObjectUtil.isEquals(Boolean.TRUE, "true"));
+ assertTrue(ObjectUtil.isEquals(Boolean.TRUE, Boolean.TRUE));
+ assertFalse(ObjectUtil.isEquals(Boolean.TRUE, Boolean.FALSE));
+
+ Object[] oa = new Object[] { new MyObject(), new MyObject() };
+ int[] ia = new int[] { 1, 2, 3 };
+ long[] la = new long[] { 1, 2, 3 };
+ short[] sa = new short[] { 1, 2, 3 };
+ byte[] ba = new byte[] { 1, 2, 3 };
+ double[] da = new double[] { 1, 2, 3 };
+ float[] fa = new float[] { 1, 2, 3 };
+ boolean[] bla = new boolean[] { true, false, true };
+ char[] ca = new char[] { 'a', 'b', 'c' };
+ Object[] combo = { oa, ia, la, sa, ba, da, fa, bla, ca, null };
+
+ assertObjectEquals(oa);
+ assertObjectEquals(ia);
+ assertObjectEquals(la);
+ assertObjectEquals(sa);
+ assertObjectEquals(ba);
+ assertObjectEquals(da);
+ assertObjectEquals(fa);
+ assertObjectEquals(bla);
+ assertObjectEquals(ca);
+ assertObjectEquals(combo);
+ }
+
+ private class MyObject {
+ @Override
+ public int hashCode() {
+ return 123;
+ }
+ }
+
+ private void assertObjectEquals(Object array) throws Exception {
+ Method clone = Object.class.getDeclaredMethod("clone");
+ clone.setAccessible(true);
+
+ assertTrue(ObjectUtil.isEquals(array, array)); // same
+ assertTrue(ObjectUtil.isEquals(array, clone.invoke(array))); // equals to copy
+
+ Object copy = Array.newInstance(array.getClass().getComponentType(), Array.getLength(array) - 1);
+ System.arraycopy(array, 0, copy, 0, Array.getLength(copy));
+
+ assertFalse(ObjectUtil.isEquals(array, copy)); // not equals
+ }
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertiesTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertiesTest.java
new file mode 100644
index 000000000..dbe81bac3
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertiesTest.java
@@ -0,0 +1,45 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import org.junit.Test;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+public class PropertiesTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(PropertiesSampeBean.class);
+
+ @Test
+ public void testToStringEqualsAndHashCode() {
+ Properties subject = classDescriptor.getProperties();
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class PropertiesSampeBean {
+
+ private String foo;
+ private String bar;
+
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptorTest.java
new file mode 100644
index 000000000..758285ad9
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/PropertyDescriptorTest.java
@@ -0,0 +1,129 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+import java.util.Optional;
+
+import javax.persistence.Version;
+
+import org.junit.Test;
+
+import lombok.Data;
+
+
+public class PropertyDescriptorTest {
+
+ private static ClassIntrospector classIntrospector = ClassIntrospector.builder()
+ .withIncludeFieldsAsProperties(true)
+ .withEnhancedProperties(true)
+ .withScanAccessible(true)
+ .withScanStatics(false)
+ .build();
+
+ // given
+ private ClassDescriptor classDescriptor = classIntrospector.introspect(SampleBean.class);
+
+ @Test
+ public void testToString() {
+ PropertyDescriptor subject = classDescriptor.getPropertyDescriptor("name", true);
+
+ // then
+ assertThatCode(() -> {
+ subject.toString();
+ subject.hashCode();
+ subject.equals(subject);
+ }).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testGetField() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("name", true)
+ .getField())
+ .isNotNull();
+
+ }
+
+ @Test
+ public void testGetGetter() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("name", true)
+ .getGetter(true))
+ .isNotNull();
+
+ }
+
+ @Test
+ public void testGetSetter() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("name", true)
+ .getSetter(true))
+ .isNotNull();
+
+ }
+
+ @Test
+ public void testResolveKeyType() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("optional", true)
+ .resolveKeyType(true))
+ .isEqualTo(String.class);
+
+ }
+
+ @Test
+ public void testResolveComponentType() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("optional", true)
+ .resolveComponentType(true))
+ .isEqualTo(String.class);
+
+ }
+
+ @Test
+ public void testIsFieldOnlyDescriptor() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("version", true)
+ .isFieldOnlyDescriptor())
+ .isFalse();
+
+ }
+
+ @Test
+ public void testGetAnnotations() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("version", true)
+ .getAnnotations())
+ .isNotNull();
+
+ }
+
+ @Test
+ public void testGetAnnotationDescriptor() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("version", true)
+ .getAnnotationDescriptor(Version.class))
+ .isNotNull();
+
+ }
+
+ @Test
+ public void testGetAnnotation() {
+ // then
+ assertThat(classDescriptor.getPropertyDescriptor("version", true)
+ .getAnnotation(Version.class))
+ .isNotNull();
+
+ }
+
+ @Data
+ static class SampleBean {
+ private final String id;
+ @Version
+ private Integer version;
+ private String name;
+ private Optional optional;
+ }
+
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtilTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtilTest.java
new file mode 100644
index 000000000..19fba6b81
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/introspection/ReflectionUtilTest.java
@@ -0,0 +1,344 @@
+package com.introproventures.graphql.jpa.query.introspection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import javax.management.loading.MLet;
+
+import org.junit.Test;
+
+
+public class ReflectionUtilTest {
+
+ @Test
+ public void getAllMethodsOfClass() {
+ assertNull(ReflectionUtil.getAllMethodsOfClass(null));
+
+ Method[] methods = ReflectionUtil.getAllMethodsOfClass(MLet.class);
+ assertTrue(methods.length > 0);
+
+ Method equalsMethod = ReflectionUtil.getMethod(Object.class, "equals", Object.class);
+
+ assertTrue(methods.length > 0);
+ List methodList = Arrays.asList(methods);
+
+ assertFalse(methodList.contains(equalsMethod));
+
+ List> list = ClassUtil.getAllInterfaces(MLet.class);
+
+ int interMethodLength = 0;
+ for (Class> clazz : list) {
+ Method[] interMethods = ReflectionUtil.getAllMethodsOfClass(clazz);
+ interMethodLength += interMethods.length;
+ }
+
+ assertTrue(methods.length > interMethodLength);
+ }
+
+ @Test
+ public void getAllFieldsOfClass() {
+ assertNull(ReflectionUtil.getAllFieldsOfClass(null));
+ assertNull(ReflectionUtil.getAllFieldsOfClass(Object.class));
+
+ assertEquals(0, ReflectionUtil.getAllFieldsOfClass(List.class).length);
+ Field[] fields = ReflectionUtil.getAllFieldsOfClass(String.class);
+ assertTrue(fields.length > 0);
+
+ Field[] instancefields = ReflectionUtil.getAllInstanceFields(String.class);
+ assertTrue(instancefields.length > 0);
+
+ assertTrue(fields.length - instancefields.length > 0);
+ }
+
+ @Test
+ public void getComponentType() throws Exception {
+ Field f1 = BaseClass.class.getField("f1");
+ Field f5 = ConcreteClass.class.getField("f5");
+
+ assertNull(ReflectionUtil.getComponentType(f1.getGenericType()));
+ assertEquals(Long.class, ReflectionUtil.getComponentType(f5.getGenericType()));
+ }
+
+ @Test
+ public void getAnnotationMethods() {
+ assertNull(ReflectionUtil.getAnnotationMethods((Class>) null, (Class extends Annotation>) null));
+
+ assertNull(ReflectionUtil.getAnnotationMethods((Class>) null, AnnotationClass.TestAnnotation.class));
+ assertNull(ReflectionUtil.getAnnotationMethods(AnnotationClass.class, (Class extends Annotation>) null));
+
+ List list =
+ ReflectionUtil.getAnnotationMethods(AnnotationClass.class, AnnotationClass.TestAnnotation.class);
+
+ assertTrue(list.size() == 8);
+
+ list = ReflectionUtil.getAnnotationMethods(AnnotationClass.class, Test.class);
+
+ assertTrue(list.size() == 0);
+ }
+
+ @Test
+ public void getAnnotationFields() {
+ assertNull(ReflectionUtil.getAnnotationFields((Class>) null, (Class extends Annotation>) null));
+
+ assertNull(ReflectionUtil.getAnnotationFields((Class>) null, AnnotationClass.TestAnnotation.class));
+ assertNull(ReflectionUtil.getAnnotationFields(AnnotationClass.class, (Class extends Annotation>) null));
+
+ Field[] fields =
+ ReflectionUtil.getAnnotationFields(AnnotationClass.class, AnnotationClass.TestAnnotation.class);
+
+ assertTrue(fields.length == 2);
+
+ fields = ReflectionUtil.getAnnotationFields(AnnotationClass.class, Test.class);
+
+ assertTrue(ArrayUtil.isEmpty(fields));
+
+ }
+
+ @Test
+ public void getGenericSuperType() throws Exception {
+ Class>[] genericSupertypes = ReflectionUtil.getGenericSuperTypes(ConcreteClass.class);
+ assertEquals(String.class, genericSupertypes[0]);
+ assertEquals(Integer.class, genericSupertypes[1]);
+ }
+
+ @Test
+ public void getRawType() throws Exception {
+ Field f1 = BaseClass.class.getField("f1");
+ Field f2 = BaseClass.class.getField("f2");
+ Field f3 = BaseClass.class.getField("f3");
+ Field f4 = ConcreteClass.class.getField("f4");
+ Field f5 = ConcreteClass.class.getField("f5");
+ Field array1 = BaseClass.class.getField("array1");
+
+ assertEquals(String.class, ReflectionUtil.getRawType(f1.getGenericType(), ConcreteClass.class));
+ assertEquals(Integer.class, ReflectionUtil.getRawType(f2.getGenericType(), ConcreteClass.class));
+ assertEquals(String.class, ReflectionUtil.getRawType(f3.getGenericType(), ConcreteClass.class));
+ assertEquals(Long.class, ReflectionUtil.getRawType(f4.getGenericType(), ConcreteClass.class));
+ assertEquals(List.class, ReflectionUtil.getRawType(f5.getGenericType(), ConcreteClass.class));
+ assertEquals(String[].class, ReflectionUtil.getRawType(array1.getGenericType(), ConcreteClass.class));
+
+ assertEquals(Object.class, ReflectionUtil.getRawType(f1.getGenericType()));
+ }
+
+ @Test
+ public void invokeMethod() {
+ assertNull(ReflectionUtil.invokeMethod(null, (Object) null));
+ assertNull(ReflectionUtil.invokeMethod(null, new Object(), new Object()));
+ assertNull(ReflectionUtil.invokeMethod(null, new Object()));
+
+ assertNull(ReflectionUtil.invokeMethod((Object) null, (String) null, (Object[]) null));
+ assertNull(ReflectionUtil.invokeMethod((Object) null, (String) null, (Class>[]) null, (Object) null));
+ assertNull(ReflectionUtil.invokeMethod("", (String) null, (Class>[]) null, (Object) null));
+ assertNull(ReflectionUtil.invokeMethod((Object) null, (String) null, (Class>[]) null, new Object[] {}));
+ assertNull(ReflectionUtil.invokeMethod((Object) null, (String) null, new Class[0], (Object[]) null));
+
+ assertNull(ReflectionUtil.invokeMethod(null, new Object(), new Object()));
+ assertNull(ReflectionUtil.invokeMethod(null, new Object()));
+
+ Method method = null;
+ try {
+ method = String.class.getMethod("valueOf", int.class);
+ assertEquals("1", ReflectionUtil.invokeMethod(method, (Object) null, 1));
+ assertEquals("1", ReflectionUtil.invokeMethod(method, (Object) "", 1));
+ assertEquals("1", ReflectionUtil.invokeMethod(method, new Object(), 1));
+
+ method = String.class.getMethod("trim");
+ assertEquals("xxx", ReflectionUtil.invokeMethod(method, (Object) " xxx "));
+ assertEquals("xxx", ReflectionUtil.invokeMethod(method, new Object()));
+
+ } catch (Exception e) {
+ assertTrue(e instanceof RuntimeException);
+ }
+
+ List list = new ArrayList<>();
+
+ try {
+ method = ArrayList.class.getDeclaredMethod("RangeCheck", int.class);
+ ReflectionUtil.invokeMethod(method, list, Integer.MAX_VALUE);
+ } catch (Exception e) {
+ InvocationTargetException ex = (InvocationTargetException) e.getCause();
+
+ if (ex != null) {
+ assertTrue(ex.getTargetException() instanceof IndexOutOfBoundsException);
+ }
+ }
+
+ try {
+
+ assertEquals("xxx", ReflectionUtil.invokeMethod(" xxx ", "trim", null, (Object[]) null));
+ assertEquals("xxx", ReflectionUtil.invokeMethod(new Object(), "trim", null, (Object[]) null));
+
+ } catch (Exception e) {
+ assertTrue(e instanceof RuntimeException);
+ }
+
+ list = new ArrayList<>();
+
+ try {
+ ReflectionUtil.invokeMethod(list, "RangeCheck", new Class>[] { int.class }, Integer.MAX_VALUE);
+ } catch (Exception e) {
+
+ if (e.getCause() instanceof NoSuchMethodException) {
+
+ } else {
+
+ InvocationTargetException ex = (InvocationTargetException) e.getCause();
+
+ assertTrue(ex.getTargetException() instanceof IndexOutOfBoundsException);
+ }
+ }
+
+ }
+ public static class BaseClass {
+ public A f1;
+ public B f2;
+ public String f3;
+ public A[] array1;
+ }
+
+ public static class ConcreteClass extends BaseClass {
+ public Long f4;
+ public List f5;
+ }
+
+ public static class BaseClass2 extends BaseClass {
+ }
+
+ public static class ConcreteClass2 extends BaseClass2 {
+ }
+
+ public static class Soo {
+ public List stringList;
+ public String[] strings;
+ public String string;
+
+ public List getIntegerList() {
+ return null;
+ }
+
+ public Integer[] getIntegers() {
+ return null;
+ }
+
+ public Integer getInteger() {
+ return null;
+ }
+
+ public T getTemplate(T foo) {
+ return null;
+ }
+
+ public Collection extends Number> getCollection() {
+ return null;
+ }
+
+ public Collection> getCollection2() {
+ return null;
+ }
+ }
+
+ public interface SomeGuy {
+ }
+
+ public interface Cool extends SomeGuy {
+ }
+
+ public interface Vigilante {
+ }
+
+ public interface Flying extends Vigilante {
+ }
+
+ public interface SuperMario extends Flying, Cool {
+ };
+
+ public class User implements SomeGuy {
+ }
+
+ public class SuperUser extends User implements Cool {
+ }
+
+ public class SuperMan extends SuperUser implements Flying {
+ }
+
+ public static class AnnotationClass {
+
+ private int x;
+ @TestAnnotation(value = "y")
+ private int y;
+
+ private String z;
+ @TestAnnotation(value = "d")
+ private Date d;
+
+ public int getX() {
+ return x;
+ }
+
+ @TestAnnotation(value = "setX")
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ @TestAnnotation(value = "getY")
+ public int getY() {
+ return y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ @TestAnnotation(value = "getZ")
+ public String getZ() {
+ return z;
+ }
+
+ @TestAnnotation(value = "setZ")
+ public void setZ(String z) {
+ this.z = z;
+ }
+
+ @TestAnnotation(value = "getD")
+ public Date getD() {
+ return d;
+ }
+
+ @TestAnnotation(value = "setD")
+ public void setD(Date d) {
+ this.d = d;
+ }
+
+ @Override
+ @TestAnnotation(value = "toString")
+ public String toString() {
+ return super.toString();
+ }
+
+ @Override
+ @TestAnnotation(value = "clone")
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface TestAnnotation {
+ String value();
+ }
+
+ }
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java
index 044d298ed..1f390a590 100644
--- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java
@@ -1,16 +1,14 @@
package com.introproventures.graphql.jpa.query.schema;
-import java.util.Optional;
-
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.util.Lists.list;
+import java.util.Optional;
+
import javax.persistence.EntityManager;
-import graphql.schema.GraphQLFieldDefinition;
-import graphql.schema.GraphQLSchema;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +23,8 @@
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
import graphql.ExecutionResult;
+import graphql.schema.GraphQLFieldDefinition;
+import graphql.schema.GraphQLSchema;
import graphql.validation.ValidationErrorType;
@RunWith(SpringRunner.class)
@@ -84,6 +84,7 @@ public void testIgnoreFields() {
" fieldMem" +
" fieldFun" +
" logic" +
+ " age" +
" customLogic" +
" hideField" +
" hideFieldFunction" +
@@ -100,6 +101,10 @@ public void testIgnoreFields() {
" parentTransientGraphQLIgnore" +
" parentTransientModifierGraphQLIgnore" +
" parentTransientGraphQLIgnoreGetter" +
+ " Uppercase" +
+ " UppercaseGetter" +
+ " UppercaseGetterIgnore" +
+ " protectedGetter" +
" } " +
" } " +
"}";
@@ -121,7 +126,9 @@ public void testIgnoreFields() {
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "parentTransientGraphQLIgnore")),
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "parentTransientModifierGraphQLIgnore")),
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "parentTransientGraphQLIgnoreGetter")),
- tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "transientModifierGraphQLIgnore"))
+ tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "transientModifierGraphQLIgnore")),
+ tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "transientModifierGraphQLIgnore")),
+ tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "UppercaseGetterIgnore"))
);
}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/EntityIntrospectorTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/EntityIntrospectorTest.java
new file mode 100644
index 000000000..ba2fee12e
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/EntityIntrospectorTest.java
@@ -0,0 +1,403 @@
+package com.introproventures.graphql.jpa.query.schema;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.Mockito.when;
+
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+import javax.persistence.EntityManager;
+import javax.persistence.metamodel.Attribute;
+import javax.persistence.metamodel.EntityType;
+import javax.persistence.metamodel.ManagedType;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector;
+import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult;
+import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult.AttributePropertyDescriptor;
+import com.introproventures.graphql.jpa.query.schema.model.calculated.CalculatedEntity;
+import com.introproventures.graphql.jpa.query.schema.model.calculated.ParentCalculatedEntity;
+import com.introproventures.graphql.jpa.query.schema.model.metamodel.ClassWithCustomMetamodel;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
+@TestPropertySource({"classpath:hibernate.properties"})
+public class EntityIntrospectorTest {
+
+ @SpringBootApplication
+ static class Application {
+ }
+
+ @Autowired
+ private EntityManager entityManager;
+
+ // given
+ private final Class entityClass = CalculatedEntity.class;
+
+ private EntityIntrospectionResult subject;
+
+ @Before
+ public void setUp() {
+ ManagedType entityType = entityManager.getMetamodel()
+ .managedType(entityClass);
+
+ this.subject = EntityIntrospector.introspect(entityType);
+ }
+
+
+ @Test(expected = NoSuchElementException.class)
+ public void testResultOfNoSuchElementException() throws Exception {
+ // then
+ EntityIntrospector.resultOf(Object.class);
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testIsTransientNonExisting() throws Exception {
+ // then
+ assertThat(subject.isTransient("notFound")).isFalse();
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testIsIgnoredNonExisting() throws Exception {
+ // then
+ assertThat(subject.isIgnored("notFound")).isFalse();
+ }
+
+ @Test
+ public void shouldExcludeClassPropertyDescriptor() throws Exception {
+ // then
+ assertThat(subject.getPropertyDescriptor("class")).isEmpty();
+ }
+
+ @Test
+ public void testIsTransientFunction() throws Exception {
+ // then
+ assertThat(subject.isTransient("fieldFun")).isTrue();
+ assertThat(subject.isTransient("hideFieldFunction")).isTrue();
+ }
+
+ @Test
+ public void testIsPersistentFunction() throws Exception {
+ // then
+ assertThat(subject.isPersistent("fieldFun")).isFalse();
+ assertThat(subject.isPersistent("hideFieldFunction")).isFalse();
+ }
+
+ @Test
+ public void testIsTransientFields() throws Exception {
+ // then
+ assertThat(subject.isTransient("fieldFun")).isTrue();
+ assertThat(subject.isTransient("fieldMem")).isTrue();
+ assertThat(subject.isTransient("hideField")).isTrue();
+ assertThat(subject.isTransient("logic")).isTrue();
+ assertThat(subject.isTransient("transientModifier")).isTrue();
+ assertThat(subject.isTransient("parentTransientModifier")).isTrue();
+ assertThat(subject.isTransient("parentTransient")).isTrue();
+ assertThat(subject.isTransient("parentTransientGetter")).isTrue();
+ }
+
+ @Test
+ public void testNotTransientFields() throws Exception {
+ // then
+ assertThat(subject.isTransient("id")).isFalse();
+ assertThat(subject.isTransient("info")).isFalse();
+ assertThat(subject.isTransient("title")).isFalse();
+ assertThat(subject.isTransient("parentField")).isFalse();
+ }
+
+ @Test
+ public void testByPassSetMethod() throws Exception {
+ // then
+ assertThat(subject.isTransient("something")).isTrue();
+ }
+
+ @Test
+ public void shouldIgnoreMethodsThatAreAnnotatedWithGraphQLIgnore() {
+ //then
+ assertThat(subject.isIgnored("propertyIgnoredOnGetter")).isTrue();
+ assertThat(subject.isIgnored("ignoredTransientValue")).isTrue();
+ assertThat(subject.isIgnored("hideField")).isTrue();
+ assertThat(subject.isIgnored("parentGraphQLIgnore")).isTrue();
+
+ assertThat(subject.isIgnored("transientModifier")).isFalse();
+ assertThat(subject.isIgnored("parentTransientModifier")).isFalse();
+ assertThat(subject.isIgnored("parentTransient")).isFalse();
+ assertThat(subject.isIgnored("parentTransientGetter")).isFalse();
+ }
+
+ @Test
+ public void shouldNotIgnoreMethodsThatAreNotAnnotatedWithGraphQLIgnore() {
+ //then
+ assertThat(subject.isNotIgnored("propertyIgnoredOnGetter")).isFalse();
+ assertThat(subject.isNotIgnored("ignoredTransientValue")).isFalse();
+ assertThat(subject.isNotIgnored("hideField")).isFalse();
+ assertThat(subject.isNotIgnored("parentGraphQLIgnore")).isFalse();
+
+ assertThat(subject.isNotIgnored("transientModifier")).isTrue();
+ assertThat(subject.isNotIgnored("parentTransientModifier")).isTrue();
+ assertThat(subject.isNotIgnored("parentTransient")).isTrue();
+ assertThat(subject.isNotIgnored("parentTransientGetter")).isTrue();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Test
+ public void shouldGetClassesInHierarchy() {
+ //when
+ Class[] result = EntityIntrospector.resultOf(entityClass)
+ .getClasses()
+ .toArray(Class[]::new);
+
+ //then
+ assertThat(result).containsExactly(CalculatedEntity.class,
+ ParentCalculatedEntity.class,
+ Object.class);
+ }
+
+ @Test
+ public void testGetPropertyDescriptorsSchemaDescription() throws Exception {
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ assertThat(result.getPropertyDescriptors()).extracting(AttributePropertyDescriptor::getSchemaDescription)
+ .filteredOn(Optional::isPresent)
+ .extracting(Optional::get)
+ .containsOnly("i desc function",
+ "getParentTransientGetter",
+ "UppercaseGetter",
+ "title",
+ "transientModifier",
+ "i desc member",
+ "parentTransientModifier",
+ "Uppercase",
+ "protectedGetter");
+ }
+
+ @Test
+ public void testGetPropertyDescriptorSchemaDescriptionByAttribute() throws Exception {
+ Attribute, ?> attribute = Mockito.mock(Attribute.class);
+
+ when(attribute.getName()).thenReturn("title");
+
+ // when
+ Optional result = EntityIntrospector.resultOf(CalculatedEntity.class)
+ .getPropertyDescriptor(attribute);
+ // then
+ assertThat(result.isPresent()).isTrue();
+ }
+
+ @Test
+ public void testGetParentEntitySchemaDescription() throws Exception {
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ assertThat(result.getSchemaDescription()).contains("ParentCalculatedEntity description");
+ assertThat(result.hasSchemaDescription()).isTrue();
+ }
+
+ @Test
+ public void testUppercasePropertyNamesAreSupported() throws Exception {
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ assertThat(result.getPropertyDescriptor("Uppercase")).isPresent();
+
+ assertThat(result.getPropertyDescriptor("Uppercase")
+ .get())
+ .extracting(AttributePropertyDescriptor::isIgnored)
+ .isEqualTo(false);
+
+ assertThat(result.getPropertyDescriptor("Uppercase")
+ .get())
+ .extracting(AttributePropertyDescriptor::isTransient)
+ .isEqualTo(false);
+
+ assertThat(result.getPropertyDescriptor("Uppercase")
+ .get()
+ .getSchemaDescription())
+ .contains("Uppercase");
+
+ assertThat(result.getPropertyDescriptor("UppercaseGetter")
+ .get())
+ .extracting(AttributePropertyDescriptor::isIgnored)
+ .isEqualTo(false);
+
+ assertThat(result.getPropertyDescriptor("UppercaseGetter")
+ .get()
+ .getSchemaDescription())
+ .contains("UppercaseGetter");
+
+ assertThat(result.getPropertyDescriptor("UppercaseGetter")
+ .get())
+ .extracting(AttributePropertyDescriptor::isTransient)
+ .isEqualTo(false);
+
+ assertThat(result.getPropertyDescriptor("uppercaseGetterIgnore")
+ .get())
+ .extracting(AttributePropertyDescriptor::isIgnored)
+ .isEqualTo(true);
+ }
+
+ @Test
+ public void testPrivateModifierOnGetterProperty() throws Exception {
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ assertThat(subject.isIgnored("age")).isFalse();
+ assertThat(subject.isPersistent("age")).isTrue();
+ assertThat(subject.isTransient("age")).isFalse();
+
+ assertThat(result.getPropertyDescriptor("age")).isPresent();
+ assertThat(result.getPropertyDescriptor("age")
+ .get()
+ .getReadMethod())
+ .isEmpty();
+ }
+
+ @Test
+ public void testProtectedModifierOnGetterProperty() throws Exception {
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ assertThat(subject.isIgnored("protectedGetter")).isFalse();
+ assertThat(subject.isPersistent("protectedGetter")).isTrue();
+ assertThat(subject.isTransient("protectedGetter")).isFalse();
+
+ assertThat(result.getPropertyDescriptor("protectedGetter")).isPresent();
+ assertThat(result.getPropertyDescriptor("protectedGetter")
+ .get()
+ .getReadMethod())
+ .isPresent();
+ }
+
+ @Test
+ public void shouldNotFailWhenPropertyIsDuplicatedInParentAndChild() {
+ // given
+ // There is a duplicated property in parent and child
+
+ // then
+ assertThatCode(() -> EntityIntrospector.resultOf(CalculatedEntity.class)).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void shouldCorrectlyIntrospectPropertyDuplicatedInParentAndChild() {
+ // given
+ // There is a duplicated property in parent and child
+
+ // when
+ EntityIntrospectionResult introspectionResult = EntityIntrospector.resultOf(CalculatedEntity.class);
+
+ // then
+ Optional propertyOverriddenInChild = introspectionResult.getPropertyDescriptor("propertyDuplicatedInChild");
+ assertThat(propertyOverriddenInChild).isPresent();
+ }
+
+ @Test
+ public void testGetTransientPropertyDescriptors() {
+ // given
+ ManagedType> managedType = entityManager.getMetamodel().entity(CalculatedEntity.class);
+
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.introspect(managedType);
+
+ // then
+ assertThat(result.getTransientPropertyDescriptors()).extracting(AttributePropertyDescriptor::getName)
+ .containsOnly("fieldFun",
+ "fieldMem",
+ "hideField",
+ "logic",
+ "transientModifier",
+ "parentTransientModifier",
+ "parentTransient",
+ "parentTransientGetter",
+ "uppercaseGetterIgnore",
+ "hideFieldFunction",
+ "transientModifierGraphQLIgnore",
+ "customLogic",
+ "parentTransientModifierGraphQLIgnore",
+ "ignoredTransientValue",
+ "something",
+ "parentTransientGraphQLIgnore");
+ ;
+ }
+
+ @Test
+ public void testGetPersistentPropertyDescriptors() {
+ // given
+ ManagedType> managedType = entityManager.getMetamodel().entity(CalculatedEntity.class);
+
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.introspect(managedType);
+
+ // then
+ assertThat(result.getPersistentPropertyDescriptors()).extracting(AttributePropertyDescriptor::getName)
+ .containsOnly("Uppercase",
+ "title",
+ "parentGraphQLIgnore",
+ "parentGraphQLIgnoreGetter",
+ "propertyIgnoredOnGetter",
+ "id",
+ "info",
+ "parentTransientGraphQLIgnoreGetter",
+ "protectedGetter",
+ "parentField",
+ "UppercaseGetter",
+ "propertyDuplicatedInChild",
+ "age");
+ }
+
+ @Test
+ public void testGetIgnoredPropertyDescriptors() {
+ // given
+ ManagedType> managedType = entityManager.getMetamodel().entity(CalculatedEntity.class);
+
+ // when
+ EntityIntrospectionResult result = EntityIntrospector.introspect(managedType);
+
+ // then
+ assertThat(result.getIgnoredPropertyDescriptors()).extracting(AttributePropertyDescriptor::getName)
+ .containsOnly("uppercaseGetterIgnore",
+ "hideFieldFunction",
+ "parentGraphQLIgnore",
+ "parentGraphQLIgnoreGetter",
+ "transientModifierGraphQLIgnore",
+ "propertyIgnoredOnGetter",
+ "parentTransientModifierGraphQLIgnore",
+ "ignoredTransientValue",
+ "hideField",
+ "parentTransientGraphQLIgnoreGetter",
+ "parentTransientGraphQLIgnore");
+ }
+
+ @Test
+ public void shouldIntrospectEntityWithCustomMetamodel() {
+ //given
+ EntityType entity = entityManager.getMetamodel()
+ .entity(ClassWithCustomMetamodel.class);
+
+ //when
+ EntityIntrospectionResult result = EntityIntrospector.introspect(entity);
+
+ //then
+ assertThat(result.getPropertyDescriptors())
+ .extracting(AttributePropertyDescriptor::getName)
+ .containsOnly("id",
+ "publicValue",
+ "protectedValue",
+ "ignoredProtectedValue");
+ }
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java
deleted file mode 100644
index 4fcc65c8c..000000000
--- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-package com.introproventures.graphql.jpa.query.schema.impl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.when;
-
-import java.util.Optional;
-
-import javax.persistence.metamodel.Attribute;
-
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult;
-import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor;
-import com.introproventures.graphql.jpa.query.schema.model.calculated.CalculatedEntity;
-import com.introproventures.graphql.jpa.query.schema.model.calculated.ParentCalculatedEntity;
-
-public class IntrospectionUtilsTest {
-
- // given
- private final Class entity = CalculatedEntity.class;
-
- @Test(expected = RuntimeException.class)
- public void testIsTransientNonExisting() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity, "notFound")).isFalse();
- }
-
- @Test(expected = RuntimeException.class)
- public void testIsIgnoredNonExisting() throws Exception {
- // then
- assertThat(IntrospectionUtils.isIgnored(entity, "notFound")).isFalse();
- }
-
- @Test
- public void testIsTransientClass() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity, "class")).isFalse();
- }
-
- @Test
- public void testIsTransientFunction() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity, "fieldFun")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "hideFieldFunction")).isFalse();
- }
-
- @Test
- public void testIsPersistentFunction() throws Exception {
- // then
- assertThat(IntrospectionUtils.isPesistent(entity, "fieldFun")).isFalse();
- assertThat(IntrospectionUtils.isPesistent(entity, "hideFieldFunction")).isTrue();
- }
-
- @Test
- public void testIsTransientFields() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity, "fieldFun")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "fieldMem")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "hideField")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "logic")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "transientModifier")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "parentTransientModifier")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "parentTransient")).isTrue();
- assertThat(IntrospectionUtils.isTransient(entity, "parentTransientGetter")).isTrue();
- }
-
- @Test
- public void testNotTransientFields() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity, "id")).isFalse();
- assertThat(IntrospectionUtils.isTransient(entity, "info")).isFalse();
- assertThat(IntrospectionUtils.isTransient(entity, "title")).isFalse();
- assertThat(IntrospectionUtils.isTransient(entity, "parentField")).isFalse();
- }
-
- @Test
- public void testByPassSetMethod() throws Exception {
- // then
- assertThat(IntrospectionUtils.isTransient(entity,"something")).isFalse();
- }
-
- @Test
- public void shouldIgnoreMethodsThatAreAnnotatedWithGraphQLIgnore() {
- //then
- assertThat(IntrospectionUtils.isIgnored(entity, "propertyIgnoredOnGetter")).isTrue();
- assertThat(IntrospectionUtils.isIgnored(entity, "ignoredTransientValue")).isTrue();
- assertThat(IntrospectionUtils.isIgnored(entity, "hideField")).isTrue();
- assertThat(IntrospectionUtils.isIgnored(entity, "parentGraphQLIgnore")).isTrue();
-
- assertThat(IntrospectionUtils.isIgnored(entity, "transientModifier")).isFalse();
- assertThat(IntrospectionUtils.isIgnored(entity, "parentTransientModifier")).isFalse();
- assertThat(IntrospectionUtils.isIgnored(entity, "parentTransient")).isFalse();
- assertThat(IntrospectionUtils.isIgnored(entity, "parentTransientGetter")).isFalse();
- }
-
- @Test
- public void shouldNotIgnoreMethodsThatAreNotAnnotatedWithGraphQLIgnore() {
- //then
- assertThat(IntrospectionUtils.isNotIgnored(entity, "propertyIgnoredOnGetter")).isFalse();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "ignoredTransientValue")).isFalse();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "hideField")).isFalse();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "parentGraphQLIgnore")).isFalse();
-
- assertThat(IntrospectionUtils.isNotIgnored(entity, "transientModifier")).isTrue();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransientModifier")).isTrue();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransient")).isTrue();
- assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransientGetter")).isTrue();
- }
-
- @SuppressWarnings("rawtypes")
- @Test
- public void shouldGetClassesInHierarchy() {
- //when
- Class[] result = IntrospectionUtils.introspect(entity)
- .getClasses()
- .toArray(Class[]::new);
-
- //then
- assertThat(result).containsExactly(CalculatedEntity.class,
- ParentCalculatedEntity.class,
- Object.class);
- }
-
- @Test
- public void testGetPropertyDescriptorsSchemaDescription() throws Exception {
- // when
- EntityIntrospectionResult result = IntrospectionUtils.introspect(CalculatedEntity.class);
-
- // then
- assertThat(result.getPropertyDescriptors())
- .extracting(AttributePropertyDescriptor::getSchemaDescription)
- .filteredOn(Optional::isPresent)
- .extracting(Optional::get)
- .containsOnly("title",
- "transientModifier",
- "i desc member",
- "i desc function",
- "getParentTransientGetter",
- "parentTransientModifier");
- }
-
- @Test
- public void testGetPropertyDescriptorSchemaDescriptionByAttribute() throws Exception {
- Attribute, ?> attribute = Mockito.mock(Attribute.class);
-
- when(attribute.getName()).thenReturn("title");
-
- // when
- Optional result = IntrospectionUtils.introspect(CalculatedEntity.class)
- .getPropertyDescriptor(attribute);
- // then
- assertThat(result.isPresent()).isTrue();
- }
-
- @Test
- public void testGetParentEntitySchemaDescription() throws Exception {
- // when
- EntityIntrospectionResult result = IntrospectionUtils.introspect(CalculatedEntity.class);
-
- // then
- assertThat(result.getSchemaDescription()).contains("ParentCalculatedEntity description");
- assertThat(result.hasSchemaDescription()).isTrue();
- }
-
-}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java
index 250e2b901..b08e3dd89 100644
--- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java
@@ -1,5 +1,8 @@
package com.introproventures.graphql.jpa.query.schema.model.calculated;
+import java.time.LocalDate;
+import java.time.Period;
+
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;
@@ -52,6 +55,26 @@ public class CalculatedEntity extends ParentCalculatedEntity {
String info;
+ @GraphQLDescription("Uppercase")
+ String Uppercase;
+
+ private Integer age;
+
+ private Integer getAge(){
+ return Period.between(LocalDate.now(),
+ LocalDate.of(2000, 1, 1))
+ .getYears();
+ }
+
+ private Integer protectedGetter;
+
+ @GraphQLDescription("protectedGetter")
+ protected Integer getProtectedGetter(){
+ return protectedGetter;
+ }
+
+ String UppercaseGetter;
+
@GraphQLDescription("transientModifier")
transient Integer transientModifier; // transient property
@@ -70,6 +93,8 @@ public class CalculatedEntity extends ParentCalculatedEntity {
String hideField = "hideField";
String propertyIgnoredOnGetter;
+
+ String propertyDuplicatedInChild;
@Transient
@GraphQLDescription("i desc function")
@@ -82,6 +107,7 @@ public boolean isCustomLogic() {
return false;
}
+ @GraphQLIgnore
public String getHideFieldFunction() {
return "getHideFieldFunction";
}
@@ -98,4 +124,17 @@ public String getPropertyIgnoredOnGetter() {
public String getIgnoredTransientValue(){
return "IgnoredTransientValue";
}
+
+ @Transient
+ @GraphQLDescription("UppercaseGetter")
+ public String getUppercaseGetter() {
+ return Uppercase;
+ }
+
+ // transient getter
+ @GraphQLIgnore
+ public String getUppercaseGetterIgnore() {
+ return UppercaseGetter;
+ }
+
}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java
index 83527d7fb..ad3803344 100644
--- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java
@@ -31,20 +31,21 @@ public class ParentCalculatedEntity {
@GraphQLIgnore
private String parentGraphQLIgnore;
+ @Transient // transient getter property
private String parentTransientGetter;
private String parentGraphQLIgnoreGetter;
- private String parentTransientGraphQLIgnoreGetter;
-
- @Transient // transient getter property
+ private String parentTransientGraphQLIgnoreGetter;
+
+ private String propertyDuplicatedInChild;
+
@GraphQLDescription("getParentTransientGetter")
public String getParentTransientGetter() {
return parentTransientGetter;
}
@GraphQLIgnore
- @Transient // transient getter property
public String getParentTransientGraphQLIgnoreGetter() {
return parentTransientGraphQLIgnoreGetter;
}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel.java
new file mode 100644
index 000000000..3a551c721
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel.java
@@ -0,0 +1,52 @@
+package com.introproventures.graphql.jpa.query.schema.model.metamodel;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
+
+@Entity
+public class ClassWithCustomMetamodel {
+
+ @Id
+ private Long id;
+
+ private String publicValue;
+
+ private String protectedValue;
+
+ private String ignoredProtectedValue;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getPublicValue() {
+ return publicValue;
+ }
+
+ public void setPublicValue(String publicValue) {
+ this.publicValue = publicValue;
+ }
+
+ protected String getProtectedValue() {
+ return protectedValue;
+ }
+
+ protected void setProtectedValue(String protectedValue) {
+ this.protectedValue = protectedValue;
+ }
+
+ @GraphQLIgnore
+ protected String getIgnoredProtectedValue() {
+ return ignoredProtectedValue;
+ }
+
+ protected void setIgnoredProtectedValue(String ignoredProtectedValue) {
+ this.ignoredProtectedValue = ignoredProtectedValue;
+ }
+}
diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel_.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel_.java
new file mode 100644
index 000000000..e45ef03b8
--- /dev/null
+++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/metamodel/ClassWithCustomMetamodel_.java
@@ -0,0 +1,18 @@
+package com.introproventures.graphql.jpa.query.schema.model.metamodel;
+
+import javax.persistence.metamodel.SingularAttribute;
+import javax.persistence.metamodel.StaticMetamodel;
+
+@StaticMetamodel(ClassWithCustomMetamodel.class)
+public class ClassWithCustomMetamodel_ {
+
+ public static volatile SingularAttribute id;
+ public static volatile SingularAttribute publicValue;
+ public static volatile SingularAttribute protectedValue;
+ public static volatile SingularAttribute ignoredProtectedValue;
+
+ public static final String ID = "id";
+ public static final String PUBLIC_VALUE = "publicValue";
+ public static final String PROTECTED_VALUE = "protectedValue";
+ public static final String IGNORED_PROTECTED_VALUE = "ignoredProtectedValue";
+}