From 8dfa082a542b78127b6b95e2f63f7d475519361a Mon Sep 17 00:00:00 2001 From: Simone Baffelli <simone.baffelli@empa.ch> Date: Tue, 15 Aug 2023 22:12:53 +0200 Subject: [PATCH] Added custom extension to extract method signatures to ts. --- api-openbis-java/build.gradle | 57 +++--- api-openbis-java/settings.gradle | 2 +- .../ch/empa/tsprocessor/MethodExtension.java | 176 ++++++++++++++++++ build/build.gradle | 5 +- build/repository.gradle | 3 + build/settings.gradle | 2 +- 6 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java diff --git a/api-openbis-java/build.gradle b/api-openbis-java/build.gradle index d99b470e16c..91855a760cb 100644 --- a/api-openbis-java/build.gradle +++ b/api-openbis-java/build.gradle @@ -1,5 +1,8 @@ +import groovy.json.JsonOutput +import cz.habarta.typescript.generator.Settings.ConfiguredExtension evaluationDependsOn(':lib-commonbase') evaluationDependsOn(':lib-common') +evaluationDependsOn(':lib-typescriptprocessor') apply from: '../build/javaproject.gradle' @@ -16,27 +19,33 @@ buildscript { } -task('generateModuleNames'){ - doLast{ - def file = fileTree(dir: '../api-openbis-javascript/src/v3', include: 'as/**/*.js') + +task('generateModuleNames') { + doLast { + def file = fileTree(dir: '../api-openbis-javascript/src/v3', include: ['as/**/*.js', 'ds/**/*.js', 'openbis.js']) def modules = file.collect { File f -> def name = f.name def moduleName = name.substring(0, name.lastIndexOf('.')) - return "\"${moduleName}\"" + return "${moduleName}" } - def moduleNames = modules.unique().join(', \n') + def names = [names: modules] + def json = JsonOutput.prettyPrint(JsonOutput.toJson(names)) def moduleNamesFile = new File('../api-openbis-javascript/src/v3/moduleNames.json') - moduleNamesFile.write("{\"names\":[$moduleNames]}") + moduleNamesFile.write(json) } } dependencies { - api project(':lib-common'), + api project(':lib-common'), project(":lib-typescriptprocessor"), 'sis:sis-file-transfer:19.03.1', 'fasterxml:jackson-core:2.9.10', - 'fasterxml:jackson-annotations:2.9.10' + 'fasterxml:jackson-annotations:2.9.10', + "cz.habarta.typescript-generator:typescript-generator-core:3.2.1263" + + api "cz.habarta.typescript-generator:typescript-generator-core:3.2.1263" + api 'org.bsc.processor:java2ts-processor:1.3.1' testImplementation project(path: ':lib-commonbase', configuration: 'tests'), project(path: ':lib-common', configuration: 'tests'), @@ -44,32 +53,36 @@ dependencies { 'testng:testng:6.8-CISD', 'reflections:reflections:0.9.10' - + api(project(":lib-typescriptprocessor")) } 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.datastore.**', - 'ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.**', - 'ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi', - 'ch.ethz.sis.openbis.generic.OpenBIS', +// '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.datastore.**', +// 'ch.ethz.sis.openbis.generic.asapi.v3.dto.vocabulary.**', + 'ch.ethz.sis.openbis.generic.OpenBIS' ] - outputKind = 'ambientModule' + excludeClassPatterns = ["**.v1.**", + "**.generic.shared.**", + "ch.ethz.sis.openbis.generic.asapi.v3.dto.session.search.PersonalAccessTokenSessionNameSearchCriteria", + "ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.search.ExternalDmsSearchCriteria", + "ch.ethz.sis.openbis.generic.asapi.v3.dto.externaldms.search.ExternalDmsSearchCriteria"] + outputKind = 'module' + outputFileType = 'declarationFile' + //customTypeProcessor = "ch.empa.tsprocessor.CustomTypeProcessor" + extensionsWithConfiguration = [ new ConfiguredExtension(className:'ch.empa.tsprocessor.MethodExtension', configuration: ['asnycClasses':['ch.ethz.sis.openbis.generic.OpenBIS']])] jackson2ModuleDiscovery = true outputFile = file('../api-openbis-javascript/src/v3/openbis.d.ts') - module = 'dto' + //module = 'dto' } diff --git a/api-openbis-java/settings.gradle b/api-openbis-java/settings.gradle index d1561187ade..aafaece0ea9 100644 --- a/api-openbis-java/settings.gradle +++ b/api-openbis-java/settings.gradle @@ -1 +1 @@ -includeFlat 'lib-commonbase', 'lib-common' +includeFlat 'lib-commonbase', 'lib-common', 'lib-typescriptprocessor' diff --git a/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java b/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java new file mode 100644 index 00000000000..1d048fd094a --- /dev/null +++ b/api-openbis-java/source/java/ch/empa/tsprocessor/MethodExtension.java @@ -0,0 +1,176 @@ +package ch.empa.tsprocessor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import cz.habarta.typescript.generator.*; +import cz.habarta.typescript.generator.compiler.ModelCompiler; +import cz.habarta.typescript.generator.compiler.SymbolTable; +import cz.habarta.typescript.generator.compiler.TsModelTransformer; +import cz.habarta.typescript.generator.emitter.*; +import cz.habarta.typescript.generator.parser.BeanModel; +import cz.habarta.typescript.generator.util.Utils; + +import java.io.IOException; +import java.lang.reflect.*; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.ObjectMapper;// in play 2.3 + +interface MethodProcessor { + TsPropertyModel makeFunction(Method method, TsModelTransformer.Context context, SymbolTable symbolTable); +} + +public class MethodExtension extends Extension { + + static final ObjectMapper mapper = new ObjectMapper(); + + public static final String CFG_ASYNC_CLASSES = "asyncClasses"; + + private List<String> asnycClasses = new ArrayList<>(); + + public MethodExtension() { + } + public MethodExtension(List<String> asyncClasses) { + this.asnycClasses = asyncClasses; + } + + + + @Override + public void setConfiguration(Map<String, String> configuration) throws RuntimeException { + if (configuration.containsKey(CFG_ASYNC_CLASSES)) { + String classString = configuration.get(CFG_ASYNC_CLASSES); + try { + String[] classes = mapper.readValue(classString, String[].class); + asnycClasses = Arrays.asList(classes); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println(classString); + + } + } + + private static String formatAllMethods(TsBeanModel bean) { + System.out.println("MethodExtension.formatAllMethos"); + Class<?> origin = bean.getOrigin(); + return Arrays.stream(origin.getMethods()).map(method -> method.getName()).collect(Collectors.joining("\n")); + + } + + private static boolean filterMethods(Method method) { + System.out.println("MethodExtension.filterMethods"); + return !((method.getDeclaringClass() == Object.class) || (method.getName().matches("^(set|get|is).*|hashCode|toString|equals"))); + } + + private static List<TsParameter> getMethodParameters(Method method, SymbolTable symbolTable) { + System.out.println("MethodExtension.getMethodParameters"); + 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 { + Class<?> rawClass = (Class<?>) parameterizedType.getRawType(); + 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 ? new TsType.ReferenceType(symbolTable.getSymbol(upperBounds[0].getClass())) : TsType.Any; + } else { + // Handle TypeVariable case + return TsType.Any; + } + } + + private static TsType getReturnType(Method method, TsModelTransformer.Context context, SymbolTable symbolTable) { + System.out.println("MethodExtension.getReturnType"); + TsType typeParams = ResolveGenericType(method.getGenericReturnType(), symbolTable); + return typeParams; + + } + + + private static TsPropertyModel makeFunction(Method method, TsModelTransformer.Context context, SymbolTable symbolTable) { + //System.out.println("MethodExtension.makeFunction"); + List<TsParameter> params = getMethodParameters(method, symbolTable); + TsType returnType = getReturnType(method, context, symbolTable); + return new TsPropertyModel(method.getName(), new TsType.FunctionType(params, returnType), TsModifierFlags.None, false, null); + } + + private static TsPropertyModel makeCallBackFunction(Method method, TsModelTransformer.Context context, SymbolTable symbolTable) { + //System.out.println("MethodExtension.makeCallBackFunction"); + List<TsParameter> params = getMethodParameters(method, symbolTable); + TsType returnType = getReturnType(method, context, symbolTable); + TsParameter callbackParam = new TsParameter("callback", returnType); + TsType.FunctionType arrowFunction = new TsType.FunctionType(List.of(callbackParam), TsType.Null); + TsParameter callback = new TsParameter("callback", arrowFunction); + List<TsParameter> paramsWithCallback = Stream.concat(params.stream(), Stream.of(callback)).collect(Collectors.toList()); + return new TsPropertyModel(method.getName(), new TsType.FunctionType(paramsWithCallback, TsType.Null), TsModifierFlags.None, false, null); + } + + + private static TsBeanModel addFunctions(TsBeanModel bean, TsModelTransformer.Context context, SymbolTable symbolTable, MethodProcessor processor) { + //System.out.println("MethodExtension.addFunction"); + Class<?> origin = bean.getOrigin(); + Stream<TsPropertyModel> params = Arrays.stream(origin.getMethods()).filter(mt -> filterMethods(mt)).map(method -> processor.makeFunction(method, context, symbolTable)); + List<TsPropertyModel> allProps = Stream.concat(params, bean.getProperties().stream()).collect(Collectors.toList()); + return bean.withProperties(allProps); + } + + @Override + public EmitterExtensionFeatures getFeatures() { + final EmitterExtensionFeatures features = new EmitterExtensionFeatures(); + features.generatesRuntimeCode = false; + features.generatesModuleCode = true; + features.worksWithPackagesMappedToNamespaces = true; + features.generatesJaxrsApplicationClient = false; + return features; + } + + @Override + public List<TransformerDefinition> getTransformers() { + return List.of(new TransformerDefinition(ModelCompiler.TransformationPhase.BeforeSymbolResolution, (TsModelTransformer) (context, model) -> { + //System.out.println("MethodExtension.getTransformers"); + Stream<TsBeanModel> processedBeans = model.getBeans().stream().map(bean -> { + System.out.printf("bean: %s\n", (bean.getOrigin().getName())); + if (asnycClasses.contains(bean.getOrigin().getName())){ + return addFunctions(bean, context, context.getSymbolTable(), MethodExtension::makeCallBackFunction); + } else { + return addFunctions(bean, context, context.getSymbolTable(), MethodExtension::makeFunction); + } + } ); + + return model.withBeans(processedBeans.collect(Collectors.toList())); + })); + } + + public void emitElements(Writer writer, Settings settings, boolean exportKeyword, TsModel model) { + System.out.println("MethodExtension.emitElements"); + for (TsBeanModel bean : model.getBeans()) { + //System.out.printf("bean: %s\n", formatAllMethods(bean)); + if (bean.isJaxrsApplicationClientBean()) { + final String clientName = bean.getName().getSimpleName(); + final String clientFullName = settings.mapPackagesToNamespaces ? bean.getName().getFullName() : bean.getName().getSimpleName(); + } + } + } + +} diff --git a/build/build.gradle b/build/build.gradle index 7fdf2e012a2..92df344b5cb 100644 --- a/build/build.gradle +++ b/build/build.gradle @@ -14,5 +14,8 @@ evaluationDependsOn(':test-ui-core') evaluationDependsOn(':test-api-openbis-javascript') evaluationDependsOn(':lib-image-readers') evaluationDependsOn(':server-external-data-store') - +evaluationDependsOn(':lib-typescriptprocessor') apply from: '../build/javaproject.gradle' + + + diff --git a/build/repository.gradle b/build/repository.gradle index 1672a54cfb7..bedda7cc8d1 100644 --- a/build/repository.gradle +++ b/build/repository.gradle @@ -3,4 +3,7 @@ ext.repositoryConfig = { ivyPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/ivy.xml" artifactPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]" } + maven { + url "https://plugins.gradle.org/m2/" + } } diff --git a/build/settings.gradle b/build/settings.gradle index 89b7a93c4d7..6432614e0bb 100644 --- a/build/settings.gradle +++ b/build/settings.gradle @@ -3,4 +3,4 @@ includeFlat 'lib-commonbase', 'lib-common', 'api-openbis-java', 'lib-openbis-com 'core-plugin-openbis', 'app-openbis-installer', 'lib-image-readers', 'test-ui-core', 'test-api-openbis-javascript', 'server-external-data-store', 'ui-admin', 'lib-microservice-server', 'lib-transactional-file-system', 'server-data-store', 'ui-eln-lims', 'api-openbis-javascript', - 'lib-json', 'api-data-store-server-java', 'api-data-store-server-javascript' + 'lib-json', 'api-data-store-server-java', 'api-data-store-server-javascript', 'lib-typescriptprocessor' \ No newline at end of file -- GitLab