diff --git a/.gitignore b/.gitignore index 0195d9e5e..ca558517b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Files generated during test execution +JaxrsV2ApplicationTest.java +JsonbV1ParserTest.java + # Maven target/ pom.xml.tag diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/CustomMappingTypeProcessor.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/CustomMappingTypeProcessor.java index b4cac62bd..e49bb1025 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/CustomMappingTypeProcessor.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/CustomMappingTypeProcessor.java @@ -3,6 +3,7 @@ import cz.habarta.typescript.generator.util.GenericsResolver; import cz.habarta.typescript.generator.util.Utils; + import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -28,6 +29,7 @@ public Result processType(Type javaType, Context context) { ? m.rawClass.isAssignableFrom(rawClass) : m.rawClass.equals(rawClass) ) + .filter(m -> GenericsResolver.typeParameterNameList(m.rawClass).equals(m.javaType.typeParameters) ) .findFirst() .orElse(null); if (mapping == null) { @@ -42,20 +44,8 @@ public Result processType(Type javaType, Context context) { discoveredClasses.addAll(typeArgumentResult.getDiscoveredClasses()); return typeArgumentResult.getTsType(); }; - if (mapping.tsType.typeParameters != null) { - final List tsTypeArguments = new ArrayList<>(); - for (String typeParameter : mapping.tsType.typeParameters) { - final TsType tsType; - final int index = mapping.javaType.indexOfTypeParameter(typeParameter); - if (index != -1) { - tsType = processGenericParameter.apply(index); - } else { - tsType = new TsType.VerbatimType(typeParameter); - } - tsTypeArguments.add(tsType); - } - return new Result(new TsType.GenericBasicType(mapping.tsType.rawName, tsTypeArguments), discoveredClasses); - } else { + + if (mapping.tsType.typeParameters.isEmpty()) { final int index = mapping.javaType.indexOfTypeParameter(mapping.tsType.rawName); if (index != -1) { final TsType tsType = processGenericParameter.apply(index); @@ -64,6 +54,20 @@ public Result processType(Type javaType, Context context) { return new Result(new TsType.VerbatimType(mapping.tsType.rawName), discoveredClasses); } } + + final List tsTypeArguments = new ArrayList<>(); + for (String typeParameter : mapping.tsType.typeParameters) { + final TsType tsType; + final int index = mapping.javaType.indexOfTypeParameter(typeParameter); + if (index != -1) { + tsType = processGenericParameter.apply(index); + } else { + tsType = new TsType.VerbatimType(typeParameter); + } + tsTypeArguments.add(tsType); + } + + return new Result(new TsType.GenericBasicType(mapping.tsType.rawName, tsTypeArguments), discoveredClasses); } } diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java index 729aa8be8..d95b89ba9 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/Settings.java @@ -13,26 +13,14 @@ import cz.habarta.typescript.generator.parser.TypeParser; import cz.habarta.typescript.generator.util.Pair; import cz.habarta.typescript.generator.util.Utils; + import java.io.File; import java.io.InputStream; -import java.lang.annotation.Annotation; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.lang.reflect.TypeVariable; import java.net.URL; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -179,11 +167,29 @@ public static class GenericName { public GenericName(String rawName, List typeParameters) { this.rawName = Objects.requireNonNull(rawName); - this.typeParameters = typeParameters; + this.typeParameters = typeParameters == null ? List.of() : typeParameters; } public int indexOfTypeParameter(String typeParameter) { - return typeParameters != null ? typeParameters.indexOf(typeParameter) : -1; + return typeParameters.indexOf(typeParameter); + } + + @Override + public String toString() { + return String.format("GenericName{rawName: '%s', typeParameters: %s}", rawName, typeParameters); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof GenericName)) return false; + final var that = (GenericName) other; + return Objects.equals(rawName, that.rawName) && Objects.equals(typeParameters, that.typeParameters); + } + + @Override + public int hashCode() { + return Objects.hash(rawName, typeParameters); } } @@ -287,7 +293,7 @@ public static Map convertToMap(List items, String itemNa } return result; } - + public void validate() { if (classLoader == null) { classLoader = Thread.currentThread().getContextClassLoader(); @@ -497,7 +503,7 @@ private List validateCustomTypeMappings(Map c validateTypeParameters(genericTsName.typeParameters); final Class cls = loadClass(classLoader, genericJavaName.rawName, null); final int required = cls.getTypeParameters().length; - final int specified = genericJavaName.typeParameters != null ? genericJavaName.typeParameters.size() : 0; + final int specified = genericJavaName.typeParameters.size(); if (specified != required) { final String parameters = Stream.of(cls.getTypeParameters()) .map(TypeVariable::getName) @@ -543,10 +549,14 @@ public List validateCustomTypeAliases(Map custo return aliases; } - private static GenericName parseGenericName(String name) { - // Class - // Class[T1, T2] - final Matcher matcher = Pattern.compile("([^<\\[]+)(<|\\[)([^>\\]]+)(>|\\])").matcher(name); + /** + * Parses generic name in format Class<T1, T2>. Class[T1, T2], Class[T1[T2], T3], etc., + * splitting the class name and type parameters. + * @param name string representation of a generic name + * @return a {@link GenericName} object containing the class name and type parameters + */ + public static GenericName parseGenericName(final String name) { + final Matcher matcher = Pattern.compile("(.+?)([<\\[])([^]]{0,1}.*[^\\[])([>\\]])").matcher(name); final String rawName; final List typeParameters; if (matcher.matches()) { // is generic? @@ -558,13 +568,11 @@ private static GenericName parseGenericName(String name) { rawName = name; typeParameters = null; } + return new GenericName(rawName, typeParameters); } private static void validateTypeParameters(List typeParameters) { - if (typeParameters == null) { - return; - } for (String typeParameter : typeParameters) { if (!ModelCompiler.isValidIdentifierName(typeParameter)) { throw new RuntimeException(String.format("Invalid generic type parameter: '%s'", typeParameter)); @@ -805,7 +813,7 @@ static Class loadClass(ClassLoader classLoader, String classNam if (requiredClassType != null && !requiredClassType.isAssignableFrom(loadedClass)) { throw new RuntimeException(String.format("Class '%s' is not assignable to '%s'.", loadedClass, requiredClassType)); } - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") final Class castedClass = (Class) loadedClass; return castedClass; } catch (ReflectiveOperationException e) { @@ -838,11 +846,11 @@ private static Pair parseArrayClassDimensions(String className) return Pair.of(className, dimensions); } - private static Class loadPrimitiveOrRegularClass(ClassLoader classLoader, String className) throws ClassNotFoundException { + static Class loadPrimitiveOrRegularClass(final ClassLoader classLoader, final String className) throws ClassNotFoundException { + // Stripe generic types: remove them from the class name, since the class can only be loaded using its raw name + final var rawClassName = className.replaceAll("<.*>", ""); final Class primitiveType = Utils.getPrimitiveType(className); - return primitiveType != null - ? primitiveType - : classLoader.loadClass(className); + return primitiveType == null ? classLoader.loadClass(rawClassName) : primitiveType; } private static List loadInstances(ClassLoader classLoader, List classNames, Class requiredType) { diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java index 35517b622..5d52063ad 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/compiler/ModelCompiler.java @@ -1,76 +1,18 @@ package cz.habarta.typescript.generator.compiler; -import cz.habarta.typescript.generator.DateMapping; -import cz.habarta.typescript.generator.EnumMapping; -import cz.habarta.typescript.generator.Extension; -import cz.habarta.typescript.generator.IdentifierCasing; -import cz.habarta.typescript.generator.MapMapping; -import cz.habarta.typescript.generator.NullabilityDefinition; -import cz.habarta.typescript.generator.OptionalPropertiesDeclaration; -import cz.habarta.typescript.generator.RestNamespacing; -import cz.habarta.typescript.generator.Settings; -import cz.habarta.typescript.generator.TsParameter; -import cz.habarta.typescript.generator.TsProperty; -import cz.habarta.typescript.generator.TsType; -import cz.habarta.typescript.generator.TypeProcessor; -import cz.habarta.typescript.generator.TypeScriptGenerator; -import cz.habarta.typescript.generator.emitter.EmitterExtension; -import cz.habarta.typescript.generator.emitter.TsAccessibilityModifier; -import cz.habarta.typescript.generator.emitter.TsAliasModel; -import cz.habarta.typescript.generator.emitter.TsAssignmentExpression; -import cz.habarta.typescript.generator.emitter.TsBeanCategory; -import cz.habarta.typescript.generator.emitter.TsBeanModel; -import cz.habarta.typescript.generator.emitter.TsCallExpression; -import cz.habarta.typescript.generator.emitter.TsConstructorModel; -import cz.habarta.typescript.generator.emitter.TsEnumModel; -import cz.habarta.typescript.generator.emitter.TsExpression; -import cz.habarta.typescript.generator.emitter.TsExpressionStatement; -import cz.habarta.typescript.generator.emitter.TsHelper; -import cz.habarta.typescript.generator.emitter.TsIdentifierReference; -import cz.habarta.typescript.generator.emitter.TsMemberExpression; -import cz.habarta.typescript.generator.emitter.TsMethodModel; -import cz.habarta.typescript.generator.emitter.TsModel; -import cz.habarta.typescript.generator.emitter.TsModifierFlags; -import cz.habarta.typescript.generator.emitter.TsObjectLiteral; -import cz.habarta.typescript.generator.emitter.TsParameterModel; -import cz.habarta.typescript.generator.emitter.TsPropertyDefinition; -import cz.habarta.typescript.generator.emitter.TsPropertyModel; -import cz.habarta.typescript.generator.emitter.TsReturnStatement; -import cz.habarta.typescript.generator.emitter.TsStatement; -import cz.habarta.typescript.generator.emitter.TsStringLiteral; -import cz.habarta.typescript.generator.emitter.TsSuperExpression; -import cz.habarta.typescript.generator.emitter.TsTaggedTemplateLiteral; -import cz.habarta.typescript.generator.emitter.TsTemplateLiteral; -import cz.habarta.typescript.generator.emitter.TsThisExpression; -import cz.habarta.typescript.generator.parser.BeanModel; -import cz.habarta.typescript.generator.parser.EnumModel; -import cz.habarta.typescript.generator.parser.MethodModel; -import cz.habarta.typescript.generator.parser.MethodParameterModel; -import cz.habarta.typescript.generator.parser.Model; -import cz.habarta.typescript.generator.parser.PathTemplate; -import cz.habarta.typescript.generator.parser.PropertyAccess; -import cz.habarta.typescript.generator.parser.PropertyModel; -import cz.habarta.typescript.generator.parser.RestApplicationModel; -import cz.habarta.typescript.generator.parser.RestMethodModel; -import cz.habarta.typescript.generator.parser.RestQueryParam; +import cz.habarta.typescript.generator.*; +import cz.habarta.typescript.generator.emitter.*; +import cz.habarta.typescript.generator.parser.*; import cz.habarta.typescript.generator.type.JTypeWithNullability; import cz.habarta.typescript.generator.util.GenericsResolver; import cz.habarta.typescript.generator.util.Pair; import cz.habarta.typescript.generator.util.Utils; + import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -467,11 +409,9 @@ private TsModel addCustomTypeAliases(SymbolTable symbolTable, TsModel tsModel) { final List aliases = new ArrayList<>(tsModel.getTypeAliases()); for (Settings.CustomTypeAlias customTypeAlias : settings.getValidatedCustomTypeAliases()) { final Symbol name = symbolTable.getSyntheticSymbol(customTypeAlias.tsType.rawName); - final List typeParameters = customTypeAlias.tsType.typeParameters != null - ? customTypeAlias.tsType.typeParameters.stream() + final List typeParameters = customTypeAlias.tsType.typeParameters.stream() .map(TsType.GenericVariableType::new) - .collect(Collectors.toList()) - : null; + .collect(Collectors.toList()); final TsType definition = new TsType.VerbatimType(customTypeAlias.tsDefinition); aliases.add(new TsAliasModel(null, name, typeParameters, definition, null)); } @@ -1356,7 +1296,7 @@ private static boolean isValidIdentifierStart(char start) { } private static boolean isValidIdentifierPart(char c) { - return Character.isUnicodeIdentifierPart(c) || c == '$' || c == '_' || c == '\u200C' || c == '\u200D'; + return Character.isUnicodeIdentifierPart(c) || c == '.' || c == '<' || c == '>' || c == '$' || c == '_' || c == '\u200C' || c == '\u200D'; } } diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java index 76072201b..cf7d5831b 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/emitter/TsAliasModel.java @@ -3,18 +3,18 @@ import cz.habarta.typescript.generator.TsType; import cz.habarta.typescript.generator.compiler.Symbol; -import java.util.Collections; + import java.util.List; public class TsAliasModel extends TsDeclarationModel { - + private final List typeParameters; private final TsType definition; public TsAliasModel(Class origin, Symbol name, List typeParameters, TsType definition, List comments) { super(origin, null, name, comments); - this.typeParameters = typeParameters != null ? typeParameters : Collections.emptyList(); + this.typeParameters = typeParameters == null ? List.of() : typeParameters; this.definition = definition; } diff --git a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/util/GenericsResolver.java b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/util/GenericsResolver.java index 7a2380be5..5eae735c0 100644 --- a/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/util/GenericsResolver.java +++ b/typescript-generator-core/src/main/java/cz/habarta/typescript/generator/util/GenericsResolver.java @@ -4,14 +4,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; @@ -45,6 +38,19 @@ public static List mapGenericVariablesToBase(Class derivedClass, Clas return result; } + /** + * Receives a given generic class/interface (that have generic type parameters) and returns a List with the type parameter names. + * For instance, if we have a generic type Map<K,V>, this method will return the string K, V;. + * @param genericClass a class/interface that have generic type parameters + * @return a List of the type parameter names + */ + public static List typeParameterNameList(final Class genericClass){ + return + Arrays.stream(genericClass.getTypeParameters()) + .map(TypeVariable::getName) + .collect(Collectors.toList()); + } + public static List resolveBaseGenericVariables(Class baseClass, Type contextType) { final Pair, Optional>> rawClassAndTypeArguments = Utils.getRawClassAndTypeArguments(contextType); if (rawClassAndTypeArguments != null) { diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/CustomTypeMappingTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/CustomTypeMappingTest.java index 4a6ccf026..3043a5de1 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/CustomTypeMappingTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/CustomTypeMappingTest.java @@ -5,13 +5,17 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + import java.io.IOException; +import java.math.BigDecimal; import java.util.Calendar; import java.util.Collections; import java.util.Date; -import org.junit.jupiter.api.Assertions; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; @SuppressWarnings("unused") public class CustomTypeMappingTest { @@ -32,6 +36,48 @@ public void test() { assertTrue(output.contains("calendar1: myModule.MyCalendar;")); } + /** + * Checks that the custom type mapping works for non-nested generic parameters. + * That is, each type parameter is not generic by itself. + */ + @Test + public void testSimpleGenericParameter() { + class ClassWithNonNestedGenericTypes { + public List stringList; + public List bigDecimalList; + } + + final Settings settings = TestUtils.settings(); + settings.quotes = "'"; + settings.customTypeMappings.put("java.util.List", "number[]"); + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(ClassWithNonNestedGenericTypes.class)); + System.out.println(output); + + assertTrue(output.contains("stringList: string[];")); + assertTrue(output.contains("bigDecimalList: number[];")); + } + + /** + * Checks that the custom type mapping works for nested generic parameters. + * That is, a type parameter is generic by itself. + */ + @Test + public void testNestedGenericParameter() { + class ClassWithNestedGenericTypes { + public List stringList; + public List> bigDecimalMatrix; + } + + final Settings settings = TestUtils.settings(); + settings.quotes = "'"; + settings.customTypeMappings.put("java.util.List>", "number[][]"); + final String output = new TypeScriptGenerator(settings).generateTypeScript(Input.from(ClassWithNestedGenericTypes.class)); + System.out.println(output); + + assertTrue(output.contains("stringList: string[];")); + assertTrue(output.contains("bigDecimalMatrix: number[][];")); + } + private static class CustomTypesUsage { public Date date1; public Calendar calendar1; diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsResolverTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsResolverTest.java index 6bf4bd6ab..2755e96ff 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsResolverTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/GenericsResolverTest.java @@ -1,25 +1,40 @@ package cz.habarta.typescript.generator; +import cz.habarta.typescript.generator.type.JParameterizedType; import cz.habarta.typescript.generator.type.JTypeVariable; import cz.habarta.typescript.generator.util.GenericsResolver; import cz.habarta.typescript.generator.util.Utils; +import org.junit.jupiter.api.Test; + import java.lang.reflect.Type; +import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class GenericsResolverTest { + /** + * TODO: Not sure how to test this GenericsResolver.typeParameterNameList method. This test doesn't work. + */ + @Test + void testTypeParameterNameList() { + // A type for a generic attribute that is List + final var javaType = new JParameterizedType(List.class, new Type[]{BigDecimal.class}, null); + final Class attributeRawClass = Utils.getRawClassOrNull(javaType); + assertEquals(List.of("BigDecimal"), GenericsResolver.typeParameterNameList(attributeRawClass)); + //assertEquals(List.of("List"), GenericsResolver.typeParameterNameList(classOfFieldWithNestedGeneric)); + } @Test public void testStringField() throws Exception { final Class cls = F1String.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("field")); - Assertions.assertEquals(String.class, type); + assertEquals(String.class, type); } static class F1 { @@ -32,7 +47,7 @@ static class F1String extends F1 { public void testListOfStringField() throws Exception { final Class cls = F2String.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("list")); - Assertions.assertEquals(Utils.createParameterizedType(List.class, String.class), type); + assertEquals(Utils.createParameterizedType(List.class, String.class), type); } static class F2 { @@ -45,7 +60,7 @@ static class F2String extends F2 { public void testMapOfStringAndListOfLongField() throws Exception { final Class cls = F3StringLong.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("map")); - Assertions.assertEquals(Utils.createParameterizedType(Map.class, String.class, Utils.createParameterizedType(List.class, Long.class)), type); + assertEquals(Utils.createParameterizedType(Map.class, String.class, Utils.createParameterizedType(List.class, Long.class)), type); } static class F3 { @@ -58,21 +73,21 @@ static class F3StringLong extends F3 { public void testInheritancePath() throws Exception { final Class cls = P123Number.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("field")); - Assertions.assertEquals(Utils.createParameterizedType(List.class, Number.class), type); + assertEquals(Utils.createParameterizedType(List.class, Number.class), type); } @Test public void testInheritancePathWithUnresolvedVariable1() throws Exception { final Class cls = P123.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("field")); - Assertions.assertEquals(Utils.createParameterizedType(List.class, new JTypeVariable<>(P123.class, "B")), type); + assertEquals(Utils.createParameterizedType(List.class, new JTypeVariable<>(P123.class, "B")), type); } @Test public void testInheritancePathWithUnresolvedVariable2() throws Exception { final Class cls = P12.class; final Type type = GenericsResolver.resolveField(cls, cls.getField("field")); - Assertions.assertEquals(new JTypeVariable<>(P12.class, "V"), type); + assertEquals(new JTypeVariable<>(P12.class, "V"), type); } static class P1 { @@ -88,13 +103,13 @@ static class P123Number extends P123 { @Test public void testGenericVariableMappingToBase1() { final List mappedTypeParameters = GenericsResolver.mapGenericVariablesToBase(R123.class, R1.class); - Assertions.assertEquals(Arrays.asList(null, null, "T"), mappedTypeParameters); + assertEquals(Arrays.asList(null, null, "T"), mappedTypeParameters); } @Test public void testGenericVariableMappingToBase2() { final List mappedTypeParameters = GenericsResolver.mapGenericVariablesToBase(R12.class, R1.class); - Assertions.assertEquals(Arrays.asList("T", "S"), mappedTypeParameters); + assertEquals(Arrays.asList("T", "S"), mappedTypeParameters); } static class R1 { @@ -108,21 +123,21 @@ static class R123 extends R12> { public void testResolvingGenericVariablesInContextType1() throws NoSuchFieldException { final Type contextType = MyClass.class.getField("property1").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(BaseClass.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.String", "java.lang.Integer"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.String", "java.lang.Integer"), getTypeNames(resolvedTypeParameters)); } @Test public void testResolvingGenericVariablesInContextType3() throws NoSuchFieldException { final Type contextType = MyClass.class.getField("property3").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(BaseClass.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.Integer", "java.lang.Boolean"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.Integer", "java.lang.Boolean"), getTypeNames(resolvedTypeParameters)); } @Test public void testResolvingGenericVariablesInContextTypeBase() throws NoSuchFieldException { final Type contextType = MyClass.class.getField("propertyBase").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(BaseClass.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.Integer", "java.lang.String"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.Integer", "java.lang.String"), getTypeNames(resolvedTypeParameters)); } static class BaseClass {} @@ -141,14 +156,14 @@ static class MyClass { public void testResolvingRawUsage1() throws NoSuchFieldException { final Type contextType = RawUsage.class.getField("rawMap").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(Map.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.Object", "java.lang.Object"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.Object", "java.lang.Object"), getTypeNames(resolvedTypeParameters)); } @Test public void testResolvingRawUsage2() throws NoSuchFieldException { final Type contextType = RawUsage.class.getField("rawStringKeyMap").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(Map.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.Object", "java.lang.Object"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.Object", "java.lang.Object"), getTypeNames(resolvedTypeParameters)); } static class RawUsage { @@ -163,7 +178,7 @@ static interface StringKeyMap extends Map {} public void testResolvingFixedDescendant() throws NoSuchFieldException { final Type contextType = StringMapDescendantUsage.class.getField("stringMapDescendant").getGenericType(); final List resolvedTypeParameters = GenericsResolver.resolveBaseGenericVariables(Map.class, contextType); - Assertions.assertEquals(Arrays.asList("java.lang.String", "java.lang.String"), getTypeNames(resolvedTypeParameters)); + assertEquals(Arrays.asList("java.lang.String", "java.lang.String"), getTypeNames(resolvedTypeParameters)); } static class StringMapDescendantUsage { diff --git a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/SettingsTest.java b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/SettingsTest.java index 0dfb20aac..d62c6e4d2 100644 --- a/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/SettingsTest.java +++ b/typescript-generator-core/src/test/java/cz/habarta/typescript/generator/SettingsTest.java @@ -1,20 +1,79 @@ package cz.habarta.typescript.generator; -import java.lang.reflect.Modifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class SettingsTest { + /** + * Checks if the method can load a class from a given class name, + * either it has a generic type argument or not. + */ + @Test + void testLoadPrimitiveOrRegularClass() { + // A map where each key is a type reference (class/interface) and each value is a list of class names representing that type + final var typeToClassName = Map.of( + List.class, List.of("java.util.List", "java.util.List", "java.util.List>"), + Map.class, List.of("java.util.Map", "java.util.Map", "java.util.Map>") + ); + + typeToClassName.forEach((type, classNameList) -> { + classNameList.forEach(className -> assertTypeLoadedFromClassName(className, type)); + }); + } + + /** + * Asserts that a class is loaded from a given class name. + * @param className name of the class to load (that may contain generic arguments, even nested ones) + * @param expectedClass the class that sould be loaded from the given class name + */ + private void assertTypeLoadedFromClassName(final String className, final Class expectedClass) { + try { + final var loadedClass = Settings.loadPrimitiveOrRegularClass(getClass().getClassLoader(), className); + assertEquals(expectedClass, loadedClass); + } catch (ClassNotFoundException e) { + Assertions.fail(e); + } + } + + /** + * Checks if generic type arguments are parsed correctly, even when there are nested generic types. + */ + @Test + void testParseGenericName() { + final var className = "Class"; + final String[] nonNestedGenericArgumentTypes = {"T1", "T2"}; + + assertEquals(newGenericName(className, nonNestedGenericArgumentTypes), Settings.parseGenericName("Class")); + assertEquals(newGenericName(className, nonNestedGenericArgumentTypes), Settings.parseGenericName("Class[T1, T2]")); + assertEquals(newGenericName(className, "T1[T2]", "T3"), Settings.parseGenericName("Class[T1[T2], T3]")); + assertEquals(newGenericName(className, "T1", "T3"), Settings.parseGenericName("Class, T3>")); + } + + /** + * Creates a new {@link Settings.GenericName} instance. + * @param className name of a class that have generic type arguments. + * @param genericArguments generic type arguments + * @return a new {@link Settings.GenericName} instance. + */ + private static Settings.GenericName newGenericName(final String className, final String ...genericArguments) { + return new Settings.GenericName(className, Arrays.asList(genericArguments)); + } @Test public void testParseModifiers() { - Assertions.assertEquals(0, Settings.parseModifiers("", Modifier.fieldModifiers())); - Assertions.assertEquals(Modifier.STATIC, Settings.parseModifiers("static", Modifier.fieldModifiers())); - Assertions.assertEquals(Modifier.STATIC | Modifier.TRANSIENT, Settings.parseModifiers("static | transient", Modifier.fieldModifiers())); + assertEquals(0, Settings.parseModifiers("", Modifier.fieldModifiers())); + assertEquals(Modifier.STATIC, Settings.parseModifiers("static", Modifier.fieldModifiers())); + assertEquals(Modifier.STATIC | Modifier.TRANSIENT, Settings.parseModifiers("static | transient", Modifier.fieldModifiers())); } - + @Test public void testNpmDependenciesValidation() { String exceptionMessage = "'npmDependencies', 'npmDevDependencies' and 'npmPeerDependencies' parameters are only applicable when generating NPM 'package.json'."; @@ -25,9 +84,9 @@ public void testNpmDependenciesValidation() { settings.jsonLibrary = JsonLibrary.jackson2; settings.generateNpmPackageJson = false; settings.npmPackageDependencies.put("dependencies", "version"); - + RuntimeException exception = Assertions.assertThrows(RuntimeException.class, () -> settings.validate()); - Assertions.assertEquals(exceptionMessage, exception.getMessage()); + assertEquals(exceptionMessage, exception.getMessage()); } { @@ -36,9 +95,9 @@ public void testNpmDependenciesValidation() { settings.jsonLibrary = JsonLibrary.jackson2; settings.generateNpmPackageJson = false; settings.npmDevDependencies.put("dependencies", "version"); - + RuntimeException exception = Assertions.assertThrows(RuntimeException.class, () -> settings.validate()); - Assertions.assertEquals(exceptionMessage, exception.getMessage()); + assertEquals(exceptionMessage, exception.getMessage()); } { @@ -47,9 +106,9 @@ public void testNpmDependenciesValidation() { settings.jsonLibrary = JsonLibrary.jackson2; settings.generateNpmPackageJson = false; settings.npmPeerDependencies.put("dependencies", "version"); - + RuntimeException exception = Assertions.assertThrows(RuntimeException.class, () -> settings.validate()); - Assertions.assertEquals(exceptionMessage, exception.getMessage()); + assertEquals(exceptionMessage, exception.getMessage()); } } }