Skip to content

Commit

Permalink
- Add support for nested embedded objects.
Browse files Browse the repository at this point in the history
- Extract extension into separated file for multi access.
- Add Fieldable abstract class for both (Field, Embedded).
- Some improvement and code enhancement.
  • Loading branch information
hsul4n committed May 27, 2020
1 parent b4b5b55 commit 292cfb4
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 139 deletions.
14 changes: 7 additions & 7 deletions example/lib/database.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions example/lib/task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ class Task {
message == other.message;

@override
int get hashCode => id.hashCode ^ message.hashCode;
int get hashCode => id.hashCode ^ message.hashCode ^ timestamp.hashCode;

@override
String toString() {
return 'Task{id: $id, message: $message}';
return 'Task{id: $id, message: $message, timestamp: $timestamp}';
}
}
16 changes: 16 additions & 0 deletions example/lib/timestamp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,20 @@ class Timestamp {
Timestamp.now()
: createdAt = DateTime.now().toString(),
updatedAt = DateTime.now().toString();

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Timestamp &&
runtimeType == other.runtimeType &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt;

@override
int get hashCode => createdAt.hashCode ^ updatedAt.hashCode;

@override
String toString() {
return 'Task{createdAt: $createdAt, updatedAt: $updatedAt}';
}
}
14 changes: 14 additions & 0 deletions floor_generator/lib/extension/field_element_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations;
import 'package:floor_generator/misc/type_utils.dart';

extension FieldElementExtension on FieldElement {
bool shouldBeIncluded() {
final isIgnored = hasAnnotation(annotations.ignore.runtimeType);
return !(isStatic || isSynthetic || isIgnored || isEmbedded);
}

bool get isEmbedded {
return hasAnnotation(annotations.Embedded) && type.element is ClassElement;
}
}
33 changes: 25 additions & 8 deletions floor_generator/lib/processor/embedded_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,56 @@ import 'package:floor_generator/processor/field_processor.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/value_object/embedded.dart';
import 'package:floor_generator/value_object/field.dart';
import 'package:floor_generator/extension/field_element_extension.dart';

class EmbeddedProcessor extends Processor<Embedded> {
final ClassElement _classElement;
final FieldElement _fieldElement;
final String _prefix;

EmbeddedProcessor(final FieldElement fieldElement)
EmbeddedProcessor(final FieldElement fieldElement, [final String prefix = ''])
: assert(fieldElement != null),
assert(prefix != null),
_fieldElement = fieldElement,
_classElement = fieldElement.type.element as ClassElement;
_classElement = fieldElement.type.element as ClassElement,
_prefix = prefix;

@nonNull
@override
Embedded process() {
return Embedded(
_fieldElement,
_getFields(),
_getChildren(),
);
}

@nonNull
String _getPrefix() {
return _fieldElement
.getAnnotation(annotations.Embedded)
.getField(AnnotationField.embeddedPrefix)
?.toStringValue() ??
return _prefix +
_fieldElement
.getAnnotation(annotations.Embedded)
.getField(AnnotationField.embeddedPrefix)
?.toStringValue() ??
'';
}

@nonNull
List<Field> _getFields() {
final fields = _classElement.fields
.where((fieldElement) => fieldElement.shouldBeIncluded())
.map((field) => FieldProcessor(field, _getPrefix()).process())
.toList();

return fields;
}

@nonNull
List<Embedded> _getChildren() {
return _classElement.fields
.map((fieldElement) =>
FieldProcessor(fieldElement, _getPrefix()).process())
.where((fieldElement) => fieldElement.isEmbedded)
// pass the previous prefix so we can prepend it with the old ones
.map((embedded) => EmbeddedProcessor(embedded, _getPrefix()).process())
.toList();
}
}
6 changes: 3 additions & 3 deletions floor_generator/lib/processor/entity_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ class EntityProcessor extends QueryableProcessor<Entity> {
Entity process() {
final name = _getName();
final fields = getFields();
final embedded = getEmbeddeds();
final embeddeds = getEmbeddeds();

return Entity(
classElement,
name,
fields,
embedded,
embeddeds,
_getPrimaryKey(fields),
_getForeignKeys(),
_getIndices(fields, name),
getConstructor(fields, embedded),
getConstructor([...fields, ...embeddeds]),
);
}

Expand Down
133 changes: 49 additions & 84 deletions floor_generator/lib/processor/queryable_processor.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:floor_annotation/floor_annotation.dart' as annotations;
import 'package:floor_generator/misc/annotations.dart';
import 'package:floor_generator/misc/type_utils.dart';
import 'package:floor_generator/processor/embedded_processor.dart';
Expand All @@ -9,24 +8,27 @@ import 'package:floor_generator/processor/field_processor.dart';
import 'package:floor_generator/processor/processor.dart';
import 'package:floor_generator/value_object/embedded.dart';
import 'package:floor_generator/value_object/field.dart';
import 'package:floor_generator/value_object/fieldable.dart';
import 'package:floor_generator/value_object/queryable.dart';
import 'package:floor_generator/extension/field_element_extension.dart';
import 'package:meta/meta.dart';

abstract class QueryableProcessor<T extends Queryable> extends Processor<T> {
final QueryableProcessorError _queryableProcessorError;

@protected
final ClassElement classElement;
@protected
final List<FieldElement> _fields;

@protected
QueryableProcessor(this.classElement)
: assert(classElement != null),
_queryableProcessorError = QueryableProcessorError(classElement);

List<FieldElement> get _allFields => [
...classElement.fields,
...classElement.allSupertypes.expand((type) => type.element.fields),
];
_queryableProcessorError = QueryableProcessorError(classElement),
_fields = [
...classElement.fields,
...classElement.allSupertypes.expand((type) => type.element.fields),
];

@nonNull
@protected
Expand All @@ -35,7 +37,7 @@ abstract class QueryableProcessor<T extends Queryable> extends Processor<T> {
throw _queryableProcessorError.prohibitedMixinUsage;
}

return _allFields
return _fields
.where((fieldElement) => fieldElement.shouldBeIncluded())
.map((field) => FieldProcessor(field).process())
.toList();
Expand All @@ -44,84 +46,58 @@ abstract class QueryableProcessor<T extends Queryable> extends Processor<T> {
@nonNull
@protected
List<Embedded> getEmbeddeds() {
return _allFields
.where((fieldElement) => fieldElement.shouldEmbedded())
return _fields
.where((fieldElement) => fieldElement.isEmbedded)
.map((embedded) => EmbeddedProcessor(embedded).process())
.toList();
}

@nonNull
@protected
String getConstructor(
final List<Field> fields,
final List<Embedded> embeddeds,
) {
final constructorBuffer = StringBuffer();
final constructorParameters = classElement.constructors.first.parameters;

constructorBuffer.write('${classElement.displayName}(');

constructorBuffer.write(
constructorParameters
.map((parameterElement) =>
_getParameterValue(parameterElement, fields))
.where((parameterValue) => parameterValue != null)
.join(', '),
);

for (final embedded in embeddeds) {
final parameterName = embedded.fieldElement.displayName;
final field = constructorParameters.firstWhere(
(parameterElement) => parameterElement.name == parameterName,
orElse: () => null,
);

constructorBuffer.write(', ');

if (field.isNamed) {
constructorBuffer.write('$parameterName: ');
}
String getConstructor(final List<Fieldable> items) {
final buffer = StringBuffer();

constructorBuffer.write('${embedded.classElement.displayName}(');
void write(final ClassElement classElement, final List<Fieldable> items) {
final parameters = classElement.constructors.first.parameters;

constructorBuffer.write(
embedded.classElement.constructors.first.parameters
.map((parameterElement) =>
_getParameterValue(parameterElement, embedded.fields))
.where((parameterValue) => parameterValue != null)
.join(', '),
);
buffer.write('${classElement.displayName}(');

constructorBuffer.write(')');
}
parameters.asMap().forEach((index, parameter) {
final parameterName = parameter.displayName;

constructorBuffer.write(')');
if (parameter.isNamed) {
buffer.write('$parameterName: ');
}

return constructorBuffer.toString();
}
final item = items.firstWhere(
(item) => item.fieldElement.displayName == parameterName,
// whenever field is `@ignored`
orElse: () {
buffer.write('null');
return null;
},
);

/// Returns `null` whenever field is @ignored
@nullable
String _getParameterValue(
final ParameterElement parameterElement,
final List<Field> fields,
) {
final parameterName = parameterElement.displayName;
final field = fields.firstWhere(
(field) => field.name == parameterName,
orElse: () => null, // whenever field is @ignored
);
if (field != null) {
final parameterValue = "row['${field.columnName}']";
final castedParameterValue = _castParameterValue(
parameterElement.type, parameterValue, field.isNullable);
if (parameterElement.isNamed) {
return '$parameterName: $castedParameterValue';
}
return castedParameterValue; // also covers positional parameter
} else {
return null;
if (item is Field) {
final parameterValue = "row['${item.columnName}']";
final castedParameterValue = _castParameterValue(
parameter.type, parameterValue, item.isNullable);
buffer.write(castedParameterValue);
}

if (item is Embedded)
write(item.classElement, [...item.fields, ...item.children]);

/// ignore comma seprator if reach end
if (parameters.length - 1 != index) buffer.write(', ');
});

buffer.write(')');
}

write(classElement, items);

return buffer.toString();
}

@nonNull
Expand Down Expand Up @@ -149,14 +125,3 @@ abstract class QueryableProcessor<T extends Queryable> extends Processor<T> {
}
}
}

extension on FieldElement {
bool shouldBeIncluded() {
final isIgnored = hasAnnotation(annotations.ignore.runtimeType);
return !(isStatic || isSynthetic || isIgnored || shouldEmbedded());
}

bool shouldEmbedded() {
return hasAnnotation(annotations.Embedded) && type.element is ClassElement;
}
}
2 changes: 1 addition & 1 deletion floor_generator/lib/processor/view_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ViewProcessor extends QueryableProcessor<View> {
fields,
embeddeds,
_getQuery(),
getConstructor(fields, embeddeds),
getConstructor([...fields, ...embeddeds]),
);
}

Expand Down
Loading

0 comments on commit 292cfb4

Please sign in to comment.