trphoenix
2018-11-12 29fbfc5dd1d55d189f23eb6d32f000252f92985f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright (c) 2018, 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:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:json_annotation/json_annotation.dart';
 
import 'helper_core.dart';
import 'type_helper.dart';
import 'type_helpers/convert_helper.dart';
import 'utils.dart';
 
TypeHelperContext typeHelperContext(
        HelperCore helperCore, FieldElement fieldElement, JsonKey key) =>
    _TypeHelperCtx(helperCore, fieldElement, key);
 
class _TypeHelperCtx
    implements TypeHelperContextWithConfig, TypeHelperContextWithConvert {
  final HelperCore _helperCore;
  final JsonKey _key;
 
  @override
  final FieldElement fieldElement;
 
  @override
  bool get nullable => _key.nullable;
 
  @override
  ClassElement get classElement => _helperCore.element;
 
  @override
  JsonSerializable get config => _helperCore.config;
 
  _TypeHelperCtx(this._helperCore, this.fieldElement, this._key);
 
  @override
  ConvertData get serializeConvertData => _pairFromContext?.toJson;
 
  @override
  ConvertData get deserializeConvertData => _pairFromContext?.fromJson;
 
  _ConvertPair get _pairFromContext => _ConvertPair(fieldElement);
 
  @override
  void addMember(String memberContent) {
    _helperCore.addMember(memberContent);
  }
 
  @override
  Object serialize(DartType targetType, String expression) => _run(
      targetType,
      expression,
      (TypeHelper th) => th.serialize(targetType, expression, this));
 
  @override
  Object deserialize(DartType targetType, String expression) => _run(
      targetType,
      expression,
      (TypeHelper th) => th.deserialize(targetType, expression, this));
 
  Object _run(DartType targetType, String expression,
          Object invoke(TypeHelper instance)) =>
      _helperCore.allTypeHelpers.map(invoke).firstWhere((r) => r != null,
          orElse: () => throw UnsupportedTypeError(
              targetType, expression, _notSupportedWithTypeHelpersMsg));
}
 
final _notSupportedWithTypeHelpersMsg =
    'None of the provided `TypeHelper` instances support the defined type.';
 
class _ConvertPair {
  static final _expando = Expando<_ConvertPair>();
  static _ConvertPair fromJsonKey(JsonKey key) => _expando[key];
 
  final ConvertData fromJson, toJson;
 
  _ConvertPair._(this.fromJson, this.toJson);
 
  factory _ConvertPair(FieldElement element) {
    var pair = _expando[element];
 
    if (pair == null) {
      final obj = jsonKeyAnnotation(element);
      if (obj == null) {
        pair = _ConvertPair._(null, null);
      } else {
        final toJson = _convertData(obj, element, false);
        final fromJson = _convertData(obj, element, true);
        pair = _ConvertPair._(fromJson, toJson);
      }
      _expando[element] = pair;
    }
    return pair;
  }
}
 
ConvertData _convertData(DartObject obj, FieldElement element, bool isFrom) {
  final paramName = isFrom ? 'fromJson' : 'toJson';
  final objectValue = obj.getField(paramName);
 
  if (objectValue.isNull) {
    return null;
  }
 
  final type = objectValue.type as FunctionType;
 
  final executableElement = type.element as ExecutableElement;
 
  if (executableElement.parameters.isEmpty ||
      executableElement.parameters.first.isNamed ||
      executableElement.parameters.where((pe) => !pe.isOptional).length > 1) {
    throwUnsupported(
        element,
        'The `$paramName` function `${executableElement.name}` must have one '
        'positional paramater.');
  }
 
  final argType = executableElement.parameters.first.type;
  if (isFrom) {
    final returnType = executableElement.returnType;
 
    if (returnType is TypeParameterType) {
      // We keep things simple in this case. We rely on inferred type arguments
      // to the `fromJson` function.
      // TODO: consider adding error checking here if there is confusion.
    } else if (!returnType.isAssignableTo(element.type)) {
      throwUnsupported(
          element,
          'The `$paramName` function `${executableElement.name}` return type '
          '`$returnType` is not compatible with field type `${element.type}`.');
    }
  } else {
    if (argType is TypeParameterType) {
      // We keep things simple in this case. We rely on inferred type arguments
      // to the `fromJson` function.
      // TODO: consider adding error checking here if there is confusion.
    } else if (!element.type.isAssignableTo(argType)) {
      throwUnsupported(
          element,
          'The `$paramName` function `${executableElement.name}` argument type '
          '`$argType` is not compatible with field type'
          ' `${element.type}`.');
    }
  }
 
  var name = executableElement.name;
 
  if (executableElement is MethodElement) {
    name = '${executableElement.enclosingElement.name}.$name';
  }
 
  return ConvertData(name, argType);
}