// 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 param declarations to List 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 _generatedPaths = new Set(); JavaGenerator(this.srcDirPath); Iterable get allWrittenFiles => _generatedPaths; /// Generate a Java class/interface in the given package void writeType(String typeName, WriteType write) { var classWriter = new TypeWriter(typeName); write(classWriter); var pkgDirPath = join(srcDirPath, joinAll(pkgNameFor(typeName).split('.'))); var pkgDir = new Directory(pkgDirPath); if (!pkgDir.existsSync()) pkgDir.createSync(recursive: true); var classFilePath = join(pkgDirPath, '${classNameFor(typeName)}.java'); var classFile = new 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 = new 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 _imports = new Set(); String superclassName; List interfaceNames = []; final StringBuffer _content = new StringBuffer(); final List _fields = []; final Map _methods = new Map(); 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 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(')'); if (write != null) { _content.writeln(' {'); StatementWriter writer = new StatementWriter(this); write(writer); _content.write(writer.toSource()); _content.writeln(' }'); } else { _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 = new 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 != null && 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 args, WriteStatements write, { String javadoc, String modifiers = 'public', String returnType = 'void', bool isOverride = false, }) { var methodDecl = new 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 = new 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 = new StringBuffer(); if (fileHeader != null) buffer.write(fileHeader); if (pkgName != null) { 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(); } }