DATACMNS-1322 - Cleanup refactorings.

Add missing since tags. Extract methods to maintain single abstraction level per method. Move visitDefaultValue from ClassGeneratingPropertyAccessorFactory to BytecodeUtil. Extend Javadoc.
This commit is contained in:
Mark Paluch
2018-06-15 10:29:20 +02:00
committed by Oliver Gierke
parent 79119000bb
commit 85b507ce10
7 changed files with 501 additions and 397 deletions

View File

@@ -114,10 +114,27 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
}
/**
* Returns the wither method to set a property value on a new object instance. Might return {@literal null} in case
* there is no wither available.
* Returns the wither {@link Method} to set a property value on a new object instance. Might return {@literal null} in
* case there is no wither available.
* <p/>
* Wither {@link Method methods} are property-bound instance {@link Method methods} that accept a single argument of
* the property type creating a new object instance.
*
* @return the wither method to set a property value on a new object instance if available, otherwise {@literal null}.
* <pre class="code">
* class Person {
* final String id;
* final String name;
*
* // …
*
* Person withName(String name) {
* return new Person(this.id, name);
* }
* }
* </pre>
*
* @return the wither {@link Method} to set a property value on a new object instance if available, otherwise
* {@literal null}.
* @since 2.1
*/
@Nullable
@@ -247,6 +264,7 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
* {@code final}.
*
* @return
* @see java.lang.reflect.Modifier#isFinal(int)
* @since 2.1
*/
boolean isImmutable();
@@ -366,7 +384,7 @@ public interface PersistentProperty<P extends PersistentProperty<P>> {
/**
* Return the type the property refers to in case it's an association.
*
*
* @return the type the property refers to in case it's an association or {@literal null} in case it's not an
* association, the target entity type is not explicitly defined (either explicitly or through the property
* type itself).

View File

@@ -123,7 +123,8 @@ public interface PersistentPropertyAccessor {
}
/**
* Returns the underlying bean. May change between {@link #setProperty(PersistentProperty, Object)} calls.
* Returns the underlying bean. The actual instance may change between
* {@link #setProperty(PersistentProperty, Object)} calls.
*
* @return will never be {@literal null}.
*/

View File

@@ -160,37 +160,62 @@ class BeanWrapper<T> implements PersistentPropertyAccessor {
/**
* Utility class to leverage Kotlin's copy method for immutable data classes.
*
* @since 2.1
*/
static class KotlinCopyUtil {
/**
* Set a single property by calling {@code copy(…)} on a Kotlin data class. Copying creates a new instance that
* holds all values of the original instance and the newly set {@link PersistentProperty} value.
*
* @see KCallable#callBy(Map)
*/
static <T> Object setProperty(PersistentProperty<?> property, T bean, @Nullable Object value) {
Class<?> type = property.getOwner().getType();
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(type);
KCallable<?> copy = getCopyMethod(kotlinClass);
if (copy == null) {
throw new UnsupportedOperationException(String.format(
"Kotlin class %s has no .copy(…) method. Cannot set property %s!", type.getName(), property.getName()));
}
return copy.callBy(getCallArgs(copy, property, bean, value));
}
private static <T> Map<KParameter, Object> getCallArgs(KCallable<?> callable, PersistentProperty<?> property,
T bean, @Nullable Object value) {
Map<KParameter, Object> args = new LinkedHashMap<>(2, 1);
for (KCallable<?> kCallable : kotlinClass.getMembers()) {
List<KParameter> parameters = kCallable.getParameters();
List<KParameter> parameters = callable.getParameters();
for (KParameter parameter : parameters) {
for (KParameter parameter : parameters) {
if (parameter.getKind() == Kind.INSTANCE) {
args.put(parameter, bean);
}
if (parameter.getKind() == Kind.VALUE && parameter.getName().equals(property.getName())) {
args.put(parameter, value);
}
if (parameter.getKind() == Kind.INSTANCE) {
args.put(parameter, bean);
}
if (parameter.getKind() == Kind.VALUE && parameter.getName().equals(property.getName())) {
args.put(parameter, value);
}
}
return args;
}
@Nullable
private static KCallable<?> getCopyMethod(KClass<?> kotlinClass) {
for (KCallable<?> kCallable : kotlinClass.getMembers()) {
if (kCallable.getName().equals("copy")) {
return kCallable.callBy(args);
return kCallable;
}
}
throw new UnsupportedOperationException(String.format(
"Kotlin class %s has no .copy(…) method. Cannot set property %s!", type.getName(), property.getName()));
return null;
}
}
}

View File

@@ -22,12 +22,14 @@ import lombok.experimental.UtilityClass;
import java.lang.reflect.Modifier;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
/**
* Utility methods used for ASM-based class generation during runtime.
*
* @author Mark Paluch
* @since 2.1
*/
@UtilityClass
class BytecodeUtil {
@@ -81,7 +83,7 @@ class BytecodeUtil {
*
* @param in the input type
* @param out the expected output type
* @param visitor
* @param visitor must not be {@literal null}.
*/
static void autoboxIfNeeded(Class<?> in, Class<?> out, MethodVisitor visitor) {
@@ -154,7 +156,7 @@ class BytecodeUtil {
* Checks whether the class is accessible by inspecting modifiers (i.e. whether the class is {@code private}).
*
* @param type must not be {@literal null}.
* @return
* @return {@literal true} if the {@link Class} is accessible.
* @see Modifier#isPrivate(int)
*/
static boolean isAccessible(Class<?> type) {
@@ -165,7 +167,7 @@ class BytecodeUtil {
* Checks whether the class is accessible by inspecting modifiers (i.e. whether the class is {@code private}).
*
* @param type must not be {@literal null}.
* @return
* @return {@literal true} if the {@code modifiers} do not indicate the private flag.
* @see Modifier#isPrivate(int)
*/
static boolean isAccessible(int modifiers) {
@@ -177,7 +179,7 @@ class BytecodeUtil {
* {@literal private}/{@literal protected}/{@literal public}).
*
* @param type must not be {@literal null}.
* @return
* @return {@literal true} if the {@code modifiers} indicate {@literal default}.
* @see Modifier#isPrivate(int)
*/
static boolean isDefault(int modifiers) {
@@ -188,12 +190,18 @@ class BytecodeUtil {
* Create a reference type name in the form of {@literal Ljava/lang/Object;}.
*
* @param type must not be {@literal null}.
* @return
* @return reference type name in the form of {@literal Ljava/lang/Object;}.
*/
static String referenceName(Class<?> type) {
return type.isArray() ? Type.getInternalName(type) : referenceName(Type.getInternalName(type));
}
/**
* Create a reference type name in the form of {@literal Ljava/lang/Object;}.
*
* @param internalTypeName must not be {@literal null}.
* @return reference type name in the form of {@literal Ljava/lang/Object;}.
*/
static String referenceName(String internalTypeName) {
return String.format("L%s;", internalTypeName);
}
@@ -244,4 +252,39 @@ class BytecodeUtil {
return referenceName(type);
}
/**
* Create a byte code instruction that puts a default value for {@link Class} on the stack. Primitive types default to
* zero, reference types use {@literal null}.
*
* @param parameterType must not be {@literal null}.
* @param mv must not be {@literal null}.
*/
static void visitDefaultValue(Class<?> parameterType, MethodVisitor mv) {
if (parameterType.isPrimitive()) {
if (parameterType == Integer.TYPE || parameterType == Short.TYPE || parameterType == Boolean.TYPE) {
mv.visitInsn(Opcodes.ICONST_0);
}
if (parameterType == Long.TYPE) {
mv.visitInsn(Opcodes.LCONST_0);
}
if (parameterType == Double.TYPE) {
mv.visitInsn(Opcodes.DCONST_0);
}
if (parameterType == Float.TYPE) {
mv.visitInsn(Opcodes.FCONST_0);
}
if (parameterType == Character.TYPE || parameterType == Byte.TYPE) {
mv.visitIntInsn(Opcodes.BIPUSH, 0);
}
} else {
mv.visitInsn(Opcodes.ACONST_NULL);
}
}
}

View File

@@ -83,7 +83,8 @@ public class Property {
private static Optional<Method> findWither(TypeInformation<?> owner, String propertyName, Class<?> rawType) {
Method method = ReflectionUtils.findMethod(owner.getType(), "with" + StringUtils.capitalize(propertyName), rawType);
Method method = ReflectionUtils.findMethod(owner.getType(),
String.format("with%s", StringUtils.capitalize(propertyName)), rawType);
if (method != null && owner.isAssignableFrom(owner.getReturnType(method))) {
return Optional.of(method);
@@ -147,7 +148,7 @@ public class Property {
*/
public static boolean supportsStandalone(PropertyDescriptor descriptor) {
Assert.notNull(descriptor, "PropertDescriptor must not be null!");
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return descriptor.getPropertyType() != null;
}

View File

@@ -410,6 +410,7 @@ public class ReflectionUtils {
*
* @param type must not be {@literal null}.
* @return boxed primitive default value.
* @since 2.1
*/
public static Object getPrimitiveDefault(Class<?> type) {