blob: c08157f9c6e77f44135c00d81ec5ec6d9f45cd29 [file]
// 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',
};
}