org.eclipse.jnosql.mapping.reflection.Reflections Maven / Gradle / Ivy
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
*
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
*
* Otavio Santana
* Maximillian Arruda
*/
package org.eclipse.jnosql.mapping.reflection;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.nosql.Column;
import jakarta.nosql.DiscriminatorValue;
import jakarta.nosql.Entity;
import jakarta.nosql.Id;
import jakarta.nosql.DiscriminatorColumn;
import jakarta.nosql.Inheritance;
import jakarta.nosql.MappedSuperclass;
import org.eclipse.jnosql.mapping.metadata.InheritanceMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
/**
* Utilitarian class to reflection
*/
@ApplicationScoped
public class Reflections {
private static final Logger LOGGER = Logger.getLogger(Reflections.class.getName());
private static final Predicate IS_ID_ANNOTATION = Id.class.getName()::equals;
private static final Predicate IS_COLUMN_ANNOTATION = Column.class.getName()::equals;
private static final Predicate IS_NOSQL_ANNOTATION = IS_ID_ANNOTATION.or(IS_COLUMN_ANNOTATION);
/**
* Return The Object from the Column.
*
* @param object the object
* @param field the field to return object
* @return - the field value in Object
*/
Object getValue(Object object, Field field) {
try {
return field.get(object);
} catch (Exception exception) {
LOGGER.log(Level.FINEST, "There is an issue with returning value from this field.", exception);
}
return null;
}
/**
* Set the field in the Object.
*
* @param object the object
* @param field the field to return object
* @param value the value to object
* @return - if the operation was executed with success
*/
boolean setValue(Object object, Field field, Object value) {
try {
field.set(object, value);
} catch (Exception exception) {
LOGGER.log(Level.FINEST, "There is an issue with setting value from this field.", exception);
return false;
}
return true;
}
/**
* Create new instance of this class.
*
* @param constructor the constructor
* @param the instance type
* @return the new instance that class
*/
public static T newInstance(Constructor constructor) {
try {
return constructor.newInstance();
} catch (Exception exception) {
LOGGER.log(Level.FINEST, "There is an issue to creating an entity from this constructor", exception);
return null;
}
}
/**
* Create new instance of this class.
*
* @param type the class's type
* @param the instance type
* @return the new instance that class
*/
public static T newInstance(Class type) {
try {
Constructor constructor = getConstructor(type);
return newInstance(constructor);
} catch (Exception exception) {
LOGGER.log(Level.FINEST, "There is an issue to creating an entity from this constructor", exception);
return null;
}
}
/**
* Make the given field accessible, explicitly setting it accessible
* if necessary. The setAccessible(true) method is only
* called when actually necessary, to avoid unnecessary
* conflicts with a JVM SecurityManager (if active).
*
* @param field field the field to make accessible
*/
void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier
.isPublic(field.getDeclaringClass().getModifiers()))
&& !field.isAccessible()) {
field.setAccessible(true);
}
}
/**
* Make the given a constructor class accessible, explicitly setting it accessible
* if necessary. The setAccessible(true) method is only
* called when actually necessary, to avoid unnecessary
* conflicts with a JVM SecurityManager (if active).
*
* @param type the class constructor accessible
* @param the entity type
* @return the constructor class
* @throws ConstructorException when the constructor has public and default
*/
public static Constructor getConstructor(Class type) {
final Predicate> defaultConstructorPredicate = c -> c.getParameterCount() == 0;
final Predicate> customConstructorPredicate = c -> {
for (Parameter parameter : c.getParameters()) {
if (hasNoSQLAnnotation(parameter)) {
return true;
}
}
return false;
};
List> constructors = Stream.
of(type.getDeclaredConstructors())
.filter(defaultConstructorPredicate.or(customConstructorPredicate))
.toList();
if (constructors.isEmpty()) {
throw new ConstructorException(type);
}
Optional> publicConstructor = constructors
.stream()
.sorted(ConstructorComparable.INSTANCE)
.filter(c -> Modifier.isPublic(c.getModifiers()))
.findFirst();
if (publicConstructor.isPresent()) {
return (Constructor) publicConstructor.get();
}
Constructor> constructor = constructors.get(0);
constructor.setAccessible(true);
return (Constructor) constructor;
}
/**
* Checks if the {@link Parameter} instance is annotated with
* Jakarta NoSQL annotations (@{@link Id} or @{@link Column}).
*
* @param parameter the parameter
* @return if the provided {@link Parameter} instance is annotated with
* Jakarta NoSQL annotations (@{@link Id} or @{@link Column}).
*/
static boolean hasNoSQLAnnotation(Parameter parameter) {
return parameter != null && Arrays.stream(parameter.getAnnotations())
.map(Annotation::annotationType)
.map(Class::getName)
.anyMatch(IS_NOSQL_ANNOTATION);
}
/**
* Returns the name of the entity. So it tries to read the {@link Entity} otherwise
* {@link Class#getSimpleName()}
*
* @param entity the class to read
* @return the {@link Entity} when is not blank otherwise {@link Class#getSimpleName()}
* @throws NullPointerException when entity is null
*/
String getEntityName(Class> entity) {
requireNonNull(entity, "class entity is required");
if (isInheritance(entity)) {
return readEntity(entity.getSuperclass());
}
return readEntity(entity);
}
/**
* Returns the fields from the entity class
*
* @param type the entity class
* @return the list of fields that is annotated with either {@link Column} or
* {@link Id}
* @throws NullPointerException when class entity is null
*/
List getFields(Class> type) {
requireNonNull(type, "class entity is required");
List fields = new ArrayList<>();
if (isMappedSuperclass(type)) {
fields.addAll(getFields(type.getSuperclass()));
}
Predicate hasColumnAnnotation = f -> f.getAnnotation(Column.class) != null;
Predicate hasIdAnnotation = f -> f.getAnnotation(Id.class) != null;
Stream.of(type.getDeclaredFields())
.filter(hasColumnAnnotation.or(hasIdAnnotation))
.forEach(fields::add);
return fields;
}
/**
* Checks if the class is annotated with {@link MappedSuperclass} or
* {@link Inheritance}
*
* @param type the entity class
* @return if the class is annotated
* @throws NullPointerException when type is null
*/
boolean isMappedSuperclass(Class> type) {
requireNonNull(type, "class entity is required");
Class> superclass = type.getSuperclass();
return superclass.getAnnotation(MappedSuperclass.class) != null
|| superclass.getAnnotation(Inheritance.class) != null;
}
/**
* Checks if the field is annotated with {@link Column}
*
* @param field the field
* @return if the field is annotated with {@link Column}
* @throws NullPointerException when the field is null
*/
boolean isIdField(Field field) {
requireNonNull(field, "field is required");
return field.getAnnotation(Id.class) != null;
}
/**
* Gets the name from the field, so it reads the {@link Column#value()}
* otherwise {@link Field#getName()}
*
* @param field the fields
* @return the column name
* @throws NullPointerException when the field is null
*/
String getColumnName(Field field) {
requireNonNull(field, "field is required");
return Optional.ofNullable(field.getAnnotation(Column.class))
.map(Column::value)
.filter(StringUtils::isNotBlank)
.orElse(field.getName());
}
/**
* Gets the id name, so it reads the {@link Id#value()} otherwise {@link Field#getName()}
*
* @param field the field
* @return the column name
* @throws NullPointerException when the field is null
*/
String getIdName(Field field) {
requireNonNull(field, "field is required");
return Optional.ofNullable(field.getAnnotation(Id.class))
.map(Id::value)
.filter(StringUtils::isNotBlank)
.orElse(field.getName());
}
/**
* Reads the type annotation and checks if the inheritance has an
* {@link Inheritance} annotation.
* If it has, it will return the {@link InheritanceMetadata} otherwise it will return
* {@link Optional#empty()}
*
* @param type the type class
* @return the {@link InheritanceMetadata} or {@link Optional#empty()}
* @throws NullPointerException when type is null
*/
Optional getInheritance(Class> type) {
Objects.requireNonNull(type, "entity is required");
if (isInheritance(type)) {
Class> parent = type.getSuperclass();
String discriminatorColumn = getDiscriminatorColumn(parent);
String discriminatorValue = getDiscriminatorValue(type);
return Optional.of(new InheritanceMetadata(discriminatorValue, discriminatorColumn,
parent, type));
} else if (type.getAnnotation(Inheritance.class) != null) {
String discriminatorColumn = getDiscriminatorColumn(type);
String discriminatorValue = getDiscriminatorValue(type);
return Optional.of(new InheritanceMetadata(discriminatorValue, discriminatorColumn,
type, type));
}
return Optional.empty();
}
/**
* Check if the entity has the {@link Inheritance} annotation
*
* @param entity the entity
* @return true if it has the {@link Inheritance} annotation
*/
boolean hasInheritanceAnnotation(Class> entity) {
Objects.requireNonNull(entity, "entity is required");
return entity.getAnnotation(Inheritance.class) != null;
}
/**
* Retrieves the User-Defined Type (UDT) name associated with the given field.
*
*
* This method retrieves the UDT name specified in the {@link Column} annotation of the provided field.
* If the field is not annotated with {@link Column}, or if the UDT name is blank or not specified,
* this method returns {@code null}.
*
*
* @param field the field from which to retrieve the UDT name
* @return the UDT name specified in the {@link Column} annotation of the field, or {@code null} if not specified
* @throws NullPointerException if the field is null
*/
public String getUDTName(Field field) {
Objects.requireNonNull(field, "field is required");
return Optional.ofNullable(field.getAnnotation(Column.class))
.map(Column::udt)
.filter(StringUtils::isNotBlank)
.orElse(null);
}
private String getDiscriminatorColumn(Class> parent) {
return Optional
.ofNullable(parent.getAnnotation(DiscriminatorColumn.class))
.map(DiscriminatorColumn::value)
.orElse(DiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN);
}
private String getDiscriminatorValue(Class> entity) {
return Optional
.ofNullable(entity.getAnnotation(DiscriminatorValue.class))
.map(DiscriminatorValue::value)
.orElse(entity.getSimpleName());
}
private String readEntity(Class> entity) {
return Optional.ofNullable(entity.getAnnotation(Entity.class))
.map(Entity::value)
.filter(StringUtils::isNotBlank)
.orElse(entity.getSimpleName());
}
private boolean isInheritance(Class> entity) {
Class> superclass = entity.getSuperclass();
return superclass.getAnnotation(Inheritance.class) != null;
}
}