blob: b67c2037ef83b0db25cf15c969cfc10a08e7b937 [file] [log] [blame]
// Copyright (c) 2015, 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.
/// A library to generate Java source code. See [JavaGenerator].
library src_gen_java;
import 'dart:io';
import 'package:path/path.dart';
import '../common/src_gen_common.dart';
/// The maximum length for javadoc comments.
int colBoundary = 100;
/// The header for every generated file.
String? fileHeader;
String classNameFor(String typeName) {
// Convert ElementList<Foo> param declarations to List<Foo> declarations.
if (typeName.startsWith('ElementList<')) {
return typeName.substring('Element'.length);
}
var index = typeName.lastIndexOf('.');
typeName = index > 0 ? typeName.substring(index + 1) : typeName;
if (typeName.startsWith('_')) typeName = typeName.substring(1);
return typeName;
}
String pkgNameFor(String typeName) {
var index = typeName.lastIndexOf('.');
return index > 0 ? typeName.substring(0, index) : '';
}
typedef WriteStatements = void Function(StatementWriter writer);
typedef WriteType = void Function(TypeWriter writer);
/// [JavaGenerator] generates java source files, one per Java type.
/// Typical usage:
///
/// var generator = new JavaGenerator('/path/to/java/src');
/// generator.writeType('some.package.Foo', (TypeWriter) writer) {
/// ...
/// });
/// ...
///
class JavaGenerator {
/// The java source directory into which files are generated.
final String srcDirPath;
Set<String> _generatedPaths = Set();
JavaGenerator(this.srcDirPath);
Iterable<String> get allWrittenFiles => _generatedPaths;
/// Generate a Java class/interface in the given package
void writeType(String typeName, WriteType write) {
var classWriter = TypeWriter(typeName);
write(classWriter);
var pkgDirPath = join(srcDirPath, joinAll(pkgNameFor(typeName).split('.')));
var pkgDir = Directory(pkgDirPath);
if (!pkgDir.existsSync()) pkgDir.createSync(recursive: true);
var classFilePath = join(pkgDirPath, '${classNameFor(typeName)}.java');
var classFile = File(classFilePath);
_generatedPaths.add(classFilePath);
classFile.writeAsStringSync(classWriter.toSource());
}
}
class JavaMethodArg {
final String? name;
final String? typeName;
JavaMethodArg(this.name, this.typeName);
}
class StatementWriter {
final TypeWriter typeWriter;
final StringBuffer _content = StringBuffer();
StatementWriter(this.typeWriter);
void addImport(String typeName) {
typeWriter.addImport(typeName);
}
void addLine(String line) {
_content.writeln(' $line');
}
String toSource() => _content.toString();
}
/// [TypeWriter] describes a Java type to be generated.
/// Typical usage:
///
/// writer.addImport('package.one.Bar');
/// writer.addImport('package.two.*');
/// writer.superclassName = 'package.three.Blat';
/// writer.addMethod('foo', [
/// new JavaMethodArg('arg1', 'LocalType'),
/// new JavaMethodArg('arg2', 'java.util.List'),
/// ], (StatementWriter writer) {
/// ...
/// });
///
/// The [toSource()] method generates the source,
/// but need not be called if used in conjunction with
/// [JavaGenerator].
class TypeWriter {
final String pkgName;
final String className;
bool isInterface = false;
bool isEnum = false;
String? javadoc;
String modifiers = 'public';
final Set<String> _imports = Set<String>();
String? superclassName;
List<String> interfaceNames = <String>[];
final StringBuffer _content = StringBuffer();
final List<String> _fields = <String>[];
final Map<String, String> _methods = Map<String, String>();
TypeWriter(String typeName)
: this.pkgName = pkgNameFor(typeName),
this.className = classNameFor(typeName);
String get kind {
if (isInterface) return 'interface';
if (isEnum) return 'enum';
return 'class';
}
void addConstructor(Iterable<JavaMethodArg> args, WriteStatements write,
{String? javadoc, String modifiers = 'public'}) {
_content.writeln();
if (javadoc != null && javadoc.isNotEmpty) {
_content.writeln(' /**');
wrap(javadoc.trim(), colBoundary - 6)
.split('\n')
.forEach((line) => _content.writeln(' * $line'));
_content.writeln(' */');
}
_content.write(' $modifiers $className(');
_content.write(
args.map((a) => '${classNameFor(a.typeName!)} ${a.name}').join(', '));
_content.write(')');
_content.writeln(' {');
StatementWriter writer = StatementWriter(this);
write(writer);
_content.write(writer.toSource());
_content.writeln(' }');
}
void addEnumValue(
String? name, {
String? javadoc,
bool isLast = false,
}) {
_content.writeln();
if (javadoc != null && javadoc.isNotEmpty) {
_content.writeln(' /**');
wrap(javadoc.trim(), colBoundary - 6)
.split('\n')
.forEach((line) => _content.writeln(' * $line'));
_content.writeln(' */');
}
_content.write(' $name');
if (!isLast) {
_content.writeln(',');
} else {
_content.writeln();
}
}
void addField(String name, String typeName,
{String modifiers = 'public', String? value, String? javadoc}) {
var fieldDecl = StringBuffer();
if (javadoc != null && javadoc.isNotEmpty) {
fieldDecl.writeln(' /**');
wrap(javadoc.trim(), colBoundary - 6)
.split('\n')
.forEach((line) => fieldDecl.writeln(' * $line'));
fieldDecl.writeln(' */');
}
fieldDecl.write(' ');
if (modifiers.isNotEmpty) {
fieldDecl.write('$modifiers ');
}
fieldDecl.write('$typeName $name');
if (value != null && value.isNotEmpty) {
fieldDecl.write(' = $value');
}
fieldDecl.writeln(';');
_fields.add(fieldDecl.toString());
}
void addImport(String? typeName) {
if (typeName == null || typeName.isEmpty) return;
var pkgName = pkgNameFor(typeName);
if (pkgName.isNotEmpty && pkgName != this.pkgName) {
_imports.add(typeName);
}
}
void addMethod(
String name,
Iterable<JavaMethodArg> args,
WriteStatements? write, {
String? javadoc,
String? modifiers = 'public',
String? returnType = 'void',
bool isOverride = false,
}) {
var methodDecl = StringBuffer();
if (javadoc != null && javadoc.isNotEmpty) {
methodDecl.writeln(' /**');
wrap(javadoc.trim(), colBoundary - 6)
.split('\n')
.forEach((line) => methodDecl.writeln(' * $line'.trimRight()));
methodDecl.writeln(' */');
}
if (isOverride) {
methodDecl.writeln(' @Override');
}
methodDecl.write(' ');
if (modifiers != null && modifiers.isNotEmpty) {
if (!isInterface || modifiers != 'public') {
methodDecl.write('$modifiers ');
}
}
methodDecl.write('$returnType $name(');
methodDecl.write(args
.map(
(JavaMethodArg arg) => '${classNameFor(arg.typeName!)} ${arg.name}')
.join(', '));
methodDecl.write(')');
if (write != null) {
methodDecl.writeln(' {');
StatementWriter writer = StatementWriter(this);
write(writer);
methodDecl.write(writer.toSource());
methodDecl.writeln(' }');
} else {
methodDecl.writeln(';');
}
String key = (modifiers != null && modifiers.contains('public'))
? '1 $name('
: '2 $name(';
key = args.fold(key, (String k, JavaMethodArg a) => '$k${a.typeName},');
_methods[key] = methodDecl.toString();
}
String toSource() {
var buffer = StringBuffer();
if (fileHeader != null) buffer.write(fileHeader);
buffer.writeln('package $pkgName;');
buffer.writeln();
buffer.writeln('// This is a generated file.');
buffer.writeln();
addImport(superclassName);
interfaceNames.forEach((t) => addImport(t));
if (_imports.isNotEmpty) {
var sorted = _imports.toList()..sort();
for (String typeName in sorted) {
buffer.writeln('import $typeName;');
}
buffer.writeln();
}
if (javadoc != null && javadoc!.isNotEmpty) {
buffer.writeln('/**');
wrap(javadoc!.trim(), colBoundary - 4)
.split('\n')
.forEach((line) => buffer.writeln(' * $line'));
buffer.writeln(' */');
}
buffer.writeln('@SuppressWarnings({"WeakerAccess", "unused"})');
buffer.write('$modifiers $kind $className');
if (superclassName != null) {
buffer.write(' extends ${classNameFor(superclassName!)}');
}
if (interfaceNames.isNotEmpty) {
var classNames = interfaceNames.map((t) => classNameFor(t));
buffer.write(
' ${isInterface ? 'extends' : 'implements'} ${classNames.join(', ')}');
}
buffer.writeln(' {');
buffer.write(_content.toString());
_fields.forEach((f) {
buffer.writeln();
buffer.write(f);
});
_methods.keys.toList()
..sort()
..forEach((String methodName) {
buffer.writeln();
buffer.write(_methods[methodName]);
});
buffer.writeln('}');
return buffer.toString();
}
}