[jnigen] Add basic YAML config support (https://github.com/dart-lang/jnigen/issues/32)

diff --git a/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java
index 589ba85..ffda30a 100644
--- a/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java
+++ b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 package dev.dart.jni;
 
 import android.app.Activity;
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart
index 67d9f96..c4befc6 100644
--- a/pkgs/jni/bin/setup.dart
+++ b/pkgs/jni/bin/setup.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:args/args.dart';
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart
index c21802b..0303900 100644
--- a/pkgs/jni/example/lib/main.dart
+++ b/pkgs/jni/example/lib/main.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 // ignore_for_file: library_private_types_in_public_api
 
 import 'package:flutter/material.dart';
diff --git a/pkgs/jni/example/test/widget_test.dart b/pkgs/jni/example/test/widget_test.dart
index 2607462..ec585ec 100644
--- a/pkgs/jni/example/test/widget_test.dart
+++ b/pkgs/jni/example/test/widget_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:flutter/material.dart';
diff --git a/pkgs/jni/lib/jni_object.dart b/pkgs/jni/lib/jni_object.dart
index f12a8fd..e0916f0 100644
--- a/pkgs/jni/lib/jni_object.dart
+++ b/pkgs/jni/lib/jni_object.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 /// jni_object library provides an easier interface to JNI's object references,
 /// providing various helper methods for one-off uses.
 ///
diff --git a/pkgs/jni/lib/src/direct_methods_generated.dart b/pkgs/jni/lib/src/direct_methods_generated.dart
index fc04c0f..d1f0191 100644
--- a/pkgs/jni/lib/src/direct_methods_generated.dart
+++ b/pkgs/jni/lib/src/direct_methods_generated.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 // Autogenerated; DO NOT EDIT
 // Generated by running the script in tool/gen_aux_methods.dart
 // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/extensions.dart b/pkgs/jni/lib/src/extensions.dart
index d749e3e..995eb58 100644
--- a/pkgs/jni/lib/src/extensions.dart
+++ b/pkgs/jni/lib/src/extensions.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 
 import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jl_object.dart b/pkgs/jni/lib/src/jl_object.dart
index 36558f4..2778dfc 100644
--- a/pkgs/jni/lib/src/jl_object.dart
+++ b/pkgs/jni/lib/src/jl_object.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 
 import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart
index b3ce6df..a188d86 100644
--- a/pkgs/jni/lib/src/jni.dart
+++ b/pkgs/jni/lib/src/jni.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 import 'dart:io';
 
diff --git a/pkgs/jni/lib/src/jni_class.dart b/pkgs/jni/lib/src/jni_class.dart
index b4a6763..308981a 100644
--- a/pkgs/jni/lib/src/jni_class.dart
+++ b/pkgs/jni/lib/src/jni_class.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 
 import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni_class_methods_generated.dart b/pkgs/jni/lib/src/jni_class_methods_generated.dart
index 3e3a7ca..195deef 100644
--- a/pkgs/jni/lib/src/jni_class_methods_generated.dart
+++ b/pkgs/jni/lib/src/jni_class_methods_generated.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 // Autogenerated; DO NOT EDIT
 // Generated by running the script in tool/gen_aux_methods.dart
 // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/jni_exceptions.dart b/pkgs/jni/lib/src/jni_exceptions.dart
index c765ec4..a9cb639 100644
--- a/pkgs/jni/lib/src/jni_exceptions.dart
+++ b/pkgs/jni/lib/src/jni_exceptions.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 
 import 'third_party/jni_bindings_generated.dart';
diff --git a/pkgs/jni/lib/src/jni_object.dart b/pkgs/jni/lib/src/jni_object.dart
index 217c832..47d480a 100644
--- a/pkgs/jni/lib/src/jni_object.dart
+++ b/pkgs/jni/lib/src/jni_object.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 
 import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni_object_methods_generated.dart b/pkgs/jni/lib/src/jni_object_methods_generated.dart
index 490f8fc..3c41707 100644
--- a/pkgs/jni/lib/src/jni_object_methods_generated.dart
+++ b/pkgs/jni/lib/src/jni_object_methods_generated.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 // Autogenerated; DO NOT EDIT
 // Generated by running the script in tool/gen_aux_methods.dart
 // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/jvalues.dart b/pkgs/jni/lib/src/jvalues.dart
index 63945b0..94a9cbe 100644
--- a/pkgs/jni/lib/src/jvalues.dart
+++ b/pkgs/jni/lib/src/jvalues.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:ffi';
 import 'package:ffi/ffi.dart';
 
diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c
index 82f1597..8ba5a89 100644
--- a/pkgs/jni/src/dartjni.c
+++ b/pkgs/jni/src/dartjni.c
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 #include <jni.h>
 #include <stdint.h>
 
diff --git a/pkgs/jni/src/dartjni.h b/pkgs/jni/src/dartjni.h
index 407312b..cd94b15 100644
--- a/pkgs/jni/src/dartjni.h
+++ b/pkgs/jni/src/dartjni.h
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 #include <jni.h>
 #include <stdint.h>
 #include <stdio.h>
diff --git a/pkgs/jni/test/exception_test.dart b/pkgs/jni/test/exception_test.dart
index 109a7e6..0e86ec9 100644
--- a/pkgs/jni/test/exception_test.dart
+++ b/pkgs/jni/test/exception_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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:test/test.dart';
diff --git a/pkgs/jni/test/jni_object_test.dart b/pkgs/jni/test/jni_object_test.dart
index 6124d1f..d234101 100644
--- a/pkgs/jni/test/jni_object_test.dart
+++ b/pkgs/jni/test/jni_object_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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 'dart:ffi';
 import 'dart:isolate';
diff --git a/pkgs/jni/test/jni_test.dart b/pkgs/jni/test/jni_test.dart
index 29f536b..ce31fc4 100644
--- a/pkgs/jni/test/jni_test.dart
+++ b/pkgs/jni/test/jni_test.dart
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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 'dart:ffi';
 
diff --git a/pkgs/jni/tool/gen_aux_methods.dart b/pkgs/jni/tool/gen_aux_methods.dart
index 0c7ae58..a30f54db32 100644
--- a/pkgs/jni/tool/gen_aux_methods.dart
+++ b/pkgs/jni/tool/gen_aux_methods.dart
@@ -1,8 +1,16 @@
-/// Run from templates directory
+// Copyright (c) 2022, 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' as io;
 import 'package:path/path.dart';
 
+const _license = '''
+// Copyright (c) 2022, 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.
+
+''';
 final targetTypes = {
   "String": "String",
   "Object": "JniObject",
@@ -53,6 +61,7 @@
     sInvoke: join(outputDir, "direct_methods_generated.dart")
   };
   for (final s in [sInst, sStatic, sInvoke]) {
+    s.write(_license);
     s.write("// Autogenerated; DO NOT EDIT\n"
         "// Generated by running the script in tool/gen_aux_methods.dart\n");
     s.write("// coverage:ignore-file\n");
diff --git a/pkgs/jni_gen/README.md b/pkgs/jni_gen/README.md
index 373127b..1ae735d 100644
--- a/pkgs/jni_gen/README.md
+++ b/pkgs/jni_gen/README.md
@@ -5,3 +5,27 @@
 
 This is a GSoC 2022 project.
 
+Currently this package is highly experimental and proof-of-concept. See `test/jackson_core_test` for an example of generating bindings for a library. It is possible to specify some dependencies to be downloaded automatically through `maven`.
+
+## Basics
+### Running `jni_gen`
+There are 2 ways to use `jni_gen`:
+
+* Import `package:jni_gen/jni_gen.dart` from a script in `tool/` directory of your project.
+* Run as command line tool with a YAML config.
+
+Both approaches are almost identical. If using YAML, it's possible to selectively override configuration properties with command line, using `-Dproperty.name=value` syntax.
+
+### Generated bindings
+Generated bindings will consist of 2 parts - C bindings which call JNI, and Dart bindings which call C bindings. The generated bindings will depend on `package:jni` for instantiating / obtaining a JVM instance.
+
+The following properties must be specified in yaml.
+
+* `c_root`: root folder to write generated C bindings.
+* `dart_root`: root folder to write generated Dart bindings (see below).
+* `library_name`: specifies name of the generated library in CMakeFiles.txt.
+
+The generated C file has to be linked to JNI libraries. Therefore a CMake configuration is always generated which builds the generated code as shared library. The `init.dart` in generated dart code loads the library on first time a method is accessed. On dart standalone, it will be loaded from the same directory specified in `Jni.spawn` call.
+
+## Examples
+See [jackson_core_test](test/jackson_core_test) folder for an example how bindings are generated. Runnable examples will be added soon.
diff --git a/pkgs/jni_gen/bin/jni_gen.dart b/pkgs/jni_gen/bin/jni_gen.dart
new file mode 100644
index 0000000..855ae6e
--- /dev/null
+++ b/pkgs/jni_gen/bin/jni_gen.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, 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 'package:jni_gen/jni_gen.dart';
+
+void main(List<String> args) async {
+  final config = Config.parseArgs(args);
+  await generateJniBindings(config);
+}
diff --git a/pkgs/jni_gen/bin/setup.dart b/pkgs/jni_gen/bin/setup.dart
index 656b5da..18e7a8d 100644
--- a/pkgs/jni_gen/bin/setup.dart
+++ b/pkgs/jni_gen/bin/setup.dart
@@ -2,67 +2,20 @@
 // 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.
 
-// This script gets the java sources using the copy of this package, and builds
-// ApiSummarizer jar using Maven.
 import 'dart:io';
 
-import 'package:path/path.dart';
-
-import 'package:jni_gen/src/util/find_package.dart';
-
-final toolPath = join('.', '.dart_tool', 'jni_gen');
-final mvnTargetDir = join(toolPath, 'target');
-final jarFile = join(toolPath, 'ApiSummarizer.jar');
-final targetJarFile = join(mvnTargetDir, 'ApiSummarizer.jar');
-
-Future<void> buildApiSummarizer() async {
-  final pkg = await findPackageRoot('jni_gen');
-  if (pkg == null) {
-    stderr.writeln('package jni_gen not found!');
-    exitCode = 2;
-    return;
-  }
-  final pom = pkg.resolve('java/pom.xml');
-  await Directory(toolPath).create(recursive: true);
-  final mvnProc = await Process.start(
-      'mvn',
-      [
-        '--batch-mode',
-        '--update-snapshots',
-        '-f',
-        pom.toFilePath(),
-        'assembly:assembly'
-      ],
-      workingDirectory: toolPath,
-      mode: ProcessStartMode.inheritStdio);
-  await mvnProc.exitCode;
-  // move ApiSummarizer.jar from target to current directory
-  File(targetJarFile).renameSync(jarFile);
-  Directory(mvnTargetDir).deleteSync(recursive: true);
-}
+import 'package:jni_gen/src/tools/tools.dart';
 
 void main(List<String> args) async {
   bool force = false;
   if (args.isNotEmpty) {
     if (args.length != 1 || args[0] != '-f') {
       stderr.writeln('usage: dart run jni_gen:setup [-f]');
-      stderr.writeln('use -f option to rebuild ApiSummarizer jar '
-          'even if it already exists.');
+      stderr.writeln('* -f\trebuild ApiSummarizer jar even if it already '
+          'exists.');
     } else {
       force = true;
     }
   }
-  final jarExists = await File(jarFile).exists();
-  final isJarStale = jarExists &&
-      await isPackageModifiedAfter(
-          'jni_gen', await File(jarFile).lastModified(), 'java/');
-  if (isJarStale) {
-    stderr.writeln('Rebuilding ApiSummarizer component since sources '
-        'have changed. This might take some time.');
-  }
-  if (!jarExists || isJarStale || force) {
-    await buildApiSummarizer();
-  } else {
-    stderr.writeln('ApiSummarizer.jar exists. Skipping build..');
-  }
+  buildSummarizerIfNotExists(force: force);
 }
diff --git a/pkgs/jni_gen/lib/jni_gen.dart b/pkgs/jni_gen/lib/jni_gen.dart
index 0bc7895..33068f4 100644
--- a/pkgs/jni_gen/lib/jni_gen.dart
+++ b/pkgs/jni_gen/lib/jni_gen.dart
@@ -9,4 +9,5 @@
 
 export 'src/elements/elements.dart';
 export 'src/config/config.dart';
-export 'src/writers/writers.dart';
+export 'src/config/filters.dart';
+export 'src/generate_bindings.dart';
diff --git a/pkgs/jni_gen/lib/src/bindings/c_bindings.dart b/pkgs/jni_gen/lib/src/bindings/c_bindings.dart
index 71be9c1..f6b7ddf 100644
--- a/pkgs/jni_gen/lib/src/bindings/c_bindings.dart
+++ b/pkgs/jni_gen/lib/src/bindings/c_bindings.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
+import 'package:jni_gen/src/config/config.dart';
 
 import 'common.dart';
 
@@ -28,8 +28,8 @@
   String _cParamRename(String paramName) =>
       _cTypeKeywords.contains(paramName) ? '${paramName}0' : paramName;
 
-  CBindingGenerator(this.options);
-  WrapperOptions options;
+  CBindingGenerator(this.config);
+  Config config;
 
   String generateBinding(ClassDecl c) {
     return _class(c);
diff --git a/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart b/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart
index b994441..7a27303 100644
--- a/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart
+++ b/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart
@@ -5,7 +5,7 @@
 import 'dart:io';
 
 import 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
+import 'package:jni_gen/src/config/config.dart';
 import 'package:jni_gen/src/util/rename_conflict.dart';
 
 import 'symbol_resolver.dart';
@@ -29,8 +29,8 @@
 
   static const String _jlObject = '${jni}JlObject';
 
-  DartBindingsGenerator(this.options, this.resolver);
-  WrapperOptions options;
+  DartBindingsGenerator(this.config, this.resolver);
+  Config config;
   SymbolResolver resolver;
 
   String generateBinding(ClassDecl decl) {
@@ -176,7 +176,7 @@
 
     void writeAccessor({bool isSetter = false}) {
       final symPrefix = isSetter ? 'set' : 'get';
-      final sym = '_$symPrefix$name';
+      final sym = '_${symPrefix}_$name';
       final ffiSig = dartSigForField(f, isSetter: isSetter, isFfiSig: true);
       final dartSig = dartSigForField(f, isSetter: isSetter, isFfiSig: false);
       s.write('${_indent}static final $sym = $_jlookup'
@@ -300,6 +300,7 @@
 
   String _literal(dynamic value) {
     if (value is String) {
+      // TODO(#31): escape string literal.
       return '"$value"';
     }
     if (value is int || value is double || value is bool) {
diff --git a/pkgs/jni_gen/lib/src/bindings/preprocessor.dart b/pkgs/jni_gen/lib/src/bindings/preprocessor.dart
index 6fd1ee3..2177123 100644
--- a/pkgs/jni_gen/lib/src/bindings/preprocessor.dart
+++ b/pkgs/jni_gen/lib/src/bindings/preprocessor.dart
@@ -5,25 +5,22 @@
 import 'dart:io';
 
 import 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
+import 'package:jni_gen/src/config/config.dart';
 import 'package:jni_gen/src/util/rename_conflict.dart';
 import 'common.dart';
 
 /// Preprocessor which fills information needed by both Dart and C generators.
 class ApiPreprocessor {
-  ApiPreprocessor(this.classes, this.options);
-  final Map<String, ClassDecl> classes;
-  final WrapperOptions options;
-
-  void preprocessAll() {
+  static void preprocessAll(Map<String, ClassDecl> classes, Config config) {
     for (var c in classes.values) {
-      _preprocess(c);
+      _preprocess(c, classes, config);
     }
   }
 
-  void _preprocess(ClassDecl decl) {
+  static void _preprocess(
+      ClassDecl decl, Map<String, ClassDecl> classes, Config config) {
     if (decl.isPreprocessed) return;
-    if (!_isClassIncluded(decl)) {
+    if (!_isClassIncluded(decl, config)) {
       decl.isIncluded = false;
       stdout.writeln('exclude class ${decl.binaryName}');
       decl.isPreprocessed = true;
@@ -32,7 +29,7 @@
     ClassDecl? superclass;
     if (decl.superclass != null && classes.containsKey(decl.superclass?.name)) {
       superclass = classes[decl.superclass!.name]!;
-      _preprocess(superclass);
+      _preprocess(superclass, classes, config);
       // again, un-consider superclass if it was excluded through config
       if (!superclass.isIncluded) {
         superclass = null;
@@ -42,7 +39,7 @@
     }
 
     for (var field in decl.fields) {
-      if (!_isFieldIncluded(decl, field)) {
+      if (!_isFieldIncluded(decl, field, config)) {
         field.isIncluded = false;
         stderr.writeln('exclude ${decl.binaryName}#${field.name}');
         continue;
@@ -51,7 +48,7 @@
     }
 
     for (var method in decl.methods) {
-      if (!_isMethodIncluded(decl, method)) {
+      if (!_isMethodIncluded(decl, method, config)) {
         method.isIncluded = false;
         stderr.writeln('exclude method ${decl.binaryName}#${method.name}');
         continue;
@@ -81,10 +78,12 @@
     decl.isPreprocessed = true;
   }
 
-  bool _isFieldIncluded(ClassDecl decl, Field field) =>
-      options.fieldFilter?.included(decl, field) != false;
-  bool _isMethodIncluded(ClassDecl decl, Method method) =>
-      options.methodFilter?.included(decl, method) != false;
-  bool _isClassIncluded(ClassDecl decl) =>
-      options.classFilter?.included(decl) != false;
+  static bool _isFieldIncluded(ClassDecl decl, Field field, Config config) =>
+      !field.name.startsWith('_') &&
+      config.exclude?.fields?.included(decl, field) != false;
+  static bool _isMethodIncluded(ClassDecl decl, Method method, Config config) =>
+      !method.name.startsWith('_') &&
+      config.exclude?.methods?.included(decl, method) != false;
+  static bool _isClassIncluded(ClassDecl decl, Config config) =>
+      config.exclude?.classes?.included(decl) != false;
 }
diff --git a/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart b/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart
index bfaaab4..43b928f 100644
--- a/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart
+++ b/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart
@@ -14,8 +14,6 @@
   List<String> getImportStrings();
 }
 
-// TODO(#24): resolve all included classes without requiring import mappings.
-
 class PackagePathResolver implements SymbolResolver {
   PackagePathResolver(this.packages, this.currentPackage, this.inputClassNames,
       {this.predefined = const {}});
diff --git a/pkgs/jni_gen/lib/src/config/config.dart b/pkgs/jni_gen/lib/src/config/config.dart
index 03ce6f2..a0272f3 100644
--- a/pkgs/jni_gen/lib/src/config/config.dart
+++ b/pkgs/jni_gen/lib/src/config/config.dart
@@ -2,7 +2,288 @@
 // 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.
 
-export 'summary_source.dart';
-export 'task.dart';
-export 'wrapper_options.dart';
-export 'errors.dart';
+import 'dart:io';
+
+import 'package:jni_gen/src/elements/elements.dart';
+
+import 'yaml_reader.dart';
+import 'filters.dart';
+
+/// Configuration for dependencies to be downloaded using maven.
+///
+/// Dependency names should be listed in groupId:artifactId:version format.
+/// For [sourceDeps], sources will be unpacked to [sourceDir] root and JAR files
+/// will also be downloaded. For the packages in jarOnlyDeps, only JAR files
+/// will be downloaded.
+///
+/// When passed as a parameter to [Config], the downloaded sources and
+/// JAR files will be automatically added to source path and class path
+/// respectively.
+class MavenDownloads {
+  static const defaultMavenSourceDir = 'mvn_java';
+  static const defaultMavenJarDir = 'mvn_jar';
+
+  MavenDownloads({
+    this.sourceDeps = const [],
+    // ASK: Should this be changed to a gitignore'd directory like build ?
+    this.sourceDir = defaultMavenSourceDir,
+    this.jarOnlyDeps = const [],
+    this.jarDir = defaultMavenJarDir,
+  });
+  List<String> sourceDeps;
+  String sourceDir;
+  List<String> jarOnlyDeps;
+  String jarDir;
+}
+
+/// Configuration for Android SDK sources and stub JAR files.
+///
+/// The SDK directories for platform stub JARs and sources are searched in the
+/// same order in which [versions] are specified.
+///
+/// If [sdkRoot] is not provided, an attempt is made to discover it
+/// using the environment variable `ANDROID_SDK_ROOT`, which will fail if the
+/// environment variable is not set.
+///
+/// If [includeSources] is true, `jni_gen` searches for Android SDK sources
+/// as well in the SDK directory and adds them to the source path.
+class AndroidSdkConfig {
+  AndroidSdkConfig(
+      {required this.versions, this.sdkRoot, this.includeSources = false});
+  List<int> versions;
+  String? sdkRoot;
+  bool includeSources;
+}
+
+/// Additional options to pass to the summary generator component.
+class SummarizerOptions {
+  SummarizerOptions(
+      {this.extraArgs = const [], this.workingDirectory, this.backend});
+  List<String> extraArgs;
+  Uri? workingDirectory;
+  String? backend;
+}
+
+/// Backend for reading summary of Java libraries
+enum SummarizerBackend {
+  /// Generate Java API summaries using JARs in provided `classPath`s.
+  asm,
+
+  /// Generate Java API summaries using source files in provided `sourcePath`s.
+  doclet,
+}
+
+class BindingExclusions {
+  BindingExclusions({this.methods, this.fields, this.classes});
+  MethodFilter? methods;
+  FieldFilter? fields;
+  ClassFilter? classes;
+}
+
+/// Configuration for jni_gen binding generation.
+class Config {
+  Config({
+    required this.classes,
+    required this.libraryName,
+    required this.cRoot,
+    required this.dartRoot,
+    this.exclude,
+    this.sourcePath,
+    this.classPath,
+    this.preamble,
+    this.importMap,
+    this.androidSdkConfig,
+    this.mavenDownloads,
+    this.summarizerOptions,
+    this.dumpJsonTo,
+  });
+
+  /// List of classes or packages for which bindings have to be generated.
+  ///
+  /// The names must be fully qualified, and it's assumed that the directory
+  /// structure corresponds to package naming. For example, com.abc.MyClass
+  /// should be resolvable as `com/abc/MyClass.java` from one of the provided
+  /// source paths. Same applies if ASM backend is used, except that the file
+  /// name suffix is `.class`.
+  List<String> classes;
+
+  /// Name of generated library in CMakeLists.txt configuration.
+  ///
+  /// This will also determine the name of shared object file.
+  String libraryName;
+
+  /// Directory to write JNI C Bindings.
+  Uri cRoot;
+
+  /// Directory to write Dart bindings.
+  Uri dartRoot;
+
+  /// Methods and fields to be excluded from generated bindings.
+  BindingExclusions? exclude;
+
+  /// Paths to search for java source files.
+  ///
+  /// If a source package is downloaded through [mavenDownloads] option,
+  /// the corresponding source folder is automatically added and does not
+  /// need to be explicitly specified.
+  List<Uri>? sourcePath;
+
+  /// class path for scanning java libraries. If [backend] is `asm`, the
+  /// specified classpath is used to search for [classes], otherwise it's
+  /// merely used by the doclet API to find transitively referenced classes,
+  /// but not the specified classes / packages themselves.
+  List<Uri>? classPath;
+
+  /// Common text to be pasted on top of generated C and Dart files.
+  String? preamble;
+
+  /// Additional java package -> dart package mappings (Experimental).
+  Map<String, String>? importMap;
+
+  /// Configuration to search for Android SDK libraries (Experimental).
+  AndroidSdkConfig? androidSdkConfig;
+
+  /// Configuration for auto-downloading JAR / source packages using maven,
+  /// along with their transitive dependencies.
+  MavenDownloads? mavenDownloads;
+
+  /// Additional options for the summarizer component
+  SummarizerOptions? summarizerOptions;
+
+  String? dumpJsonTo;
+
+  static Uri? _toDirUri(String? path) =>
+      path != null ? Uri.directory(path) : null;
+  static List<Uri>? _toUris(List<String>? paths) =>
+      paths?.map(Uri.file).toList();
+
+  static Config parseArgs(List<String> args) {
+    final prov = YamlReader.parseArgs(args);
+
+    final List<String> missingValues = [];
+    T must<T>(T? Function(String) f, T ifNull, String property) {
+      final res = f(property);
+      if (res == null) {
+        missingValues.add(property);
+        return ifNull;
+      }
+      return res;
+    }
+
+    MemberFilter<T>? regexFilter<T extends ClassMember>(String property) {
+      final exclusions = prov.getStringList(property);
+      if (exclusions == null) return null;
+      final List<MemberFilter<T>> filters = [];
+      for (var exclusion in exclusions) {
+        final split = exclusion.split('#');
+        if (split.length != 2) {
+          throw FormatException('Error parsing exclusion: "$exclusion"; '
+              'expected to be in binaryName#member format.');
+        }
+        filters.add(MemberNameFilter<T>.exclude(
+          RegExp(split[0]),
+          RegExp(split[1]),
+        ));
+      }
+      return CombinedMemberFilter<T>(filters);
+    }
+
+    String getSdkRoot() {
+      final root = prov.getString(_Props.androidSdkRoot) ??
+          Platform.environment['ANDROID_SDK_ROOT'];
+      if (root == null) {
+        missingValues.add(_Props.androidSdkRoot);
+        return '?';
+      }
+      return root;
+    }
+
+    final config = Config(
+      sourcePath: _toUris(prov.getStringList(_Props.sourcePath)),
+      classPath: _toUris(prov.getStringList(_Props.classPath)),
+      classes: must(prov.getStringList, [], _Props.classes),
+      summarizerOptions: SummarizerOptions(
+        extraArgs: prov.getStringList(_Props.summarizerArgs) ?? const [],
+        backend: prov.getString(_Props.backend),
+        workingDirectory:
+            _toDirUri(prov.getString(_Props.summarizerWorkingDir)),
+      ),
+      exclude: BindingExclusions(
+        methods: regexFilter<Method>(_Props.excludeMethods),
+        fields: regexFilter<Field>(_Props.excludeFields),
+      ),
+      cRoot: Uri.directory(must(prov.getString, '', _Props.cRoot)),
+      dartRoot: Uri.directory(must(prov.getString, '', _Props.dartRoot)),
+      preamble: prov.getString(_Props.preamble),
+      libraryName: must(prov.getString, '', _Props.libraryName),
+      importMap: prov.getStringMap(_Props.importMap),
+      mavenDownloads: prov.hasValue(_Props.mavenDownloads)
+          ? MavenDownloads(
+              sourceDeps: prov.getStringList(_Props.sourceDeps) ?? const [],
+              sourceDir: prov.getString(_Props.mavenSourceDir) ??
+                  MavenDownloads.defaultMavenSourceDir,
+              jarOnlyDeps: prov.getStringList(_Props.jarOnlyDeps) ?? const [],
+              jarDir: prov.getString(_Props.mavenJarDir) ??
+                  MavenDownloads.defaultMavenJarDir,
+            )
+          : null,
+      androidSdkConfig: prov.hasValue(_Props.androidSdkConfig)
+          ? AndroidSdkConfig(
+              versions: must<List<String>>(
+                      prov.getStringList, [], _Props.androidSdkVersions)
+                  .map(int.parse)
+                  .toList(),
+              sdkRoot: getSdkRoot(),
+              includeSources:
+                  prov.getBool(_Props.includeAndroidSources) ?? false,
+            )
+          : null,
+    );
+    if (missingValues.isNotEmpty) {
+      stderr.write('Following config values are required but not provided\n'
+          'Please provide these properties through YAML '
+          'or use the command line switch -D<property_name>=<value>.\n');
+      for (var missing in missingValues) {
+        stderr.writeln('* $missing');
+      }
+      if (missingValues.contains(_Props.androidSdkRoot)) {
+        stderr.writeln('Please specify ${_Props.androidSdkRoot} through '
+            'command line or ensure that the ANDROID_SDK_ROOT environment '
+            'variable is set.');
+      }
+      exit(1);
+    }
+    return config;
+  }
+}
+
+class _Props {
+  static const summarizer = 'summarizer';
+  static const summarizerArgs = '$summarizer.extra_args';
+  static const summarizerWorkingDir = '$summarizer.working_dir';
+  static const backend = '$summarizer.backend';
+
+  static const sourcePath = 'source_path';
+  static const classPath = 'class_path';
+  static const classes = 'classes';
+  static const exclude = 'exclude';
+  static const excludeMethods = '$exclude.methods';
+  static const excludeFields = '$exclude.fields';
+
+  static const importMap = 'import_map';
+  static const cRoot = 'c_root';
+  static const dartRoot = 'dart_root';
+  static const preamble = 'preamble';
+  static const libraryName = 'library_name';
+
+  static const mavenDownloads = 'maven_downloads';
+  static const sourceDeps = '$mavenDownloads.source_deps';
+  static const mavenSourceDir = '$mavenDownloads.source_dir';
+  static const jarOnlyDeps = '$mavenDownloads.jar_only_deps';
+  static const mavenJarDir = '$mavenDownloads.jar_dir';
+
+  static const androidSdkConfig = 'android_sdk_config';
+  static const androidSdkRoot = '$androidSdkConfig.sdk_root';
+  static const androidSdkVersions = '$androidSdkConfig.versions';
+  static const includeAndroidSources = '$androidSdkConfig.include_sources';
+}
diff --git a/pkgs/jni_gen/lib/src/config/wrapper_options.dart b/pkgs/jni_gen/lib/src/config/filters.dart
similarity index 76%
rename from pkgs/jni_gen/lib/src/config/wrapper_options.dart
rename to pkgs/jni_gen/lib/src/config/filters.dart
index 7f1101d..66f1612 100644
--- a/pkgs/jni_gen/lib/src/config/wrapper_options.dart
+++ b/pkgs/jni_gen/lib/src/config/filters.dart
@@ -4,6 +4,11 @@
 
 import 'package:jni_gen/src/elements/elements.dart';
 
+bool _matchesCompletely(String string, Pattern pattern) {
+  final match = pattern.matchAsPrefix(string);
+  return match != null && match.group(0) == string;
+}
+
 /// A filter which tells if bindings for given [ClassDecl] are generated.
 abstract class ClassFilter {
   bool included(ClassDecl decl);
@@ -19,11 +24,6 @@
   }
 }
 
-bool _matchesCompletely(String string, Pattern pattern) {
-  final match = pattern.matchAsPrefix(string);
-  return match != null && match.group(0) == string;
-}
-
 /// Filter to include / exclude classes by matching on the binary name.
 /// A binary name is like qualified name but with a `$` used to indicate nested
 /// class instead of `.`, guaranteeing a unique name.
@@ -45,6 +45,7 @@
   bool included(ClassDecl classDecl, T member);
 }
 
+/// Filter that excludes or includes members based on class and member name.
 class MemberNameFilter<T extends ClassMember> implements MemberFilter<T> {
   MemberNameFilter.include(this.classPattern, this.namePattern)
       : onMatch = true;
@@ -60,6 +61,7 @@
   }
 }
 
+/// Filter that includes or excludes a member based on a custom callback.
 class CustomMemberFilter<T extends ClassMember> implements MemberFilter<T> {
   CustomMemberFilter(this.predicate);
   bool Function(ClassDecl, T) predicate;
@@ -67,6 +69,7 @@
   bool included(ClassDecl classDecl, T member) => predicate(classDecl, member);
 }
 
+/// Filter which excludes classes excluded by any one filter in [filters].
 class CombinedClassFilter implements ClassFilter {
   CombinedClassFilter.all(this.filters);
   final List<ClassFilter> filters;
@@ -74,6 +77,7 @@
   bool included(ClassDecl decl) => filters.every((f) => f.included(decl));
 }
 
+/// Filter which excludes members excluded by any one filter in [filters].
 class CombinedMemberFilter<T extends ClassMember> implements MemberFilter<T> {
   CombinedMemberFilter(this.filters);
 
@@ -112,37 +116,3 @@
   return CombinedMemberFilter<T>(
       names.map((p) => MemberNameFilter<T>.exclude(p[0], p[1])).toList());
 }
-
-/// Options that affect the semantics of the generated code.
-class WrapperOptions {
-  const WrapperOptions({
-    this.classFilter,
-    this.fieldFilter,
-    this.methodFilter,
-    this.classTransformer,
-    this.methodTransformer,
-    this.fieldTransformer,
-    this.importPaths = const {},
-  });
-
-  /// Mapping from java package names to dart packages.
-  /// A mapping `a.b` -> `package:a_b/' means that
-  /// any import `a.b.C` will be resolved as `package:a_b/a/b.dart` in dart.
-  /// Note that dart bindings use the same hierarchy as the java packages.
-  final Map<String, String> importPaths;
-
-  /// [ClassFilter] to decide if bindings for a class should be generated.
-  final ClassFilter? classFilter;
-
-  /// [FieldFilter] to decide if bindings for a field should be generated.
-  final FieldFilter? fieldFilter;
-
-  /// [MethodFilter] to decide if bindings for a method should be generated.
-  final MethodFilter? methodFilter;
-
-  // TODO(#26): This allows us to implement flexible renaming and more customization
-  // via the dart API.
-  final ClassDecl? Function(ClassDecl decl)? classTransformer;
-  final Method? Function(Method method)? methodTransformer;
-  final Field? Function(Field field)? fieldTransformer;
-}
diff --git a/pkgs/jni_gen/lib/src/config/task.dart b/pkgs/jni_gen/lib/src/config/task.dart
deleted file mode 100644
index a5e015d..0000000
--- a/pkgs/jni_gen/lib/src/config/task.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) 2022, 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 'dart:convert';
-
-import 'package:jni_gen/src/writers/bindings_writer.dart';
-import 'package:jni_gen/src/config/config.dart';
-import 'package:jni_gen/src/elements/elements.dart';
-
-/// Represents a complete jni_gen binding generation configuration.
-/// * [summarySource] handles the API summary generation.
-/// * [options] specify any semantic options regarding generated code.
-/// * [outputWriter] handles the output configuration.
-class JniGenTask {
-  JniGenTask({
-    required this.summarySource,
-    this.options = const WrapperOptions(),
-    required this.outputWriter,
-  });
-  BindingsWriter outputWriter;
-  SummarySource summarySource;
-  WrapperOptions options;
-
-  // execute this task
-  Future<void> run({bool dumpJson = false}) async {
-    Stream<List<int>> input;
-    try {
-      input = await summarySource.getInputStream();
-    } on Exception catch (e) {
-      stderr.writeln('error obtaining API summary: $e');
-      return;
-    }
-    final stream = JsonDecoder().bind(Utf8Decoder().bind(input));
-    dynamic json;
-    try {
-      json = await stream.single;
-    } on Exception catch (e) {
-      stderr.writeln('error while parsing summary: $e');
-      return;
-    }
-    if (json == null) {
-      stderr.writeln('error: expected JSON element from summarizer.');
-      return;
-    }
-    if (dumpJson) {
-      stderr.writeln(json);
-    }
-    final list = json as List;
-    try {
-      await outputWriter.writeBindings(
-          list.map((c) => ClassDecl.fromJson(c)), options);
-    } on Exception catch (e, trace) {
-      stderr.writeln(trace);
-      stderr.writeln('error writing bindings: $e');
-    }
-  }
-}
diff --git a/pkgs/jni_gen/lib/src/config/yaml_reader.dart b/pkgs/jni_gen/lib/src/config/yaml_reader.dart
new file mode 100644
index 0000000..3d12dae
--- /dev/null
+++ b/pkgs/jni_gen/lib/src/config/yaml_reader.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2022, 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:args/args.dart';
+import 'package:yaml/yaml.dart';
+
+class ConfigError extends Error {
+  ConfigError(this.message);
+  final String message;
+  @override
+  String toString() => message;
+}
+
+/// YAML Reader which enables to override specific values from command line.
+class YamlReader {
+  YamlReader.of(this.cli, this.yaml);
+  YamlReader.fromYaml(this.yaml) : cli = const {};
+  YamlReader.fromMap(this.cli) : yaml = const {};
+  Map<String, String> cli;
+  Map<dynamic, dynamic> yaml;
+
+  /// Parses the provided command line arguments and returns a [YamlReader].
+  ///
+  /// This is a utility function which does all things a program would do when
+  /// parsing command line arguments, including exiting from the program when
+  /// arguments are invalid.
+  static YamlReader parseArgs(List<String> args,
+      {bool allowYamlConfig = true}) {
+    final parser = ArgParser();
+    parser.addFlag('help', abbr: 'h', help: 'Show this help.');
+
+    // Sometimes it's required to change a config value for a single invocation,
+    // then this option can be used. Conventionally in -D switch is used in
+    // C to set preprocessor variable & in java to override a config property.
+
+    parser.addMultiOption('override',
+        abbr: 'D',
+        help: 'Override or assign a config property from command line.');
+    if (allowYamlConfig) {
+      parser.addOption('config', abbr: 'c', help: 'Path to YAML config.');
+    }
+
+    final results = parser.parse(args);
+    if (results['help']) {
+      stderr.writeln(parser.usage);
+      exit(1);
+    }
+    final configFile = results['config'] as String?;
+    Map<dynamic, dynamic> yamlMap = {};
+    if (configFile != null) {
+      try {
+        final yamlInput = loadYaml(File(configFile).readAsStringSync(),
+            sourceUrl: Uri.file(configFile));
+        if (yamlInput is Map) {
+          yamlMap = yamlInput;
+        } else {
+          throw ConfigError('YAML config must be set of key value pairs');
+        }
+      } on Exception catch (e) {
+        stderr.writeln('cannot read $configFile: $e');
+      }
+    }
+    final regex = RegExp('([a-z-_.]+)=(.*)');
+    final properties = <String, String>{};
+    for (var prop in results['override']) {
+      final match = regex.matchAsPrefix(prop as String);
+      if (match != null && match.group(0) == prop) {
+        final propertyName = match.group(1);
+        final propertyValue = match.group(2);
+        properties[propertyName!] = propertyValue!;
+      } else {
+        throw ConfigError('override does not match expected pattern');
+      }
+    }
+    return YamlReader.of(properties, yamlMap);
+  }
+
+  bool? getBool(String property) {
+    if (cli.containsKey(property)) {
+      final v = cli[property]!;
+      if (v == 'true') {
+        return true;
+      }
+      if (v == 'false') {
+        return false;
+      }
+      throw ConfigError('expected boolean value for $property, got $v');
+    }
+    return null;
+  }
+
+  String? getString(String property) {
+    final configValue = cli[property] ?? getYamlValue<String>(property);
+    return configValue;
+  }
+
+  List<String>? getStringList(String property) {
+    final configValue = cli[property]?.split(',') ??
+        getYamlValue<YamlList>(property)?.cast<String>();
+    return configValue;
+  }
+
+  String? getOneOf(String property, Set<String> values) {
+    final value = cli[property] ?? getYamlValue<String>(property);
+    if (value == null || values.contains(value)) {
+      return value;
+    }
+    throw ConfigError('expected one of $values for $property');
+  }
+
+  Map<String, String>? getStringMap(String property) {
+    final value = getYamlValue<YamlMap>(property);
+    return value?.cast<String, String>();
+  }
+
+  bool hasValue(String property) => getYamlValue<dynamic>(property) != null;
+
+  T? getYamlValue<T>(String property) {
+    final path = property.split('.');
+    dynamic cursor = yaml;
+    String current = '';
+    for (var i in path) {
+      if (cursor is YamlMap || cursor is Map) {
+        cursor = cursor[i];
+      } else {
+        throw ConfigError('expected $current to be a YAML map');
+      }
+      current = [if (current != '') current, i].join('.');
+      if (cursor == null) {
+        return null;
+      }
+    }
+    if (cursor is! T) {
+      throw ConfigError('expected $T for $property, got ${cursor.runtimeType}');
+    }
+    return cursor;
+  }
+}
diff --git a/pkgs/jni_gen/lib/src/generate_bindings.dart b/pkgs/jni_gen/lib/src/generate_bindings.dart
new file mode 100644
index 0000000..b939f9c
--- /dev/null
+++ b/pkgs/jni_gen/lib/src/generate_bindings.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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 'dart:convert';
+
+import 'elements/elements.dart';
+import 'summary/summary.dart';
+import 'config/config.dart';
+import 'tools/tools.dart';
+import 'writers/writers.dart';
+
+Future<void> generateJniBindings(Config config) async {
+  await buildSummarizerIfNotExists();
+
+  final summarizer = SummarizerCommand(
+    sourcePath: config.sourcePath,
+    classPath: config.classPath,
+    classes: config.classes,
+    workingDirectory: config.summarizerOptions?.workingDirectory,
+    extraArgs: config.summarizerOptions?.extraArgs ?? const [],
+    backend: config.summarizerOptions?.backend,
+  );
+
+  final extraSources = <Uri>[];
+  final extraJars = <Uri>[];
+  final mavenDl = config.mavenDownloads;
+  if (mavenDl != null) {
+    final sourcePath = mavenDl.sourceDir;
+    Directory(sourcePath).create(recursive: true);
+    await MavenTools.downloadMavenSources(
+        MavenTools.deps(mavenDl.sourceDeps), sourcePath);
+    extraSources.add(Uri.directory(sourcePath));
+    final jarPath = mavenDl.jarDir;
+    Directory(jarPath).create(recursive: true);
+    await MavenTools.downloadMavenJars(
+        MavenTools.deps(mavenDl.sourceDeps + mavenDl.jarOnlyDeps), jarPath);
+    extraJars.addAll(await Directory(jarPath)
+        .list()
+        .where((entry) => entry.path.endsWith('.jar'))
+        .map((entry) => entry.uri)
+        .toList());
+  }
+
+  final androidConfig = config.androidSdkConfig;
+  if (androidConfig != null) {
+    final androidJar = await AndroidSdkTools.getAndroidJarPath(
+        sdkRoot: androidConfig.sdkRoot, versionOrder: androidConfig.versions);
+    if (androidJar != null) {
+      extraJars.add(Uri.directory(androidJar));
+    }
+    if (androidConfig.includeSources) {
+      final androidSources = await AndroidSdkTools.getAndroidSourcesPath(
+          sdkRoot: androidConfig.sdkRoot, versionOrder: androidConfig.versions);
+      if (androidSources != null) {
+        extraSources.add(Uri.directory(androidSources));
+      }
+    }
+  }
+
+  summarizer.addSourcePaths(extraSources);
+  summarizer.addClassPaths(extraJars);
+
+  Stream<List<int>> input;
+  try {
+    input = await summarizer.getInputStream();
+  } on Exception catch (e) {
+    stderr.writeln('error obtaining API summary: $e');
+    return;
+  }
+  final stream = JsonDecoder().bind(Utf8Decoder().bind(input));
+  dynamic json;
+  try {
+    json = await stream.single;
+  } on Exception catch (e) {
+    stderr.writeln('error while parsing summary: $e');
+    return;
+  }
+  if (json == null) {
+    stderr.writeln('error: expected JSON element from summarizer.');
+    return;
+  }
+  final list = json as List;
+  final outputWriter = FilesWriter(config);
+  try {
+    await outputWriter.writeBindings(list.map((c) => ClassDecl.fromJson(c)));
+  } on Exception catch (e, trace) {
+    stderr.writeln(trace);
+    stderr.writeln('error writing bindings: $e');
+  }
+}
diff --git a/pkgs/jni_gen/lib/src/config/summary_source.dart b/pkgs/jni_gen/lib/src/summary/summary.dart
similarity index 80%
rename from pkgs/jni_gen/lib/src/config/summary_source.dart
rename to pkgs/jni_gen/lib/src/summary/summary.dart
index 622c36b..a259f27 100644
--- a/pkgs/jni_gen/lib/src/config/summary_source.dart
+++ b/pkgs/jni_gen/lib/src/summary/summary.dart
@@ -5,10 +5,6 @@
 import 'dart:io';
 import 'package:jni_gen/src/util/command_output.dart';
 
-abstract class SummarySource {
-  Future<Stream<List<int>>> getInputStream();
-}
-
 /// A command based summary source which calls the ApiSummarizer command.
 /// [sourcePaths] and [classPaths] can be provided for the summarizer to find
 /// required dependencies. The [classes] argument specifies the fully qualified
@@ -21,25 +17,41 @@
 ///
 /// The default summarizer needs to be built with `jni_gen:setup`
 /// script before this API is used.
-class SummarizerCommand extends SummarySource {
+class SummarizerCommand {
   SummarizerCommand({
     this.command = "java -jar .dart_tool/jni_gen/ApiSummarizer.jar",
-    required this.sourcePaths,
-    this.classPaths = const [],
+    List<Uri>? sourcePath,
+    List<Uri>? classPath,
     this.extraArgs = const [],
     required this.classes,
     this.workingDirectory,
-  });
+    this.backend,
+  })  : sourcePaths = sourcePath ?? [],
+        classPaths = classPath ?? [] {
+    if (backend != null && !{'asm', 'doclet'}.contains(backend)) {
+      throw ArgumentError('Supported backends: asm, doclet');
+    }
+  }
 
   static const sourcePathsOption = '-s';
   static const classPathsOption = '-c';
 
   String command;
   List<Uri> sourcePaths, classPaths;
+
   List<String> extraArgs;
   List<String> classes;
 
   Uri? workingDirectory;
+  String? backend;
+
+  void addSourcePaths(List<Uri> paths) {
+    sourcePaths.addAll(paths);
+  }
+
+  void addClassPaths(List<Uri> paths) {
+    classPaths.addAll(paths);
+  }
 
   void _addPathParam(List<String> args, String option, List<Uri> paths) {
     if (paths.isNotEmpty) {
@@ -54,7 +66,6 @@
     }
   }
 
-  @override
   Future<Stream<List<int>>> getInputStream() async {
     final commandSplit = command.split(" ");
     final exec = commandSplit[0];
@@ -62,6 +73,9 @@
 
     _addPathParam(args, sourcePathsOption, sourcePaths);
     _addPathParam(args, classPathsOption, classPaths);
+    if (backend != null) {
+      args.addAll(['--backend', backend!]);
+    }
     args.addAll(extraArgs);
     args.addAll(classes);
 
@@ -73,15 +87,3 @@
     return proc.stdout;
   }
 }
-
-/// A JSON file based summary source.
-// (Did not test it yet)
-class SummaryFile extends SummarySource {
-  Uri path;
-  SummaryFile(this.path);
-  SummaryFile.fromPath(String path) : path = Uri.file(path);
-
-  @override
-  Future<Stream<List<int>>> getInputStream() async =>
-      File.fromUri(path).openRead();
-}
diff --git a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
new file mode 100644
index 0000000..e1092b2
--- /dev/null
+++ b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2022, 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';
+
+class AndroidSdkTools {
+  /// get path for android API sources
+  static Future<String?> _getVersionDir(
+      String relative, String? sdkRoot, List<int> versionOrder) async {
+    sdkRoot ??= Platform.environment['ANDROID_SDK_ROOT'];
+    if (sdkRoot == null) {
+      throw ArgumentError('SDK Root not provided and ANDROID_SDK_ROOT not set');
+    }
+    final parent = join(sdkRoot, relative);
+    for (var version in versionOrder) {
+      final dir = Directory(join(parent, 'android-$version'));
+      if (await dir.exists()) {
+        return dir.path;
+      }
+    }
+    return null;
+  }
+
+  static Future<String?> getAndroidSourcesPath(
+      {String? sdkRoot, required List<int> versionOrder}) async {
+    return _getVersionDir('sources', sdkRoot, versionOrder);
+  }
+
+  static Future<String?> _getFile(String relative, String file, String? sdkRoot,
+      List<int> versionOrder) async {
+    final platform = await _getVersionDir(relative, sdkRoot, versionOrder);
+    if (platform == null) return null;
+    final filePath = join(platform, file);
+    if (await File(filePath).exists()) {
+      return filePath;
+    }
+    return null;
+  }
+
+  static Future<String?> getAndroidJarPath(
+          {String? sdkRoot, required List<int> versionOrder}) async =>
+      await _getFile('platforms', 'android.jar', sdkRoot, versionOrder);
+}
diff --git a/pkgs/jni_gen/lib/src/tools/build_summarizer.dart b/pkgs/jni_gen/lib/src/tools/build_summarizer.dart
new file mode 100644
index 0000000..46847fb
--- /dev/null
+++ b/pkgs/jni_gen/lib/src/tools/build_summarizer.dart
@@ -0,0 +1,60 @@
+// Copyright (c) 2022, 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';
+
+import 'package:jni_gen/src/util/find_package.dart';
+
+final toolPath = join('.', '.dart_tool', 'jni_gen');
+final mvnTargetDir = join(toolPath, 'target');
+final jarFile = join(toolPath, 'ApiSummarizer.jar');
+final targetJarFile = join(mvnTargetDir, 'ApiSummarizer.jar');
+
+Future<void> buildApiSummarizer() async {
+  final pkg = await findPackageRoot('jni_gen');
+  if (pkg == null) {
+    stderr.writeln('package jni_gen not found!');
+    exitCode = 2;
+    return;
+  }
+  final pom = pkg.resolve('java/pom.xml');
+  await Directory(toolPath).create(recursive: true);
+  final mvnProc = await Process.start(
+      'mvn',
+      [
+        '--batch-mode',
+        '--update-snapshots',
+        '-f',
+        pom.toFilePath(),
+        'assembly:assembly'
+      ],
+      workingDirectory: toolPath,
+      mode: ProcessStartMode.inheritStdio);
+  await mvnProc.exitCode;
+  File(targetJarFile).renameSync(jarFile);
+  Directory(mvnTargetDir).deleteSync(recursive: true);
+}
+
+Future<void> buildSummarizerIfNotExists({bool force = false}) async {
+  final jarExists = await File(jarFile).exists();
+  final isJarStale = jarExists &&
+      await isPackageModifiedAfter(
+          'jni_gen', await File(jarFile).lastModified(), 'java/');
+  if (isJarStale) {
+    stderr.writeln('Rebuilding ApiSummarizer component since sources '
+        'have changed. This might take some time.');
+  }
+  if (!jarExists) {
+    stderr.write('Building ApiSummarizer component. '
+        'This might take some time. \n'
+        'The build will be cached for subsequent runs\n');
+  }
+  if (!jarExists || isJarStale || force) {
+    await buildApiSummarizer();
+  } else {
+    stderr.writeln('ApiSummarizer.jar exists. Skipping build..');
+  }
+}
diff --git a/pkgs/jni_gen/lib/src/tools/maven_tools.dart b/pkgs/jni_gen/lib/src/tools/maven_tools.dart
new file mode 100644
index 0000000..52835d9
--- /dev/null
+++ b/pkgs/jni_gen/lib/src/tools/maven_tools.dart
@@ -0,0 +1,137 @@
+// Copyright (c) 2022, 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';
+
+/// This class provides some utility methods to download a sources / jars
+/// using maven along with transitive dependencies.
+class MavenTools {
+  static const _tempPom = '__temp_pom.xml';
+  static const _tempClassPath = '__temp_classpath.xml';
+  static const _tempTarget = '__mvn_target';
+
+  static bool _verbose = false;
+  static void setVerbose(bool enabled) => _verbose = enabled;
+
+  static void _verboseLog(Object? value) {
+    if (_verbose) {
+      stderr.writeln(value);
+    }
+  }
+
+  /// Helper method since we can't pass inheritStdio option to [Process.run].
+  static Future<int> _runCmd(String exec, List<String> args,
+      [String? workingDirectory]) async {
+    _verboseLog('[exec] $exec ${args.join(" ")}');
+    final proc = await Process.start(exec, args,
+        workingDirectory: workingDirectory,
+        mode: ProcessStartMode.inheritStdio);
+    return proc.exitCode;
+  }
+
+  static Future<void> _runMavenCommand(
+      List<MavenDependency> deps, List<String> mvnArgs) async {
+    final pom = _getStubPom(deps);
+    _verboseLog('using POM stub:\n$pom');
+    await File(_tempPom).writeAsString(pom);
+    await Directory(_tempTarget).create();
+    await _runCmd('mvn', ['-f', _tempPom, ...mvnArgs]);
+    await File(_tempPom).delete();
+    await Directory(_tempTarget).delete(recursive: true);
+  }
+
+  /// Create a list of [MavenDependency] objects from maven coordinates in string form.
+  static List<MavenDependency> deps(List<String> depNames) =>
+      depNames.map(MavenDependency.fromString).toList();
+
+  /// Downloads and unpacks source files of [deps] into [targetDir].
+  static Future<void> downloadMavenSources(
+      List<MavenDependency> deps, String targetDir) async {
+    await _runMavenCommand(deps, [
+      'dependency:unpack-dependencies',
+      '-DexcludeTransitive=true',
+      '-DoutputDirectory=$targetDir',
+      '-Dclassifier=sources',
+    ]);
+  }
+
+  /// Downloads JAR files of all [deps] transitively into [targetDir].
+  static Future<void> downloadMavenJars(
+      List<MavenDependency> deps, String targetDir) async {
+    await _runMavenCommand(deps, [
+      'dependency:copy-dependencies',
+      '-DoutputDirectory=$targetDir',
+    ]);
+  }
+
+  /// Get classpath string using JARs in maven's local repository.
+  static Future<String> getMavenClassPath(List<MavenDependency> deps) async {
+    await _runMavenCommand(deps, [
+      'dependency:build-classpath',
+      '-Dmdep.outputFile=$_tempClassPath',
+    ]);
+    final classPathFile = File(_tempClassPath);
+    final classpath = await classPathFile.readAsString();
+    await classPathFile.delete();
+    return classpath;
+  }
+
+  static String _getStubPom(List<MavenDependency> deps,
+      {String javaVersion = '11'}) {
+    final depDecls = <String>[];
+    for (var dep in deps) {
+      final otherTags = StringBuffer();
+      for (var entry in dep.otherTags.entries) {
+        otherTags.write('''
+      <${entry.key}>
+        ${entry.value}
+      </${entry.key}>
+      ''');
+      }
+      depDecls.add('''
+      <dependency>
+        <groupId>${dep.groupID}</groupId>
+        <artifactId>${dep.artifactID}</artifactId>
+        <version>${dep.version}</version>
+        ${otherTags.toString()}
+      </dependency>''');
+    }
+
+    return '''
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+  http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.mycompany.app</groupId>
+    <artifactId>jnigen_maven_stub</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <properties>
+      <maven.compiler.source>$javaVersion</maven.compiler.source>
+      <maven.compiler.target>$javaVersion</maven.compiler.target>
+    </properties>
+    <dependencies>
+${depDecls.join("\n")}
+    </dependencies>
+    <build>
+      <directory>$_tempTarget</directory>
+    </build>
+</project>''';
+  }
+}
+
+/// Maven dependency with group ID, artifact ID, and version.
+class MavenDependency {
+  MavenDependency(this.groupID, this.artifactID, this.version,
+      {this.otherTags = const {}});
+  factory MavenDependency.fromString(String fullName) {
+    final components = fullName.split(':');
+    if (components.length != 3) {
+      throw ArgumentError('invalid name for maven dependency: $fullName');
+    }
+    return MavenDependency(components[0], components[1], components[2]);
+  }
+  String groupID, artifactID, version;
+  Map<String, String> otherTags;
+}
diff --git a/pkgs/jni_gen/lib/src/tools/maven_utils.dart b/pkgs/jni_gen/lib/src/tools/maven_utils.dart
deleted file mode 100644
index 2c28657..0000000
--- a/pkgs/jni_gen/lib/src/tools/maven_utils.dart
+++ /dev/null
@@ -1,134 +0,0 @@
-// Copyright (c) 2022, 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';
-
-/// This class provides some utility methods to download a sources / jars
-/// using maven along with transitive dependencies.
-class MvnTools {
-  static const _tempPom = '__temp_pom.xml';
-  static const _tempClassPath = '__temp_classpath.xml';
-  static const _tempTarget = '__mvn_target';
-
-  static bool _verbose = false;
-  static void setVerbose(bool enabled) => _verbose = enabled;
-
-  static void _verboseLog(Object? value) {
-    if (_verbose) {
-      stderr.writeln(value);
-    }
-  }
-
-  /// Helper method since we can't pass inheritStdio option to [Process.run].
-  static Future<int> _runCmd(String exec, List<String> args,
-      [String? workingDirectory]) async {
-    _verboseLog('[exec] $exec ${args.join(" ")}');
-    final proc = await Process.start(exec, args,
-        workingDirectory: workingDirectory,
-        mode: ProcessStartMode.inheritStdio);
-    return proc.exitCode;
-  }
-
-  static Future<void> _runMavenCommand(
-      List<MvnDep> deps, List<String> mvnArgs) async {
-    final pom = _getStubPom(deps);
-    _verboseLog('using POM stub:\n$pom');
-    await File(_tempPom).writeAsString(pom);
-    await Directory(_tempTarget).create();
-    await _runCmd(
-        'mvn', ['-f', _tempPom, '-DbuildDirectory=$_tempTarget', ...mvnArgs]);
-    await File(_tempPom).delete();
-    await Directory(_tempTarget).delete(recursive: true);
-  }
-
-  /// Create a list of [MvnDep] objects from maven coordinates in string form.
-  static List<MvnDep> makeDependencyList(List<String> depNames) =>
-      depNames.map(MvnDep.fromString).toList();
-
-  /// Downloads and unpacks source files of [deps] into [targetDir].
-  static Future<void> downloadMavenSources(
-      List<MvnDep> deps, String targetDir) async {
-    await _runMavenCommand(deps, [
-      'dependency:unpack-dependencies',
-      '-DoutputDirectory=$targetDir',
-      '-Dclassifier=sources'
-    ]);
-  }
-
-  /// Downloads JAR files of all [deps] transitively into [targetDir].
-  static Future<void> downloadMavenJars(
-      List<MvnDep> deps, String targetDir) async {
-    await _runMavenCommand(deps, [
-      'dependency:copy-dependencies',
-      '-DoutputDirectory=$targetDir',
-    ]);
-  }
-
-  /// Get classpath string using JARs in maven's local repository.
-  static Future<String> getMavenClassPath(List<MvnDep> deps) async {
-    await _runMavenCommand(deps, [
-      'dependency:build-classpath',
-      '-Dmdep.outputFile=$_tempClassPath',
-    ]);
-    final classPathFile = File(_tempClassPath);
-    final classpath = await classPathFile.readAsString();
-    await classPathFile.delete();
-    return classpath;
-  }
-
-  static String _getStubPom(List<MvnDep> deps, {String javaVersion = '11'}) {
-    final i2 = ' ' * 2;
-    final i4 = ' ' * 4;
-    final i6 = ' ' * 6;
-    final i8 = ' ' * 8;
-    final depDecls = <String>[];
-
-    for (var dep in deps) {
-      final otherTags = StringBuffer();
-      for (var entry in dep.otherTags.entries) {
-        otherTags.write('$i6<${entry.key}>\n'
-            '$i8${entry.value}\n'
-            '$i6</${entry.key}>\n');
-      }
-      depDecls.add('$i4<dependency>\n'
-          '$i6<groupId>${dep.groupID}</groupId>\n'
-          '$i6<artifactId>${dep.artifactID}</artifactId>\n'
-          '$i6<version>${dep.version}</version>\n'
-          '${otherTags.toString()}\n'
-          '$i4</dependency>\n');
-    }
-
-    return '<project xmlns="http://maven.apache.org/POM/4.0.0" '
-        'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n'
-        'xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 '
-        'http://maven.apache.org/xsd/maven-4.0.0.xsd">\n'
-        '$i2<modelVersion>4.0.0</modelVersion>\n'
-        '$i2<groupId>com.mycompany.app</groupId>\n'
-        '$i2<artifactId>my-app</artifactId>\n'
-        '$i2<version>1.0-SNAPSHOT</version>\n'
-        '$i2<properties>\n'
-        '$i4<maven.compiler.source>$javaVersion</maven.compiler.source>\n'
-        '$i4<maven.compiler.target>$javaVersion</maven.compiler.target>\n'
-        '$i2</properties>\n'
-        '$i4<dependencies>\n'
-        '${depDecls.join("\n")}'
-        '$i2</dependencies>\n'
-        '</project>';
-  }
-}
-
-/// Maven dependency with group ID, artifact ID, and version.
-class MvnDep {
-  MvnDep(this.groupID, this.artifactID, this.version,
-      {this.otherTags = const {}});
-  factory MvnDep.fromString(String fullName) {
-    final components = fullName.split(':');
-    if (components.length != 3) {
-      throw ArgumentError('invalid name for maven dependency: $fullName');
-    }
-    return MvnDep(components[0], components[1], components[2]);
-  }
-  String groupID, artifactID, version;
-  Map<String, String> otherTags;
-}
diff --git a/pkgs/jni_gen/lib/src/config/errors.dart b/pkgs/jni_gen/lib/src/tools/tools.dart
similarity index 64%
rename from pkgs/jni_gen/lib/src/config/errors.dart
rename to pkgs/jni_gen/lib/src/tools/tools.dart
index 516e8f8..9677a3e 100644
--- a/pkgs/jni_gen/lib/src/config/errors.dart
+++ b/pkgs/jni_gen/lib/src/tools/tools.dart
@@ -2,5 +2,6 @@
 // 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.
 
-/// Base class for all unexpected errors in JniGen (Except Skip)
-abstract class JniGenException implements Exception {}
+export 'android_sdk_tools.dart';
+export 'maven_tools.dart';
+export 'build_summarizer.dart';
diff --git a/pkgs/jni_gen/lib/src/writers/bindings_writer.dart b/pkgs/jni_gen/lib/src/writers/bindings_writer.dart
deleted file mode 100644
index ed50a41..0000000
--- a/pkgs/jni_gen/lib/src/writers/bindings_writer.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2022, 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 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
-
-abstract class BindingsWriter {
-  Future<void> writeBindings(
-      Iterable<ClassDecl> classes, WrapperOptions options);
-}
diff --git a/pkgs/jni_gen/lib/src/writers/callback_writer.dart b/pkgs/jni_gen/lib/src/writers/callback_writer.dart
deleted file mode 100644
index 0ab7ed0..0000000
--- a/pkgs/jni_gen/lib/src/writers/callback_writer.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2022, 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 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
-
-import 'bindings_writer.dart';
-
-/// A writer for debugging purpose.
-class CallbackWriter extends BindingsWriter {
-  CallbackWriter(this.callback);
-  Future<void> Function(Iterable<ClassDecl>, WrapperOptions) callback;
-
-  @override
-  Future<void> writeBindings(
-      Iterable<ClassDecl> classes, WrapperOptions options) async {
-    callback(classes, options);
-  }
-}
diff --git a/pkgs/jni_gen/lib/src/writers/files_writer.dart b/pkgs/jni_gen/lib/src/writers/files_writer.dart
deleted file mode 100644
index 0e8a6ad..0000000
--- a/pkgs/jni_gen/lib/src/writers/files_writer.dart
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (c) 2022, 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:jni_gen/src/bindings/bindings.dart';
-
-import 'package:jni_gen/src/elements/elements.dart';
-import 'package:jni_gen/src/config/wrapper_options.dart';
-import 'package:jni_gen/src/util/find_package.dart';
-
-import 'bindings_writer.dart';
-
-/// Writer which takes writes C and Dart bindings to specified directories.
-///
-/// The structure of dart files is determined by package structure of java.
-/// One dart file corresponds to one java package, and it's path is decided by
-/// fully qualified name of the package.
-///
-/// Example:
-/// `android.os` -> `$dartWrappersRoot`/`android/os.dart`
-class FilesWriter extends BindingsWriter {
-  static const _initFileName = 'init.dart';
-
-  FilesWriter(
-      {required this.cWrapperDir,
-      required this.dartWrappersRoot,
-      this.javaWrappersRoot,
-      this.preamble,
-      required this.libraryName});
-  Uri cWrapperDir, dartWrappersRoot;
-  Uri? javaWrappersRoot;
-  String? preamble;
-  String libraryName;
-  @override
-  Future<void> writeBindings(
-      Iterable<ClassDecl> classes, WrapperOptions options) async {
-    // If the file already exists, show warning.
-    // sort classes so that all classes get written at once.
-    final Map<String, List<ClassDecl>> packages = {};
-    final Map<String, ClassDecl> classesByName = {};
-    for (var c in classes) {
-      classesByName.putIfAbsent(c.binaryName, () => c);
-      packages.putIfAbsent(c.packageName!, () => <ClassDecl>[]);
-      packages[c.packageName!]!.add(c);
-    }
-    final classNames = classesByName.keys.toSet();
-
-    stderr.writeln('Creating dart init file ...');
-    final initFileUri = dartWrappersRoot.resolve(_initFileName);
-    final initFile = await File.fromUri(initFileUri).create(recursive: true);
-    await initFile.writeAsString(DartPreludes.initFile(libraryName),
-        flush: true);
-
-    final cFile = await File.fromUri(cWrapperDir.resolve('$libraryName.c'))
-        .create(recursive: true);
-    final cFileStream = cFile.openWrite();
-    if (preamble != null) {
-      cFileStream.writeln(preamble);
-    }
-    cFileStream.write(CPreludes.prelude);
-    final preprocessor = ApiPreprocessor(classesByName, options);
-    preprocessor.preprocessAll();
-    for (var packageName in packages.keys) {
-      final relativeFileName = '${packageName.replaceAll('.', '/')}.dart';
-      final dartFileUri = dartWrappersRoot.resolve(relativeFileName);
-      stderr.writeln('Writing bindings for $packageName...');
-      final dartFile = await File.fromUri(dartFileUri).create(recursive: true);
-      final resolver = PackagePathResolver(
-          options.importPaths, packageName, classNames,
-          predefined: {'java.lang.String': 'jni.JlString'});
-      final cgen = CBindingGenerator(options);
-      final dgen = DartBindingsGenerator(options, resolver);
-
-      final package = packages[packageName]!;
-      final cBindings = package.map(cgen.generateBinding).toList();
-      final dartBindings = package.map(dgen.generateBinding).toList();
-      // write imports from bindings
-      final dartFileStream = dartFile.openWrite();
-      final initImportPath = ('../' *
-              relativeFileName.codeUnits
-                  .where((cu) => '/'.codeUnitAt(0) == cu)
-                  .length) +
-          _initFileName;
-      if (preamble != null) {
-        dartFileStream.writeln(preamble);
-      }
-      dartFileStream
-        ..write(DartPreludes.bindingFileHeaders)
-        ..write(resolver.getImportStrings().join('\n'))
-        ..write('import "$initImportPath" show jlookup;\n\n');
-      // write dart bindings only after all imports are figured out
-      dartBindings.forEach(dartFileStream.write);
-      cBindings.forEach(cFileStream.write);
-      await dartFileStream.close();
-    }
-    await cFileStream.close();
-    stderr.writeln('Running dart format...');
-    final formatRes =
-        await Process.run('dart', ['format', dartWrappersRoot.toFilePath()]);
-    if (formatRes.exitCode != 0) {
-      stderr.writeln('ERROR: dart format completed with '
-          'exit code ${formatRes.exitCode}');
-    }
-
-    stderr.writeln('Copying auxiliary files...');
-    await _copyFileFromPackage(
-        'jni', 'src/dartjni.h', cWrapperDir.resolve('dartjni.h'));
-    await _copyFileFromPackage('jni_gen', 'cmake/CMakeLists.txt.tmpl',
-        cWrapperDir.resolve('CMakeLists.txt'),
-        transform: (s) => s.replaceAll('{{LIBRARY_NAME}}', libraryName));
-    stderr.writeln('Completed.');
-  }
-
-  Future<void> _copyFileFromPackage(String package, String relPath, Uri target,
-      {String Function(String)? transform}) async {
-    final packagePath = await findPackageRoot(package);
-    if (packagePath != null) {
-      final sourceFile = File.fromUri(packagePath.resolve(relPath));
-      final targetFile = await File.fromUri(target).create();
-      var source = await sourceFile.readAsString();
-      if (transform != null) {
-        source = transform(source);
-      }
-      await targetFile.writeAsString(source);
-    } else {
-      stderr.writeln('package $package not found! '
-          'skipped copying ${target.toFilePath()}');
-    }
-  }
-}
diff --git a/pkgs/jni_gen/lib/src/writers/writers.dart b/pkgs/jni_gen/lib/src/writers/writers.dart
index 887e6a9..38b202e 100644
--- a/pkgs/jni_gen/lib/src/writers/writers.dart
+++ b/pkgs/jni_gen/lib/src/writers/writers.dart
@@ -2,6 +2,141 @@
 // 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.
 
-export 'bindings_writer.dart';
-export 'files_writer.dart';
-export 'callback_writer.dart';
+import 'dart:io';
+
+import 'package:jni_gen/src/bindings/bindings.dart';
+
+import 'package:jni_gen/src/elements/elements.dart';
+import 'package:jni_gen/src/config/config.dart';
+import 'package:jni_gen/src/util/find_package.dart';
+
+abstract class BindingsWriter {
+  Future<void> writeBindings(Iterable<ClassDecl> classes);
+}
+
+/// Writer which executes custom callback on passed class elements.
+///
+/// This class is provided for debugging purposes.
+class CallbackWriter implements BindingsWriter {
+  CallbackWriter(this.callback);
+  Future<void> Function(Iterable<ClassDecl>) callback;
+  @override
+  Future<void> writeBindings(Iterable<ClassDecl> classes) async {
+    await callback(classes);
+  }
+}
+
+/// Writer which takes writes C and Dart bindings to specified directories.
+///
+/// The structure of dart files is determined by package structure of java.
+/// One dart file corresponds to one java package, and it's path is decided by
+/// fully qualified name of the package.
+///
+/// Example:
+/// `android.os` -> `$dartWrappersRoot`/`android/os.dart`
+class FilesWriter extends BindingsWriter {
+  static const _initFileName = 'init.dart';
+
+  FilesWriter(this.config);
+  Config config;
+
+  @override
+  Future<void> writeBindings(Iterable<ClassDecl> classes) async {
+    // If the file already exists, show warning.
+    // sort classes so that all classes get written at once.
+    final cRoot = config.cRoot;
+    final dartRoot = config.dartRoot;
+    final libraryName = config.libraryName;
+    final preamble = config.preamble;
+
+    final Map<String, List<ClassDecl>> packages = {};
+    final Map<String, ClassDecl> classesByName = {};
+    for (var c in classes) {
+      classesByName.putIfAbsent(c.binaryName, () => c);
+      packages.putIfAbsent(c.packageName!, () => <ClassDecl>[]);
+      packages[c.packageName!]!.add(c);
+    }
+    final classNames = classesByName.keys.toSet();
+
+    stderr.writeln('Creating dart init file ...');
+    final initFileUri = dartRoot.resolve(_initFileName);
+    final initFile = await File.fromUri(initFileUri).create(recursive: true);
+    await initFile.writeAsString(DartPreludes.initFile(config.libraryName),
+        flush: true);
+
+    final cFile = await File.fromUri(cRoot.resolve('$libraryName.c'))
+        .create(recursive: true);
+    final cFileStream = cFile.openWrite();
+    if (preamble != null) {
+      cFileStream.writeln(preamble);
+    }
+    cFileStream.write(CPreludes.prelude);
+    ApiPreprocessor.preprocessAll(classesByName, config);
+    for (var packageName in packages.keys) {
+      final relativeFileName = '${packageName.replaceAll('.', '/')}.dart';
+      final dartFileUri = dartRoot.resolve(relativeFileName);
+      stderr.writeln('Writing bindings for $packageName...');
+      final dartFile = await File.fromUri(dartFileUri).create(recursive: true);
+      final resolver = PackagePathResolver(
+          config.importMap ?? const {}, packageName, classNames,
+          predefined: {'java.lang.String': 'jni.JlString'});
+      final cgen = CBindingGenerator(config);
+      final dgen = DartBindingsGenerator(config, resolver);
+
+      final package = packages[packageName]!;
+      final cBindings = package.map(cgen.generateBinding).toList();
+      final dartBindings = package.map(dgen.generateBinding).toList();
+      // write imports from bindings
+      final dartFileStream = dartFile.openWrite();
+      final initImportPath = ('../' *
+              relativeFileName.codeUnits
+                  .where((cu) => '/'.codeUnitAt(0) == cu)
+                  .length) +
+          _initFileName;
+      if (preamble != null) {
+        dartFileStream.writeln(preamble);
+      }
+      dartFileStream
+        ..write(DartPreludes.bindingFileHeaders)
+        ..write(resolver.getImportStrings().join('\n'))
+        ..write('import "$initImportPath" show jlookup;\n\n');
+      // write dart bindings only after all imports are figured out
+      dartBindings.forEach(dartFileStream.write);
+      cBindings.forEach(cFileStream.write);
+      await dartFileStream.close();
+    }
+    await cFileStream.close();
+    stderr.writeln('Running dart format...');
+    final formatRes =
+        await Process.run('dart', ['format', dartRoot.toFilePath()]);
+    if (formatRes.exitCode != 0) {
+      stderr.writeln('ERROR: dart format completed with '
+          'exit code ${formatRes.exitCode}');
+    }
+
+    stderr.writeln('Copying auxiliary files...');
+    await _copyFileFromPackage(
+        'jni', 'src/dartjni.h', cRoot.resolve('dartjni.h'));
+    await _copyFileFromPackage(
+        'jni_gen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'),
+        transform: (s) => s.replaceAll('{{LIBRARY_NAME}}', libraryName));
+    stderr.writeln('Completed.');
+  }
+
+  Future<void> _copyFileFromPackage(String package, String relPath, Uri target,
+      {String Function(String)? transform}) async {
+    final packagePath = await findPackageRoot(package);
+    if (packagePath != null) {
+      final sourceFile = File.fromUri(packagePath.resolve(relPath));
+      final targetFile = await File.fromUri(target).create();
+      var source = await sourceFile.readAsString();
+      if (transform != null) {
+        source = transform(source);
+      }
+      await targetFile.writeAsString(source);
+    } else {
+      stderr.writeln('package $package not found! '
+          'skipped copying ${target.toFilePath()}');
+    }
+  }
+}
diff --git a/pkgs/jni_gen/lib/tools.dart b/pkgs/jni_gen/lib/tools.dart
index 19e0eab..91934d3 100644
--- a/pkgs/jni_gen/lib/tools.dart
+++ b/pkgs/jni_gen/lib/tools.dart
@@ -4,4 +4,4 @@
 
 library jni_gen_tools;
 
-export 'src/tools/maven_utils.dart';
+export 'src/tools/tools.dart';
diff --git a/pkgs/jni_gen/pubspec.yaml b/pkgs/jni_gen/pubspec.yaml
index c75369b..f347657 100644
--- a/pkgs/jni_gen/pubspec.yaml
+++ b/pkgs/jni_gen/pubspec.yaml
@@ -13,7 +13,9 @@
 dependencies:
   json_annotation: ^4.6.0
   package_config: ^2.1.0
-  path:
+  path: ^1.8.0
+  args: ^2.3.0
+  yaml: ^3.1.0
 
 dev_dependencies:
   lints: ^2.0.0
diff --git a/pkgs/jni_gen/test/config_test.dart b/pkgs/jni_gen/test/config_test.dart
new file mode 100644
index 0000000..e7b8c25
--- /dev/null
+++ b/pkgs/jni_gen/test/config_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2022, 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 'package:jni_gen/src/config/config.dart';
+import 'package:test/test.dart';
+import 'package:path/path.dart' hide equals;
+
+import 'jackson_core_test/generate.dart';
+
+const packageTests = 'test';
+final jacksonCoreTests = join(packageTests, 'jackson_core_test');
+final thirdParty = join(jacksonCoreTests, 'third_party');
+final lib = join(thirdParty, 'lib');
+final src = join(thirdParty, 'src');
+final testLib = join(thirdParty, 'test_lib');
+final testSrc = join(thirdParty, 'test_src');
+
+/// Compares 2 [Config] objects using [expect] to give useful errors when
+/// two fields are not equal.
+void expectConfigsAreEqual(Config a, Config b) {
+  expect(a.classes, equals(b.classes));
+  expect(a.libraryName, equals(b.libraryName));
+  expect(a.cRoot, equals(b.cRoot));
+  expect(a.dartRoot, equals(b.dartRoot));
+  expect(a.sourcePath, equals(b.sourcePath));
+  expect(a.classPath, equals(b.classPath));
+  expect(a.preamble, equals(b.preamble));
+  expect(a.importMap, equals(b.importMap));
+  final am = a.mavenDownloads;
+  final bm = b.mavenDownloads;
+  if (am != null) {
+    expect(bm, isNotNull);
+    expect(am.sourceDeps, bm!.sourceDeps);
+    expect(am.sourceDir, bm.sourceDir);
+    expect(am.jarOnlyDeps, bm.jarOnlyDeps);
+    expect(am.jarDir, bm.jarDir);
+  } else {
+    expect(bm, isNull);
+  }
+  final aa = a.androidSdkConfig;
+  final ba = b.androidSdkConfig;
+  if (aa != null) {
+    expect(ba, isNotNull);
+    expect(aa.versions, ba!.versions);
+    expect(aa.sdkRoot, ba.sdkRoot);
+    expect(aa.includeSources, ba.includeSources);
+  } else {
+    expect(ba, isNull);
+  }
+  final aso = a.summarizerOptions;
+  final bso = b.summarizerOptions;
+  if (aso != null) {
+    expect(bso, isNotNull);
+    expect(aso.extraArgs, bso!.extraArgs);
+    expect(aso.workingDirectory, bso.workingDirectory);
+    expect(aso.backend, bso.backend);
+  } else {
+    expect(bso, isNull);
+  }
+}
+
+void main() {
+  final config = Config.parseArgs([
+    '--config',
+    join(jacksonCoreTests, 'jnigen.yaml'),
+    '-Dc_root=$testSrc',
+    '-Ddart_root=$testLib',
+  ]);
+
+  test('compare configuration values', () {
+    expectConfigsAreEqual(config, getConfig(isTest: true));
+  });
+}
diff --git a/pkgs/jni_gen/test/jackson_core_test/generate.dart b/pkgs/jni_gen/test/jackson_core_test/generate.dart
index 5d62592..919fadb 100644
--- a/pkgs/jni_gen/test/jackson_core_test/generate.dart
+++ b/pkgs/jni_gen/test/jackson_core_test/generate.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:jni_gen/jni_gen.dart';
-import '../test_util/test_util.dart';
+import 'package:path/path.dart' hide equals;
 
 const jacksonPreamble = '// Generated from jackson-core which is licensed under'
     ' the Apache License 2.0.\n'
@@ -23,18 +23,27 @@
     '// See the License for the specific language governing permissions and\n'
     '// limitations under the License.\n';
 
-Future<void> generate(
+const testName = 'jackson_core_test';
+final thirdParty = join('test', testName, 'third_party');
+const deps = ['com.fasterxml.jackson.core:jackson-core:2.13.3'];
+
+Config getConfig(
     {bool isTest = false,
     bool generateFullVersion = false,
-    bool useAsm = false}) async {
-  final deps = ['com.fasterxml.jackson.core:jackson-core:2.13.3'];
-  await generateBindings(
-    testName: 'jackson_core_test',
-    sourceDepNames: deps,
-    jarDepNames: deps,
-    useAsmBackend: useAsm,
+    bool useAsm = false}) {
+  final config = Config(
+    mavenDownloads: MavenDownloads(
+      sourceDeps: deps,
+      sourceDir: join(thirdParty, 'java'),
+      jarDir: join(thirdParty, 'jar'),
+    ),
+    summarizerOptions: SummarizerOptions(
+      backend: useAsm ? 'asm' : null,
+    ),
     preamble: jacksonPreamble,
-    isThirdParty: true,
+    libraryName: testName,
+    cRoot: Uri.directory(join(thirdParty, isTest ? 'test_src' : 'src')),
+    dartRoot: Uri.directory(join(thirdParty, isTest ? 'test_lib' : 'lib')),
     classes: (generateFullVersion)
         ? ['com.fasterxml.jackson.core']
         : [
@@ -42,20 +51,25 @@
             'com.fasterxml.jackson.core.JsonParser',
             'com.fasterxml.jackson.core.JsonToken',
           ],
-    isGeneratedFileTest: isTest,
-    options: WrapperOptions(
-        fieldFilter: CombinedFieldFilter([
-          excludeAll<Field>([
-            ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'],
-            ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'],
-            ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'],
-            ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'],
-          ]),
-          CustomFieldFilter((decl, field) => !field.name.startsWith("_")),
-        ]),
-        methodFilter:
-            CustomMethodFilter((decl, method) => !method.name.startsWith('_'))),
+    exclude: BindingExclusions(
+      fields: excludeAll<Field>([
+        ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'],
+        ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'],
+        ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'],
+        ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'],
+      ]),
+    ),
   );
+  return config;
+}
+
+Future<void> generate(
+    {bool isTest = false,
+    bool generateFullVersion = false,
+    bool useAsm = false}) async {
+  final config = getConfig(
+      isTest: isTest, generateFullVersion: generateFullVersion, useAsm: useAsm);
+  await generateJniBindings(config);
 }
 
 void main() => generate(isTest: false);
diff --git a/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml b/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml
new file mode 100644
index 0000000..96988bd
--- /dev/null
+++ b/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml
@@ -0,0 +1,36 @@
+maven_downloads:
+  source_deps:
+    - 'com.fasterxml.jackson.core:jackson-core:2.13.3'
+  source_dir: test/jackson_core_test/third_party/java
+  jar_dir: test/jackson_core_test/third_party/jar
+
+dart_root: test/jackson_core_test/third_party/lib
+c_root: test/jackson_core_test/third_party/src
+library_name: jackson_core_test
+classes:
+  - 'com.fasterxml.jackson.core.JsonFactory'
+  - 'com.fasterxml.jackson.core.JsonParser'
+  - 'com.fasterxml.jackson.core.JsonToken'
+
+exclude:
+  fields:
+    - 'com.fasterxml.jackson.core.JsonFactory#DEFAULT_QUOTE_CHAR'
+
+preamble: |
+  // Generated from jackson-core which is licensed under the Apache License 2.0.
+  // The following copyright from the original authors applies.
+  // See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE
+  //
+  // Copyright (c) 2007 - The Jackson Project Authors
+  // Licensed under the Apache License, Version 2.0 (the "License")
+  // you may not use this file except in compliance with the License.
+  // You may obtain a copy of the License at
+  //
+  //     http://www.apache.org/licenses/LICENSE-2.0
+  //
+  // Unless required by applicable law or agreed to in writing, software
+  // distributed under the License is distributed on an "AS IS" BASIS,
+  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  // See the License for the specific language governing permissions and
+  // limitations under the License.
+
diff --git a/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart b/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart
index ee2de71..6ecc806 100644
--- a/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart
+++ b/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart
@@ -61,7 +61,7 @@
   /// (and returned by \#getFormatName()
   static const FORMAT_NAME_JSON = "JSON";
 
-  static final _getDEFAULT_FACTORY_FEATURE_FLAGS = jlookup<
+  static final _get_DEFAULT_FACTORY_FEATURE_FLAGS = jlookup<
               ffi.NativeFunction<ffi.Int32 Function()>>(
           "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_FACTORY_FEATURE_FLAGS")
       .asFunction<int Function()>();
@@ -70,9 +70,9 @@
   ///
   /// Bitfield (set of flags) of all factory features that are enabled by default.
   static int get DEFAULT_FACTORY_FEATURE_FLAGS =>
-      _getDEFAULT_FACTORY_FEATURE_FLAGS();
+      _get_DEFAULT_FACTORY_FEATURE_FLAGS();
 
-  static final _getDEFAULT_PARSER_FEATURE_FLAGS = jlookup<
+  static final _get_DEFAULT_PARSER_FEATURE_FLAGS = jlookup<
               ffi.NativeFunction<ffi.Int32 Function()>>(
           "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_PARSER_FEATURE_FLAGS")
       .asFunction<int Function()>();
@@ -82,9 +82,9 @@
   /// Bitfield (set of flags) of all parser features that are enabled
   /// by default.
   static int get DEFAULT_PARSER_FEATURE_FLAGS =>
-      _getDEFAULT_PARSER_FEATURE_FLAGS();
+      _get_DEFAULT_PARSER_FEATURE_FLAGS();
 
-  static final _getDEFAULT_GENERATOR_FEATURE_FLAGS = jlookup<
+  static final _get_DEFAULT_GENERATOR_FEATURE_FLAGS = jlookup<
               ffi.NativeFunction<ffi.Int32 Function()>>(
           "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_GENERATOR_FEATURE_FLAGS")
       .asFunction<int Function()>();
@@ -94,9 +94,9 @@
   /// Bitfield (set of flags) of all generator features that are enabled
   /// by default.
   static int get DEFAULT_GENERATOR_FEATURE_FLAGS =>
-      _getDEFAULT_GENERATOR_FEATURE_FLAGS();
+      _get_DEFAULT_GENERATOR_FEATURE_FLAGS();
 
-  static final _getDEFAULT_ROOT_VALUE_SEPARATOR = jlookup<
+  static final _get_DEFAULT_ROOT_VALUE_SEPARATOR = jlookup<
               ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
           "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_ROOT_VALUE_SEPARATOR")
       .asFunction<ffi.Pointer<ffi.Void> Function()>();
@@ -104,7 +104,7 @@
   /// from: static public final com.fasterxml.jackson.core.SerializableString DEFAULT_ROOT_VALUE_SEPARATOR
   /// The returned object must be deleted after use, by calling the `delete` method.
   static jni.JlObject get DEFAULT_ROOT_VALUE_SEPARATOR =>
-      jni.JlObject.fromRef(_getDEFAULT_ROOT_VALUE_SEPARATOR());
+      jni.JlObject.fromRef(_get_DEFAULT_ROOT_VALUE_SEPARATOR());
 
   static final _ctor =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
@@ -1682,7 +1682,7 @@
   /// from: private static final int MAX_SHORT_I
   static const MAX_SHORT_I = 32767;
 
-  static final _getDEFAULT_READ_CAPABILITIES = jlookup<
+  static final _get_DEFAULT_READ_CAPABILITIES = jlookup<
               ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
           "get_com_fasterxml_jackson_core_JsonParser_DEFAULT_READ_CAPABILITIES")
       .asFunction<ffi.Pointer<ffi.Void> Function()>();
@@ -1695,7 +1695,7 @@
   /// set needs to be passed).
   ///@since 2.12
   static jni.JlObject get DEFAULT_READ_CAPABILITIES =>
-      jni.JlObject.fromRef(_getDEFAULT_READ_CAPABILITIES());
+      jni.JlObject.fromRef(_get_DEFAULT_READ_CAPABILITIES());
 
   static final _ctor =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
diff --git a/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h b/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h
index 407312b..cd94b15 100644
--- a/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h
+++ b/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 #include <jni.h>
 #include <stdint.h>
 #include <stdio.h>
diff --git a/pkgs/jni_gen/test/simple_package_test/generate.dart b/pkgs/jni_gen/test/simple_package_test/generate.dart
index 427098d..dd6eeca 100644
--- a/pkgs/jni_gen/test/simple_package_test/generate.dart
+++ b/pkgs/jni_gen/test/simple_package_test/generate.dart
@@ -20,7 +20,6 @@
 }
 
 Future<void> generateSources(String lib, String src) async {
-  await runCmd('dart', ['run', 'jni_gen:setup']);
   await compileJavaSources(javaPath, javaFiles);
   final cWrapperDir = Uri.directory(join(testRoot, src));
   final dartWrappersRoot = Uri.directory(join(testRoot, lib));
@@ -31,17 +30,15 @@
       await dir.delete(recursive: true);
     }
   }
-  await JniGenTask(
-    summarySource: SummarizerCommand(
-      sourcePaths: [Uri.directory(javaPath)],
-      classPaths: [Uri.directory(javaPath)],
-      classes: ['dev.dart.simple_package', 'dev.dart.pkg2'],
-    ),
-    outputWriter: FilesWriter(
-        cWrapperDir: cWrapperDir,
-        dartWrappersRoot: dartWrappersRoot,
-        libraryName: 'simple_package'),
-  ).run();
+  final config = Config(
+    sourcePath: [Uri.directory(javaPath)],
+    classPath: [Uri.directory(javaPath)],
+    classes: ['dev.dart.simple_package', 'dev.dart.pkg2'],
+    cRoot: cWrapperDir,
+    dartRoot: dartWrappersRoot,
+    libraryName: 'simple_package',
+  );
+  await generateJniBindings(config);
 }
 
 void main() async => await generateSources('lib', 'src');
diff --git a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart
index 618b175..0fa32cd 100644
--- a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart
+++ b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart
@@ -17,19 +17,20 @@
 class C2 extends jni.JlObject {
   C2.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref);
 
-  static final _getCONSTANT = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>(
-          "get_dev_dart_pkg2_C2_CONSTANT")
-      .asFunction<int Function()>();
+  static final _get_CONSTANT =
+      jlookup<ffi.NativeFunction<ffi.Int32 Function()>>(
+              "get_dev_dart_pkg2_C2_CONSTANT")
+          .asFunction<int Function()>();
 
   /// from: static public int CONSTANT
-  static int get CONSTANT => _getCONSTANT();
-  static final _setCONSTANT =
+  static int get CONSTANT => _get_CONSTANT();
+  static final _set_CONSTANT =
       jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Int32)>>(
               "set_dev_dart_pkg2_C2_CONSTANT")
           .asFunction<void Function(int)>();
 
   /// from: static public int CONSTANT
-  static set CONSTANT(int value) => _setCONSTANT(value);
+  static set CONSTANT(int value) => _set_CONSTANT(value);
 
   static final _ctor =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
diff --git a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart
index da6ec32..173c493 100644
--- a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart
+++ b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart
@@ -23,36 +23,36 @@
   /// from: static public final int OFF
   static const OFF = 0;
 
-  static final _getaux =
+  static final _get_aux =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
               "get_dev_dart_simple_package_Example_aux")
           .asFunction<ffi.Pointer<ffi.Void> Function()>();
 
   /// from: static public dev.dart.simple_package.Example.Aux aux
   /// The returned object must be deleted after use, by calling the `delete` method.
-  static Example_Aux get aux => Example_Aux.fromRef(_getaux());
-  static final _setaux =
+  static Example_Aux get aux => Example_Aux.fromRef(_get_aux());
+  static final _set_aux =
       jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
               "set_dev_dart_simple_package_Example_aux")
           .asFunction<void Function(ffi.Pointer<ffi.Void>)>();
 
   /// from: static public dev.dart.simple_package.Example.Aux aux
   /// The returned object must be deleted after use, by calling the `delete` method.
-  static set aux(Example_Aux value) => _setaux(value.reference);
+  static set aux(Example_Aux value) => _set_aux(value.reference);
 
-  static final _getnum = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>(
+  static final _get_num = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>(
           "get_dev_dart_simple_package_Example_num")
       .asFunction<int Function()>();
 
   /// from: static public int num
-  static int get num => _getnum();
-  static final _setnum =
+  static int get num => _get_num();
+  static final _set_num =
       jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Int32)>>(
               "set_dev_dart_simple_package_Example_num")
           .asFunction<void Function(int)>();
 
   /// from: static public int num
-  static set num(int value) => _setnum(value);
+  static set num(int value) => _set_num(value);
 
   static final _ctor =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
@@ -111,7 +111,7 @@
 class Example_Aux extends jni.JlObject {
   Example_Aux.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref);
 
-  static final _getvalue = jlookup<
+  static final _get_value = jlookup<
           ffi.NativeFunction<
               ffi.Uint8 Function(
     ffi.Pointer<ffi.Void>,
@@ -122,15 +122,15 @@
   )>();
 
   /// from: public boolean value
-  bool get value => _getvalue(reference) != 0;
-  static final _setvalue = jlookup<
+  bool get value => _get_value(reference) != 0;
+  static final _set_value = jlookup<
           ffi.NativeFunction<
               ffi.Void Function(ffi.Pointer<ffi.Void>,
                   ffi.Uint8)>>("set_dev_dart_simple_package_Example__Aux_value")
       .asFunction<void Function(ffi.Pointer<ffi.Void>, int)>();
 
   /// from: public boolean value
-  set value(bool value) => _setvalue(reference, value ? 1 : 0);
+  set value(bool value) => _set_value(reference, value ? 1 : 0);
 
   static final _ctor =
       jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function(ffi.Uint8)>>(
diff --git a/pkgs/jni_gen/test/simple_package_test/src/dartjni.h b/pkgs/jni_gen/test/simple_package_test/src/dartjni.h
index 407312b..cd94b15 100644
--- a/pkgs/jni_gen/test/simple_package_test/src/dartjni.h
+++ b/pkgs/jni_gen/test/simple_package_test/src/dartjni.h
@@ -1,3 +1,7 @@
+// Copyright (c) 2022, 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.
+
 #include <jni.h>
 #include <stdint.h>
 #include <stdio.h>
diff --git a/pkgs/jni_gen/test/test_util/test_util.dart b/pkgs/jni_gen/test/test_util/test_util.dart
index c0a1681..49bc5ff 100644
--- a/pkgs/jni_gen/test/test_util/test_util.dart
+++ b/pkgs/jni_gen/test/test_util/test_util.dart
@@ -1,13 +1,9 @@
 import 'dart:io';
 
 import 'package:path/path.dart' hide equals;
-import 'package:jni_gen/jni_gen.dart';
-import 'package:jni_gen/tools.dart';
 import 'package:test/test.dart';
 
-const packageTestsDir = 'test';
-
-Future<bool> isEmptyDir(String path) async {
+Future<bool> hasNoFilesInDir(String path) async {
   final dir = Directory(path);
   return (!await dir.exists()) || (await dir.list().length == 0);
 }
@@ -22,72 +18,15 @@
   return proc.exitCode;
 }
 
-Future<void> buildNativeLibs(String testName) async {
-  final testRoot = join(packageTestsDir, testName);
-  await runCmd('dart', ['run', 'jni:setup']);
-  await runCmd('dart', ['run', 'jni:setup', '-S', join(testRoot, 'src')]);
-}
-
 Future<List<String>> getJarPaths(String testRoot) {
   final jarPath = join(testRoot, 'jar');
   return Directory(jarPath)
       .list()
       .map((entry) => entry.path)
-      .where((path) => path.endsWith('jar'))
+      .where((path) => path.endsWith('.jar'))
       .toList();
 }
 
-/// Download dependencies using maven and generate bindings.
-Future<void> generateBindings({
-  required String testName,
-  required List<String> sourceDepNames,
-  required List<String> jarDepNames,
-  required List<String> classes,
-  required WrapperOptions options,
-  required bool isGeneratedFileTest,
-  bool useAsmBackend = false,
-  bool isThirdParty = false,
-  String? preamble,
-}) async {
-  final testRoot =
-      join(packageTestsDir, testName, isThirdParty ? 'third_party' : '');
-  final jarPath = join(testRoot, 'jar');
-  final javaPath = join(testRoot, 'java');
-  final src = join(testRoot, isGeneratedFileTest ? 'test_src' : 'src');
-  final lib = join(testRoot, isGeneratedFileTest ? 'test_lib' : 'lib');
-
-  final sourceDeps = MvnTools.makeDependencyList(sourceDepNames);
-  final jarDeps = MvnTools.makeDependencyList(jarDepNames);
-
-  await runCmd('dart', ['run', 'jni_gen:setup']);
-
-  MvnTools.setVerbose(true);
-  if (await isEmptyDir(jarPath)) {
-    await Directory(jarPath).create(recursive: true);
-    await MvnTools.downloadMavenJars(jarDeps, jarPath);
-  }
-  if (await isEmptyDir(javaPath)) {
-    await Directory(javaPath).create(recursive: true);
-    await MvnTools.downloadMavenSources(sourceDeps, javaPath);
-  }
-  final jars = await getJarPaths(testRoot);
-  stderr.writeln('using classpath: $jars');
-  await JniGenTask(
-          summarySource: SummarizerCommand(
-            sourcePaths: [Uri.directory(javaPath)],
-            classPaths: jars.map(Uri.file).toList(),
-            classes: classes,
-            extraArgs: useAsmBackend ? ['--backend', 'asm'] : [],
-          ),
-          options: options,
-          outputWriter: FilesWriter(
-              cWrapperDir: Uri.directory(src),
-              dartWrappersRoot: Uri.directory(lib),
-              preamble: preamble,
-              libraryName: testName))
-      .run();
-}
-
 /// compares 2 hierarchies, with and without prefix 'test_'
 void compareDirs(String path1, String path2) {
   final list1 = Directory(path1).listSync(recursive: true);
diff --git a/pkgs/jni_gen/test/yaml_config_test.dart b/pkgs/jni_gen/test/yaml_config_test.dart
new file mode 100644
index 0000000..2f180a6
--- /dev/null
+++ b/pkgs/jni_gen/test/yaml_config_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, 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.
+
+// End-to-end test confirming yaml config works as expected.
+
+import 'dart:io';
+
+import 'package:path/path.dart' hide equals;
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  final thirdParty = join('test', 'jackson_core_test', 'third_party');
+  final testLib = join(thirdParty, 'test_lib');
+  final testSrc = join(thirdParty, 'test_src');
+  final lib = join(thirdParty, 'lib');
+  final src = join(thirdParty, 'src');
+  final config = join('test', 'jackson_core_test', 'jnigen.yaml');
+  test('generate and compare bindings using YAML config', () {
+    final jnigenProc = Process.runSync('dart', [
+      'run',
+      'jni_gen',
+      '--config',
+      config,
+      '-Dc_root=$testSrc',
+      '-Ddart_root=$testLib'
+    ]);
+    expect(jnigenProc.exitCode, equals(0));
+
+    final analyzeProc = Process.runSync('dart', ['analyze', testLib]);
+    expect(analyzeProc.exitCode, equals(0));
+
+    compareDirs(lib, testLib);
+    compareDirs(src, testSrc);
+
+    for (var dir in [testLib, testSrc]) {
+      Directory(dir).deleteSync(recursive: true);
+    }
+  }, timeout: Timeout.factor(4));
+}