mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Invoke defaults constructor in kotlin code gen (#896)
* Add Util#invokeDefaultConstructor * Add defaultPrimitiveValue This will be used to initialize required properties for later invocation of the default constructor * Move isTransient into PropertyGenerator We will need this in order to know how to invoke constructors even if they have transient parameters * Add notion of hasLocalIsPresentName to PropertyGenerator * Switch to using invokeDefaultConstructor for any default property types * Add code gen versions of default constructor test * Fix mismatched names * Use Arrays.copyOf * Unwrap InvocationTargetException * Use name allocator * Rename createMask to createDefaultValuesParametersMask, use it directly * Opportunistically clean up result variable holder Only needs to be made if we have non-parameter instances, otherwise we can just return directly * Fix mask name * Remove unnecessary mod * Switch to local lazily-initialized constructor reference Not working because of some issue in kotlinpoet I don't understand * Fix named usage * Clean up debugging dots * Add proguard/R8 rule for keeping defaults constructor in targets * Make constructor lookup property private * Add another defensive dot * Rework invokeDefaultConstructor to accept vararg args A little more idiomatic * Update proguard rules
This commit is contained in:
@@ -44,6 +44,17 @@ import static com.squareup.moshi.Types.supertypeOf;
|
||||
public final class Util {
|
||||
public static final Set<Annotation> NO_ANNOTATIONS = Collections.emptySet();
|
||||
public static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
|
||||
@Nullable private static final Class<?> DEFAULT_CONSTRUCTOR_MARKER;
|
||||
|
||||
static {
|
||||
Class<?> clazz;
|
||||
try {
|
||||
clazz = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker");
|
||||
} catch (ClassNotFoundException e) {
|
||||
clazz = null;
|
||||
}
|
||||
DEFAULT_CONSTRUCTOR_MARKER = clazz;
|
||||
}
|
||||
|
||||
private Util() {
|
||||
}
|
||||
@@ -521,4 +532,91 @@ public final class Util {
|
||||
throw rethrowCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflectively looks up the defaults constructor of a kotlin class.
|
||||
*
|
||||
* @param targetClass the target kotlin class to instantiate.
|
||||
* @param <T> the type of {@code targetClass}.
|
||||
* @return the instantiated {@code targetClass} instance.
|
||||
* @see #createDefaultValuesParametersMask(boolean...)
|
||||
*/
|
||||
public static <T> Constructor<T> lookupDefaultsConstructor(Class<T> targetClass) {
|
||||
if (DEFAULT_CONSTRUCTOR_MARKER == null) {
|
||||
throw new IllegalStateException("DefaultConstructorMarker not on classpath. Make sure the "
|
||||
+ "Kotlin stdlib is on the classpath.");
|
||||
}
|
||||
Constructor<T> defaultConstructor = findConstructor(targetClass);
|
||||
defaultConstructor.setAccessible(true);
|
||||
return defaultConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflectively invokes the defaults constructor of a kotlin class. This allows indicating which
|
||||
* arguments are "set" or not, and thus recreate the behavior of named a arguments invocation
|
||||
* dynamically.
|
||||
*
|
||||
* @param targetClass the target kotlin class to instantiate.
|
||||
* @param defaultsConstructor the target class's defaults constructor in kotlin invoke.
|
||||
* @param mask an int mask indicating which {@code args} are present.
|
||||
* @param args the constructor arguments, including "unset" values (set to null or the primitive
|
||||
* default).
|
||||
* @param <T> the type of {@code targetClass}.
|
||||
* @return the instantiated {@code targetClass} instance.
|
||||
* @see #createDefaultValuesParametersMask(boolean...)
|
||||
*/
|
||||
public static <T> T invokeDefaultConstructor(
|
||||
Class<T> targetClass,
|
||||
Constructor<T> defaultsConstructor,
|
||||
int mask,
|
||||
Object... args) {
|
||||
Object[] finalArgs = Arrays.copyOf(args, args.length + 2);
|
||||
finalArgs[finalArgs.length - 2] = mask;
|
||||
finalArgs[finalArgs.length - 1] = null; // DefaultConstructorMarker param
|
||||
try {
|
||||
return defaultsConstructor.newInstance(finalArgs);
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalStateException("Could not instantiate instance of " + targetClass);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Could not access defaults constructor of " + targetClass);
|
||||
} catch (InvocationTargetException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof RuntimeException) throw (RuntimeException) cause;
|
||||
if (cause instanceof Error) throw (Error) cause;
|
||||
throw new RuntimeException("Could not invoke defaults constructor of " + targetClass, cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Constructor<T> findConstructor(Class<T> targetClass) {
|
||||
for (Constructor<?> constructor : targetClass.getDeclaredConstructors()) {
|
||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||
if (paramTypes.length != 0
|
||||
&& paramTypes[paramTypes.length - 1].equals(DEFAULT_CONSTRUCTOR_MARKER)) {
|
||||
//noinspection unchecked
|
||||
return (Constructor<T>) constructor;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("No defaults constructor found for " + targetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an mask with bits set to indicate which indices of a default constructor's parameters
|
||||
* are set.
|
||||
*
|
||||
* @param argPresentValues vararg of all present values (set or unset). Max allowable size is 32.
|
||||
* @return the created mask.
|
||||
*/
|
||||
public static int createDefaultValuesParametersMask(boolean... argPresentValues) {
|
||||
if (argPresentValues.length > 32) {
|
||||
throw new IllegalArgumentException("Arg present values exceeds max allowable 32.");
|
||||
}
|
||||
int mask = 0;
|
||||
for (int i = 0; i < argPresentValues.length; ++i) {
|
||||
if (!argPresentValues[i]) {
|
||||
mask = mask | (1 << i);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,16 @@
|
||||
# The name of @JsonClass types is used to look up the generated adapter.
|
||||
-keepnames @com.squareup.moshi.JsonClass class *
|
||||
|
||||
# Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's
|
||||
# name. We will look this up reflectively to invoke the type's constructor.
|
||||
#
|
||||
# We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard
|
||||
# matching preceding parameters.
|
||||
-keepnames class kotlin.jvm.internal.DefaultConstructorMarker
|
||||
-keepclassmembers @com.squareup.moshi.JsonClass class * {
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
# Retain generated JsonAdapters if annotated type is retained.
|
||||
-if @com.squareup.moshi.JsonClass class *
|
||||
-keep class <1>JsonAdapter {
|
||||
|
Reference in New Issue
Block a user