按名称生成静态访问器

时间:2015-05-27 09:38:00

标签: dart

假设我有这个课程:

abstract class DynamicallyAccessible{
  operator [](String key){
    throw 'DynamicallyAcessible [] operator is not implemented';
  }

  operator []=(String key,Object value){
    throw 'DynamicallyAcessible []= operator is not implemented';
  }

  call(String methodName,List<Object> params){
    throw 'DynamicallyAcessible call method is not implemented';
  }
}

以及扩展上述内容的这个类:

class Model extends DynamicallyAccessible{

  String _a;
  String get a => _a;
  set a(v)=> _a=v;

  String _b;
  String get b => _b;
  set b(v)=> _b=v;

  int c;


  dummyMethod(int a,int b){
    return 6+a+b;
  }

  //this is to be generated by the transformer
  //calls a getter or returns a value field by name
  operator [](String key){
    switch (key){
      case 'a':return a;
      case 'b':return b;
      case 'c':return c;
      default:throw 'no getter or field called $key';
    }
  }

  //calls a setter or sets a field by name
  operator []=(String key,Object value){
    switch (key){
      case 'a':a=value;return;
      case 'b':b=value;return;
      case 'c':c=value;return;
      default:throw 'no setter or field called $key';
    }
  }

  //calls a method by name
  call(String key,List<Object> params){
    switch(key){
      case 'dummyMethod':return dummyMethod(params[0],params[1]);
      default : throw 'no method called $key';
    }
  }
}

我手动实现这些方法是浪费时间,我的问题是是否存在类似存在的变换器,或者我必须从头开始编写一个,如果没有任何建议可以做得更好吗?

原因是避免使用带有dart2js的镜像

2 个答案:

答案 0 :(得分:3)

以下是使用分析器包查找扩展DynamicallyAccessible并为操作符[]注入代码的类的转换器示例。

import 'package:barback/barback.dart';
import 'package:analyzer/analyzer.dart';

class DynamicallyAccessibleTransformer extends Transformer {

  DynamicallyAccessibleTransformer.asPlugin();

  get allowedExtensions => '.dart';

  apply(Transform transform) async {
    var content = await transform.readInputAsString(transform.primaryInput.id);
    var newContent = _transformDartFile(content);

    transform.addOutput(new Asset.fromString(transform.primaryInput.id, newContent));
  }

  String _transformDartFile(String content) {
    CompilationUnit unit = parseCompilationUnit(content);

    String newContent = content;

    for (ClassDeclaration declaration in unit.declarations.reversed.where((d) => d is ClassDeclaration)) {
      if (_isDynamicallyAccessible(declaration)) {
        String sourceToInject = _createSourceToInject(declaration);

        String before = content.substring(0, declaration.endToken.offset);
        String after = content.substring(declaration.endToken.offset);

        newContent = before + "\n$sourceToInject\n" + after;
      }
    }

    return newContent;
  }

  /// TODO: this is a fragile approach as we only check for the class name
  /// and not the library from where it comes. We probably should resolve
  /// the Element and check the library.
  _isDynamicallyAccessible(ClassDeclaration declaration) {
    ExtendsClause extendsClause = declaration.extendsClause;
    if (extendsClause != null) {
      return extendsClause.superclass.name.name == 'DynamicallyAccessible';
    }
    return false;
  }

  _createSourceToInject(ClassDeclaration declaration) {
    //TODO: do the same things for setters, methods and variable declaration.
    String getterCases = declaration.members
      .where((member) => member is MethodDeclaration && member.isGetter)
      .map((MethodDeclaration getter) => getter.name.name)
      .map((String name) => "   case '$name': return $name;")
      .join('\n');

    return '''
  operator [](String key) {
    switch (key) {
$getterCases
      default:throw 'no getter called \$key';
    }
  }
''';
  }
}

答案 1 :(得分:1)

这是我的版本,也支持调用方法的位置和命名参数尚未经过深度测试但工作正常

//TODO:support multiple inheritance where the sub class gets access to the super class members
class DynamicTransformer extends Transformer {


  DynamicTransformer.asPlugin();


  String get allowedExtensions => ".dart";


  Future apply(Transform transform) async{
    var logger = new BuildLogger(transform);

      var content = await transform.primaryInput.readAsString();
      var sourceFile = new SourceFile(content);
      var transaction = _transformCompilationUnit(content,sourceFile,logger);
      var id = transform.primaryInput.id;
      if(transaction.hasEdits){
        var printer = transaction.commit();
        var url = id.path.startsWith('lib/')
                  ? 'package:${id.package}/${id.path.substring(4)}' : id.path;
        printer.build(url);
        transform.addOutput(new Asset.fromString(id,printer.text));
      }
      return logger.writeOutput();
  }

  Future<bool> isPrimary(id) {
    return new Future.value(id.extension == '.dart');
  }


  TextEditTransaction _transformCompilationUnit(
    String inputCode, SourceFile sourceFile, BuildLogger logger) {
    var unit = parseCompilationUnit(inputCode, suppressErrors: true);
    var editor = new TextEditTransaction(inputCode, sourceFile);

    unit.declarations
    .where((dec) => dec is ClassDeclaration &&
    isSubClassOrHasMixinOf(dec, 'DynamicAccess'))
    .forEach((classDec) {
      _transformClass(classDec,editor,sourceFile,logger);
    });

    return editor;
  }

  void _transformClass(ClassDeclaration cls, TextEditTransaction editor,
                       SourceFile file, BuildLogger logger) {



    //TODO:throw another exception for private fields

    var readWriteFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true);
    var readOnlyFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true,readOnly:true);
    var getters = getClassDeclarationMethodDeclarationNames(cls,getter:true).union(readWriteFieldNames).union(readOnlyFieldNames);
    var setters = getClassDeclarationMethodDeclarationNames(cls,setter:true).union(readWriteFieldNames);
    var methods = getClassDeclarationMethodDeclarations(cls,normal:true);

    var methodsString = _buildSquareBracketsOperatorGet(getters);
    methodsString+=_buildSquareBracketsOperatorAssignment(setters,readOnlyFieldNames);
    methodsString+=_buildCallMethod(methods);
    editor.edit(cls.endToken.offset,cls.endToken.offset,'$methodsString');

  }

  //build [] operator method
  String _buildSquareBracketsOperatorGet(Set<String> getters) {
    var sb = new StringBuffer();
    sb.writeln('operator [](String key){');
    sb.writeln('switch (key){');
    getters.forEach((getter) {
      sb.writeln("case '$getter': return this.$getter;");
    });
    sb.writeln("default:throw 'no getter or field called \$key';");
    sb.writeln('}}');
    return sb.toString();
  }

  String _buildSquareBracketsOperatorAssignment(Set<String> setters,Set<String> readOnlyFields) {
    var sb = new StringBuffer();
    sb.writeln('operator []=(String key,Object value){');
    sb.writeln('switch (key){');
    setters.forEach((setter) {
      sb.writeln("case '$setter':this.$setter=value;return;");
    });
    readOnlyFields.forEach((readOnlyField) {
      sb.writeln("case '$readOnlyField':throw 'field \$key is read only';return;");
    });
    sb.writeln("default:throw 'no setter or field called \$key';");
    sb.writeln('}}');
    return sb.toString();
  }


  String _buildCallMethod(Set<MethodDeclaration> methods){
    var sb = new StringBuffer();
    sb.writeln('call(String key,{List<Object> required:null,List<Object> positional:null,Map<String,Object> named:null}){');
    sb.writeln('switch (key){');
    methods.forEach((md){
        sb.writeln("case '${md.name.name}': return this.${buildMethodCallString(md)}");
    });
    sb.writeln("default:throw 'no setter or field called \$key';");
    sb.writeln('}}');
    return sb.toString();
  }
}

一些实用方法:

isSubClassOrHasMixinOf(ClassDeclaration classDec, String superClassOrMixinName) {
  if(classDec.withClause!=null){
    print(classDec.withClause.mixinTypes);
  }
  if ((classDec.extendsClause != null &&
  classDec.extendsClause.superclass.name.name == superClassOrMixinName) ||
  (classDec.withClause != null &&
  classDec.withClause.mixinTypes.any((type)=>type.name.name==superClassOrMixinName))) {
    return true;
  }
  return false;
}

Set<String> getClassDeclarationFieldDeclarationNames(ClassDeclaration cd,
    {public: true, private: false, readOnly: false}) {
  var fieldNames = new Set<String>();

  cd.members.forEach((m) {
    if (m is FieldDeclaration) {
      var fd = m as FieldDeclaration;
      if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
        fd.fields.variables.forEach((variable) {
          var fName = variable.name.name;
          if ((Identifier.isPrivateName(fName) && private) || (!Identifier.isPrivateName(fName) && public)) {
            fieldNames.add(fName);
          }
        });
      }
    }
  });
  return fieldNames;
}

Set<FieldDeclaration> getClassDeclarationFieldDeclarations(ClassDeclaration cd,
    {public: true, private: false, readOnly: false}) {
  var fieldsDeclarations = new Set<FieldDeclaration>();

  cd.members.forEach((m) {
    if (m is FieldDeclaration) {
      var fd = m as FieldDeclaration;
      if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
        fd.fields.variables.forEach((variable) {
          if ((Identifier.isPrivateName(variable.name.name) && private) || (!Identifier.isPrivateName(variable.name.name) && public)) {
            fieldsDeclarations.add(fd);
          }
        });
      }
    }
  });
  return fieldsDeclarations;
}


String buildMethodCallString(MethodDeclaration md){
  var sb = new StringBuffer();

  sb.write('${md.name.name}(');

  var requiredParams = getMethodFormalParameterDeclarations(md,required:true);
  if(requiredParams.length>0){
    for(int i=0;i<requiredParams.length;i++){
      sb.write('required[$i],');
    };
  }

  var positionalParams = getMethodFormalParameterDeclarations(md,positional:true);
  if(positionalParams.length>0){
    for(int i=0;i<positionalParams.length;i++){
      sb.write('(positional==null || positional[$i]==null)?${positionalParams[i].childEntities.last}:positional[$i],');
    };
  }

  var namedParams = getMethodFormalParameterDeclarations(md,named:true);
  if(namedParams.length > 0){
    for(int i=0;i<namedParams.length;i++){

      sb.write("${namedParams[i].identifier.name}:(named==null || !named.containsKey('${namedParams[i].identifier}'))?${namedParams[i].childEntities.last}:named['${namedParams[i].identifier}'],");
    };
  }

  sb.write(');');
  return sb.toString().replaceAll(r',)',')');
}

Set<MethodDeclaration> getClassDeclarationMethodDeclarations(
    ClassDeclaration cd, {public: true, private: false, getter: false,
    setter: false, operator: false, normal: false}) {
  var methodDeclarations = new Set<MethodDeclaration>();

  cd.members.forEach((d) {
    if (d is MethodDeclaration) {
      var md = d as MethodDeclaration;
      var mName = md.name.name;
      if ((Identifier.isPrivateName(mName) && private) ||
      (!Identifier.isPrivateName(mName) && public)) {
        if (md.isSetter && setter) {
          methodDeclarations.add(md);
        } else if (md.isGetter && getter) {
          methodDeclarations.add(md);
        } else if (md.isOperator && operator) {
          //to do warn if [] []= are already overloaded and terminate
          return;
        } else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
          methodDeclarations.add(md);
        }
      }
    }
  });

  return methodDeclarations;
}

Set<String> getClassDeclarationMethodDeclarationNames(
    ClassDeclaration cd, {public: true, private: false, getter: false,
    setter: false, operator: false, normal: false}) {

  var methodDeclarationNames = new Set<String>();

  cd.members.forEach((d) {
    if (d is MethodDeclaration) {
      var md = d as MethodDeclaration;
      var mName = md.name.name;
      if ((Identifier.isPrivateName(mName) && private) ||
      (!Identifier.isPrivateName(mName) && public)) {
        if (md.isSetter && setter) {
          methodDeclarationNames.add(mName);
        } else if (md.isGetter && getter) {
          methodDeclarationNames.add(mName);
        } else if (md.isOperator && operator) {
          //to do warn if [] []= are already overloaded and terminate
          return;
        } else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
          methodDeclarationNames.add(mName);
        }
      }
    }
  });

  return methodDeclarationNames;

}

List<FormalParameter> getMethodFormalParameterDeclarations(MethodDeclaration md,
                                                           {required: false, positional: false, named: false}) {
  var formalParameters = new List<FormalParameter>();
  md.parameters.parameters.forEach((fp) {
    if ((fp.kind == ParameterKind.REQUIRED && required) ||
    (fp.kind == ParameterKind.POSITIONAL && positional) ||
    (fp.kind == ParameterKind.NAMED && named)) {
      formalParameters.add(fp);
    }
  });
  return formalParameters;
}

示例:

class Model extends Object with DynamicAccess{
  String _a;
  String get a => _a;
  set a(v)=> _a=v;

  String _b;
  String get b => _b;
  set b(v)=> _b=v;

  final int c=9;

  String _z = 'xx';


  dummyMethod(int a,int b){
    return 6+a+b;
  }

  dummyNoParams(){
    return 0;
  }

  dummyMethodWithPositionalParams(int a,String d,[int x=4,y=6]){
    return '${a+x+y} $d';
  }


  dummyMethodWithNamedParams(int x,int y,{int f:4,String g:'no'}){
    return '${x+y+f} $g';
  }
}

给出:

class Model extends Object with DynamicAccess {
  String _a;
  String get a => _a;
  set a(v) => _a = v;

  String _b;
  String get b => _b;
  set b(v) => _b = v;

  final int c = 9;

  String _z = 'xx';

  dummyMethod(int a, int b) {
    return 6 + a + b;
  }

  dummyNoParams() {
    return 0;
  }

  dummyMethodWithPositionalParams(int a, String d, [int x = 4, y = 6]) {
    return '${a+x+y} $d';
  }

  dummyMethodWithNamedParams(int x, int y, {int f: 4, String g: 'no'}) {
    return '${x+y+f} $g';
  }
  operator [](String key) {
    switch (key) {
      case 'a':
        return this.a;
      case 'b':
        return this.b;
      case 'c':
        return this.c;
      default:
        throw 'no getter or field called $key';
    }
  }
  operator []=(String key, Object value) {
    switch (key) {
      case 'a':
        this.a = value;
        return;
      case 'b':
        this.b = value;
        return;
      case 'c':
        throw 'field $key is read only';
        return;
      default:
        throw 'no setter or field called $key';
    }
  }
  call(String key, {List<Object> required: null, List<Object> positional: null,
      Map<String, Object> named: null}) {
    switch (key) {
      case 'dummyMethod':
        return this.dummyMethod(required[0], required[1]);
      case 'dummyNoParams':
        return this.dummyNoParams();
      case 'dummyMethodWithPositionalParams':
        return this.dummyMethodWithPositionalParams(required[0], required[1],
            (positional == null || positional[0] == null) ? 4 : positional[0],
            (positional == null || positional[1] == null) ? 6 : positional[1]);
      case 'dummyMethodWithNamedParams':
        return this.dummyMethodWithNamedParams(required[0], required[1],
            f: (named == null || !named.containsKey('f')) ? 4 : named['f'],
            g: (named == null || !named.containsKey('g')) ? 'no' : named['g']);
      default:
        throw 'no setter or field called $key';
    }
  }
}