| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as p; |
| import 'package:test_case_selector/test_case_selector.dart'; |
| |
| import '../test_util/test_util.dart'; |
| |
| const _copyrightHeader = ''' |
| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| '''; |
| |
| // The kind of top level type under test. |
| enum TopLevelKind { class_, interface, enum_, record } |
| |
| // The member inside the top level declaration. |
| enum Member { field, method, constructor, initializer } |
| |
| // Java supports nesting types inside other types. These are all the nestable |
| // types. Nested classes can either be static or non-static. The nested type is |
| // pretty standalone, and not affected by any of the below enums. |
| enum NestedKind { none, innerClass, staticClass, interface, enum_, record } |
| |
| // Modifier applied to the top level type. |
| enum TopLevelModifier { none, final_, sealed } |
| |
| // Modifier applied to the member. |
| enum MemberModifier { |
| none, |
| static_, |
| final_, |
| abstract_, |
| default_, |
| synchronized, |
| throws, |
| transient, |
| volatile, |
| native |
| } |
| |
| // For members with params, test different numbers of params. |
| enum ParamCount { zero, one, two } |
| |
| // Some member names are special cased inside JNIgen, so test those. |
| enum MemberName { any, getFoo, setFoo, isFoo } |
| |
| // Affects what the top level type declaration inherits from. |
| enum Inheritance { |
| none, |
| extends_, |
| implements_, |
| multipleImplements, |
| extendsGenericSpecialized, |
| extendsGenericUnspecialized, |
| diamond, |
| complexDag |
| } |
| |
| // The generic type params of the top level type. |
| enum Generics { none, oneParam, twoParams, upperBound, lowerBound, wildcard } |
| |
| // The generic type params of the member. |
| enum MemberGenerics { |
| none, |
| oneParam, |
| twoParams, |
| upperBound, |
| lowerBound, |
| wildcard |
| } |
| |
| // The type of the member. For constructors this is the type of the first arg, |
| // and for methods this is the type of the first arg and the return type. |
| enum MemberType { |
| void_, |
| boolean_, |
| char_, |
| int_, |
| long_, |
| short_, |
| float_, |
| double_, |
| byte_, |
| object, |
| string, |
| typeParam, |
| memberTypeParam, |
| list, |
| set, |
| map, |
| customObject, |
| customInterface, |
| customEnum, |
| customRecord, |
| nestedCustom |
| } |
| |
| // Whether the MemberType is also an array. |
| enum IsArray { no, yes } |
| |
| // Whether the MemberType is nullable. |
| enum MemberNullability { none, nullable, nonnull } |
| |
| // Whether the Generics and MemberGenerics are nullable. |
| enum GenericNullability { none, nullable, nonnull } |
| |
| void generateJava() { |
| final selector = TestCaseSelector( |
| dimensions: { |
| TopLevelKind: TopLevelKind.values, |
| Member: Member.values, |
| NestedKind: NestedKind.values, |
| TopLevelModifier: TopLevelModifier.values, |
| MemberModifier: MemberModifier.values, |
| ParamCount: ParamCount.values, |
| MemberName: MemberName.values, |
| Inheritance: Inheritance.values, |
| Generics: Generics.values, |
| MemberGenerics: MemberGenerics.values, |
| MemberType: MemberType.values, |
| IsArray: IsArray.values, |
| MemberNullability: MemberNullability.values, |
| GenericNullability: GenericNullability.values, |
| }, |
| interactionGroups: [ |
| // Test that each member works in each top level type. |
| {TopLevelKind, Member}, |
| // The top level type and modifier affect what kind of nesting is allowed. |
| {TopLevelKind, NestedKind, TopLevelModifier}, |
| // Inheritance and generics can interact in weird ways. |
| {TopLevelKind, Inheritance, Generics}, |
| // So can top level generics and member generics. |
| {TopLevelKind, Generics, MemberGenerics}, |
| // The member name special casing is affected by modifiers and generics. |
| {Member, MemberModifier, MemberName, MemberGenerics}, |
| // All member kinds should work with all param counts. |
| {Member, ParamCount}, |
| // All member types should all be array-able and nullable-able. |
| {MemberType, IsArray, MemberNullability}, |
| // Make sure MemberType.memberTypeParam touches all members and generics. |
| {Member, MemberGenerics, MemberType}, |
| // Test that generics nullability works with both kinds of generics. |
| {Generics, MemberGenerics, GenericNullability}, |
| ], |
| isValid: (tc) { |
| final top = tc.get<TopLevelKind>(); |
| final member = tc.get<Member>(); |
| final modifier = tc.get<TopLevelModifier>(); |
| final mod = tc.get<MemberModifier>(); |
| final name = tc.get<MemberName>(); |
| final inheritance = tc.get<Inheritance>(); |
| final generics = tc.get<Generics>(); |
| final memberGenerics = tc.get<MemberGenerics>(); |
| final type = tc.get<MemberType>(); |
| final paramCount = tc.get<ParamCount>(); |
| final isArray = tc.get<IsArray>(); |
| final memberNullability = tc.get<MemberNullability>(); |
| final genericNullability = tc.get<GenericNullability>(); |
| |
| // Basic Java constraints |
| if (type == MemberType.void_ && isArray == IsArray.yes) { |
| return false; |
| } |
| if (type == MemberType.void_ && |
| memberNullability != MemberNullability.none) { |
| return false; |
| } |
| if (isArray == IsArray.no && |
| memberNullability != MemberNullability.none && |
| isPrimitive(type)) { |
| return false; |
| } |
| |
| if (genericNullability != GenericNullability.none) { |
| if (generics == Generics.none && |
| memberGenerics == MemberGenerics.none && |
| inheritance != Inheritance.extendsGenericSpecialized && |
| inheritance != Inheritance.extendsGenericUnspecialized) { |
| return false; |
| } |
| } |
| |
| if (memberGenerics != MemberGenerics.none) { |
| if (member != Member.method && member != Member.constructor) { |
| return false; |
| } |
| } |
| |
| if (top == TopLevelKind.interface) { |
| if (member == Member.constructor || member == Member.initializer) { |
| return false; |
| } |
| if (modifier == TopLevelModifier.final_) { |
| return false; |
| } |
| } |
| if (modifier == TopLevelModifier.final_ && |
| mod == MemberModifier.abstract_) { |
| return false; |
| } |
| if (top == TopLevelKind.enum_ || top == TopLevelKind.record) { |
| if (inheritance != Inheritance.none && |
| inheritance != Inheritance.implements_) { |
| return false; |
| } |
| if (modifier == TopLevelModifier.final_ || |
| modifier == TopLevelModifier.sealed) { |
| return false; |
| } |
| } |
| if (top == TopLevelKind.enum_) { |
| if (mod == MemberModifier.abstract_ || generics != Generics.none) { |
| return false; |
| } |
| } |
| |
| if (modifier == TopLevelModifier.sealed) { |
| if (mod == MemberModifier.abstract_) { |
| return false; |
| } |
| if (top == TopLevelKind.interface && member == Member.method) { |
| if (mod != MemberModifier.default_ && mod != MemberModifier.static_) { |
| return false; |
| } |
| } |
| if (member == Member.constructor && paramCount != ParamCount.zero) { |
| return false; |
| } |
| if (inheritance != Inheritance.none) { |
| return false; |
| } |
| if (memberGenerics != MemberGenerics.none) { |
| return false; |
| } |
| } |
| |
| if (generics == Generics.lowerBound || generics == Generics.wildcard) { |
| return false; |
| } |
| if (memberGenerics == MemberGenerics.lowerBound || |
| memberGenerics == MemberGenerics.wildcard) { |
| return false; |
| } |
| |
| if (top == TopLevelKind.record) { |
| if (member == Member.initializer || |
| memberGenerics != MemberGenerics.none) { |
| return false; |
| } |
| } |
| |
| if (mod != MemberModifier.none) { |
| if (mod == MemberModifier.final_) { |
| if (member == Member.constructor || member == Member.initializer) { |
| return false; |
| } |
| if (top == TopLevelKind.interface && member == Member.method) { |
| return false; |
| } |
| } |
| if (mod == MemberModifier.transient || mod == MemberModifier.volatile) { |
| if (member != Member.field || top == TopLevelKind.interface) { |
| return false; |
| } |
| } |
| if (mod == MemberModifier.throws && member != Member.method) { |
| return false; |
| } |
| if (member == Member.field) { |
| const fieldModifiers = { |
| MemberModifier.static_, |
| MemberModifier.final_, |
| MemberModifier.transient, |
| MemberModifier.volatile, |
| }; |
| if (!fieldModifiers.contains(mod)) { |
| return false; |
| } |
| } |
| if (member == Member.initializer && mod != MemberModifier.static_) { |
| return false; |
| } |
| if (member == Member.constructor && |
| { |
| MemberModifier.abstract_, |
| MemberModifier.native, |
| MemberModifier.default_, |
| MemberModifier.static_, |
| MemberModifier.synchronized, |
| MemberModifier.throws, |
| MemberModifier.final_, |
| }.contains(mod)) { |
| return false; |
| } |
| } |
| if (mod == MemberModifier.default_ && top != TopLevelKind.interface) { |
| return false; |
| } |
| |
| if (name != MemberName.any && member != Member.method) { |
| return false; |
| } |
| |
| if (type == MemberType.void_ && member != Member.method) { |
| return false; |
| } |
| if (top == TopLevelKind.record && type == MemberType.void_) { |
| return false; |
| } |
| if (type == MemberType.void_ && paramCount != ParamCount.zero) { |
| return false; |
| } |
| |
| if (mod == MemberModifier.static_) { |
| final useMemberScope = memberGenerics != MemberGenerics.none && |
| (type == MemberType.memberTypeParam || |
| memberGenerics == MemberGenerics.lowerBound || |
| memberGenerics == MemberGenerics.wildcard); |
| |
| if (!useMemberScope && generics != Generics.none) { |
| const genericTypes = { |
| MemberType.list, |
| MemberType.set, |
| MemberType.map, |
| MemberType.customObject, |
| MemberType.customInterface, |
| MemberType.customRecord, |
| MemberType.nestedCustom, |
| MemberType.typeParam, |
| }; |
| if (genericTypes.contains(type)) { |
| return false; |
| } |
| } |
| } |
| |
| if (type == MemberType.typeParam) { |
| if (generics == Generics.none) { |
| return false; |
| } |
| if (top == TopLevelKind.interface && member == Member.field) { |
| return false; |
| } |
| } |
| |
| if (top == TopLevelKind.interface && member == Member.field) { |
| if (generics != Generics.none) { |
| const genericTypes = { |
| MemberType.list, |
| MemberType.set, |
| MemberType.map, |
| MemberType.customObject, |
| MemberType.nestedCustom, |
| }; |
| if (genericTypes.contains(type)) { |
| return false; |
| } |
| } |
| } |
| |
| if (type == MemberType.memberTypeParam) { |
| if (memberGenerics == MemberGenerics.none) { |
| return false; |
| } |
| if (member == Member.field || top == TopLevelKind.record) { |
| return false; |
| } |
| } |
| |
| return true; |
| }, |
| ); |
| |
| final testCases = selector.select(); |
| print('Generated ${testCases.length} test cases.'); |
| |
| final javaDir = Directory(p.join(pkgDir, 'test', 'large_java_test', 'java')); |
| if (javaDir.existsSync()) { |
| javaDir.deleteSync(recursive: true); |
| } |
| javaDir.createSync(recursive: true); |
| |
| writeCoreClasses(javaDir); |
| |
| final outputDir = Directory(p.join(javaDir.path, 'com', 'example')); |
| outputDir.createSync(recursive: true); |
| |
| for (var i = 0; i < testCases.length; i++) { |
| final tc = testCases[i]; |
| final className = 'TestClass${i.toString().padLeft(3, '0')}'; |
| final sb = StringBuffer(); |
| sb.writeln(_copyrightHeader); |
| sb.writeln('package com.example;'); |
| sb.writeln('import java.util.*;'); |
| sb.writeln('import org.jetbrains.annotations.Nullable;'); |
| sb.writeln('import org.jetbrains.annotations.NotNull;'); |
| sb.writeln(); |
| for (final line in tc.toString().split(', ')) { |
| sb.writeln('// $line'); |
| } |
| generateTestCase(sb, className, tc); |
| |
| final file = File(p.join(outputDir.path, '$className.java')); |
| file.writeAsStringSync(sb.toString()); |
| } |
| |
| print('Wrote files to ${outputDir.path}'); |
| } |
| |
| void writeCoreClasses(Directory baseDir) { |
| final coreClasses = { |
| 'Nullable': ''' |
| package org.jetbrains.annotations; |
| import java.lang.annotation.*; |
| @Retention(RetentionPolicy.RUNTIME) |
| @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE}) |
| public @interface Nullable {} |
| ''', |
| 'NotNull': ''' |
| package org.jetbrains.annotations; |
| import java.lang.annotation.*; |
| @Retention(RetentionPolicy.RUNTIME) |
| @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE}) |
| public @interface NotNull {} |
| ''', |
| 'GrandParent': ''' |
| public class GrandParent { |
| public void grandParentMethod() {} |
| } |
| ''', |
| 'OtherInterface': ''' |
| public interface OtherInterface { |
| void otherInterfaceMethod(); |
| } |
| ''', |
| 'GenericParent': ''' |
| public class GenericParent<T> { |
| public void genericParentMethod(T t) {} |
| } |
| ''', |
| 'GenericInterface': ''' |
| public interface GenericInterface<T> { |
| T genericInterfaceMethod(T t); |
| } |
| ''', |
| 'BaseInterface': ''' |
| public interface BaseInterface { |
| int BASE_FIELD = 0; |
| void baseMethod(); |
| } |
| ''', |
| 'DiamondLeft': ''' |
| public interface DiamondLeft extends BaseInterface { |
| int LEFT_FIELD = 1; |
| void leftMethod(); |
| @Override |
| default void baseMethod() {} |
| } |
| ''', |
| 'DiamondRight': ''' |
| public interface DiamondRight extends BaseInterface { |
| int RIGHT_FIELD = 2; |
| void rightMethod(); |
| @Override |
| default void baseMethod() {} |
| } |
| ''', |
| 'DagA': ''' |
| public interface DagA { |
| int A_FIELD = 3; |
| void aMethod(); |
| } |
| ''', |
| 'DagB': ''' |
| public interface DagB extends DagA { |
| int B_FIELD = 4; |
| void bMethod(); |
| } |
| ''', |
| 'DagC': ''' |
| public interface DagC extends DagA, DagB { |
| int C_FIELD = 5; |
| void cMethod(); |
| } |
| ''', |
| 'DagD': ''' |
| public interface DagD extends DagA, DagB, DagC { |
| int D_FIELD = 6; |
| void dMethod(); |
| } |
| ''', |
| 'DagE': ''' |
| public interface DagE extends DagB, DagD { |
| int E_FIELD = 7; |
| void eMethod(); |
| } |
| ''', |
| 'CustomRecord': ''' |
| public record CustomRecord<T>(T x, String y) {} |
| ''', |
| 'CustomObject': ''' |
| public class CustomObject<T> { |
| public T value; |
| public CustomObject(T value) { this.value = value; } |
| } |
| ''', |
| 'CustomInterface': ''' |
| public interface CustomInterface<T> { |
| T getValue(); |
| } |
| ''', |
| 'CustomEnum': ''' |
| public enum CustomEnum { |
| V1, V2 |
| } |
| ''', |
| 'NestedCustom': ''' |
| public class NestedCustom<T, U> { |
| public class Nested<V> { |
| public T t; |
| public U u; |
| public V v; |
| } |
| } |
| ''', |
| }; |
| |
| for (final entry in coreClasses.entries) { |
| final content = entry.value.trim(); |
| final isOrgJetbrains = |
| content.startsWith('package org.jetbrains.annotations;'); |
| final relativePath = isOrgJetbrains |
| ? 'org/jetbrains/annotations/${entry.key}.java' |
| : 'com/example/${entry.key}.java'; |
| final file = File(p.join(baseDir.path, relativePath)); |
| file.parent.createSync(recursive: true); |
| |
| final packageLine = |
| content.startsWith('package ') ? '' : 'package com.example;\n\n'; |
| file.writeAsStringSync(''' |
| $_copyrightHeader |
| $packageLine$content |
| '''); |
| } |
| } |
| |
| void generateTestCase(StringBuffer sb, String className, TestCase tc) { |
| final top = tc.get<TopLevelKind>(); |
| final member = tc.get<Member>(); |
| final nested = tc.get<NestedKind>(); |
| final modifier = tc.get<TopLevelModifier>(); |
| final mod = tc.get<MemberModifier>(); |
| final paramCount = tc.get<ParamCount>(); |
| final name = tc.get<MemberName>(); |
| final inheritance = tc.get<Inheritance>(); |
| final generics = tc.get<Generics>(); |
| final memberGenerics = tc.get<MemberGenerics>(); |
| final typeKind = tc.get<MemberType>(); |
| final isArray = tc.get<IsArray>(); |
| final memberNullability = tc.get<MemberNullability>(); |
| final genericNullability = tc.get<GenericNullability>(); |
| |
| final typeStr = getJavaType(typeKind, isArray == IsArray.yes, generics, |
| memberGenerics, memberNullability, genericNullability); |
| final inheritanceStr = |
| getInheritanceStr(inheritance, top, genericNullability); |
| final genStr = getGenericsStr(generics, genericNullability); |
| final typeParamsStr = getTypeParamsStr(generics); |
| final memberGenStr = getMemberGenericsStr(memberGenerics, genericNullability); |
| final topModStr = getTopLevelModifierStr(modifier, mod); |
| final kind = getTopLevelKindStr(top); |
| final params = getParamsStr(paramCount, typeStr); |
| |
| if (top == TopLevelKind.record) { |
| sb.write(''' |
| public record $className$genStr($typeStr field) $inheritanceStr { |
| ${getInheritanceMethodsStr(inheritance, top)}} |
| '''); |
| return; |
| } |
| |
| sb.write(''' |
| public $topModStr$kind $className$genStr $inheritanceStr { |
| '''); |
| |
| if (top == TopLevelKind.enum_) { |
| sb.writeln(getEnumConstantsStr(member, paramCount, typeStr)); |
| } |
| |
| sb.write(getInheritanceMethodsStr(inheritance, top)); |
| |
| sb.write(switch (member) { |
| Member.field => getFieldStr(top, mod, typeStr), |
| Member.method => |
| getMethodStr(top, mod, memberGenStr, typeStr, name, params), |
| Member.constructor => |
| getConstructorStr(top, memberGenStr, className, params), |
| Member.initializer => getInitializerStr(mod), |
| }); |
| |
| if (nested != NestedKind.none) { |
| sb.writeln(getNestedStr(nested)); |
| } |
| |
| if (modifier == TopLevelModifier.sealed) { |
| final keyword = top == TopLevelKind.interface ? 'implements' : 'extends'; |
| sb.write(''' |
| public static final class Sub$genStr $keyword $className$typeParamsStr {} |
| '''); |
| } |
| |
| sb.writeln('}'); |
| } |
| |
| String getInheritanceStr( |
| Inheritance inheritance, TopLevelKind top, GenericNullability gn) { |
| final g = getGenericNullabilityStr(gn); |
| switch (inheritance) { |
| case Inheritance.none: |
| return ''; |
| case Inheritance.extends_: |
| if (top == TopLevelKind.interface) return ' extends OtherInterface'; |
| if (top == TopLevelKind.record) return ''; |
| return ' extends GrandParent'; |
| case Inheritance.implements_: |
| if (top == TopLevelKind.interface) return ' extends OtherInterface'; |
| return ' implements OtherInterface'; |
| case Inheritance.multipleImplements: |
| if (top == TopLevelKind.interface) { |
| return ' extends OtherInterface, BaseInterface'; |
| } |
| return ' implements OtherInterface, BaseInterface'; |
| case Inheritance.extendsGenericSpecialized: |
| if (top == TopLevelKind.interface) { |
| return ' extends GenericInterface<${g}String>'; |
| } |
| if (top == TopLevelKind.record) { |
| return ' implements GenericInterface<${g}String>'; |
| } |
| return ' extends GenericParent<${g}String>'; |
| case Inheritance.extendsGenericUnspecialized: |
| if (top == TopLevelKind.interface) return ' extends GenericInterface'; |
| if (top == TopLevelKind.record) return ' implements GenericInterface'; |
| return ' extends GenericParent'; |
| case Inheritance.diamond: |
| if (top == TopLevelKind.interface) { |
| return ' extends DiamondLeft, DiamondRight'; |
| } |
| return ' implements DiamondLeft, DiamondRight'; |
| case Inheritance.complexDag: |
| if (top == TopLevelKind.interface) { |
| return ' extends DagA, DagD, DagE'; |
| } |
| return ' implements DagA, DagD, DagE'; |
| } |
| } |
| |
| String getInheritanceMethodsStr(Inheritance inheritance, TopLevelKind top) { |
| final sb = StringBuffer(); |
| final isInterface = top == TopLevelKind.interface; |
| |
| void add(String decl, [String body = '{}']) { |
| if (isInterface) { |
| sb.write(''' |
| @Override |
| default $decl $body |
| '''); |
| } else { |
| sb.write(''' |
| @Override |
| public $decl $body |
| '''); |
| } |
| } |
| |
| switch (inheritance) { |
| case Inheritance.none: |
| break; |
| case Inheritance.extends_: |
| if (top == TopLevelKind.interface) { |
| add('void otherInterfaceMethod()'); |
| } else if (top != TopLevelKind.record) { |
| add('void grandParentMethod()'); |
| } |
| break; |
| case Inheritance.implements_: |
| add('void otherInterfaceMethod()'); |
| break; |
| case Inheritance.multipleImplements: |
| add('void otherInterfaceMethod()'); |
| add('void baseMethod()'); |
| break; |
| case Inheritance.extendsGenericSpecialized: |
| if (top == TopLevelKind.interface || top == TopLevelKind.record) { |
| add('String genericInterfaceMethod(String t)', ' { return t; }'); |
| } else { |
| add('void genericParentMethod(String t)'); |
| } |
| break; |
| case Inheritance.extendsGenericUnspecialized: |
| if (top == TopLevelKind.interface || top == TopLevelKind.record) { |
| add('Object genericInterfaceMethod(Object t)', ' { return t; }'); |
| } else { |
| add('void genericParentMethod(Object t)'); |
| } |
| break; |
| case Inheritance.diamond: |
| add('void baseMethod()'); |
| add('void leftMethod()'); |
| add('void rightMethod()'); |
| break; |
| case Inheritance.complexDag: |
| add('void aMethod()'); |
| add('void bMethod()'); |
| add('void cMethod()'); |
| add('void dMethod()'); |
| add('void eMethod()'); |
| break; |
| } |
| return sb.toString(); |
| } |
| |
| String getGenericNullabilityStr(GenericNullability gn) { |
| return switch (gn) { |
| GenericNullability.none => '', |
| GenericNullability.nullable => '@Nullable ', |
| GenericNullability.nonnull => '@NotNull ', |
| }; |
| } |
| |
| String getMemberNullabilityStr(MemberNullability mn) { |
| return switch (mn) { |
| MemberNullability.none => '', |
| MemberNullability.nullable => '@Nullable ', |
| MemberNullability.nonnull => '@NotNull ', |
| }; |
| } |
| |
| String getGenericsStr(Generics generics, GenericNullability gn) { |
| final g = getGenericNullabilityStr(gn); |
| return switch (generics) { |
| Generics.none => '', |
| Generics.oneParam => '<${g}T>', |
| Generics.twoParams => '<${g}T, ${g}U>', |
| Generics.upperBound => '<${g}T extends Number>', |
| Generics.lowerBound => '<${g}T>', |
| Generics.wildcard => '<${g}T>', |
| }; |
| } |
| |
| String getTypeParamsStr(Generics generics) { |
| return switch (generics) { |
| Generics.none => '', |
| Generics.oneParam => '<T>', |
| Generics.twoParams => '<T, U>', |
| _ => '<T>', |
| }; |
| } |
| |
| String getMemberGenericsStr( |
| MemberGenerics memberGenerics, GenericNullability gn) { |
| final g = getGenericNullabilityStr(gn); |
| return switch (memberGenerics) { |
| MemberGenerics.none => '', |
| MemberGenerics.oneParam => '<${g}S> ', |
| MemberGenerics.twoParams => '<${g}S, ${g}V> ', |
| MemberGenerics.upperBound => '<${g}S extends Number> ', |
| MemberGenerics.lowerBound => '<${g}S> ', |
| MemberGenerics.wildcard => '<${g}S> ', |
| }; |
| } |
| |
| String getTopLevelModifierStr(TopLevelModifier modifier, MemberModifier mod) { |
| var s = switch (modifier) { |
| TopLevelModifier.none => '', |
| TopLevelModifier.final_ => 'final ', |
| TopLevelModifier.sealed => 'sealed ', |
| }; |
| if (mod == MemberModifier.abstract_) { |
| s += 'abstract '; |
| } |
| return s; |
| } |
| |
| String getTopLevelKindStr(TopLevelKind top) { |
| return switch (top) { |
| TopLevelKind.class_ => 'class', |
| TopLevelKind.interface => 'interface', |
| TopLevelKind.enum_ => 'enum', |
| TopLevelKind.record => 'record', |
| }; |
| } |
| |
| String getMemberModifierStr(MemberModifier mod) { |
| return switch (mod) { |
| MemberModifier.static_ => 'static ', |
| MemberModifier.final_ => 'final ', |
| MemberModifier.synchronized => 'synchronized ', |
| MemberModifier.native => 'native ', |
| MemberModifier.abstract_ => 'abstract ', |
| MemberModifier.transient => 'transient ', |
| MemberModifier.volatile => 'volatile ', |
| _ => '', |
| }; |
| } |
| |
| String getParamsStr(ParamCount paramCount, String typeStr) { |
| return switch (paramCount) { |
| ParamCount.zero => '', |
| ParamCount.one => '$typeStr p1', |
| ParamCount.two => '$typeStr p1, int p2', |
| }; |
| } |
| |
| String getEnumConstantsStr( |
| Member member, ParamCount paramCount, String typeStr) { |
| if (member == Member.constructor) { |
| final args = switch (paramCount) { |
| ParamCount.zero => '', |
| ParamCount.one => getJavaDefaultValue(typeStr), |
| ParamCount.two => '${getJavaDefaultValue(typeStr)}, 0', |
| }; |
| return ' VALUE1($args), VALUE2($args);'; |
| } |
| return ' VALUE1, VALUE2;'; |
| } |
| |
| String getFieldStr(TopLevelKind top, MemberModifier mod, String typeStr) { |
| final modStr = getMemberModifierStr(mod); |
| final defaultValue = getJavaDefaultValue(typeStr); |
| if (top == TopLevelKind.interface) { |
| return ''' |
| $modStr$typeStr myField = $defaultValue; |
| '''; |
| } |
| final init = mod == MemberModifier.final_ ? ' = $defaultValue' : ''; |
| return ''' |
| public $modStr$typeStr myField$init; |
| '''; |
| } |
| |
| String getMethodStr(TopLevelKind top, MemberModifier mod, String memberGenStr, |
| String typeStr, MemberName name, String params) { |
| final methodName = name == MemberName.any ? 'myMethod' : name.name; |
| final defaultValue = getJavaDefaultValue(typeStr); |
| final body = typeStr == 'void' ? '' : 'return $defaultValue;'; |
| final throwsStr = mod == MemberModifier.throws ? ' throws Exception' : ''; |
| |
| if (top == TopLevelKind.interface) { |
| return switch (mod) { |
| MemberModifier.default_ => ''' |
| default $memberGenStr$typeStr $methodName($params)$throwsStr { $body } |
| ''', |
| MemberModifier.static_ => ''' |
| static $memberGenStr$typeStr $methodName($params)$throwsStr { $body } |
| ''', |
| _ => ''' |
| $memberGenStr$typeStr $methodName($params)$throwsStr; |
| ''', |
| }; |
| } |
| |
| final modStr = getMemberModifierStr(mod); |
| return switch (mod) { |
| MemberModifier.abstract_ => ''' |
| public abstract $memberGenStr$typeStr $methodName($params)$throwsStr; |
| ''', |
| MemberModifier.native => ''' |
| public native $memberGenStr$typeStr $methodName($params)$throwsStr; |
| ''', |
| _ => ''' |
| public $modStr$memberGenStr$typeStr $methodName($params)$throwsStr { $body } |
| ''', |
| }; |
| } |
| |
| String getConstructorStr( |
| TopLevelKind top, String memberGenStr, String className, String params) { |
| final visibility = top == TopLevelKind.enum_ ? 'private' : 'public'; |
| return ''' |
| $visibility $memberGenStr$className($params) {} |
| '''; |
| } |
| |
| String getInitializerStr(MemberModifier mod) { |
| final staticStr = mod == MemberModifier.static_ ? 'static ' : ''; |
| return ''' |
| $staticStr{ } |
| '''; |
| } |
| |
| String getNestedStr(NestedKind nested) { |
| final nKind = switch (nested) { |
| NestedKind.innerClass || NestedKind.staticClass => 'class', |
| NestedKind.interface => 'interface', |
| NestedKind.enum_ => 'enum', |
| NestedKind.record => 'record', |
| _ => 'class', |
| }; |
| if (nested == NestedKind.record) { |
| return ''' |
| public static record NestedRecord(int x) {} |
| '''; |
| } |
| if (nested == NestedKind.enum_) { |
| return ''' |
| public enum NestedEnum { V1 } |
| '''; |
| } |
| final staticStr = nested == NestedKind.innerClass ? '' : 'static '; |
| return ''' |
| public $staticStr$nKind Nested {} |
| '''; |
| } |
| |
| bool isPrimitive(MemberType type) { |
| return type == MemberType.int_ || |
| type == MemberType.long_ || |
| type == MemberType.short_ || |
| type == MemberType.float_ || |
| type == MemberType.double_ || |
| type == MemberType.byte_ || |
| type == MemberType.boolean_ || |
| type == MemberType.char_ || |
| type == MemberType.void_; |
| } |
| |
| String getJavaType(MemberType kind, bool isArray, Generics generics, |
| MemberGenerics mGenerics, MemberNullability mn, GenericNullability gn) { |
| final useMemberScope = mGenerics != MemberGenerics.none; |
| |
| final scopeVar = useMemberScope ? 'S' : 'T'; |
| final scopeEnum = useMemberScope ? mGenerics : generics; |
| |
| final ggn = getGenericNullabilityStr(gn); |
| final mmn = getMemberNullabilityStr(mn); |
| |
| final g = switch (scopeEnum) { |
| Generics.oneParam || MemberGenerics.oneParam => '$ggn$scopeVar', |
| Generics.twoParams || MemberGenerics.twoParams => '$ggn$scopeVar', |
| Generics.upperBound || MemberGenerics.upperBound => '$ggn$scopeVar', |
| Generics.lowerBound || MemberGenerics.lowerBound => '? super $ggn$scopeVar', |
| Generics.wildcard || MemberGenerics.wildcard => '?', |
| _ => '${mmn}String', |
| }; |
| |
| final t = switch (kind) { |
| MemberType.void_ => 'void', |
| MemberType.boolean_ => 'boolean', |
| MemberType.char_ => 'char', |
| MemberType.int_ => 'int', |
| MemberType.long_ => 'long', |
| MemberType.short_ => 'short', |
| MemberType.float_ => 'float', |
| MemberType.double_ => 'double', |
| MemberType.byte_ => 'byte', |
| MemberType.object => 'Object', |
| MemberType.string => 'String', |
| MemberType.typeParam => 'T', |
| MemberType.memberTypeParam => 'S', |
| MemberType.list => 'List<$g>', |
| MemberType.set => 'Set<$g>', |
| MemberType.map => 'Map<$g, $g>', |
| MemberType.customObject => 'CustomObject<$g>', |
| MemberType.customInterface => 'CustomInterface<$g>', |
| MemberType.customEnum => 'CustomEnum', |
| MemberType.customRecord => 'CustomRecord<$g>', |
| MemberType.nestedCustom => 'NestedCustom<$g, $g>.Nested<$g>', |
| }; |
| if (isArray) { |
| if (isPrimitive(kind)) { |
| return '$t $mmn[]'; |
| } |
| return '$mmn$t[]'; |
| } |
| return isPrimitive(kind) ? t : '$mmn$t'; |
| } |
| |
| String getJavaDefaultValue(String type) { |
| if (type.endsWith('[]')) { |
| return 'null'; |
| } |
| return switch (type) { |
| 'void' => '', |
| 'int' => '0', |
| 'long' => '0', |
| 'byte' => '0', |
| 'short' => '0', |
| 'float' => '0.0f', |
| 'double' => '0.0', |
| 'boolean' => 'false', |
| 'char' => "' '", |
| 'CustomEnum' => 'CustomEnum.V1', |
| _ => 'null', |
| }; |
| } |