Provide an API to record various runtime hints
This commit provides an API to record the need for reflection, resources, serialization, and proxies so that the runtime can be optimized accordingly. `RuntimeHints` provides an entry point to register the following: * Reflection hints: individual elements of a type can be defined, as well as a predefined categories (identified by the `MemberCategory` enum). A method or constructor hint can refine whether the executable should only be introspected or also invoked. * Resource hints: patterns using includes/excludes identify the resources to include at runtime. Resource bundles are also supported. * Java Serialization hints: types that use java serialization can be registered. * Proxy hints: both interfaces-based (JDK) proxy and class-based proxy can be defined. This commit also introduces a `TypeReference` abstraction that permits to record hints for types that are not available on the classpath, or not compiled yet (generated code). Closes gh-27829
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Base {@link TypeReference} implementation that ensures consistent behaviour
|
||||
* for {@code equals()}, {@code hashCode()}, and {@code toString()} based on
|
||||
* the {@linkplain #getCanonicalName() canonical name}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public abstract class AbstractTypeReference implements TypeReference {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof TypeReference otherReference)) {
|
||||
return false;
|
||||
}
|
||||
return getCanonicalName().equals(otherReference.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCanonicalName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A hint that describes the need for a proxy against a concrete class.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ClassProxyHint {
|
||||
|
||||
private final TypeReference targetClass;
|
||||
|
||||
private final List<TypeReference> proxiedInterfaces;
|
||||
|
||||
|
||||
private ClassProxyHint(Builder builder) {
|
||||
this.targetClass = builder.targetClass;
|
||||
this.proxiedInterfaces = builder.proxiedInterfaces.stream().distinct().toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the target class to use.
|
||||
* @param targetClass the target class of the proxy
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(TypeReference targetClass) {
|
||||
return new Builder(targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the target class to use.
|
||||
* @param targetClass the target class of the proxy
|
||||
* @return a builder for the hint
|
||||
*/
|
||||
public static Builder of(Class<?> targetClass) {
|
||||
return of(TypeReference.of(targetClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the target class of the proxy.
|
||||
* @return the target class
|
||||
*/
|
||||
public TypeReference getTargetClass() {
|
||||
return this.targetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interfaces to be proxied.
|
||||
* @return the interfaces that the proxy should implement
|
||||
*/
|
||||
public List<TypeReference> getProxiedInterfaces() {
|
||||
return this.proxiedInterfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ClassProxyHint that = (ClassProxyHint) o;
|
||||
return this.targetClass.equals(that.targetClass)
|
||||
&& this.proxiedInterfaces.equals(that.proxiedInterfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.targetClass, this.proxiedInterfaces);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link ClassProxyHint}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final TypeReference targetClass;
|
||||
|
||||
private final LinkedList<TypeReference> proxiedInterfaces = new LinkedList<>();
|
||||
|
||||
|
||||
public Builder(TypeReference targetClass) {
|
||||
this.targetClass = targetClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the proxy should implement.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder proxiedInterfaces(TypeReference... proxiedInterfaces) {
|
||||
this.proxiedInterfaces.addAll(Arrays.asList(proxiedInterfaces));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the proxy should implement.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder proxiedInterfaces(Class<?>... proxiedInterfaces) {
|
||||
this.proxiedInterfaces.addAll(Arrays.stream(proxiedInterfaces)
|
||||
.map(TypeReference::of).collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ClassProxyHint} based on the state of this builder.
|
||||
* @return a class proxy hint
|
||||
*/
|
||||
public ClassProxyHint build() {
|
||||
return new ClassProxyHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A hint that describes the need for reflection on a {@link Method} or
|
||||
* {@link Constructor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ExecutableHint extends MemberHint {
|
||||
|
||||
private final List<TypeReference> parameterTypes;
|
||||
|
||||
private final List<ExecutableMode> modes;
|
||||
|
||||
|
||||
private ExecutableHint(Builder builder) {
|
||||
super(builder.name);
|
||||
this.parameterTypes = List.copyOf(builder.parameterTypes);
|
||||
this.modes = List.copyOf(builder.modes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the parameter types of a constructor.
|
||||
* @param parameterTypes the parameter types of the constructor
|
||||
* @return a builder
|
||||
*/
|
||||
public static Builder ofConstructor(List<TypeReference> parameterTypes) {
|
||||
return new Builder("<init>", parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder with the name and parameters types of a method.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types of the method
|
||||
* @return a builder
|
||||
*/
|
||||
public static Builder ofMethod(String name, List<TypeReference> parameterTypes) {
|
||||
return new Builder(name, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parameter types of the executable.
|
||||
* @return the parameter types
|
||||
* @see Executable#getParameterTypes()
|
||||
*/
|
||||
public List<TypeReference> getParameterTypes() {
|
||||
return this.parameterTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain ExecutableMode modes} that apply to this hint.
|
||||
* @return the modes
|
||||
*/
|
||||
public List<ExecutableMode> getModes() {
|
||||
return this.modes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link ExecutableHint}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<TypeReference> parameterTypes;
|
||||
|
||||
private final Set<ExecutableMode> modes = new LinkedHashSet<>();
|
||||
|
||||
|
||||
private Builder(String name, List<TypeReference> parameterTypes) {
|
||||
this.name = name;
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified {@linkplain ExecutableMode mode} if necessary.
|
||||
* @param mode the mode to add
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withMode(ExecutableMode mode) {
|
||||
this.modes.add(mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@linkplain ExecutableMode modes} to use.
|
||||
* @param modes the mode to use
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder setModes(ExecutableMode... modes) {
|
||||
this.modes.clear();
|
||||
if (!ObjectUtils.isEmpty(modes)) {
|
||||
this.modes.addAll(Arrays.asList(modes));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ExecutableHint} based on the state of this builder.
|
||||
* @return an executable hint
|
||||
*/
|
||||
public ExecutableHint build() {
|
||||
return new ExecutableHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
|
||||
/**
|
||||
* Represent the need of reflection for a given {@link Executable}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum ExecutableMode {
|
||||
|
||||
/**
|
||||
* Only retrieving the {@link Executable} and its metadata is required.
|
||||
*/
|
||||
INTROSPECT,
|
||||
|
||||
/**
|
||||
* Full reflection support is required, including the ability to invoke
|
||||
* the {@link Executable}.
|
||||
*/
|
||||
INVOKE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* A hint that describes the need of reflection on a {@link Field}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class FieldHint extends MemberHint {
|
||||
|
||||
private final boolean allowWrite;
|
||||
|
||||
private final boolean allowUnsafeAccess;
|
||||
|
||||
|
||||
private FieldHint(Builder builder) {
|
||||
super(builder.name);
|
||||
this.allowWrite = builder.allowWrite;
|
||||
this.allowUnsafeAccess = builder.allowUnsafeAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether setting the value of the field should be allowed.
|
||||
* @return {@code true} to allow {@link Field#set(Object, Object)}.
|
||||
*/
|
||||
public boolean isAllowWrite() {
|
||||
return this.allowWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether if using {@code Unsafe} on the field should be allowed.
|
||||
* @return {@code true} to allow unsafe access
|
||||
*/
|
||||
public boolean isAllowUnsafeAccess() {
|
||||
return this.allowUnsafeAccess;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link FieldHint}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final String name;
|
||||
|
||||
private boolean allowWrite;
|
||||
|
||||
private boolean allowUnsafeAccess;
|
||||
|
||||
|
||||
public Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if setting the value of the field should be allowed.
|
||||
* @param allowWrite {@code true} to allow {@link Field#set(Object, Object)}
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder allowWrite(boolean allowWrite) {
|
||||
this.allowWrite = allowWrite;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify if using {@code Unsafe} on the field should be allowed.
|
||||
* @param allowUnsafeAccess {@code true} to allow unsafe access
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder allowUnsafeAccess(boolean allowUnsafeAccess) {
|
||||
this.allowUnsafeAccess = allowUnsafeAccess;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link FieldHint} based on the state of this builder.
|
||||
* @return a field hint
|
||||
*/
|
||||
public FieldHint build() {
|
||||
return new FieldHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Gather the need for Java serialization at runtime.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
* @see Serializable
|
||||
*/
|
||||
public class JavaSerializationHints {
|
||||
|
||||
private final Set<TypeReference> types;
|
||||
|
||||
|
||||
public JavaSerializationHints() {
|
||||
this.types = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link TypeReference types} that need to be serialized using
|
||||
* Java serialization at runtime.
|
||||
* @return a stream of {@link Serializable} types
|
||||
*/
|
||||
public Stream<TypeReference> types() {
|
||||
return this.types.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the type defined by the specified {@link TypeReference}
|
||||
* need to be serialized using java serialization.
|
||||
* @param type the type to register
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public JavaSerializationHints registerType(TypeReference type) {
|
||||
this.types.add(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the specified type need to be serialized using java
|
||||
* serialization.
|
||||
* @param type the type to register
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public JavaSerializationHints registerType(Class<? extends Serializable> type) {
|
||||
return registerType(TypeReference.of(type));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A hint that describes the need of a JDK {@link Proxy}, that is an
|
||||
* interfaces-based proxy.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class JdkProxyHint {
|
||||
|
||||
private final List<TypeReference> proxiedInterfaces;
|
||||
|
||||
|
||||
private JdkProxyHint(Builder builder) {
|
||||
this.proxiedInterfaces = List.copyOf(builder.proxiedInterfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the interfaces to be proxied.
|
||||
* @return the interfaces that the proxy should implement
|
||||
*/
|
||||
public List<TypeReference> getProxiedInterfaces() {
|
||||
return this.proxiedInterfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JdkProxyHint that = (JdkProxyHint) o;
|
||||
return this.proxiedInterfaces.equals(that.proxiedInterfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.proxiedInterfaces);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link JdkProxyHint}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final LinkedList<TypeReference> proxiedInterfaces = new LinkedList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the proxy should implement.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder proxiedInterfaces(TypeReference... proxiedInterfaces) {
|
||||
this.proxiedInterfaces.addAll(Arrays.asList(proxiedInterfaces));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified interfaces that the proxy should implement.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder proxiedInterfaces(Class<?>... proxiedInterfaces) {
|
||||
this.proxiedInterfaces.addAll(Arrays.stream(proxiedInterfaces)
|
||||
.map(TypeReference::of).collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link JdkProxyHint} based on the state of this builder.
|
||||
* @return a jdk proxy hint
|
||||
*/
|
||||
public JdkProxyHint build() {
|
||||
return new JdkProxyHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Represent predefined {@linkplain Member members} groups.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public enum MemberCategory {
|
||||
|
||||
/**
|
||||
* A category that represents public {@linkplain Field fields}.
|
||||
* @see Class#getFields()
|
||||
*/
|
||||
PUBLIC_FIELDS,
|
||||
|
||||
/**
|
||||
* A category that represents {@linkplain Class#getDeclaredFields() declared
|
||||
* fields}, that is all fields defined by the class, but not inherited ones.
|
||||
* @see Class#getDeclaredFields()
|
||||
*/
|
||||
DECLARED_FIELDS,
|
||||
|
||||
/**
|
||||
* A category that defines public {@linkplain Constructor constructors} can
|
||||
* be introspected, but not invoked.
|
||||
* @see Class#getConstructors()
|
||||
* @see ExecutableMode#INTROSPECT
|
||||
*/
|
||||
INTROSPECT_PUBLIC_CONSTRUCTORS,
|
||||
|
||||
/**
|
||||
* A category that defines {@linkplain Class#getDeclaredConstructors() all
|
||||
* constructors} can be introspected, but not invoked.
|
||||
* @see Class#getDeclaredConstructors()
|
||||
* @see ExecutableMode#INTROSPECT
|
||||
*/
|
||||
INTROSPECT_DECLARED_CONSTRUCTORS,
|
||||
|
||||
/**
|
||||
* A category that defines public {@linkplain Constructor constructors} can
|
||||
* be invoked.
|
||||
* @see Class#getConstructors()
|
||||
* @see ExecutableMode#INVOKE
|
||||
*/
|
||||
INVOKE_PUBLIC_CONSTRUCTORS,
|
||||
|
||||
/**
|
||||
* A category that defines {@linkplain Class#getDeclaredConstructors() all
|
||||
* constructors} can be invoked.
|
||||
* @see Class#getDeclaredConstructors()
|
||||
* @see ExecutableMode#INVOKE
|
||||
*/
|
||||
INVOKE_DECLARED_CONSTRUCTORS,
|
||||
|
||||
/**
|
||||
* A category that defines public {@linkplain Method methods}, including
|
||||
* inherited ones can be introspect, but not invoked.
|
||||
* @see Class#getMethods()
|
||||
* @see ExecutableMode#INTROSPECT
|
||||
*/
|
||||
INTROSPECT_PUBLIC_METHODS,
|
||||
|
||||
/**
|
||||
* A category that defines {@linkplain Class#getDeclaredMethods() all
|
||||
* methods}, excluding inherited ones can be introspected, but not invoked.
|
||||
* @see Class#getDeclaredMethods()
|
||||
* @see ExecutableMode#INTROSPECT
|
||||
*/
|
||||
INTROSPECT_DECLARED_METHODS,
|
||||
|
||||
/**
|
||||
* A category that defines public {@linkplain Method methods}, including
|
||||
* inherited ones can be invoked.
|
||||
* @see Class#getMethods()
|
||||
* @see ExecutableMode#INVOKE
|
||||
*/
|
||||
INVOKE_PUBLIC_METHODS,
|
||||
|
||||
/**
|
||||
* A category that defines {@linkplain Class#getDeclaredMethods() all
|
||||
* methods}, excluding inherited ones can be invoked.
|
||||
* @see Class#getDeclaredMethods()
|
||||
* @see ExecutableMode#INVOKE
|
||||
*/
|
||||
INVOKE_DECLARED_METHODS,
|
||||
|
||||
/**
|
||||
* A category that represents public {@linkplain Class#getClasses() inner
|
||||
* classes}. Contrary to other categories, this does not register any
|
||||
* particular reflection for them but rather make sure they are available
|
||||
* via a call to {@link Class#getClasses}.
|
||||
*/
|
||||
PUBLIC_CLASSES,
|
||||
|
||||
/**
|
||||
* A category that represents all {@linkplain Class#getDeclaredClasses()
|
||||
* inner classes}. Contrary to other categories, this does not register any
|
||||
* particular reflection for them but rather make sure they are available
|
||||
* via a call to {@link Class#getDeclaredClasses}.
|
||||
*/
|
||||
DECLARED_CLASSES;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
/**
|
||||
* Base hint that describes the need for reflection on a {@link Member}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public abstract class MemberHint {
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
protected MemberHint(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the member.
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.hint.ClassProxyHint.Builder;
|
||||
|
||||
/**
|
||||
* Gather the need of using proxies at runtime.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ProxyHints {
|
||||
|
||||
private final Set<JdkProxyHint> jdkProxies = new LinkedHashSet<>();
|
||||
|
||||
private final Set<ClassProxyHint> classProxies = new LinkedHashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Return the interfaces-based proxies that are required.
|
||||
* @return a stream of {@link JdkProxyHint}
|
||||
*/
|
||||
public Stream<JdkProxyHint> jdkProxies() {
|
||||
return this.jdkProxies.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class-based proxies that are required.
|
||||
* @return a stream of {@link ClassProxyHint}
|
||||
*/
|
||||
public Stream<ClassProxyHint> classProxies() {
|
||||
return this.classProxies.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link JdkProxyHint}.
|
||||
* @param hint the supplier to the hint
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ProxyHints registerJdkProxy(Supplier<JdkProxyHint> hint) {
|
||||
this.jdkProxies.add(hint.get());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that a JDK proxy implementing the interfaces defined by the
|
||||
* specified {@link TypeReference type references} is required.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ProxyHints registerJdkProxy(TypeReference... proxiedInterfaces) {
|
||||
return registerJdkProxy(() -> new JdkProxyHint.Builder()
|
||||
.proxiedInterfaces(proxiedInterfaces).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that a JDK proxy implementing the specified interfaces is
|
||||
* required.
|
||||
* @param proxiedInterfaces the interfaces the proxy should implement
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ProxyHints registerJdkProxy(Class<?>... proxiedInterfaces) {
|
||||
List<String> concreteTypes = Arrays.stream(proxiedInterfaces)
|
||||
.filter(candidate -> !candidate.isInterface()).map(Class::getName).collect(Collectors.toList());
|
||||
if (!concreteTypes.isEmpty()) {
|
||||
throw new IllegalArgumentException("Not an interface: " + concreteTypes);
|
||||
}
|
||||
return registerJdkProxy(() -> new JdkProxyHint.Builder()
|
||||
.proxiedInterfaces(proxiedInterfaces).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that a class proxy is required for the class defined by the
|
||||
* specified {@link TypeReference}.
|
||||
* @param targetClass the target class of the proxy
|
||||
* @param classProxyHint a builder to further customize the hint for that proxy
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ProxyHints registerClassProxy(TypeReference targetClass, Consumer<Builder> classProxyHint) {
|
||||
Builder builder = ClassProxyHint.of(targetClass);
|
||||
classProxyHint.accept(builder);
|
||||
this.classProxies.add(builder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that a class proxy is required for the specified class.
|
||||
* @param targetClass the target class of the proxy
|
||||
* @param classProxyHint a builder to further customize the hint for that proxy
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ProxyHints registerClassProxy(Class<?> targetClass, Consumer<Builder> classProxyHint) {
|
||||
if (targetClass.isInterface()) {
|
||||
throw new IllegalArgumentException("Should not be an interface: " + targetClass);
|
||||
}
|
||||
return registerClassProxy(TypeReference.of(targetClass), classProxyHint);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.hint.TypeHint.Builder;
|
||||
|
||||
/**
|
||||
* Gather the need for reflection at runtime.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ReflectionHints {
|
||||
|
||||
private final Map<TypeReference, TypeHint.Builder> types = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Return the types that require reflection.
|
||||
* @return the type hints
|
||||
*/
|
||||
public Stream<TypeHint> typeHints() {
|
||||
return this.types.values().stream().map(TypeHint.Builder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or customize reflection hints for the type defined by the
|
||||
* specified {@link TypeReference}.
|
||||
* @param type the type to customize
|
||||
* @param typeHint a builder to further customize hints for that type
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerType(TypeReference type, Consumer<TypeHint.Builder> typeHint) {
|
||||
Builder builder = this.types.computeIfAbsent(type, TypeHint.Builder::new);
|
||||
typeHint.accept(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register or customize reflection hints for the specified type.
|
||||
* @param type the type to customize
|
||||
* @param typeHint a builder to further customize hints for that type
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerType(Class<?> type, Consumer<TypeHint.Builder> typeHint) {
|
||||
return registerType(TypeReference.of(type), typeHint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Field}.
|
||||
* @param field the field that requires reflection
|
||||
* @param fieldHint a builder to further customize the hints of this field
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerField(Field field, Consumer<FieldHint.Builder> fieldHint) {
|
||||
return registerType(TypeReference.of(field.getDeclaringClass()),
|
||||
typeHint -> typeHint.withField(field.getName(), fieldHint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Field},
|
||||
* enabling write access.
|
||||
* @param field the field that requires reflection
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerField(Field field) {
|
||||
return registerField(field, fieldHint -> fieldHint.allowWrite(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Constructor}.
|
||||
* @param constructor the constructor that requires reflection
|
||||
* @param constructorHint a builder to further customize the hints of this
|
||||
* constructor
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerConstructor(Constructor<?> constructor, Consumer<ExecutableHint.Builder> constructorHint) {
|
||||
return registerType(TypeReference.of(constructor.getDeclaringClass()),
|
||||
typeHint -> typeHint.withConstructor(mapParameters(constructor), constructorHint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Constructor},
|
||||
* enabling {@link ExecutableMode#INVOKE}.
|
||||
* @param constructor the constructor that requires reflection
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerConstructor(Constructor<?> constructor) {
|
||||
return registerConstructor(constructor, constructorHint ->
|
||||
constructorHint.withMode(ExecutableMode.INVOKE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Method}.
|
||||
* @param method the method that requires reflection
|
||||
* @param methodHint a builder to further customize the hints of this method
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerMethod(Method method, Consumer<ExecutableHint.Builder> methodHint) {
|
||||
return registerType(TypeReference.of(method.getDeclaringClass()),
|
||||
typeHint -> typeHint.withMethod(method.getName(), mapParameters(method), methodHint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Method},
|
||||
* enabling {@link ExecutableMode#INVOKE}.
|
||||
* @param method the method that requires reflection
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerMethod(Method method) {
|
||||
return registerMethod(method, methodHint -> methodHint.withMode(ExecutableMode.INVOKE));
|
||||
}
|
||||
|
||||
private List<TypeReference> mapParameters(Executable executable) {
|
||||
return Arrays.stream(executable.getParameterTypes()).map(TypeReference::of)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link TypeReference} based on a {@link Class}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
final class ReflectionTypeReference extends AbstractTypeReference {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
@Nullable
|
||||
private final TypeReference enclosing;
|
||||
|
||||
|
||||
private ReflectionTypeReference(Class<?> type) {
|
||||
this.type = type;
|
||||
this.enclosing = (type.getEnclosingClass() != null
|
||||
? TypeReference.of(type.getEnclosingClass()) : null);
|
||||
}
|
||||
|
||||
static ReflectionTypeReference of(Class<?> type) {
|
||||
return new ReflectionTypeReference(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalName() {
|
||||
return this.type.getCanonicalName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return this.type.getPackageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSimpleName() {
|
||||
return this.type.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeReference getEnclosingType() {
|
||||
return this.enclosing;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* A hint that describes the need to access to a {@link ResourceBundle}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ResourceBundleHint {
|
||||
|
||||
private final String baseName;
|
||||
|
||||
|
||||
ResourceBundleHint(String baseName) {
|
||||
this.baseName = baseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@code baseName} of the resource bundle.
|
||||
* @return the base name
|
||||
*/
|
||||
public String getBaseName() {
|
||||
return this.baseName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.hint.ResourcePatternHint.Builder;
|
||||
|
||||
/**
|
||||
* Gather the need for resources available at runtime.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class ResourceHints {
|
||||
|
||||
private final Set<TypeReference> types;
|
||||
|
||||
private final List<Builder> resourcePatternHints;
|
||||
|
||||
private final Set<String> resourceBundleHints;
|
||||
|
||||
|
||||
public ResourceHints() {
|
||||
this.types = new HashSet<>();
|
||||
this.resourcePatternHints = new ArrayList<>();
|
||||
this.resourceBundleHints = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resources that should be made available at runtime.
|
||||
* @return a stream of {@link ResourcePatternHint}
|
||||
*/
|
||||
public Stream<ResourcePatternHint> resourcePatterns() {
|
||||
Stream<ResourcePatternHint> patterns = this.resourcePatternHints.stream().map(Builder::build);
|
||||
return (this.types.isEmpty() ? patterns
|
||||
: Stream.concat(Stream.of(typesPatternResourceHint()), patterns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource bundles that should be made available at runtime.
|
||||
* @return a stream of {@link ResourceBundleHint}
|
||||
*/
|
||||
public Stream<ResourceBundleHint> resourceBundles() {
|
||||
return this.resourceBundleHints.stream().map(ResourceBundleHint::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the resources matching the specified pattern should be
|
||||
* made available at runtime.
|
||||
* @param include a pattern of the resources to include
|
||||
* @param resourceHint a builder to further customize the resource pattern
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerPattern(String include, Consumer<Builder> resourceHint) {
|
||||
Builder builder = new Builder().includes(include);
|
||||
if (resourceHint != null) {
|
||||
resourceHint.accept(builder);
|
||||
}
|
||||
this.resourcePatternHints.add(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the resources matching the specified pattern should be
|
||||
* made available at runtime.
|
||||
* @param include a pattern of the resources to include
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerPattern(String include) {
|
||||
return registerPattern(include, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the bytecode of the type defined by the specified
|
||||
* {@link TypeReference} should be made available at runtime.
|
||||
* @param type the type to include
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerType(TypeReference type) {
|
||||
this.types.add(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the bytecode of the specified type should be made
|
||||
* available at runtime.
|
||||
* @param type the type to include
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerType(Class<?> type) {
|
||||
return registerType(TypeReference.of(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register that the resource bundle with the specified base name should
|
||||
* be made available at runtime.
|
||||
* @param baseName the base name of the resource bundle
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ResourceHints registerResourceBundle(String baseName) {
|
||||
this.resourceBundleHints.add(baseName);
|
||||
return this;
|
||||
}
|
||||
|
||||
private ResourcePatternHint typesPatternResourceHint() {
|
||||
Builder builder = new Builder();
|
||||
this.types.forEach(type -> builder.includes(toIncludePattern(type)));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private String toIncludePattern(TypeReference type) {
|
||||
StringBuilder names = new StringBuilder();
|
||||
buildName(type, names);
|
||||
String candidate = type.getPackageName() + "." + names;
|
||||
return candidate.replace(".", "/") + ".class";
|
||||
}
|
||||
|
||||
private void buildName(TypeReference type, StringBuilder sb) {
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
String typeName = (type.getEnclosingType() != null) ? "$" + type.getSimpleName() : type.getSimpleName();
|
||||
sb.insert(0, typeName);
|
||||
buildName(type.getEnclosingType(), sb);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A hint that describes resources that should be made available at runtime.
|
||||
*
|
||||
* <p>The patterns may be a simple path which has a one-to-one mapping to a
|
||||
* resource on the classpath, or alternatively may contain the special
|
||||
* {@code *} character to indicate a wildcard search.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ResourcePatternHint {
|
||||
|
||||
private final List<String> includes;
|
||||
|
||||
private final List<String> excludes;
|
||||
|
||||
|
||||
private ResourcePatternHint(Builder builder) {
|
||||
this.includes = new ArrayList<>(builder.includes);
|
||||
this.excludes = new ArrayList<>(builder.excludes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the include patterns to use to identify the resources to match.
|
||||
* @return the include patterns
|
||||
*/
|
||||
public List<String> getIncludes() {
|
||||
return this.includes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exclude patterns to use to identify the resources to match.
|
||||
* @return the exclude patterns
|
||||
*/
|
||||
public List<String> getExcludes() {
|
||||
return this.excludes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link ResourcePatternHint}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final Set<String> includes = new LinkedHashSet<>();
|
||||
|
||||
private final Set<String> excludes = new LinkedHashSet<>();
|
||||
|
||||
|
||||
/**
|
||||
* Includes the resources matching the specified pattern.
|
||||
* @param includes the include patterns
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder includes(String... includes) {
|
||||
this.includes.addAll(Arrays.asList(includes));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude resources matching the specified pattern.
|
||||
* @param excludes the excludes pattern
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder excludes(String... excludes) {
|
||||
this.excludes.addAll(Arrays.asList(excludes));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ResourcePatternHint} based on the state of this
|
||||
* builder.
|
||||
* @return a resource pattern hint
|
||||
*/
|
||||
public ResourcePatternHint build() {
|
||||
return new ResourcePatternHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
/**
|
||||
* Gather hints that can be used to optimize the application runtime.
|
||||
*
|
||||
* <p>Use of reflection can be recorded for individual members of a type, as
|
||||
* well as broader {@linkplain MemberCategory member categories}. Access to
|
||||
* resources can be specified using patterns or the base name of a resource
|
||||
* bundle.
|
||||
*
|
||||
* <p>Hints that require the need for Java serialization of proxies can be
|
||||
* recorded as well.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public class RuntimeHints {
|
||||
|
||||
private final ReflectionHints reflection = new ReflectionHints();
|
||||
|
||||
private final ResourceHints resources = new ResourceHints();
|
||||
|
||||
private final JavaSerializationHints javaSerialization = new JavaSerializationHints();
|
||||
|
||||
private final ProxyHints proxies = new ProxyHints();
|
||||
|
||||
|
||||
/**
|
||||
* Provide access to reflection-based hints.
|
||||
* @return reflection hints
|
||||
*/
|
||||
public ReflectionHints reflection() {
|
||||
return this.reflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to resource-based hints.
|
||||
* @return resource hints
|
||||
*/
|
||||
public ResourceHints resources() {
|
||||
return this.resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to serialization-based hints.
|
||||
* @return java serialization hints
|
||||
*/
|
||||
public JavaSerializationHints javaSerialization() {
|
||||
return this.javaSerialization;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to proxy-based hints.
|
||||
* @return proxy hints
|
||||
*/
|
||||
public ProxyHints proxies() {
|
||||
return this.proxies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link TypeReference} based on fully qualified name.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
final class SimpleTypeReference extends AbstractTypeReference {
|
||||
|
||||
private String canonicalName;
|
||||
|
||||
private final String packageName;
|
||||
|
||||
private final String simpleName;
|
||||
|
||||
@Nullable
|
||||
private final TypeReference enclosingType;
|
||||
|
||||
|
||||
SimpleTypeReference(String packageName, String simpleName, @Nullable TypeReference enclosingType) {
|
||||
this.packageName = packageName;
|
||||
this.simpleName = simpleName;
|
||||
this.enclosingType = enclosingType;
|
||||
}
|
||||
|
||||
static SimpleTypeReference of(String className) {
|
||||
Assert.notNull(className, "ClassName must not be null");
|
||||
if (!className.contains("$")) {
|
||||
return createTypeReference(className);
|
||||
}
|
||||
String[] elements = className.split("\\$");
|
||||
SimpleTypeReference typeReference = createTypeReference(elements[0]);
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
typeReference = new SimpleTypeReference(typeReference.getPackageName(), elements[i], typeReference);
|
||||
}
|
||||
return typeReference;
|
||||
}
|
||||
|
||||
private static SimpleTypeReference createTypeReference(String className) {
|
||||
int i = className.lastIndexOf('.');
|
||||
return new SimpleTypeReference(className.substring(0, i), className.substring(i + 1), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalName() {
|
||||
if (this.canonicalName == null) {
|
||||
StringBuilder names = new StringBuilder();
|
||||
buildName(this, names);
|
||||
this.canonicalName = this.packageName + "." + names;
|
||||
}
|
||||
return this.canonicalName;
|
||||
}
|
||||
|
||||
private static void buildName(TypeReference type, StringBuilder sb) {
|
||||
if (type == null) {
|
||||
return;
|
||||
}
|
||||
String typeName = (type.getEnclosingType() != null) ? "." + type.getSimpleName() : type.getSimpleName();
|
||||
sb.insert(0, typeName);
|
||||
buildName(type.getEnclosingType(), sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return this.packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSimpleName() {
|
||||
return this.simpleName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeReference getEnclosingType() {
|
||||
return this.enclosingType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A hint that describes the need for reflection on a type.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class TypeHint {
|
||||
|
||||
private final TypeReference type;
|
||||
|
||||
private final TypeReference reachableType;
|
||||
|
||||
private final Set<FieldHint> fields;
|
||||
|
||||
private final Set<ExecutableHint> constructors;
|
||||
|
||||
private final Set<ExecutableHint> methods;
|
||||
|
||||
private final Set<MemberCategory> memberCategories;
|
||||
|
||||
|
||||
private TypeHint(Builder builder) {
|
||||
this.type = builder.type;
|
||||
this.reachableType = builder.reachableType;
|
||||
this.memberCategories = Set.copyOf(builder.memberCategories);
|
||||
this.fields = builder.fields.values().stream().map(FieldHint.Builder::build).collect(Collectors.toSet());
|
||||
this.constructors = builder.constructors.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
|
||||
this.methods = builder.methods.values().stream().map(ExecutableHint.Builder::build).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a builder for the type defined by the specified
|
||||
* {@link TypeReference}.
|
||||
* @param type the type to use
|
||||
* @return a builder
|
||||
*/
|
||||
public static Builder of(TypeReference type) {
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
return new Builder(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type that this hint handles.
|
||||
* @return the type
|
||||
*/
|
||||
public TypeReference getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type that should be reachable for this hint to apply, or
|
||||
* {@code null} if this hint should always been applied.
|
||||
* @return the reachable type, if any
|
||||
*/
|
||||
public TypeReference getReachableType() {
|
||||
return this.reachableType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fields that require reflection.
|
||||
* @return a stream of {@link FieldHint}
|
||||
*/
|
||||
public Stream<FieldHint> fields() {
|
||||
return this.fields.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the constructors that require reflection.
|
||||
* @return a stream of {@link ExecutableHint}
|
||||
*/
|
||||
public Stream<ExecutableHint> constructors() {
|
||||
return this.constructors.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the methods that require reflection.
|
||||
* @return a stream of {@link ExecutableHint}
|
||||
*/
|
||||
public Stream<ExecutableHint> methods() {
|
||||
return this.methods.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the member categories that apply.
|
||||
* @return the member categories to enable
|
||||
*/
|
||||
public Set<MemberCategory> getMemberCategories() {
|
||||
return this.memberCategories;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder for {@link TypeHint}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final TypeReference type;
|
||||
|
||||
private TypeReference reachableType;
|
||||
|
||||
private final Map<String, FieldHint.Builder> fields = new HashMap<>();
|
||||
|
||||
private final Map<ExecutableKey, ExecutableHint.Builder> constructors = new HashMap<>();
|
||||
|
||||
private final Map<ExecutableKey, ExecutableHint.Builder> methods = new HashMap<>();
|
||||
|
||||
private final Set<MemberCategory> memberCategories = new HashSet<>();
|
||||
|
||||
|
||||
public Builder(TypeReference type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this hint conditional on the fact that the specified type
|
||||
* can be resolved.
|
||||
* @param reachableType the type that should be reachable for this
|
||||
* hint to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder onReachableType(TypeReference reachableType) {
|
||||
this.reachableType = reachableType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the field with the specified name.
|
||||
* @param name the name of the field
|
||||
* @param fieldHint a builder to further customize the hints of this field
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withField(String name, Consumer<FieldHint.Builder> fieldHint) {
|
||||
FieldHint.Builder builder = this.fields.computeIfAbsent(name, FieldHint.Builder::new);
|
||||
fieldHint.accept(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the constructor with the specified
|
||||
* parameter types.
|
||||
* @param parameterTypes the parameter types of the constructor
|
||||
* @param constructorHint a builder to further customize the hints of this
|
||||
* constructor
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withConstructor(List<TypeReference> parameterTypes, Consumer<ExecutableHint.Builder> constructorHint) {
|
||||
ExecutableKey key = new ExecutableKey("<init>", parameterTypes);
|
||||
ExecutableHint.Builder builder = this.constructors.computeIfAbsent(key,
|
||||
k -> ExecutableHint.ofConstructor(parameterTypes));
|
||||
constructorHint.accept(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the method with the specified name
|
||||
* and parameter types.
|
||||
* @param name the name of the method
|
||||
* @param parameterTypes the parameter types of the constructor
|
||||
* @param methodHint a builder to further customize the hints of this method
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withMethod(String name, List<TypeReference> parameterTypes, Consumer<ExecutableHint.Builder> methodHint) {
|
||||
ExecutableKey key = new ExecutableKey(name, parameterTypes);
|
||||
ExecutableHint.Builder builder = this.methods.computeIfAbsent(key,
|
||||
k -> ExecutableHint.ofMethod(name, parameterTypes));
|
||||
methodHint.accept(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified {@linkplain MemberCategory member categories}.
|
||||
* @param memberCategories the categories to apply
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withMembers(MemberCategory... memberCategories) {
|
||||
this.memberCategories.addAll(Arrays.asList(memberCategories));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link TypeHint} based on the state of this builder.
|
||||
* @return a type hint
|
||||
*/
|
||||
public TypeHint build() {
|
||||
return new TypeHint(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ExecutableKey {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<String> parameterTypes;
|
||||
|
||||
|
||||
private ExecutableKey(String name, List<TypeReference> parameterTypes) {
|
||||
this.name = name;
|
||||
this.parameterTypes = parameterTypes.stream().map(TypeReference::getCanonicalName)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExecutableKey that = (ExecutableKey) o;
|
||||
return this.name.equals(that.name) && this.parameterTypes.equals(that.parameterTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.name, this.parameterTypes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Type abstraction that can be used to refer to types that are not available as
|
||||
* a {@link Class} yet.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
public interface TypeReference {
|
||||
|
||||
/**
|
||||
* Return the {@linkplain Class#getCanonicalName() canonical name} of this
|
||||
* type reference.
|
||||
* @return the canonical name
|
||||
*/
|
||||
String getCanonicalName();
|
||||
|
||||
/**
|
||||
* Return the package name of this type.
|
||||
* @return the package name
|
||||
*/
|
||||
String getPackageName();
|
||||
|
||||
/**
|
||||
* Return the {@linkplain Class#getSimpleName() simple name} of this type
|
||||
* reference.
|
||||
* @return the simple name
|
||||
*/
|
||||
String getSimpleName();
|
||||
|
||||
/**
|
||||
* Return the enclosing type reference, or {@code null} if this type reference
|
||||
* does not have an enclosing type.
|
||||
* @return the enclosing type, if any
|
||||
*/
|
||||
@Nullable
|
||||
TypeReference getEnclosingType();
|
||||
|
||||
/**
|
||||
* Create an instance based on the specified type.
|
||||
* @param type the type to wrap
|
||||
* @return a type reference for the specified type
|
||||
*/
|
||||
static TypeReference of(Class<?> type) {
|
||||
return ReflectionTypeReference.of(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance based on the specified class name.
|
||||
* The format of the class name must follow {@linkplain Class#getName()},
|
||||
* in particular inner classes should be separated by a {@code $}.
|
||||
* @param className the class name of the type to wrap
|
||||
* @return a type reference for the specified class name
|
||||
*/
|
||||
static TypeReference of(String className) {
|
||||
return SimpleTypeReference.of(className);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Support for registering the need for reflection, resources, java serialization
|
||||
* and proxies.
|
||||
*/
|
||||
package org.springframework.core.hint;
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.Serializable;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.hint.JdkProxyHint.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassProxyHint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ClassProxyHintTests {
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameInstanceIsTrue() {
|
||||
ClassProxyHint hint = ClassProxyHint.of(Properties.class).build();
|
||||
assertThat(hint).isEqualTo(hint);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameTargetClassIsTrue() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
|
||||
ClassProxyHint second = ClassProxyHint.of(TypeReference.of(Properties.class)).build();
|
||||
assertThat(first).isEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameProxiedInterfacesIsTrue() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(Serializable.class).build();
|
||||
ClassProxyHint second = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(TypeReference.of(Serializable.class)).build();
|
||||
assertThat(first).isEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithDifferentTargetClassIsFalse() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
|
||||
ClassProxyHint second = ClassProxyHint.of(Hashtable.class).build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(Serializable.class, Closeable.class).build();
|
||||
ClassProxyHint second = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(TypeReference.of(Closeable.class), TypeReference.of(Serializable.class))
|
||||
.build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithDifferentProxiedInterfacesIsFalse() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(Serializable.class).build();
|
||||
ClassProxyHint second = ClassProxyHint.of(Properties.class)
|
||||
.proxiedInterfaces(TypeReference.of(Closeable.class)).build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithNonClassProxyHintIsFalse() {
|
||||
ClassProxyHint first = ClassProxyHint.of(Properties.class).build();
|
||||
JdkProxyHint second = new Builder().proxiedInterfaces(Function.class).build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JavaSerializationHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JavaSerializationHintsTests {
|
||||
|
||||
private final JavaSerializationHints javaSerializationHints = new JavaSerializationHints();
|
||||
|
||||
@Test
|
||||
void registerTypeTwiceExposesOneHint() {
|
||||
this.javaSerializationHints.registerType(URL.class);
|
||||
this.javaSerializationHints.registerType(TypeReference.of(URL.class.getName()));
|
||||
assertThat(this.javaSerializationHints.types()).singleElement()
|
||||
.isEqualTo(TypeReference.of(URL.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.hint.JdkProxyHint.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JdkProxyHint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JdkProxyHintTests {
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameInstanceIsTrue() {
|
||||
JdkProxyHint hint = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
|
||||
assertThat(hint).isEqualTo(hint);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameProxiedInterfacesIsTrue() {
|
||||
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
|
||||
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()),
|
||||
TypeReference.of(Consumer.class)).build();
|
||||
assertThat(first).isEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithSameProxiedInterfacesDifferentOrderIsFalse() {
|
||||
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class, Consumer.class).build();
|
||||
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Consumer.class),
|
||||
TypeReference.of(Function.class.getName())).build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithWithDifferentProxiedInterfacesIsFalse() {
|
||||
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class).build();
|
||||
JdkProxyHint second = new Builder().proxiedInterfaces(TypeReference.of(Function.class.getName()),
|
||||
TypeReference.of(Consumer.class)).build();
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithNonJdkProxyHintIsFalse() {
|
||||
JdkProxyHint first = new Builder().proxiedInterfaces(Function.class).build();
|
||||
TypeReference second = TypeReference.of(Function.class);
|
||||
assertThat(first).isNotEqualTo(second);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.hint.JdkProxyHint.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProxyHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ProxyHintsTests {
|
||||
|
||||
private final ProxyHints proxyHints = new ProxyHints();
|
||||
|
||||
|
||||
@Test
|
||||
void registerJdkProxyWithInterfaceClass() {
|
||||
this.proxyHints.registerJdkProxy(Function.class);
|
||||
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(Function.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerJdkProxyWithConcreteClass() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.proxyHints.registerJdkProxy(String.class))
|
||||
.withMessageContaining(String.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerJdkProxyWithInterfaceClassNames() {
|
||||
this.proxyHints.registerJdkProxy(TypeReference.of(Function.class),
|
||||
TypeReference.of("com.example.Advised"));
|
||||
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(
|
||||
Function.class.getName(), "com.example.Advised"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerJdkProxyWithSupplier() {
|
||||
this.proxyHints.registerJdkProxy(springProxy(TypeReference.of("com.example.Test")));
|
||||
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(
|
||||
"org.springframework.aop.SpringProxy",
|
||||
"org.springframework.aop.framework.Advised",
|
||||
"org.springframework.core.DecoratingProxy",
|
||||
"com.example.Test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerJdkProxyTwiceExposesOneHint() {
|
||||
this.proxyHints.registerJdkProxy(Function.class);
|
||||
this.proxyHints.registerJdkProxy(TypeReference.of(Function.class.getName()));
|
||||
assertThat(this.proxyHints.jdkProxies()).singleElement().satisfies(proxiedInterfaces(Function.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerClassProxyWithTargetClass() {
|
||||
this.proxyHints.registerClassProxy(Properties.class, classProxyHint ->
|
||||
classProxyHint.proxiedInterfaces(Serializable.class));
|
||||
assertThat(this.proxyHints.classProxies()).singleElement().satisfies(classProxyHint -> {
|
||||
assertThat(classProxyHint.getTargetClass()).isEqualTo(TypeReference.of(Properties.class));
|
||||
assertThat(classProxyHint.getProxiedInterfaces()).containsOnly(TypeReference.of(Serializable.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerClassProxyWithTargetInterface() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.proxyHints.registerClassProxy(Serializable.class, classProxyHint -> {
|
||||
})).withMessageContaining(Serializable.class.getName());
|
||||
}
|
||||
|
||||
private static Supplier<JdkProxyHint> springProxy(TypeReference proxiedInterface) {
|
||||
return () -> new Builder().proxiedInterfaces(Stream.of("org.springframework.aop.SpringProxy",
|
||||
"org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy")
|
||||
.map(TypeReference::of).toArray(TypeReference[]::new))
|
||||
.proxiedInterfaces(proxiedInterface).build();
|
||||
}
|
||||
|
||||
private Consumer<JdkProxyHint> proxiedInterfaces(String... proxiedInterfaces) {
|
||||
return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
|
||||
.containsExactly(Arrays.stream(proxiedInterfaces)
|
||||
.map(TypeReference::of).toArray(TypeReference[]::new));
|
||||
}
|
||||
|
||||
private Consumer<JdkProxyHint> proxiedInterfaces(Class<?>... proxiedInterfaces) {
|
||||
return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
|
||||
.containsExactly(Arrays.stream(proxiedInterfaces)
|
||||
.map(TypeReference::of).toArray(TypeReference[]::new));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ReflectionHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ReflectionHintsTests {
|
||||
|
||||
private final ReflectionHints reflectionHints = new ReflectionHints();
|
||||
|
||||
@Test
|
||||
void registerType() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS));
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(
|
||||
typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeReuseBuilder() {
|
||||
this.reflectionHints.registerType(TypeReference.of(String.class),
|
||||
typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(String.class, "value"));
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> assertThat(fieldHint.getName()).isEqualTo("value"));
|
||||
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerClass() {
|
||||
this.reflectionHints.registerType(Integer.class,
|
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(
|
||||
typeWithMemberCategories(Integer.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerField() {
|
||||
this.reflectionHints.registerField(ReflectionUtils.findField(TestType.class, "field"));
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
|
||||
assertThat(fieldHint.getName()).isEqualTo("field"));
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerConstructor() {
|
||||
this.reflectionHints.registerConstructor(TestType.class.getDeclaredConstructors()[0]);
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.constructors()).singleElement().satisfies(constructorHint -> {
|
||||
assertThat(constructorHint.getParameterTypes()).isEmpty();
|
||||
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE);
|
||||
});
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.getMemberCategories()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerMethod() {
|
||||
this.reflectionHints.registerMethod(ReflectionUtils.findMethod(TestType.class, "setName", String.class));
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
|
||||
assertThat(methodHint.getName()).isEqualTo("setName");
|
||||
assertThat(methodHint.getParameterTypes()).containsOnly(TypeReference.of(String.class));
|
||||
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Consumer<TypeHint> typeWithMemberCategories(Class<?> type, MemberCategory... memberCategories) {
|
||||
return typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(type.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.getMemberCategories()).containsExactly(memberCategories);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class TestType {
|
||||
|
||||
private String field;
|
||||
|
||||
void setName(String name) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.hint.ResourceHintsTests.Nested.Inner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ResourceHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ResourceHintsTests {
|
||||
|
||||
private final ResourceHints resourceHints = new ResourceHints();
|
||||
|
||||
@Test
|
||||
void registerType() {
|
||||
this.resourceHints.registerType(String.class);
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
|
||||
patternOf("java/lang/String.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeWithNestedType() {
|
||||
this.resourceHints.registerType(TypeReference.of(Nested.class));
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
|
||||
patternOf("org/springframework/core/hint/ResourceHintsTests$Nested.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeWithInnerNestedType() {
|
||||
this.resourceHints.registerType(TypeReference.of(Inner.class));
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
|
||||
patternOf("org/springframework/core/hint/ResourceHintsTests$Nested$Inner.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerTypeSeveralTimesAddsOnlyOneEntry() {
|
||||
this.resourceHints.registerType(String.class);
|
||||
this.resourceHints.registerType(TypeReference.of(String.class));
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
|
||||
patternOf("java/lang/String.class"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerExactMatch() {
|
||||
this.resourceHints.registerPattern("com/example/test.properties");
|
||||
this.resourceHints.registerPattern("com/example/another.properties");
|
||||
assertThat(this.resourceHints.resourcePatterns())
|
||||
.anySatisfy(patternOf("com/example/test.properties"))
|
||||
.anySatisfy(patternOf("com/example/another.properties"))
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPattern() {
|
||||
this.resourceHints.registerPattern("com/example/*.properties");
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(
|
||||
patternOf("com/example/*.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerPatternWithIncludesAndExcludes() {
|
||||
this.resourceHints.registerPattern("com/example/*.properties",
|
||||
resourceHint -> resourceHint.excludes("com/example/to-ignore.properties"));
|
||||
assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies(patternOf(
|
||||
List.of("com/example/*.properties"),
|
||||
List.of("com/example/to-ignore.properties")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerResourceBundle() {
|
||||
this.resourceHints.registerResourceBundle("com.example.message");
|
||||
assertThat(this.resourceHints.resourceBundles()).singleElement()
|
||||
.satisfies(resourceBundle("com.example.message"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerResourceBundleSeveralTimesAddsOneEntry() {
|
||||
this.resourceHints.registerResourceBundle("com.example.message")
|
||||
.registerResourceBundle("com.example.message");
|
||||
assertThat(this.resourceHints.resourceBundles()).singleElement()
|
||||
.satisfies(resourceBundle("com.example.message"));
|
||||
}
|
||||
|
||||
|
||||
private Consumer<ResourcePatternHint> patternOf(String... includes) {
|
||||
return patternOf(Arrays.asList(includes), Collections.emptyList());
|
||||
}
|
||||
|
||||
private Consumer<ResourceBundleHint> resourceBundle(String baseName) {
|
||||
return resourceBundleHint -> assertThat(resourceBundleHint.getBaseName()).isEqualTo(baseName);
|
||||
}
|
||||
|
||||
private Consumer<ResourcePatternHint> patternOf(List<String> includes, List<String> excludes) {
|
||||
return pattern -> {
|
||||
assertThat(pattern.getIncludes()).containsExactlyElementsOf(includes);
|
||||
assertThat(pattern.getExcludes()).containsExactlyElementsOf(excludes);
|
||||
};
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
|
||||
static class Inner {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RuntimeHints}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class RuntimeHintsTests {
|
||||
|
||||
private final RuntimeHints hints = new RuntimeHints();
|
||||
|
||||
@Test
|
||||
void reflectionHintWithClass() {
|
||||
this.hints.reflection().registerType(String.class,
|
||||
hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
assertThat(this.hints.reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(String.class.getCanonicalName());
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceHintWithClass() {
|
||||
this.hints.resources().registerType(String.class);
|
||||
assertThat(this.hints.resources().resourcePatterns()).singleElement().satisfies(resourceHint -> {
|
||||
assertThat(resourceHint.getIncludes()).containsExactly("java/lang/String.class");
|
||||
assertThat(resourceHint.getExcludes()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaSerializationHintWithClass() {
|
||||
this.hints.javaSerialization().registerType(String.class);
|
||||
assertThat(this.hints.javaSerialization().types()).containsExactly(TypeReference.of(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jdkProxyWithClass() {
|
||||
this.hints.proxies().registerJdkProxy(Function.class);
|
||||
assertThat(this.hints.proxies().jdkProxies()).singleElement().satisfies(jdkProxyHint ->
|
||||
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(TypeReference.of(Function.class)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.hint.TypeHint.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link TypeHint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class TypeHintTests {
|
||||
|
||||
@Test
|
||||
void createWithNullTypeReference() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> TypeHint.of(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithType() {
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).build();
|
||||
assertThat(hint).isNotNull();
|
||||
assertThat(hint.getType().getCanonicalName()).isEqualTo("java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithTypeAndReachableType() {
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
|
||||
.onReachableType(TypeReference.of("com.example.Test")).build();
|
||||
assertThat(hint).isNotNull();
|
||||
assertThat(hint.getReachableType()).isNotNull();
|
||||
assertThat(hint.getReachableType().getCanonicalName()).isEqualTo("com.example.Test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithField() {
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
|
||||
.withField("value", fieldHint -> fieldHint.allowWrite(true)).build();
|
||||
assertThat(hint.fields()).singleElement().satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithFieldReuseBuilder() {
|
||||
Builder builder = TypeHint.of(TypeReference.of(String.class));
|
||||
builder.withField("value", fieldHint -> fieldHint.allowUnsafeAccess(true));
|
||||
builder.withField("value", fieldHint -> {
|
||||
fieldHint.allowWrite(true);
|
||||
fieldHint.allowUnsafeAccess(false);
|
||||
});
|
||||
TypeHint hint = builder.build();
|
||||
assertThat(hint.fields()).singleElement().satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithConstructor() {
|
||||
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(int.class));
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes,
|
||||
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)).build();
|
||||
assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> {
|
||||
assertThat(constructorHint.getParameterTypes()).containsOnlyOnceElementsOf(parameterTypes);
|
||||
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createConstructorReuseBuilder() {
|
||||
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(int.class));
|
||||
Builder builder = TypeHint.of(TypeReference.of(String.class)).withConstructor(parameterTypes,
|
||||
constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE));
|
||||
TypeHint hint = builder.withConstructor(parameterTypes, constructorHint ->
|
||||
constructorHint.withMode(ExecutableMode.INTROSPECT)).build();
|
||||
assertThat(hint.constructors()).singleElement().satisfies(constructorHint -> {
|
||||
assertThat(constructorHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
|
||||
assertThat(constructorHint.getModes()).containsOnly(ExecutableMode.INVOKE, ExecutableMode.INTROSPECT);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithMethod() {
|
||||
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes,
|
||||
methodHint -> methodHint.withMode(ExecutableMode.INVOKE)).build();
|
||||
assertThat(hint.methods()).singleElement().satisfies(methodHint -> {
|
||||
assertThat(methodHint.getName()).isEqualTo("valueOf");
|
||||
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
|
||||
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INVOKE);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithMethodReuseBuilder() {
|
||||
List<TypeReference> parameterTypes = List.of(TypeReference.of(char[].class));
|
||||
Builder builder = TypeHint.of(TypeReference.of(String.class)).withMethod("valueOf", parameterTypes,
|
||||
methodHint -> methodHint.withMode(ExecutableMode.INVOKE));
|
||||
TypeHint hint = builder.withMethod("valueOf", parameterTypes,
|
||||
methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT)).build();
|
||||
assertThat(hint.methods()).singleElement().satisfies(methodHint -> {
|
||||
assertThat(methodHint.getName()).isEqualTo("valueOf");
|
||||
assertThat(methodHint.getParameterTypes()).containsExactlyElementsOf(parameterTypes);
|
||||
assertThat(methodHint.getModes()).containsOnly(ExecutableMode.INTROSPECT);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithMemberCategory() {
|
||||
TypeHint hint = TypeHint.of(TypeReference.of(String.class))
|
||||
.withMembers(MemberCategory.DECLARED_FIELDS).build();
|
||||
assertThat(hint.getMemberCategories()).containsOnly(MemberCategory.DECLARED_FIELDS);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.hint;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link TypeReference}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class TypeReferenceTests {
|
||||
|
||||
@Test
|
||||
void typeReferenceWithClassName() {
|
||||
TypeReference type = TypeReference.of("java.lang.String");
|
||||
assertThat(type.getCanonicalName()).isEqualTo("java.lang.String");
|
||||
assertThat(type.getPackageName()).isEqualTo("java.lang");
|
||||
assertThat(type.getSimpleName()).isEqualTo("String");
|
||||
assertThat(type.getEnclosingType()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeReferenceWithInnerClassName() {
|
||||
TypeReference type = TypeReference.of("com.example.Example$Inner");
|
||||
assertThat(type.getCanonicalName()).isEqualTo("com.example.Example.Inner");
|
||||
assertThat(type.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(type.getSimpleName()).isEqualTo("Inner");
|
||||
assertThat(type.getEnclosingType()).satisfies(enclosingType -> {
|
||||
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example");
|
||||
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(enclosingType.getSimpleName()).isEqualTo("Example");
|
||||
assertThat(enclosingType.getEnclosingType()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeReferenceWithNestedInnerClassName() {
|
||||
TypeReference type = TypeReference.of("com.example.Example$Inner$Nested");
|
||||
assertThat(type.getCanonicalName()).isEqualTo("com.example.Example.Inner.Nested");
|
||||
assertThat(type.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(type.getSimpleName()).isEqualTo("Nested");
|
||||
assertThat(type.getEnclosingType()).satisfies(enclosingType -> {
|
||||
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example.Inner");
|
||||
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(enclosingType.getSimpleName()).isEqualTo("Inner");
|
||||
assertThat(enclosingType.getEnclosingType()).satisfies(parentEnclosingType -> {
|
||||
assertThat(parentEnclosingType.getCanonicalName()).isEqualTo("com.example.Example");
|
||||
assertThat(parentEnclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(parentEnclosingType.getSimpleName()).isEqualTo("Example");
|
||||
assertThat(parentEnclosingType.getEnclosingType()).isNull();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithIdenticalNameIsTrue() {
|
||||
assertThat(TypeReference.of(String.class)).isEqualTo(
|
||||
TypeReference.of("java.lang.String"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithNonTypeReferenceIsFalse() {
|
||||
assertThat(TypeReference.of(String.class)).isNotEqualTo("java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringUsesCanonicalName() {
|
||||
assertThat(TypeReference.of(String.class)).hasToString("java.lang.String");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user