blob: 2a33b45ec8543bc701c893e4bc409f89b995e879 [file] [log] [blame]
// Copyright (c) 2015, Google Inc. 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.
library built_value_generator.source_library;
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value_generator/src/library_elements.dart';
import 'package:built_value_generator/src/serializer_source_class.dart';
import 'package:quiver/iterables.dart';
import 'package:source_gen/source_gen.dart';
import 'dart_types.dart';
part 'serializer_source_library.g.dart';
abstract class SerializerSourceLibrary
implements Built<SerializerSourceLibrary, SerializerSourceLibraryBuilder> {
LibraryElement get element;
factory SerializerSourceLibrary(LibraryElement element) =>
_$SerializerSourceLibrary._(element: element);
SerializerSourceLibrary._();
@memoized
ParsedLibraryResult get parsedLibrary =>
element.library.session.getParsedLibraryByElement(element.library);
@memoized
bool get hasSerializers => serializersForAnnotations.isNotEmpty;
/// Returns a map of `Serializers` declarations; the keys are field names
/// and the values are the `@SerializersFor` annotations.
@memoized
BuiltMap<String, ElementAnnotation> get serializersForAnnotations {
var result = MapBuilder<String, ElementAnnotation>();
var accessors = element.definingCompilationUnit.accessors
.where((element) =>
element.isGetter &&
DartTypes.getName(element.returnType) == 'Serializers')
.toList();
for (var accessor in accessors) {
final annotations = accessor.variable.metadata
.where((annotation) =>
DartTypes.getName(annotation.computeConstantValue()?.type) ==
'SerializersFor')
.toList();
if (annotations.isEmpty) continue;
result[accessor.name] = annotations.single;
}
return result.build();
}
/// Returns the set of serializable classes in this library. A serializer
/// needs to be installed for each of these. A serialized needs to be
/// generated for each, except where the serializer is marked `custom`.
@memoized
BuiltSet<SerializerSourceClass> get sourceClasses {
var result = SetBuilder<SerializerSourceClass>();
var classElements = LibraryElements.getClassElements(element);
for (var classElement in classElements) {
final sourceClass = SerializerSourceClass(classElement);
if (sourceClass.isSerializable) {
result.add(sourceClass);
}
}
return result.build();
}
/// Returns a map from `Serializers` declaration field names to the classes
/// that each serializer is required to be able to serialize.
@memoized
BuiltSetMultimap<String, SerializerSourceClass> get serializeForClasses {
var result = SetMultimapBuilder<String, SerializerSourceClass>();
for (var field in serializersForAnnotations.keys) {
final serializersForAnnotation = serializersForAnnotations[field];
final types = serializersForAnnotation
.computeConstantValue()
.getField('types')
.toListValue()
?.map((dartObject) => dartObject.toTypeValue());
if (types == null) {
// This only happens if the source code is invalid.
throw InvalidGenerationSourceError(
'Broken @SerializersFor annotation. Are all the types imported?');
}
result.addValues(
field,
types.map(
(type) => SerializerSourceClass(type.element as ClassElement)));
}
return result.build();
}
/// Returns a map from `Serializers` declaration field names to the
/// transitive set of serializable classes implied by `serializeForClasses`.
@memoized
BuiltSetMultimap<String, SerializerSourceClass>
get serializeForTransitiveClasses {
var result = SetMultimapBuilder<String, SerializerSourceClass>();
for (var field in serializersForAnnotations.keys) {
var currentResult = BuiltSet<SerializerSourceClass>(
serializeForClasses[field].where(
(serializerSourceClass) => serializerSourceClass.isSerializable));
BuiltSet<SerializerSourceClass> expandedResult;
while (currentResult != expandedResult) {
currentResult = expandedResult ?? currentResult;
expandedResult = currentResult.rebuild((b) => b
..addAll(currentResult.expand((sourceClass) => sourceClass
.fieldClasses
.where((fieldClass) => fieldClass.isSerializable))));
}
result.addValues(field, currentResult);
}
return result.build();
}
bool get needsBuiltJson => sourceClasses.isNotEmpty;
/// Generates serializer source for this library.
String generateCode() {
var errors = concat(sourceClasses
.map((sourceClass) => sourceClass.computeErrors())
.toList());
if (errors.isNotEmpty) throw _makeError(errors);
return _generateSerializersTopLevelFields() +
sourceClasses
.where((sourceClass) => sourceClass.needsGeneratedSerializer)
.map((sourceClass) => sourceClass.generateSerializerDeclaration())
.join('\n') +
sourceClasses
.where((sourceClass) => sourceClass.needsGeneratedSerializer)
.map((sourceClass) => sourceClass.generateSerializer())
.join('\n');
}
String _generateSerializersTopLevelFields() => serializersForAnnotations.keys
.map((field) =>
'Serializers _\$$field = (new Serializers().toBuilder()' +
(serializeForTransitiveClasses[field]
.map((sourceClass) =>
sourceClass.generateTransitiveSerializerAdder())
.toList()
..sort())
.join('\n') +
(serializeForTransitiveClasses[field]
.map((sourceClass) =>
sourceClass.generateBuilderFactoryAdders(
element.definingCompilationUnit))
.toList()
..sort())
.join('\n') +
').build();')
.join('\n');
}
InvalidGenerationSourceError _makeError(Iterable<String> todos) {
var message = StringBuffer(
'Please make the following changes to use built_value serialization:\n');
for (var i = 0; i != todos.length; ++i) {
message.write('\n${i + 1}. ${todos.elementAt(i)}');
}
return InvalidGenerationSourceError(message.toString());
}