diff --git a/README.md b/README.md index 2f45fbe..a132c1c 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ dependencies { ### 2. Usage #### 2.1 Import ```java +import com.zeoflow.parcelled.Default; import com.zeoflow.parcelled.Parcelled; import com.zeoflow.parcelled.ParcelledAdapter; import com.zeoflow.parcelled.ParcelledVersion; @@ -61,6 +62,7 @@ import com.zeoflow.parcelled.ParcelledVersion; public abstract class CustomBean implements Parcelable { @Nullable + @Default(code = "null") public String firstName; @ParcelledVersion(after = 1, before = 2) @@ -69,6 +71,7 @@ public abstract class CustomBean implements Parcelable { @ParcelledAdapter(DateTypeAdapter.class) @ParcelledVersion(before = 1) + @Default(code = "new Date()") public Date birthday; public static CustomBean create( diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java b/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java index c9d70a3..3ca8958 100644 --- a/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java +++ b/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java @@ -64,10 +64,10 @@ protected void onCreate(Bundle savedInstanceState) date.setText(getString(R.string.format_date, person.birthday.toString())); age.setText(getString(R.string.format_age, person.age)); fullAddress.setText(getString(R.string.full_address, - TextUtils.isEmpty(person.address.street) ? "" : person.address.street, - TextUtils.isEmpty(person.address.postCode) ? "" : person.address.postCode, - TextUtils.isEmpty(person.address.city) ? "" : person.address.city, - TextUtils.isEmpty(person.address.country) ? "" : person.address.country)); + TextUtils.isEmpty(person.address.street) ? "street: " : person.address.street, + TextUtils.isEmpty(person.address.postCode) ? "\npost code: " : person.address.postCode, + TextUtils.isEmpty(person.address.city) ? "\ncity: " : person.address.city, + TextUtils.isEmpty(person.address.country) ? "\ncountry: " : person.address.country)); } } diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/model/Address.java b/app/src/main/java/com/zeoflow/parcelled/demo/model/Address.java index 26c5147..61c4eed 100644 --- a/app/src/main/java/com/zeoflow/parcelled/demo/model/Address.java +++ b/app/src/main/java/com/zeoflow/parcelled/demo/model/Address.java @@ -16,18 +16,23 @@ import android.os.Parcelable; +import com.zeoflow.parcelled.Default; import com.zeoflow.parcelled.Parcelled; @Parcelled public abstract class Address implements Parcelable { + @Default(code = "null") public String street; + @Default(code = "null") public String postCode; + @Default(code = "null") public String city; + @Default(code = "null") public String country; public static Address create(String street, String postCode, String city, String country) @@ -35,4 +40,9 @@ public static Address create(String street, String postCode, String city, String return new Parcelled_Address(street, postCode, city, country); } + public static Address create() + { + return new Parcelled_Address(); + } + } diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/model/Person.java b/app/src/main/java/com/zeoflow/parcelled/demo/model/Person.java index d1650c1..162c834 100644 --- a/app/src/main/java/com/zeoflow/parcelled/demo/model/Person.java +++ b/app/src/main/java/com/zeoflow/parcelled/demo/model/Person.java @@ -19,6 +19,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.zeoflow.parcelled.Default; import com.zeoflow.parcelled.Parcelled; import com.zeoflow.parcelled.ParcelledAdapter; import com.zeoflow.parcelled.ParcelledVersion; @@ -30,26 +31,37 @@ public abstract class Person implements Parcelable { @Nullable + @Default(code = "null") public String name; @Nullable + @Default(code = "null") public String firstName; @ParcelledVersion(after = 1, before = 2) @Nullable + @Default(code = "null") public String lastName; + @Default(code = "new Date()") @ParcelledAdapter(DateTypeAdapter.class) @ParcelledVersion(before = 1) public Date birthday; + @Default(code = "0") public int age; + @Default(code = "Address.create()") public Address address; public static Person create(@NonNull String name, @NonNull String firstName, @NonNull Date birthday, int age, Address address) { - return new Parcelled_Person(name, firstName, "Doe", birthday, age, address); + return new Parcelled_Person(); + } + + public static Person create() + { + return new Parcelled_Person(); } } diff --git a/docs/README.md b/docs/README.md index 2f45fbe..a132c1c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,6 +50,7 @@ dependencies { ### 2. Usage #### 2.1 Import ```java +import com.zeoflow.parcelled.Default; import com.zeoflow.parcelled.Parcelled; import com.zeoflow.parcelled.ParcelledAdapter; import com.zeoflow.parcelled.ParcelledVersion; @@ -61,6 +62,7 @@ import com.zeoflow.parcelled.ParcelledVersion; public abstract class CustomBean implements Parcelable { @Nullable + @Default(code = "null") public String firstName; @ParcelledVersion(after = 1, before = 2) @@ -69,6 +71,7 @@ public abstract class CustomBean implements Parcelable { @ParcelledAdapter(DateTypeAdapter.class) @ParcelledVersion(before = 1) + @Default(code = "new Date()") public Date birthday; public static CustomBean create( diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ParcelledProcessor.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ParcelledProcessor.java index 866dae6..b53a8da 100644 --- a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ParcelledProcessor.java +++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ParcelledProcessor.java @@ -32,6 +32,7 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import com.zeoflow.parcelled.Default; import com.zeoflow.parcelled.Parcelled; import com.zeoflow.parcelled.ParcelledAdapter; import com.zeoflow.parcelled.ParcelledVersion; @@ -54,6 +55,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.MirroredTypeException; @@ -130,11 +132,21 @@ private void processType(TypeElement type) checkModifiersIfNested(type); + // get the fully-qualified interface name + String fqInterfaceName = generatedInterfaceName(type); + // interface name + String interfaceName = TypeUtil.simpleNameOf(fqInterfaceName); + + String sourceInterface = generateInterface(type, interfaceName, type.getSimpleName().toString()); + sourceInterface = Reformatter.fixup(sourceInterface); + writeSourceFile(fqInterfaceName, sourceInterface, type); + // get the fully-qualified class name String fqClassName = generatedSubclassName(type); // class name String className = TypeUtil.simpleNameOf(fqClassName); - String source = generateClass(type, className, type.getSimpleName().toString()); + + String source = generateClass(type, className, interfaceName, type.getSimpleName().toString()); source = Reformatter.fixup(source); writeSourceFile(fqClassName, source, type); @@ -155,7 +167,8 @@ private void writeSourceFile(String className, String text, TypeElement originat "Could not write generated class " + className + ": " + e); } } - private String generateClass(TypeElement type, String className, String classToExtend) + + private String generateClass(TypeElement type, String className, String interfaceName, String classToExtend) { if (type == null) { @@ -188,6 +201,7 @@ private String generateClass(TypeElement type, String className, String classToE // Generate the Parcelled_$ class String pkg = TypeUtil.packageNameOf(type); TypeName classTypeName = ClassName.get(pkg, className); + TypeName interfaceTypeName = ClassName.get(pkg, interfaceName); assert className != null; // generate writeToParcel() TypeSpec.Builder subClass = TypeSpec.classBuilder(className) @@ -195,8 +209,13 @@ private String generateClass(TypeElement type, String className, String classToE .addField(TypeName.INT, "version", PRIVATE) // Class must be always final .addModifiers(FINAL) + .addSuperinterface(interfaceTypeName) + // overrides IParcelled_Address + .addMethod(generateIParcelled(properties)) // extends from original abstract class .superclass(ClassName.get(pkg, classToExtend)) + // Add the AUDO-DEFAULT constructor + .addMethod(generateAutoConstructor(properties)) // Add the DEFAULT constructor .addMethod(generateConstructor(properties)) // Add the private constructor @@ -223,6 +242,65 @@ private String generateClass(TypeElement type, String className, String classToE return javaFile.toString(); } + private String generateInterface(TypeElement type, String className, String classToExtend) + { + if (type == null) + { + mErrorReporter.abortWithError("generateClass was invoked with null type", null); + } + if (className == null) + { + mErrorReporter.abortWithError("generateClass was invoked with null class name", type); + } + if (classToExtend == null) + { + mErrorReporter.abortWithError("generateClass was invoked with null parent class", type); + } + assert type != null; + List nonPrivateFields = getParcelableFieldsOrError(type); + if (nonPrivateFields.isEmpty()) + { + mErrorReporter.abortWithError("generateClass error, all fields are declared PRIVATE", type); + } + + // get the properties + ImmutableList properties = buildProperties(nonPrivateFields); + + // Generate the Parcelled_$ class + assert className != null; + // generate writeToParcel() + TypeSpec.Builder subClass = TypeSpec.interfaceBuilder(className) + // Add the private constructor + .addModifiers(PUBLIC) + .addMethod(generateInterfaceSet(properties)); + + String pkg = TypeUtil.packageNameOf(type); + JavaFile javaFile = JavaFile.builder(pkg, subClass.build()).build(); + return javaFile.toString(); + } + + private MethodSpec generateInterfaceSet(ImmutableList properties) + { + + MethodSpec.Builder builder = MethodSpec.methodBuilder("setValues") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); + builder.addJavadoc("Values Setter"); + builder.addJavadoc("\n"); + + List params = Lists.newArrayListWithCapacity(properties.size()); + for (Property property : properties) + { + params.add(ParameterSpec.builder(property.typeName, property.fieldName).build()); + } + for (ParameterSpec param : params) + { + builder.addJavadoc("\n@param " + param.name + " {@link " + param.type + "}"); + builder.addParameter(param.type, param.name); + } + + return builder.build(); + } + private ImmutableMap getTypeAdapters(ImmutableList properties) { Map typeAdapters = new LinkedHashMap<>(); @@ -280,6 +358,7 @@ private List getParcelableFieldsOrError(TypeElement type) return nonPrivateFields; } + private MethodSpec generateConstructor(ImmutableList properties) { @@ -291,7 +370,7 @@ private MethodSpec generateConstructor(ImmutableList properties) MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addParameters(params); - builder.addJavadoc("Class builder"); + builder.addJavadoc("Constructor"); builder.addJavadoc("\n"); for (ParameterSpec param : params) { @@ -301,6 +380,34 @@ private MethodSpec generateConstructor(ImmutableList properties) return builder.build(); } + + private MethodSpec generateAutoConstructor(ImmutableList properties) + { + MethodSpec.Builder builder = MethodSpec.constructorBuilder(); + builder.addJavadoc("Auto Constructor"); + builder.addJavadoc("\n"); + + List params = Lists.newArrayListWithCapacity(properties.size()); + for (Property property : properties) + { + params.add(ParameterSpec.builder(property.typeName, property.fieldName).build()); + } + for (int i=0; i properties, @@ -371,6 +478,12 @@ private String generatedSubclassName(TypeElement type) return generatedClassName(type, Strings.repeat("$", 0) + classNameSuffix); } + private String generatedInterfaceName(TypeElement type) + { + String classNameSuffix = "IParcelled_"; + return generatedClassName(type, Strings.repeat("$", 0) + classNameSuffix); + } + private String generatedClassName(TypeElement type, String prefix) { StringBuilder name = new StringBuilder(type.getSimpleName().toString()); @@ -429,6 +542,27 @@ private MethodSpec generateDescribeContents() .build(); } + private MethodSpec generateIParcelled(ImmutableList properties) + { + MethodSpec.Builder builder = MethodSpec.methodBuilder("setValues") + .addAnnotation(Override.class) + .addModifiers(PUBLIC); + + List params = Lists.newArrayListWithCapacity(properties.size()); + for (Property property : properties) + { + params.add(ParameterSpec.builder(property.typeName, property.fieldName).build()); + } + for (ParameterSpec param : params) + { + builder.addJavadoc("\n@param " + param.name + " {@link " + param.type + "}"); + builder.addParameter(param.type, param.name); + builder.addStatement("this.$N = $N", param.name, param.name); + } + + return builder.build(); + } + private FieldSpec generateCreator(TypeName type) { ClassName creator = ClassName.bestGuess("android.os.Parcelable.Creator"); @@ -520,6 +654,7 @@ static final class Property final VariableElement element; final TypeName typeName; final ImmutableSet annotations; + String defaultCode = ""; final int version; final int afterVersion; final int beforeVersion; @@ -546,6 +681,9 @@ static final class Property } + Default defaultCode = element.getAnnotation(Default.class); + this.defaultCode = defaultCode == null ? "" : defaultCode.code(); + // get the element version, default 0 ParcelledVersion parcelledVersion = element.getAnnotation(ParcelledVersion.class); this.version = parcelledVersion == null ? 0 : parcelledVersion.after(); @@ -568,6 +706,11 @@ public int getBeforeVersion() return this.beforeVersion; } + public String getDefaultCode() + { + return this.defaultCode; + } + private ImmutableSet getAnnotations(VariableElement element) { ImmutableSet.Builder builder = ImmutableSet.builder(); diff --git a/library-runtime/src/main/java/com/zeoflow/parcelled/Default.java b/library-runtime/src/main/java/com/zeoflow/parcelled/Default.java new file mode 100644 index 0000000..f479801 --- /dev/null +++ b/library-runtime/src/main/java/com/zeoflow/parcelled/Default.java @@ -0,0 +1,41 @@ +// Copyright 2021 ZeoFlow SRL +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.zeoflow.parcelled; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.ArrayList; + +/** + * An annotation to indicate the auto-parcel that the annotated class needs to be {@link android.os.Parcelable} + * + *
+ * 
+ * {@literal @}Parcelled public abstract class Foo  {...}
+ * 
+ * 
+ */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +@Documented +public @interface Default +{ + + String code() default ""; + +}