From 02aeeb45dede313889cf2a0884b23d6122196cd3 Mon Sep 17 00:00:00 2001 From: Simone Baffelli <simone.baffelli@empa.ch> Date: Fri, 25 Aug 2023 08:33:42 +0200 Subject: [PATCH] Added test, improved resolution of types using additional type processor. --- api-openbis-java/build.gradle | 18 +- .../ch/empa/tsprocessor/MethodExtension.java | 196 ++++++++++++------ .../empa/tsprocessor/MethodExtensionTest.java | 75 +++++++ api-openbis-java/sourceTest/java/tests.xml | 1 + 4 files changed, 217 insertions(+), 73 deletions(-) create mode 100644 api-openbis-java/sourceTest/java/ch/empa/tsprocessor/MethodExtensionTest.java diff --git a/api-openbis-java/build.gradle b/api-openbis-java/build.gradle index 6b128015347..fd43ab44e1a 100644 --- a/api-openbis-java/build.gradle +++ b/api-openbis-java/build.gradle @@ -63,21 +63,11 @@ apply plugin: "cz.habarta.typescript-generator" generateTypeScript { jsonLibrary = 'jackson2' classPatterns = [ -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.property.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.project.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.person.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.material.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.space.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.common.**', -// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.**', -// 'ch.ethz.sis.openbis.generic.OpenBIS', - 'ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.archive.ArchiveDataSetsOperation' - + 'ch.ethz.sis.openbis.generic.asapi.v3.dto.**', + 'ch.ethz.sis.openbis.generic.dssapi.v3.dto.**', + 'ch.ethz.sis.openbis.generic.OpenBIS', ] + excludeClassPatterns = ["**.v1.**", "**.generic.shared.**", "ch.ethz.sis.openbis.generic.asapi.v3.dto.session.search.PersonalAccessTokenSessionNameSearchCriteria", diff --git a/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java b/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java index 0373a80d585..4883f0b0ff0 100644 --- a/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java +++ b/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java @@ -17,13 +17,81 @@ import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper;// in play 2.3 interface MethodProcessor { - TsPropertyModel makeFunction(Method method, SymbolTable symbolTable); + TsPropertyModel makeFunction(Method method, TsModel model, ProcessingContext processingContext); +} + +interface MakeConstructor { + TsConstructorModel makeConstructor(Constructor<?> constructor, TsModel model, ProcessingContext processingContext); +} + +class ProcessingContext { + private final SymbolTable symbolTable; + private final MappedTypeExtractor typeExtractor; + private final TypeProcessor localProcessor; + + ProcessingContext(SymbolTable symbolTable, MappedTypeExtractor typeExtractor, TypeProcessor localProcessor) { + this.symbolTable = symbolTable; + this.typeExtractor = typeExtractor; + this.localProcessor = localProcessor; + } + + public SymbolTable getSymbolTable() { + return symbolTable; + } + + public MappedTypeExtractor getTypeExtractor() { + return typeExtractor; + } + + public TypeProcessor getLocalProcessor() { + return localProcessor; + } +} + +class MappedTypeExtractor { + + private final HashMap<Class<?>, TsType> mappedTypes = new HashMap<>(); + + public void extractMappedTypes(TsModel model) { + model.getBeans().forEach(bean -> { + System.out.printf("Extracting mapped types for bean %s\n", bean.getOrigin().getName()); + Class<?> origin = bean.getOrigin(); + Field[] fields = origin.getDeclaredFields(); + System.out.printf("Extracting mapped types for bean %s, the fields are %s\n", bean.getOrigin().getName(), Arrays.toString(fields)); + bean.getProperties().forEach(prop -> { + TsType propType = prop.tsType; + + Optional<Field> originalField = Arrays.stream(fields).filter(field -> field.getName().equals(prop.getName())).findFirst(); + Class<?> originalClass = originalField.flatMap(field -> Optional.ofNullable(field.getType())).orElse(null); + TsType tsType = originalClass != null ? mappedTypes.put(originalClass, propType) : null; + }); + mappedTypes.put(origin, new TsType.ReferenceType(bean.getName())); + }); + model.getTypeAliases().stream().forEach(tsAliasModel -> { + mappedTypes.put(tsAliasModel.getOrigin(), tsAliasModel.getDefinition()); + }); + System.out.printf("Mapped types %s\n", mappedTypes); + + } + + public HashMap<Class<?>, TsType> getMappedTypes() { + return mappedTypes; + } + + public Optional<TsType> getMappedType(Class<?> clz) { + return Optional.ofNullable(mappedTypes.get(clz)); + } } public class MethodExtension extends Extension { public static final String CFG_ASYNC_CLASSES = "asyncClasses"; static final ObjectMapper mapper = new ObjectMapper(); + private static final List<Class<?>> assignableContainers = List.of(List.class, Set.class, ArrayList.class, Collection.class, Map.class, HashMap.class, Optional.class); + + private static final Logger logger = TypeScriptGenerator.getLogger(); + //Hold types that were mapped by the bean model +// private final MappedTypeExtractor typeExtractor = new MappedTypeExtractor(); private List<String> asnycClasses = new ArrayList<>(); public MethodExtension() { @@ -33,66 +101,52 @@ public class MethodExtension extends Extension { this.asnycClasses = asyncClasses; } + private static boolean filterMethods(Method method) { return !((method.getDeclaringClass() == Object.class) || (method.getName().matches("hashCode|toString|equals"))); } - private static List<TsParameter> getMethodParameters(Method method, SymbolTable symbolTable) { - return Arrays.stream(method.getParameters()).map(parameter -> new TsParameter(method.getName(), ResolveGenericType(parameter.getParameterizedType(), symbolTable))).collect(Collectors.toList()); - } - - private static TsType ResolveGenericType(Type type, SymbolTable symbolTable) { - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - Class<?> rawType = (Class<?>) parameterizedType.getRawType(); - - if (List.class.isAssignableFrom(rawType)) { - // Manually map List<T> to Array<T> - TsType elementType = ResolveGenericType(parameterizedType.getActualTypeArguments()[0], symbolTable); - return new TsType.GenericReferenceType(symbolTable.getSymbol(Array.class), Collections.singletonList(elementType)); - } else if (Set.class.isAssignableFrom(rawType)) { - // Manually map List<T> to Array<T> - TsType elementType = ResolveGenericType(parameterizedType.getActualTypeArguments()[0], symbolTable); - return new TsType.GenericReferenceType(symbolTable.getSymbol(Set.class), Collections.singletonList(elementType)); - } else if (ArrayList.class.isAssignableFrom(rawType)) { - TsType elementType = ResolveGenericType(parameterizedType.getActualTypeArguments()[0], symbolTable); - return new TsType.GenericReferenceType(symbolTable.getSymbol(ArrayList.class), Collections.singletonList(elementType)); - } else { - Class<?> rawClass = (Class<?>) parameterizedType.getRawType(); - System.out.printf("Other type %s\n", rawClass.getName()); - TsType[] typeArguments = Arrays.stream(parameterizedType.getActualTypeArguments()).map(typeArgument -> ResolveGenericType(typeArgument, symbolTable)).toArray(TsType[]::new); - return new TsType.GenericReferenceType(symbolTable.getSymbol(rawClass), Arrays.asList(typeArguments)); - } - } else if (type instanceof GenericArrayType) { - GenericArrayType genericArrayType = (GenericArrayType) type; - return new TsType.BasicArrayType(ResolveGenericType(genericArrayType.getGenericComponentType(), symbolTable)); - } else if (type instanceof Class) { - Class<?> clz = (Class<?>) type; - return new TsType.ReferenceType(symbolTable.getSymbol(clz)); - } else if (type instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) type; - final Type[] upperBounds = wildcardType.getUpperBounds(); - return upperBounds.length > 0 ? ResolveGenericType(upperBounds[0], symbolTable) : TsType.Any; - } else { - // Handle TypeVariable case - return TsType.Any; - } + private static Optional<Class<?>> getAssignableClass(Class<?> clz) { + return assignableContainers.stream().filter(container -> container.isAssignableFrom(clz)).findFirst(); + } + + private static List<TsParameter> getMethodParameters(Executable method, TsModel model, ProcessingContext processingContext) { + return Arrays.stream(method.getParameters()).map(parameter -> new TsParameter(parameter.getName(), ResolveGenericType(parameter.getParameterizedType(), model, processingContext))).collect(Collectors.toList()); + } + + + private static TsType ResolveGenericType(Type type, TsModel model, ProcessingContext processingContext) { + + TypeProcessor.Context context = new TypeProcessor.Context(processingContext.getSymbolTable(), processingContext.getLocalProcessor(), null); + return context.processType(type).getTsType(); + } - private static TsType getReturnType(Method method, SymbolTable symbolTable) { - return ResolveGenericType(method.getGenericReturnType(), symbolTable); + private static TsType getReturnType(Method method, TsModel model, ProcessingContext processingContext) { + return ResolveGenericType(method.getGenericReturnType(), model, processingContext); } - private static TsPropertyModel makeFunction(Method method, SymbolTable symbolTable) { - List<TsParameter> params = getMethodParameters(method, symbolTable); - TsType returnType = getReturnType(method, symbolTable); + private static TsPropertyModel makeFunction(Method method, TsModel model, ProcessingContext processingContext) { + List<TsParameter> params = getMethodParameters(method, model, processingContext); + TsType returnType = getReturnType(method, model, processingContext); return new TsPropertyModel(method.getName(), new TsType.FunctionType(params, returnType), TsModifierFlags.None, false, null); } - private static TsPropertyModel makeCallBackFunction(Method method, SymbolTable symbolTable) { - List<TsParameter> params = getMethodParameters(method, symbolTable); - TsType returnType = getReturnType(method, symbolTable); + private static Optional<TsMethodModel> propertyModelToMethodModel(TsPropertyModel propertyModel) { + TsType value = propertyModel.getTsType(); + if (value instanceof TsType.FunctionType) { + TsType.FunctionType functionType = (TsType.FunctionType) value; + List<TsParameterModel> params = functionType.parameters.stream().map(param -> new TsParameterModel(param.name, param.getTsType())).collect(Collectors.toList()); + return Optional.of(new TsMethodModel(propertyModel.getName(), propertyModel.getModifiers(), null, params, functionType.type, null, null)); + } else { + return Optional.empty(); + } + } + + private static TsPropertyModel makeCallBackFunction(Method method, TsModel model, ProcessingContext processingContext) { + List<TsParameter> params = getMethodParameters(method, model, processingContext); + TsType returnType = getReturnType(method, model, processingContext); TsParameter callbackParam = new TsParameter("callback", returnType); TsType.FunctionType arrowFunction = new TsType.FunctionType(List.of(callbackParam), TsType.Null); TsParameter callback = new TsParameter("callback", arrowFunction); @@ -100,20 +154,34 @@ public class MethodExtension extends Extension { return new TsPropertyModel(method.getName(), new TsType.FunctionType(paramsWithCallback, TsType.Null), TsModifierFlags.None, false, null); } - private static TsPropertyModel makePromiseReturningFunction(Method method, SymbolTable symbolTable) { - List<TsParameter> params = getMethodParameters(method, symbolTable); - List<TsType> returnType = List.of(getReturnType(method, symbolTable)); + private static TsPropertyModel makePromiseReturningFunction(Method method, TsModel model, ProcessingContext processingContext) { + List<TsParameter> params = getMethodParameters(method, model, processingContext); + List<TsType> returnType = List.of(getReturnType(method, model, processingContext)); TsType promiseType = new TsType.GenericReferenceType(new Symbol("Promise"), returnType); return new TsPropertyModel(method.getName(), new TsType.FunctionType(params, promiseType), TsModifierFlags.None, false, null); } - private static TsBeanModel addFunctions(TsBeanModel bean, SymbolTable symbolTable, MethodProcessor processor) { + private static TsMethodModel makeConstructor(Constructor<?> constructor, TsBeanModel beanModel, TsModel model, ProcessingContext processingContext) { + List<TsParameter> params = getMethodParameters(constructor, model, processingContext); + //Exclude parameters that correspond to the bean itself + List<TsParameter> paramsWithoutDeclaringClass = params.stream().collect(Collectors.toList()); + TsType returnType = ResolveGenericType(constructor.getDeclaringClass(), model, processingContext); + System.out.printf("Constructor %s, params %s, return type %s\n", constructor, params, returnType); + TsType.FunctionType functionType = new TsType.FunctionType(paramsWithoutDeclaringClass, returnType); + TsPropertyModel propertyModel = new TsPropertyModel("new ", functionType, TsModifierFlags.None, false, null); + return propertyModelToMethodModel(propertyModel).orElse(null); + } + + private static TsBeanModel addFunctions(TsBeanModel bean, TsModel model, ProcessingContext processingContext, MethodProcessor processor) { Class<?> origin = bean.getOrigin(); - Stream<TsPropertyModel> params = Arrays.stream(origin.getMethods()).filter(MethodExtension::filterMethods).map(method -> processor.makeFunction(method, symbolTable)); - List<TsPropertyModel> allProps = Stream.concat(params, bean.getProperties().stream()).collect(Collectors.toList()); - return bean.withProperties(allProps); + + Stream<TsMethodModel> params = Arrays.stream(origin.getMethods()).filter(MethodExtension::filterMethods).map(method -> propertyModelToMethodModel(processor.makeFunction(method, model, processingContext))).flatMap(it -> it.map(Stream::of).orElse(Stream.empty())); + Stream<TsMethodModel> constructors = Arrays.stream(origin.getDeclaredConstructors()).map(constructor -> makeConstructor(constructor, bean, model, processingContext)); + List<TsMethodModel> allMethods = Stream.of(params, constructors).flatMap(it -> it).collect(Collectors.toList()); + return bean.withProperties(bean.getProperties()).withMethods(allMethods); } + @Override public void setConfiguration(Map<String, String> configuration) throws RuntimeException { @@ -142,14 +210,24 @@ public class MethodExtension extends Extension { return features; } + @Override public List<TransformerDefinition> getTransformers() { - return List.of(new TransformerDefinition(ModelCompiler.TransformationPhase.BeforeSymbolResolution, (TsModelTransformer) (context, model) -> { + return List.of(new TransformerDefinition(ModelCompiler.TransformationPhase.AfterDeclarationSorting, (TsModelTransformer) (context, model) -> { + //Extract all types that were mapped by the bean model to reuse them + MappedTypeExtractor typeExtractor = new MappedTypeExtractor(); + typeExtractor.extractMappedTypes(model); + String classNames = model.getBeans().stream().map(it -> it.getName().getFullName()).collect(Collectors.joining("\n")); + System.out.printf("model %s\n", classNames); + System.out.printf("Mapped types %s\n", typeExtractor.getMappedTypes()); + TypeProcessor localProcessor = new DefaultTypeProcessor(); + ProcessingContext processingContext = new ProcessingContext(context.getSymbolTable(), typeExtractor, localProcessor); + //Add table of mapped types Stream<TsBeanModel> processedBeans = model.getBeans().stream().map(bean -> { if (asnycClasses.contains(bean.getOrigin().getName())) { - return addFunctions(bean, context.getSymbolTable(), MethodExtension::makePromiseReturningFunction); + return addFunctions(bean, model, processingContext, MethodExtension::makePromiseReturningFunction); } else { - return addFunctions(bean, context.getSymbolTable(), MethodExtension::makeFunction); + return addFunctions(bean, model, processingContext, MethodExtension::makeFunction); } }); diff --git a/api-openbis-java/sourceTest/java/ch/empa/tsprocessor/MethodExtensionTest.java b/api-openbis-java/sourceTest/java/ch/empa/tsprocessor/MethodExtensionTest.java new file mode 100644 index 00000000000..9c157f9f053 --- /dev/null +++ b/api-openbis-java/sourceTest/java/ch/empa/tsprocessor/MethodExtensionTest.java @@ -0,0 +1,75 @@ +package ch.empa.tsprocessor; + +import ch.systemsx.cisd.base.annotation.JsonObject; +import cz.habarta.typescript.generator.*; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.util.List; + +public class MethodExtensionTest { + + interface GenericTestClass<A> { + A get(); + } + + //@JsonObject("TestClass") + class TestClass { + public boolean A; + public String[] B; + public boolean getA() {return false;} + public String[] getB() {return null;} + TestClass(String a){} + } + + class InnerTestClass { + public TestClass A; + public boolean getA() {return false;} + } + + interface InterfaceWithWildcard { + interface OuterInterface{} + + String doSomething(List<? extends OuterInterface> input); + } + + + + + final Settings settings = new Settings(); + @BeforeTest + public void setSettings(){ + settings.outputKind = (TypeScriptOutputKind.module); + settings.jsonLibrary = JsonLibrary.jackson2; + settings.extensions = List.of(new MethodExtension()); + } + + + + @Test + public void testGenericClass() { + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(GenericTestClass.class)); + System.out.println(output); + + } + + @Test + public void testNormalClass(){ + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(TestClass.class)); + System.out.println(output); + } + + @Test + public void testInnerClass(){ + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(InnerTestClass.class)); + System.out.println(output); + } + @Test + public void testWildCardType(){ + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(InterfaceWithWildcard.class)); + System.out.println(output); + } + + +} diff --git a/api-openbis-java/sourceTest/java/tests.xml b/api-openbis-java/sourceTest/java/tests.xml index c4a6566e3a2..5fb12d925a7 100644 --- a/api-openbis-java/sourceTest/java/tests.xml +++ b/api-openbis-java/sourceTest/java/tests.xml @@ -8,6 +8,7 @@ <packages> <package name="ch.ethz.sis.openbis.*" /> <package name="ch.systemsx.cisd.openbis.*" /> + <package name="ch.empa.tsprocessor.*" /> </packages> </test> </suite> -- GitLab