content: // Copyright (c) 2013, 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.
  
  part of dart2js.js_emitter;
  
  class MetadataEmitter extends CodeEmitterHelper {
    /// A list of JS expressions that represent metadata, parameter names and
    /// type, and return types.
    final List<String> globalMetadata = [];
  
    /// A map used to canonicalize the entries of globalMetadata.
    final Map<String, int> globalMetadataMap = <String, int>{};
  
    /// The metadata function returns the metadata associated with
    /// [element] in generated code.  The metadata needs to be wrapped
    /// in a function as it refers to constants that may not have been
    /// constructed yet.  For example, a class is allowed to be
    /// annotated with itself.  The metadata function is used by
    /// mirrors_patch to implement DeclarationMirror.metadata.
    jsAst.Fun buildMetadataFunction(Element element) {
      if (!backend.retainMetadataOf(element)) return null;
      return compiler.withCurrentElement(element, () {
        var metadata = [];
        Link link = element.metadata;
        // TODO(ahe): Why is metadata sometimes null?
        if (link != null) {
          for (; !link.isEmpty; link = link.tail) {
            MetadataAnnotation annotation = link.head;
            Constant value = annotation.value;
            if (value == null) {
              compiler.reportInternalError(
                  annotation, 'Internal error: value is null');
            } else {
              metadata.add(task.constantReference(value));
            }
          }
        }
        if (metadata.isEmpty) return null;
        return js.fun(
            [], [js.return_(new jsAst.ArrayInitializer.from(metadata))]);
      });
    }
  
    jsAst.Node reifyDefaultArguments(FunctionElement function) {
      FunctionSignature signature = function.computeSignature(compiler);
      if (signature.optionalParameterCount == 0) return null;
      List<int> defaultValues = <int>[];
      for (Element element in signature.orderedOptionalParameters) {
        Constant value =
            compiler.constantHandler.initialVariableValues[element];
        String stringRepresentation = (value == null)
            ? "null"
            : jsAst.prettyPrint(task.constantReference(value), compiler)
                .getText();
        defaultValues.add(addGlobalMetadata(stringRepresentation));
      }
      return js.toExpression(defaultValues);
    }
  
    int reifyMetadata(MetadataAnnotation annotation) {
      Constant value = annotation.value;
      if (value == null) {
        compiler.reportInternalError(
            annotation, 'Internal error: value is null');
        return -1;
      }
      return addGlobalMetadata(
          jsAst.prettyPrint(task.constantReference(value), compiler).getText());
    }
  
    int reifyType(DartType type) {
      jsAst.Expression representation =
          backend.rti.getTypeRepresentation(type, (variable) {
            return js.toExpression(
                task.typeVariableHandler.reifyTypeVariable(variable.element));
          });
  
      return addGlobalMetadata(
          jsAst.prettyPrint(representation, compiler).getText());
    }
  
    int reifyName(String name) {
      return addGlobalMetadata('"$name"');
    }
  
    int addGlobalMetadata(String string) {
      return globalMetadataMap.putIfAbsent(string, () {
        globalMetadata.add(string);
        return globalMetadata.length - 1;
      });
    }
  
    void emitMetadata(CodeBuffer buffer) {
      var literals = backend.typedefTypeLiterals.toList();
      Elements.sortedByPosition(literals);
      var properties = [];
      for (TypedefElement literal in literals) {
        var key = namer.getNameX(literal);
        var value = js.toExpression(reifyType(literal.rawType));
        properties.add(new jsAst.Property(js.string(key), value));
      }
      var map = new jsAst.ObjectInitializer(properties);
      buffer.write(
          jsAst.prettyPrint(
              js('init.functionAliases = #', map).toStatement(), compiler));
      buffer.write('${N}init.metadata$_=$_[');
      for (var metadata in globalMetadata) {
        if (metadata is String) {
          if (metadata != 'null') {
            buffer.write(metadata);
          }
        } else {
          throw 'Unexpected value in metadata: ${Error.safeToString(metadata)}';
        }
        buffer.write(',$n');
      }
      buffer.write('];$n');
    }
  
    jsAst.Fun extendWithMetadata(FunctionElement element, jsAst.Fun code) {
      if (!backend.retainMetadataOf(element)) return code;
      return compiler.withCurrentElement(element, () {
        List<int> metadata = <int>[];
        FunctionSignature signature = element.functionSignature;
        if (element.isConstructor()) {
          metadata.add(reifyType(element.getEnclosingClass().thisType));
        } else {
          metadata.add(reifyType(signature.returnType));
        }
        signature.forEachParameter((Element parameter) {
          metadata
              ..add(reifyName(parameter.name))
              ..add(reifyType(parameter.computeType(compiler)));
        });
        Link link = element.metadata;
        // TODO(ahe): Why is metadata sometimes null?
        if (link != null) {
          for (; !link.isEmpty; link = link.tail) {
            metadata.add(reifyMetadata(link.head));
          }
        }
        code.body.statements.add(js.string(metadata.join(',')).toStatement());
        return code;
      });
    }
  }
  