| // Copyright 2020 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // @dart = 2.8 | |
| import 'dart:core'; | |
| import 'dart:core' as core; | |
| import 'dart:mirrors'; | |
| import 'queries/index.dart'; | |
| bool _parseBool(String val) { | |
| if (val == 'true') return true; | |
| if (val == 'false') return false; | |
| throw Exception('$val is not a valid value for boolean'); | |
| } | |
| class ReflectQuery { | |
| static String nameType(Type type) { | |
| final classMirror = reflectClass(type); | |
| return Query.stripQuerySuffix(classMirror.simpleName.toString()); | |
| } | |
| static Query instantiate(QueryFactory f, Map<String, String> arguments) { | |
| final classMirror = reflectClass(f.type); | |
| final constructors = List<MethodMirror>.from(classMirror.declarations.values | |
| .whereType<MethodMirror>() | |
| .where((decl) => decl.isConstructor)); | |
| if (arguments.isNotEmpty && constructors.isEmpty) { | |
| throw Exception( | |
| '$f has no constructors, but specified arguments $arguments'); | |
| } | |
| for (final constructor in constructors) { | |
| if (constructor is MethodMirror) { | |
| final List<ParameterMirror> parameters = constructor.parameters; | |
| if (parameters.any((param) => !param.isNamed)) { | |
| throw Exception('$constructor must only use named parameters'); | |
| } | |
| final parameterNames = | |
| parameters.where((param) => param.isNamed).map((e) => e.simpleName); | |
| if (arguments.keys.any((arg) => !parameterNames.contains(Symbol(arg)))) | |
| continue; | |
| final resolvedArgs = | |
| Map<Symbol, dynamic>.fromEntries(arguments.entries.map((entry) { | |
| final key = entry.key, value = entry.value; | |
| final param = | |
| parameters.firstWhere((param) => param.simpleName == Symbol(key)); | |
| final name = param.simpleName; | |
| switch (param.type.simpleName) { | |
| case #int: | |
| return MapEntry<Symbol, dynamic>(name, int.parse(value)); | |
| case #bool: | |
| return MapEntry<Symbol, dynamic>(name, _parseBool(value)); | |
| case #String: | |
| return MapEntry<Symbol, dynamic>(name, value); | |
| default: | |
| throw Exception('Unexpected type ${param.type.simpleName}'); | |
| } | |
| })); | |
| return classMirror | |
| .newInstance(core.Symbol.empty, [], resolvedArgs) | |
| .reflectee; | |
| } | |
| } | |
| final constructorDesc = constructors | |
| .map((c) => describeConstructor(c, f)) | |
| .map((s) => ' - tried $s') | |
| .join('\n'); | |
| throw Exception( | |
| 'Did not find suitable constructor for ${f.type} with $arguments.\n\n' | |
| '$constructorDesc\n'); | |
| } | |
| /// Prints the method signature for `constructor`. | |
| static String describeConstructor(MethodMirror constructor, QueryFactory f) { | |
| final params = describeArgDeclarations(constructor); | |
| if (params.isEmpty) { | |
| return '${f.name}()'; | |
| } else { | |
| return '${f.name}({${params.join(", ")}})'; | |
| } | |
| } | |
| static Iterable<String> describeArgDeclarations(MethodMirror constructor) => | |
| constructor.parameters.map((param) { | |
| final typeName = MirrorSystem.getName(param.type.simpleName); | |
| final paramName = MirrorSystem.getName(param.simpleName); | |
| var str = '$typeName $paramName'; | |
| if (param.hasDefaultValue) { | |
| str += ' = ${param.defaultValue.reflectee}'; | |
| } | |
| return str; | |
| }); | |
| static Iterable<String> describeQueryConstructors(QueryFactory f) { | |
| final classMirror = reflectClass(f.type); | |
| final constructors = List<MethodMirror>.from(classMirror.declarations.values | |
| .whereType<MethodMirror>() | |
| .where((decl) => decl.isConstructor)); | |
| return constructors.map((c) => describeConstructor(c, f)); | |
| } | |
| static bool hasCustomArguments(QueryFactory f) { | |
| final classMirror = reflectClass(f.type); | |
| final constructors = List<MethodMirror>.from(classMirror.declarations.values | |
| .whereType<MethodMirror>() | |
| .where((decl) => decl.isConstructor)); | |
| return constructors.any((c) => c.parameters.isNotEmpty); | |
| } | |
| } |