pro.projo.internal.rcg.RuntimeCodeGenerationHandler Maven / Gradle / Ivy
// //
// Copyright 2017 - 2024 Mirko Raner //
// //
// 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 //
// //
// http://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 pro.projo.internal.rcg;
import java.io.File;
import java.io.IOError;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.AbstractMap.SimpleEntry;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeDescription.Generic;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.DynamicType.Builder.FieldDefinition.Valuable;
import net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ImplementationDefinition;
import net.bytebuddy.dynamic.DynamicType.Loaded;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.DefaultMethodCall;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.Implementation.Composable;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.utility.JavaConstant;
import pro.projo.Projo;
import pro.projo.annotations.Cached;
import pro.projo.annotations.Delegate;
import pro.projo.annotations.Expects;
import pro.projo.annotations.Implements;
import pro.projo.annotations.Inherits;
import pro.projo.annotations.Overrides;
import pro.projo.annotations.Returns;
import pro.projo.internal.Predicates;
import pro.projo.internal.ProjoHandler;
import pro.projo.internal.ProjoObject;
import pro.projo.internal.PropertyMatcher;
import pro.projo.internal.rcg.runtime.Cache;
import pro.projo.internal.rcg.runtime.DefaultToStringObject;
import pro.projo.internal.rcg.runtime.ToStringObject;
import pro.projo.internal.rcg.runtime.ToStringValueObject;
import pro.projo.internal.rcg.runtime.ValueObject;
import pro.projo.internal.rcg.utilities.UncheckedMethodDescription;
import pro.projo.utilities.AnnotationList;
import pro.projo.utilities.MethodInfo;
import static java.lang.reflect.Modifier.PUBLIC;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.function.UnaryOperator.identity;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.empty;
import static net.bytebuddy.ClassFileVersion.JAVA_V8;
import static net.bytebuddy.description.modifier.Visibility.PRIVATE;
import static net.bytebuddy.description.type.TypeDescription.OBJECT;
import static net.bytebuddy.description.type.TypeDescription.VOID;
import static net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.INJECTION;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.DEFAULT;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static pro.projo.Projo.delegateOverride;
import static pro.projo.Projo.getMethods;
import static pro.projo.internal.Predicates.cached;
import static pro.projo.internal.Predicates.expects;
import static pro.projo.internal.Predicates.getter;
import static pro.projo.internal.Predicates.overrides;
import static pro.projo.internal.Predicates.returns;
import static pro.projo.internal.Predicates.setter;
/**
* The {@link RuntimeCodeGenerationHandler} is a {@link ProjoHandler} that generates implementation classes
* dynamically at runtime (using the {@link ByteBuddy} library). For each, object property the generated class
* will contain a field of the appropriate type, and the corresponding generated getter and setter will access
* that field directly, without using reflection. Generated implementation classes can be obtained by calling
* the {@link #getImplementationOf(Class, boolean, ClassLoader)} method.
*
* @param <_Artifact_> the type of object being generated
*
* @author Mirko Raner
**/
public class RuntimeCodeGenerationHandler<_Artifact_> extends ProjoHandler<_Artifact_>
{
private final PropertyMatcher matcher = new PropertyMatcher();
private final Pattern typeNamePattern = Pattern.compile("(? [^<>]+)(<(?[^>]+)>)?");
/**
* {@code implementationClassCache} is the cache that maps interface classes to implementation classes.
* This field does not need to be static because {@code RuntimeCodeGenerationHandler} is effectively a
* singleton (because its enclosing class, {@code RuntimeCodeGenerationProjo}, is also a singleton).
* Also, if it were static the {@code _Artifact_} type parameter would not be accessible.
**/
private Map, Class extends _Artifact_>> implementationClassCache =
new ConcurrentHashMap<>();
private final static String SUFFIX = "$Projo";
private static Map, Class>> baseClasses = new HashMap<>();
static
{
@SuppressWarnings("unused")
boolean valueObject, toString;
baseClasses.put(asList(valueObject=false, toString=false), DefaultToStringObject.class);
baseClasses.put(asList(valueObject=true, toString=false), ValueObject.class);
baseClasses.put(asList(valueObject=false, toString=true), ToStringObject.class);
baseClasses.put(asList(valueObject=true, toString=true), ToStringValueObject.class);
}
/**
* Pre-loads the implementation for {@link UncheckedMethodDescription} into the cache.
* Without this, an {@code IllegalStateException: Recursive update} might be thrown when
* {@link RuntimeCodeGenerationHandler} uses its own code generation for creating a modified
* version of Byte Buddy's {@link MethodDescription} class.
**/
public void postInitialize()
{
try
{
// Just pick an arbitrary method:
Method method = getClass().getDeclaredMethod("getInterfaceName", Class.class);
MethodDescription description = new MethodDescription.ForLoadedMethod(method);
delegateOverride(description, UncheckedMethodDescription.class);
}
catch (NoSuchMethodException noSuchMethod)
{
throw new NoSuchMethodError(noSuchMethod.getMessage());
}
}
/**
* Provides a class that implements the given type. All requests for the same type will return the same
* implementation class.
*
* NOTE: a {@link ClassLoader} will be passed only when this method is invoked from
* {@link Projo#getImplementationClass(Class, ClassLoader)}; {@link RuntimeCodeGenerationProjo}
* will always pass a {@code null} reference
*
* @param type the type (i.e., Projo interface)
* @param defaultPackage flag indicating whether the generated class should be placed in the default package
* @param classLoader the {@link ClassLoader} to be used
* @return the generated implementation class
**/
@Override
public Class extends _Artifact_> getImplementationOf(Class<_Artifact_> type, boolean defaultPackage, ClassLoader classLoader)
{
return implementationClassCache.computeIfAbsent(type, it -> generateImplementation(it, defaultPackage, classLoader));
}
public Class extends _Artifact_> getProxyImplementationOf(Class<_Artifact_> type, boolean override, boolean defaultPackage, Class>... additionalTypes)
{
return implementationClassCache.computeIfAbsent(type, it -> generateProxy(it, override, defaultPackage, additionalTypes));
}
/**
* Determines the Projo interface name (if any) of an implementation type.
*
* @param type the Projo implementation type
* @return the interface name if the class was a Projo implementation class, otherwise the original type name
**/
public static String getInterfaceName(Class> type)
{
String name = type.getName();
return name.endsWith(SUFFIX)? name.substring(0, name.length()-SUFFIX.length()):name;
}
private Class extends _Artifact_> generateImplementation(Class<_Artifact_> type, boolean defaultPackage, ClassLoader classLoader)
{
Stream cachedMethods = Projo.getMethods(type, classLoader, cached);
List additionalImplements = getImplements(type);
Builder<_Artifact_> builder = create(type, additionalImplements, classLoader).name(implementationName(type, defaultPackage));
TypeDescription currentType = builder.make().getTypeDescription();
return debug(getMethods(type, classLoader, additionalImplements, getter, setter, cached, overrides, returns, expects)
.reduce(builder, (accumulator, method) -> add(accumulator, method, additionalImplements, classLoader), sequentialOnly())
.defineConstructor(PUBLIC).intercept(constructor(type, currentType, cachedMethods))
.make().load(classLoader(type, defaultPackage, classLoader), INJECTION)).getLoaded();
}
@SuppressWarnings("unchecked")
private Class extends _Artifact_> generateProxy(Class<_Artifact_> type, boolean override, boolean defaultPackage, Class>... additionalTypes)
{
Optional delegateMethod = getDelegateMethod(type.getDeclaredMethods());
Builder<_Artifact_> builder = null;
try
{
// Define the class and the delegate constructor:
//
Class> delegateType = override? type.getInterfaces()[0]:type;
builder = (Builder<_Artifact_>)codeGenerator()
.subclass(Object.class)
.implement(type)
.implement(additionalTypes)
.name(implementationName(type, defaultPackage))
.defineField("delegate", delegateType);
builder = additionalAttributes(type, override)
.reduce(builder, (accumulator, method) -> add(accumulator, method, emptyList(), null), sequentialOnly())
.defineConstructor(Modifier.PUBLIC)
.withParameter(delegateType)
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("delegate").setsArgumentAt(0)));
}
catch (NoSuchMethodException | SecurityException exception)
{
throw new Error(exception);
}
// Add proxy implementations for all non-default methods:
//
Predicate proxiable = method -> !method.isDefault()
&& (!method.getDeclaringClass().equals(type)
|| !override || method.isAnnotationPresent(Delegate.class));
builder = Projo.getMethods(type, proxiable)
.reduce(builder, (it, method) -> addProxy(it, method, delegateMethod), sequentialOnly());
// Add implementations that have @Overrides annotations:
//
builder = Stream.of(type.getDeclaredMethods())
.map(method -> new SimpleEntry<>(method, method.getAnnotation(Overrides.class)))
.filter(pair -> pair.getValue() != null)
.map(pair -> new SimpleEntry<>(pair.getValue().value(), pair.getKey()))
.reduce(builder, this::addOverrides, sequentialOnly());
// Build and return the proxy class:
//
return debug(builder.make().load(classLoader(type, defaultPackage, null))).getLoaded();
}
private Stream additionalAttributes(Class> type, boolean override)
{
return override? Stream.of(type.getDeclaredMethods()).filter(Predicates.declaredAttribute):empty();
}
private Builder<_Artifact_> addOverrides(Builder<_Artifact_> builder, Entry method)
{
String methodName = method.getKey();
Type returnType = method.getValue().getReturnType();
Type[] parameterTypes = method.getValue().getParameterTypes();
MethodCall methodCall = MethodCall.invoke(method.getValue()).withAllArguments();
return builder
.defineMethod(methodName, returnType)
.withParameters(parameterTypes)
.intercept(methodCall);
}
private Builder<_Artifact_> addProxy(Builder<_Artifact_> builder, Method method, Optional delegate)
{
String methodName = method.getName();
Type returnType = method.getReturnType();
Type[] parameterTypes = method.getParameterTypes();
Implementation methodCall;
if (method.isAnnotationPresent(Delegate.class))
{
methodCall = FieldAccessor.ofField("delegate");
}
else
{
if (delegate.isPresent())
{
MethodCall delegateMethodCall = MethodCall.invoke(delegate.get());
methodCall = MethodCall.invoke(method).onMethodCall(delegateMethodCall).withAllArguments();
}
else
{
methodCall = MethodCall.invoke(method).onField("delegate").withAllArguments();
}
}
return builder
.defineMethod(methodName, returnType)
.withParameters(parameterTypes)
.intercept(methodCall);
}
/**
* Adds method implementation and (if necessary) field definition for a method.
*
* @param builder the existing {@link Builder} to build upon
* @param method a getter, setter, delegate or cached method
* @param additionalImplements additional interfaces implemented by means of {@link Implements} annotations
* @return a new {@link Builder} with an additional method (and possibly an additional field)
**/
private Builder<_Artifact_> add(Builder<_Artifact_> builder, Method method, List additionalImplements, ClassLoader classLoader)
{
AnnotationList annotations = new AnnotationList(method);
boolean isGetter = getter.test(method) || annotations.contains(Delegate.class) || annotations.contains(Cached.class);
String methodName = annotations.get(Overrides.class).map(Overrides::value).orElse(method.getName());
String propertyName = matcher.propertyName(methodName);
UnaryOperator> addFieldForGetter;
Optional inject = annotations.getInject();
Class> returnType = method.getReturnType();
TypeDescription.Generic type = isGetter? getFieldType(annotations, returnType, classLoader):VOID.asGenericType();
addFieldForGetter = isGetter? localBuilder -> annotate(inject, localBuilder.defineField(propertyName, type, PRIVATE)):identity();
Implementation implementation = getAccessor(method, annotations, returnType, propertyName, additionalImplements, classLoader);
Optional returns = annotations.get(Returns.class);
Optional inherits = annotations.get(Inherits.class);
List> expects = Stream.of(method.getParameters())
.map(it -> Optional.ofNullable(it.getAnnotation(Expects.class)))
.collect(toList());
if (!inherits.isPresent()
&& (returns.isPresent() || expects.stream().anyMatch(Optional::isPresent)))
{
Class> returnsType = returns
.map(Returns::value)
.map(it -> Projo.forName(it, classLoader))
.map(Class.class::cast)
.orElse(returnType);
@SuppressWarnings("rawtypes")
List parameterTypes = IntStream.range(0, expects.size())
.mapToObj(index -> expects.get(index)
.map(Expects::value)
.map(it -> Projo.forName(it, classLoader))
.map(Class.class::cast)
.orElse(method.getParameterTypes()[index]))
.collect(toList());
return addFieldForGetter.apply(builder)
.defineMethod(methodName, returnsType, PUBLIC)
.withParameters(parameterTypes.toArray(new Type[] {}))
.intercept(implementation);
}
Function, ImplementationDefinition<_Artifact_>> createMethod = methodBuilder ->
{
if (inherits.isPresent())
{
return methodBuilder
.defineMethod(methodName, method.getReturnType(), PUBLIC)
.withParameters(method.getParameterTypes());
}
else
{
return methodBuilder.method(named(methodName));
}
};
return createMethod.apply(addFieldForGetter.apply(builder)).intercept(implementation);
}
Implementation getAccessor(Method method, AnnotationList annotations, Type returnType, String property, List additionalImplements, ClassLoader classLoader)
{
Optional inject;
if ((inject = annotations.getInject()).isPresent())
{
return get(property, returnType, inject.get(), classLoader);
}
if (annotations.contains(Cached.class))
{
return cached(annotations.get(Cached.class).get(), property, returnType);
}
if (annotations.contains(Overrides.class))
{
return MethodCall.invoke(method).withAllArguments().withAssigner(DEFAULT, DYNAMIC);
}
if (annotations.contains(Inherits.class))
{
MethodInfo methodInfo = new MethodInfo(method, classLoader);
TypeDescription declaring = new TypeDescription.ForLoadedType(Projo.forName(additionalImplements.get(0), classLoader));
Generic returns = new TypeDescription.ForLoadedType(methodInfo.returnType()).asGenericType();
TypeDescription[] parameters = methodInfo.parameterTypes().stream()
.map(TypeDescription.ForLoadedType::new)
.toArray(TypeDescription[]::new);
MethodDescription inherited = latent(declaring, returns, method.getName(), parameters);
return MethodCall.invoke(inherited).withAllArguments().withAssigner(DEFAULT, DYNAMIC);
}
if (annotations.contains(Returns.class) || expects.test(method))
{
MethodDescription description = new MethodDescription.ForLoadedMethod(method);
MethodDescription unchecked = delegateOverride(description, UncheckedMethodDescription.class);
return MethodCall.invoke(unchecked).onSuper().withAllArguments().withAssigner(DEFAULT, DYNAMIC);
}
Stream methods = additionalImplements.stream()
.map(it -> Projo.forName(it, classLoader))
.flatMap(type -> Stream.of(type.getDeclaredMethods()));
Predicate sameSignature = match ->
method.getName().equals(match.getName()) &&
method.getReturnType().equals(match.getReturnType()) &&
asList(method.getParameterTypes()).equals(asList(match.getParameterTypes()));
List matchingMethod = methods.filter(sameSignature).collect(toList());
if (matchingMethod.size() == 1)
{
return DefaultMethodCall.prioritize(matchingMethod.get(0).getDeclaringClass());
}
return FieldAccessor.ofField(property);
}
private Implementation constructor(Class> originalType, TypeDescription currentType, Stream cachedMethods)
{
Method cacheCreator;
try
{
cacheCreator = Cache.class.getDeclaredMethod("create", int.class, MethodHandle.class, Object.class);
}
catch (NoSuchMethodException exception)
{
throw new NoSuchMethodError(exception.getMessage());
}
Function initializer = method ->
{
TypeDescription returnType = new TypeDescription.ForLoadedType(method.getReturnType());
TypeDescription genericCacheType = new TypeDescription.ForLoadedType(Cache.class);
Generic cacheType = Generic.Builder.parameterizedType(genericCacheType, returnType).build();
Cached annotation = method.getAnnotation(Cached.class);
String fieldName = matcher.propertyName(method.getName());
FieldDescription cacheField = new FieldDescription.Latent(currentType, fieldName, Modifier.PRIVATE, cacheType, emptyList());
JavaConstant.MethodHandle originalMethod = JavaConstant.MethodHandle.ofSpecial(method, originalType);
return MethodCall
.invoke(cacheCreator)
.with(annotation.cacheSize())
.with(originalMethod)
.withThis()
.setsField(cacheField);
};
Stream initializers = cachedMethods.map(initializer);
MethodDescription superConstructor = latent(currentType.getSuperClass().asErasure(), VOID.asGenericType(), "");
Composable invokeSuper = MethodCall.invoke(superConstructor).onSuper();
return initializers.reduce(invokeSuper, Composable::andThen);
}
private Implementation cached(Cached cached, String field, Type type)
{
try
{
Method cacheAccessor = Cache.class.getDeclaredMethod("get", Object[].class);
return MethodCall
.invoke(cacheAccessor)
.onField(field)
.withArgumentArray()
.withAssigner(DEFAULT, DYNAMIC);
}
catch (NoSuchMethodException exception)
{
throw new NoSuchMethodError(exception.getMessage());
}
}
private Implementation get(String field, Type type, Annotation inject, ClassLoader classLoader)
{
Class> provider = Projo.forName(provider(inject), classLoader);
Generic genericProvider = Generic.Builder.parameterizedType(provider, type).build();
MethodDescription get = latent(genericProvider.asErasure(), OBJECT.asGenericType(), "get");
return MethodCall.invoke(get).onField(field).withAssigner(DEFAULT, DYNAMIC);
}
private MethodDescription.Latent latent(TypeDescription declaringType, Generic returnType, String name, TypeDescription... parameterTypes)
{
List extends ParameterDescription.Token> parameterTokens = Stream.of(parameterTypes)
.map(TypeDescription::asGenericType)
.map(ParameterDescription.Token::new)
.collect(toList());
return new MethodDescription.Latent(declaringType, name, PUBLIC, emptyList(), returnType, parameterTokens, emptyList(), emptyList(), null, null);
}
private ByteBuddy codeGenerator()
{
return new ByteBuddy(JAVA_V8).with(TypeValidation.DISABLED);
}
/**
* Determines the field type that is backing a method implementation. The field type is
* not necessarily the same as the method's return type:
*
* - for injected methods returning type {@code T}, the field type is {@code Provider
}
* - for cached methods returning type {@code T}, the field type is
* {@code Cache
}
* - for all other methods returning type {@code T}, the field type is {@code T}
*
* @param inject an {@link Optional} {@link javax.inject.Inject} annotation
* @param cached an {@link Optional} {@link pro.projo.annotations.Cached} annotation
* @param originalReturnType the return type of the method
* @return the appropriate field type for the method
**/
Generic getFieldType(AnnotationList annotations, Class> originalReturnType, ClassLoader classLoader)
{
if (!annotations.containsInject() && !annotations.contains(Cached.class))
{
return Generic.Builder.rawType(originalReturnType).build();
}
else
{
Optional> optionalContainer = annotations.getInject().map(inject -> Projo.forName(provider(inject), classLoader));
Class> container = optionalContainer.orElse(Cache.class);
Type wrappedType = MethodType.methodType(originalReturnType).wrap().returnType();
Optional returns = annotations.get(Returns.class);
if (returns.isPresent())
{
String returnType = returns.get().value();
int index = returnType.indexOf('<');
if (index == -1)
{
return Generic.Builder.parameterizedType(container, Projo.forName(returnType, classLoader)).build();
}
else
{
Class> rawType = Projo.forName(returnType.substring(0, index), classLoader);
String[] parameters = returnType.substring(index+1, returnType.length()-1).split("[, ]");
Stream> typeParameters = Stream.of(parameters).map(it -> Projo.forName(it, classLoader));
Generic parameterizedType = Generic.Builder.parameterizedType(rawType, typeParameters.toArray(Type[]::new)).build();
TypeDescription containerType = Generic.Builder.rawType(container).build().asErasure();
return Generic.Builder.parameterizedType(containerType, parameterizedType).build();
}
}
else
{
return Generic.Builder.parameterizedType(container, wrappedType).build();
}
}
}
/**
* Returns the corresponding provider annotation name for an inject annotation.
*
* @param inject the {@code @Inject} annotation
* @return the fully qualified name of the corresponding {@code @Provider}
**/
private String provider(Annotation inject)
{
return inject.annotationType().getPackage().getName() + ".Provider";
}
private <_ValuableBuilder_ extends Valuable<_Artifact_> & Builder<_Artifact_>>
Builder<_Artifact_> annotate(Optional annotation, _ValuableBuilder_ builder)
{
return annotation.isPresent()? builder.annotateField(annotation.get()):builder;
}
private <_Any_> BinaryOperator<_Any_> sequentialOnly()
{
return (operand1, operand2) ->
{
throw new UnsupportedOperationException("parallel stream processing not supported");
};
}
private String implementationName(Class<_Artifact_> type, boolean defaultPackage)
{
String typeName = type.getName();
if (defaultPackage)
{
typeName = type.getSimpleName();
}
else if (typeName.startsWith("java."))
{
typeName = typeName.substring("java.".length());
}
return typeName + SUFFIX;
}
private ClassLoader classLoader(Class> type, boolean defaultPackage, ClassLoader classLoader)
{
if (classLoader != null && !defaultPackage)
{
return classLoader;
}
return defaultPackage? Thread.currentThread().getContextClassLoader():type.getClassLoader();
}
private Class> baseclass(Class<_Artifact_> type)
{
List features = asList(Projo.isValueObject(type), Projo.hasCustomToString(type));
return baseClasses.get(features);
}
private Builder<_Artifact_> create(Class<_Artifact_> type, List additionalImplements, ClassLoader classLoader)
{
Stream baseTypes = Stream.of(type(type), type(ProjoObject.class));
TypeDefinition[] interfaces = Stream.concat(baseTypes, additionalImplements.stream().map(it -> type(it, classLoader))).toArray(TypeDefinition[]::new);
@SuppressWarnings("unchecked")
Builder<_Artifact_> builder = (Builder<_Artifact_>)codeGenerator().subclass(baseclass(type)).implement(interfaces);
return builder;
}
private List getImplements(Class<_Artifact_> type)
{
Implements annotation = type.getAnnotation(Implements.class);
Stream implement = annotation != null? Stream.of(annotation.value()):Stream.empty();
return implement.collect(toList());
}
private TypeDefinition type(Class> type)
{
return new TypeDescription.ForLoadedType(type);
}
private TypeDefinition type(String typeName, ClassLoader classLoader)
{
Matcher matcher = typeNamePattern.matcher(typeName);
matcher.matches();
String baseType = matcher.group("base");
String typeArguments = matcher.group("arguments");
if (typeArguments == null)
{
return type(Projo.forName(baseType, classLoader));
}
else
{
Stream arguments = Stream.of(typeArguments.split("[, ]+"));
Type[] argumentTypes = arguments.map(it -> Projo.forName(it, classLoader)).toArray(Type[]::new);
return Generic.Builder.parameterizedType(Projo.forName(baseType, classLoader), argumentTypes).build();
}
}
private <_Any_> Loaded<_Any_> debug(Loaded<_Any_> loadedType)
{
String debugPath = System.getProperty("pro.projo.debug.path");
if (debugPath != null)
{
String packageName = loadedType.getTypeDescription().getPackage().getName();
String typeName = loadedType.getTypeDescription().getSimpleName();
try
{
File dumpDirectory = new File(debugPath);
System.err.println("Saving " + packageName + "." + typeName + " in " + dumpDirectory);
loadedType.saveIn(dumpDirectory);
}
catch (IOException exception)
{
throw new IOError(exception);
}
}
return loadedType;
}
}