Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
*/
package org.springframework.data.core;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentLruCache;
Expand All @@ -29,24 +31,29 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @author Kamil Krzywański
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
class ClassTypeInformation<S> extends TypeDiscoverer<S> {

private static final ConcurrentLruCache<ResolvableType, ClassTypeInformation<?>> cache = new ConcurrentLruCache<>(128,
private static final ConcurrentLruCache<ResolvableTypeHolder, ClassTypeInformation<?>> cache = new ConcurrentLruCache<>(128,
ClassTypeInformation::new);

private static final ConcurrentLruCache<Class<?>, ResolvableType> resolvableTypeCache = new ConcurrentLruCache<>(128,
ResolvableType::forClass);

private final Class<S> type;

ClassTypeInformation(ResolvableTypeHolder resolvableTypeHolder) {
this(resolvableTypeHolder.type, resolvableTypeHolder.field);
}

ClassTypeInformation(Class<?> type) {
this(ResolvableType.forType(type));
this(ResolvableType.forType(type), null);
}

ClassTypeInformation(ResolvableType type) {
super(type);
ClassTypeInformation(ResolvableType type, @Nullable Field field) {
super(type, field);
this.type = (Class<S>) type.resolve(Object.class);
}

Expand All @@ -70,7 +77,7 @@ public static <S> ClassTypeInformation<S> from(Class<S> type) {
return from(resolvableTypeCache.get(type));
}

static <S> ClassTypeInformation<S> from(ResolvableType type) {
static <S> ClassTypeInformation<S> from(ResolvableType type, @Nullable Field field) {

Assert.notNull(type, "Type must not be null");

Expand All @@ -84,7 +91,11 @@ static <S> ClassTypeInformation<S> from(ResolvableType type) {
return (ClassTypeInformation<S>) TypeInformation.MAP;
}

return (ClassTypeInformation<S>) cache.get(type);
return (ClassTypeInformation<S>) cache.get(new ResolvableTypeHolder(type, field));
}

static <S> ClassTypeInformation<S> from(ResolvableType type) {
return from(type, null);
}

@Override
Expand All @@ -111,4 +122,7 @@ public TypeInformation<? extends S> specialize(TypeInformation<?> type) {
public String toString() {
return type.getName();
}

private record ResolvableTypeHolder(ResolvableType type, @Nullable Field field){}

}
28 changes: 27 additions & 1 deletion src/main/java/org/springframework/data/core/PropertyPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.core;

import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -43,12 +44,13 @@
* @author Mark Paluch
* @author Mariusz Mączkowski
* @author Johannes Englmeier
* @author Kamil Krzywański
* @see PropertyReference
* @see TypedPropertyPath
* @see java.beans.PropertyDescriptor
*/
public interface PropertyPath extends Streamable<PropertyPath> {

Annotation[] NO_ANNOTATIONS = new Annotation[0];
/**
* Syntax sugar to create a {@link TypedPropertyPath} from a method reference to a Java beans property.
* <p>
Expand Down Expand Up @@ -211,6 +213,30 @@ default PropertyPath nested(String path) {
return SimplePropertyPath.from(lookup, getOwningType());
}

/**
* Returns the annotations declared on the underlying field.
* <p>
* This exposes annotations present directly on the represented {@link java.lang.reflect.Field}
* (field-level annotations), for example {@code @Id} or {@code @Column}.
*
* @return annotations declared on the underlying field; never {@literal null}.
*/
default Annotation[] getPropertyAnnotations(){
return NO_ANNOTATIONS;
}

/**
* Returns the {@link java.lang.reflect.Field#getModifiers() modifiers} of the underlying field.
* <p>
* This exposes the field modifier bitmask such as {@code public}, {@code protected}, {@code private},
* {@code static}, {@code final}, {@code transient}, {@code volatile}, etc.
*
* @return modifier bitmask as defined by {@link java.lang.reflect.Modifier}.
*/
default int getPropertyModifiers(){
return 0;
}

/**
* Returns an {@link Iterator Iterator of PropertyPath} that iterates over all property path segments. For example:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.data.core;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
Expand All @@ -40,6 +41,7 @@
* @author Mark Paluch
* @author Mariusz Mączkowski
* @author Johannes Englmeier
* @author Kamil Krzywański
*/
class SimplePropertyPath implements PropertyPath {

Expand All @@ -56,6 +58,8 @@ class SimplePropertyPath implements PropertyPath {
private final TypeInformation<?> typeInformation;
private final TypeInformation<?> actualTypeInformation;
private final boolean isCollection;
private final int fieldModifiers;
private final Annotation[] fieldAnnotations;

private @Nullable SimplePropertyPath next;

Expand Down Expand Up @@ -99,6 +103,8 @@ class SimplePropertyPath implements PropertyPath {
this.isCollection = this.typeInformation.isCollectionLike();
this.actualTypeInformation = this.typeInformation.getActualType() == null ? this.typeInformation
: this.typeInformation.getRequiredActualType();
this.fieldAnnotations = property.annotations;
this.fieldModifiers = property.modifiers;
}

private static @Nullable Property lookupProperty(TypeInformation<?> owningType, String name) {
Expand Down Expand Up @@ -361,14 +367,29 @@ public String toString() {
return String.format("%s.%s", owningType.getType().getSimpleName(), toDotPath());
}


@Override
public int getPropertyModifiers() {
return fieldModifiers;
}

@Override
public Annotation[] getPropertyAnnotations() {
return fieldAnnotations;
}

private static final class Property {

private final TypeInformation<?> type;
private final String path;
private final int modifiers;
private final Annotation[] annotations;

private Property(TypeInformation<?> type, String path) {
this.type = type;
this.path = path;
this.annotations = type.getFieldAnnotations();
this.modifiers = type.getFieldModifiers();
}

@Override
Expand All @@ -392,8 +413,7 @@ public int hashCode() {

@Override
public String toString() {

return "Key[" + "type=" + type + ", " + "path=" + path + ']';
return "Property[type=" + type + ", path=" + path + ']';
}
}
}
45 changes: 37 additions & 8 deletions src/main/java/org/springframework/data/core/TypeDiscoverer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package org.springframework.data.core;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -50,11 +52,13 @@
* @author Jürgen Diez
* @author Alessandro Nistico
* @author Johannes Englmeier
* @author Kamil Krzywański
*/
class TypeDiscoverer<S> implements TypeInformation<S> {

private static final ConcurrentLruCache<ResolvableType, TypeInformation<?>> CACHE = new ConcurrentLruCache<>(64,
private static final ConcurrentLruCache<TypeHolder, TypeInformation<?>> CACHE = new ConcurrentLruCache<>(64,
TypeDiscoverer::new);
private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];

private final ResolvableType resolvableType;
private final Map<String, Optional<TypeInformation<?>>> fields = new ConcurrentHashMap<>();
Expand All @@ -74,11 +78,20 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
private final Lazy<List<TypeInformation<?>>> typeArguments;

private final Lazy<List<TypeInformation<?>>> resolvedGenerics;
private final Annotation[] annotations;
private final int modifier;

private TypeDiscoverer(TypeHolder type) {
this(type.type, type.field);
}

protected TypeDiscoverer(ResolvableType type) {
this(type, null);
}

Assert.notNull(type, "Type must not be null");
protected TypeDiscoverer(ResolvableType type, @Nullable Field field) {

Assert.notNull(type, "Type must not be null");
this.resolvableType = type;
this.componentType = Lazy.of(this::doGetComponentType);
this.valueType = Lazy.of(this::doGetMapValueType);
Expand All @@ -87,13 +100,17 @@ protected TypeDiscoverer(ResolvableType type) {
.map(TypeInformation::of) // use TypeInformation comparison to remove any attachments to variableResolver
// holding the type source
.collect(Collectors.toList()));
this.modifier = field == null ? 0 : field.getModifiers();
this.annotations = field == null ? NO_ANNOTATIONS: field.getAnnotations();
}

static TypeDiscoverer<?> ofCached(ResolvableType type) {
static TypeDiscoverer<?> ofCached(ResolvableType type, @Nullable Field field) {

Assert.notNull(type, "Type must not be null");

return (TypeDiscoverer<?>) CACHE.get(type);
var typeHolder = new TypeHolder(type, field);

return (TypeDiscoverer<?>) CACHE.get(typeHolder);
}

@Override
Expand Down Expand Up @@ -209,7 +226,7 @@ public ResolvableType toResolvableType() {

@Override
public TypeInformation<?> getRawTypeInformation() {
return new ClassTypeInformation<>(ResolvableType.forRawClass(resolvableType.toClass()));
return new ClassTypeInformation<>(ResolvableType.forRawClass(resolvableType.toClass()), null);
}

@Override
Expand Down Expand Up @@ -271,7 +288,7 @@ public List<TypeInformation<?>> getParameterTypes(Method method) {
var noGenericsResolvable = !Arrays.stream(resolvableSuperType.resolveGenerics()).filter(it -> it != null).findAny()
.isPresent();

return noGenericsResolvable ? new ClassTypeInformation<>(ResolvableType.forRawClass(superType))
return noGenericsResolvable ? new ClassTypeInformation<>(ResolvableType.forRawClass(superType), null)
: TypeInformation.of(resolvableSuperType);
}

Expand Down Expand Up @@ -322,6 +339,16 @@ public TypeInformation<? extends S> specialize(TypeInformation<?> type) {
return TypeInformation.of((Class<S>) type.getType());
}

@Override
public Annotation[] getFieldAnnotations() {
return annotations;
}

@Override
public int getFieldModifiers() {
return modifier;
}

@Override
public boolean equals(@Nullable Object o) {

Expand Down Expand Up @@ -385,9 +412,9 @@ private Optional<TypeInformation<?>> getPropertyInformation(String fieldname) {
var rawType = getType();
var field = ReflectionUtils.findField(rawType, fieldname);

return field != null ? Optional.of(TypeInformation.of(ResolvableType.forField(field, resolvableType)))
return field != null ? Optional.of(TypeInformation.of(ResolvableType.forField(field, resolvableType), field))
: Optional.ofNullable(BeanUtils.getPropertyDescriptor(rawType, fieldname))
.filter(it -> it.getName().equals(fieldname)).map(it -> from(it, rawType)).map(TypeInformation::of);
.filter(it -> it.getName().equals(fieldname)).map(it -> from(it, rawType)).map(type -> TypeInformation.of(type, field));
}

private ResolvableType from(PropertyDescriptor descriptor, Class<?> rawType) {
Expand All @@ -410,4 +437,6 @@ private ResolvableType from(PropertyDescriptor descriptor, Class<?> rawType) {
private boolean isNullableWrapper() {
return NullableWrapperConverters.supports(getType());
}

private record TypeHolder(ResolvableType type,@Nullable Field field){}
}
38 changes: 35 additions & 3 deletions src/main/java/org/springframework/data/core/TypeInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package org.springframework.data.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
Expand All @@ -40,9 +42,10 @@
* @author Alessandro Nistico
* @author Johannes Englmeier
* @author Christoph Strobl
* @author Kamil Krzywański
*/
public interface TypeInformation<S> {

Annotation[] NO_ANNOTATIONS = new Annotation[0];
@SuppressWarnings("rawtypes") TypeInformation<Collection> COLLECTION = new ClassTypeInformation<>(Collection.class);
@SuppressWarnings("rawtypes") TypeInformation<List> LIST = new ClassTypeInformation<>(List.class);
@SuppressWarnings("rawtypes") TypeInformation<Set> SET = new ClassTypeInformation<>(Set.class);
Expand All @@ -56,12 +59,16 @@ public interface TypeInformation<S> {
* @return will never be {@literal null}.
* @since 3.0
*/
static TypeInformation<?> of(ResolvableType type) {
static TypeInformation<?> of(ResolvableType type, @Nullable Field field) {

Assert.notNull(type, "Type must not be null");

return type.hasGenerics() || (type.isArray() && type.getComponentType().hasGenerics()) //
|| (type.getType() instanceof TypeVariable) ? TypeDiscoverer.ofCached(type) : ClassTypeInformation.from(type);
|| (type.getType() instanceof TypeVariable) ? TypeDiscoverer.ofCached(type, field) : ClassTypeInformation.from(type, field);
}

static TypeInformation<?> of(ResolvableType type) {
return of(type, null);
}

/**
Expand Down Expand Up @@ -376,6 +383,31 @@ default boolean isSubTypeOf(Class<?> type) {
return !type.equals(getType()) && type.isAssignableFrom(getType());
}

/**
* Returns the annotations declared on the underlying field.
* <p>
* This exposes annotations present directly on the represented {@link java.lang.reflect.Field}
* (field-level annotations), for example {@code @Id} or {@code @Column}.
*
* @return annotations declared on the underlying field; never {@literal null}.
*/
default Annotation[] getFieldAnnotations(){
return NO_ANNOTATIONS;
}

/**
* Returns the {@link java.lang.reflect.Field#getModifiers() modifiers} of the underlying field.
* <p>
* This exposes the field modifier bitmask such as {@code public}, {@code protected}, {@code private},
* {@code static}, {@code final}, {@code transient}, {@code volatile}, etc.
*
* @return modifier bitmask as defined by {@link java.lang.reflect.Modifier}.
*/
default int getFieldModifiers(){
return 0;
}


/**
* Returns the {@link TypeDescriptor} equivalent of this {@link TypeInformation}.
*
Expand Down
Loading
Loading