diff --git a/.gitignore b/.gitignore
index 56cc642..efd6ac6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,3 +83,6 @@ lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
+
+# Maven Repo
+*.gpg
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..61a9130
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml
new file mode 100644
index 0000000..e3e0b84
--- /dev/null
+++ b/.idea/intellij-javadocs-4.0.1.xml
@@ -0,0 +1,204 @@
+
+
+
+
+ UPDATE
+ false
+ true
+
+ TYPE
+ METHOD
+ FIELD
+
+
+ PROTECTED
+ PUBLIC
+ DEFAULT
+
+
+
+
+
+ ^.*(public|protected|private)*.+interface\s+\w+.*
+ /**\n
+ * The interface ${name}.\n
+<#if element.typeParameters?has_content> * \n
+</#if>
+<#list element.typeParameters as typeParameter>
+ * @param <${typeParameter.name}> the type parameter\n
+</#list>
+ */
+
+
+ ^.*(public|protected|private)*.+enum\s+\w+.*
+ /**\n
+ * The enum ${name}.\n
+ */
+
+
+ ^.*(public|protected|private)*.+class\s+\w+.*
+ /**\n
+ * The type ${name}.\n
+<#if element.typeParameters?has_content> * \n
+</#if>
+<#list element.typeParameters as typeParameter>
+ * @param <${typeParameter.name}> the type parameter\n
+</#list>
+ */
+
+
+ .+
+ /**\n
+ * The type ${name}.\n
+ */
+
+
+
+
+ .+
+ /**\n
+ * Instantiates a new ${name}.\n
+<#if element.parameterList.parameters?has_content>
+ *\n
+</#if>
+<#list element.parameterList.parameters as parameter>
+ * @param ${parameter.name} the ${paramNames[parameter.name]}\n
+</#list>
+<#if element.throwsList.referenceElements?has_content>
+ *\n
+</#if>
+<#list element.throwsList.referenceElements as exception>
+ * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
+</#list>
+ */
+
+
+
+
+ ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+
+ /**\n
+ * Gets ${partName}.\n
+<#if element.typeParameters?has_content> * \n
+</#if>
+<#list element.typeParameters as typeParameter>
+ * @param <${typeParameter.name}> the type parameter\n
+</#list>
+<#if element.parameterList.parameters?has_content>
+ *\n
+</#if>
+<#list element.parameterList.parameters as parameter>
+ * @param ${parameter.name} the ${paramNames[parameter.name]}\n
+</#list>
+<#if isNotVoid>
+ *\n
+ * @return the ${partName}\n
+</#if>
+<#if element.throwsList.referenceElements?has_content>
+ *\n
+</#if>
+<#list element.throwsList.referenceElements as exception>
+ * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
+</#list>
+ */
+
+
+ ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+
+ /**\n
+ * Sets ${partName}.\n
+<#if element.typeParameters?has_content> * \n
+</#if>
+<#list element.typeParameters as typeParameter>
+ * @param <${typeParameter.name}> the type parameter\n
+</#list>
+<#if element.parameterList.parameters?has_content>
+ *\n
+</#if>
+<#list element.parameterList.parameters as parameter>
+ * @param ${parameter.name} the ${paramNames[parameter.name]}\n
+</#list>
+<#if isNotVoid>
+ *\n
+ * @return the ${partName}\n
+</#if>
+<#if element.throwsList.referenceElements?has_content>
+ *\n
+</#if>
+<#list element.throwsList.referenceElements as exception>
+ * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
+</#list>
+ */
+
+
+ ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+
+ /**\n
+ * The entry point of application.\n
+
+ <#if element.parameterList.parameters?has_content>
+ *\n
+</#if>
+ * @param ${element.parameterList.parameters[0].name} the input arguments\n
+<#if element.throwsList.referenceElements?has_content>
+ *\n
+</#if>
+<#list element.throwsList.referenceElements as exception>
+ * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
+</#list>
+ */
+
+
+ .+
+ /**\n
+ * ${name}<#if isNotVoid> ${return}</#if>.\n
+<#if element.typeParameters?has_content> * \n
+</#if>
+<#list element.typeParameters as typeParameter>
+ * @param <${typeParameter.name}> the type parameter\n
+</#list>
+<#if element.parameterList.parameters?has_content>
+ *\n
+</#if>
+<#list element.parameterList.parameters as parameter>
+ * @param ${parameter.name} the ${paramNames[parameter.name]}\n
+</#list>
+<#if isNotVoid>
+ *\n
+ * @return the ${return}\n
+</#if>
+<#if element.throwsList.referenceElements?has_content>
+ *\n
+</#if>
+<#list element.throwsList.referenceElements as exception>
+ * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n
+</#list>
+ */
+
+
+
+
+ ^.*(public|protected|private)*.+static.*(\w\s\w)+.+
+ /**\n
+ * The constant ${element.getName()}.\n
+ */
+
+
+ ^.*(public|protected|private)*.*(\w\s\w)+.+
+ /**\n
+ <#if element.parent.isInterface()>
+ * The constant ${element.getName()}.\n
+<#else>
+ * The ${name}.\n
+</#if> */
+
+
+ .+
+ /**\n
+ <#if element.parent.isEnum()>
+ *${name} ${typeName}.\n
+<#else>
+ * The ${name}.\n
+</#if>*/
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..a5f05cd
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d5d35ec
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..ed0adc0
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,31 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ compileSdkVersion 30
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ applicationId "com.zeoflow.parcelled.demo"
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':library-runtime')
+ annotationProcessor project(':library-compiler')
+
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..6e0312d
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/aitorvs/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d35a833
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/MainActivity.java b/app/src/main/java/com/zeoflow/parcelled/demo/MainActivity.java
new file mode 100644
index 0000000..536c7f8
--- /dev/null
+++ b/app/src/main/java/com/zeoflow/parcelled/demo/MainActivity.java
@@ -0,0 +1,93 @@
+// 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.demo;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.zeoflow.parcelled.demo.model.Address;
+import com.zeoflow.parcelled.demo.model.Person;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class MainActivity extends AppCompatActivity
+{
+
+ private static final String TAG = MainActivity.class.getSimpleName();
+ private EditText mAgeEditText;
+ private EditText mNameEditText;
+ private EditText mBdayEditText;
+ private EditText mStreetEditText;
+ private EditText mPostcodeEditText;
+ private EditText mCityEditText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mNameEditText = findViewById(R.id.fullName);
+ mBdayEditText = findViewById(R.id.dateOfBirth);
+ mAgeEditText = findViewById(R.id.age);
+ mStreetEditText = findViewById(R.id.street);
+ mPostcodeEditText = findViewById(R.id.postCode);
+ mCityEditText = findViewById(R.id.city);
+
+ findViewById(R.id.imvAdd).setOnClickListener(new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View view)
+ {
+ @SuppressLint("SimpleDateFormat") SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy");
+ // Create the parcelable object using the creator
+ try
+ {
+ int age = TextUtils.isEmpty(mAgeEditText.getText()) ? 0 : Integer.parseInt(mAgeEditText.getText().toString());
+ Date date = TextUtils.isEmpty(mBdayEditText.getText()) ? new Date(System.currentTimeMillis()) : df.parse(mBdayEditText.getText().toString());
+ Address address = Address.create(
+ mStreetEditText.getText().toString(),
+ mPostcodeEditText.getText().toString(),
+ mCityEditText.getText().toString(),
+ /* Country */ null);
+ Person person = Person.create(
+ mNameEditText.getText().toString(),
+ "last",
+ date, age, address);
+
+ Intent activityIntent = PersonActivity.createIntent(MainActivity.this, person);
+
+ if (activityIntent != null)
+ {
+ MainActivity.this.startActivity(activityIntent);
+ }
+ } catch (ParseException e)
+ {
+ Log.e(TAG, "onClick: Error parsing date", e);
+ }
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java b/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java
new file mode 100644
index 0000000..c9d70a3
--- /dev/null
+++ b/app/src/main/java/com/zeoflow/parcelled/demo/PersonActivity.java
@@ -0,0 +1,74 @@
+// 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.demo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.zeoflow.parcelled.demo.model.Person;
+
+public class PersonActivity extends AppCompatActivity
+{
+
+ private static final String EXTRA_PERSON = "EXTRA_PERSON";
+
+ @Nullable
+ public static Intent createIntent(@NonNull Context context, Person person)
+ {
+ //noinspection ConstantConditions
+ if (context == null)
+ {
+ return null;
+ }
+ Intent intent = new Intent(context, PersonActivity.class);
+ // we need to cast it to Parcelable because Person does not itself implement parcelable
+ intent.putExtra(EXTRA_PERSON, person);
+ return intent;
+ }
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_person);
+
+ TextView fullName = (TextView) findViewById(R.id.fullName);
+ TextView date = (TextView) findViewById(R.id.dateOfBirth);
+ TextView age = (TextView) findViewById(R.id.age);
+ TextView fullAddress = (TextView) findViewById(R.id.fullAddress);
+
+ // get the passed intent
+ Intent intent = getIntent();
+ if (intent != null)
+ {
+ Person person = intent.getParcelableExtra(EXTRA_PERSON);
+ fullName.setText(getString(R.string.formatName, person.name));
+ 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));
+ }
+ }
+
+}
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
new file mode 100644
index 0000000..26c5147
--- /dev/null
+++ b/app/src/main/java/com/zeoflow/parcelled/demo/model/Address.java
@@ -0,0 +1,38 @@
+// 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.demo.model;
+
+import android.os.Parcelable;
+
+import com.zeoflow.parcelled.Parcelled;
+
+@Parcelled
+public abstract class Address implements Parcelable
+{
+
+ public String street;
+
+ public String postCode;
+
+ public String city;
+
+ public String country;
+
+ public static Address create(String street, String postCode, String city, String country)
+ {
+ return new Parcelled_Address(street, postCode, city, country);
+ }
+
+}
diff --git a/app/src/main/java/com/zeoflow/parcelled/demo/model/DateTypeAdapter.java b/app/src/main/java/com/zeoflow/parcelled/demo/model/DateTypeAdapter.java
new file mode 100644
index 0000000..94ad51b
--- /dev/null
+++ b/app/src/main/java/com/zeoflow/parcelled/demo/model/DateTypeAdapter.java
@@ -0,0 +1,38 @@
+// 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.demo.model;
+
+import android.os.Parcel;
+
+import com.zeoflow.parcelled.ParcelledTypeAdapter;
+
+import java.util.Date;
+
+class DateTypeAdapter implements ParcelledTypeAdapter
+{
+
+ @Override
+ public Date fromParcel(Parcel in)
+ {
+ return new Date(in.readLong());
+ }
+
+ @Override
+ public void toParcel(Date value, Parcel dest)
+ {
+ dest.writeLong(value.getTime());
+ }
+
+}
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
new file mode 100644
index 0000000..d1650c1
--- /dev/null
+++ b/app/src/main/java/com/zeoflow/parcelled/demo/model/Person.java
@@ -0,0 +1,55 @@
+// 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.demo.model;
+
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.zeoflow.parcelled.Parcelled;
+import com.zeoflow.parcelled.ParcelledAdapter;
+import com.zeoflow.parcelled.ParcelledVersion;
+
+import java.util.Date;
+
+@Parcelled(version = 1)
+public abstract class Person implements Parcelable
+{
+
+ @Nullable
+ public String name;
+
+ @Nullable
+ public String firstName;
+
+ @ParcelledVersion(after = 1, before = 2)
+ @Nullable
+ public String lastName;
+
+ @ParcelledAdapter(DateTypeAdapter.class)
+ @ParcelledVersion(before = 1)
+ public Date birthday;
+
+ public int age;
+
+ 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);
+ }
+
+}
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..e470acd
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..43c266b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..db44b1e
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_person.xml b/app/src/main/res/layout/activity_person.xml
new file mode 100644
index 0000000..4ee180c
--- /dev/null
+++ b/app/src/main/res/layout/activity_person.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..c7f8d82
--- /dev/null
+++ b/app/src/main/res/layout/content_main.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a571e60
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..61da551
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c41dd28
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..db5080a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..6dba46d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..da31a87
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..15ac681
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b216f2d
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..f25a419
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..e96783c
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..dbbdd40
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..63fc816
--- /dev/null
+++ b/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 64dp
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..812cb7b
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,6 @@
+
+
+ 16dp
+ 16dp
+ 16dp
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..daf323d
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,15 @@
+
+ Parcelled
+
+ MainActivity
+ Full name
+ dd-MM-yyyy
+ Age
+ Name: %1$s
+ Date of birth: %1$s
+ Age: %1$d
+ City
+ Postcode
+ Street
+ Address: %1$s %2$s, %3$s/%4$s
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..545b9c6
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..84048ff
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ ext {
+ kotlin_version = '1.4.31'
+ }
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31"
+ classpath "com.android.tools.build:gradle:4.1.2"
+ classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/buildSrc/upload.gradle b/buildSrc/upload.gradle
new file mode 100644
index 0000000..0bd9172
--- /dev/null
+++ b/buildSrc/upload.gradle
@@ -0,0 +1,211 @@
+apply plugin: 'maven'
+apply plugin: 'signing'
+
+version = VERSION_NAME
+group = GROUP
+
+static def localMavenRepo() {
+ 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath
+}
+
+@SuppressWarnings("GrMethodMayBeStatic")
+def isReleaseBuild() {
+ return !VERSION_NAME.contains("SNAPSHOT")
+}
+
+def getReleaseRepositoryUrl() {
+ return hasProperty('LOCAL') ? localMavenRepo()
+ : hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
+ : 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
+}
+
+def getSnapshotRepositoryUrl() {
+ return hasProperty('LOCAL') ? localMavenRepo()
+ : hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
+ : 'https://oss.sonatype.org/content/repositories/snapshots/'
+}
+
+def getRepositoryUsername() {
+ return hasProperty('USERNAME') ? USERNAME : (hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : '')
+}
+
+def getRepositoryPassword() {
+ return hasProperty('PASSWORD') ? PASSWORD : (hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : '')
+}
+
+afterEvaluate { project ->
+ def isAndroidProject = project.plugins.hasPlugin('com.android.application') || project.plugins.hasPlugin('com.android.library')
+ // To avoid uploading the default empty jar artifact in the project root directory, we use a custom
+ // configuration to specify which artifacts we want to upload.
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ // allow uploading through FTP protocol with the following command:
+ // gradle uploadArchives -PSNAPSHOT_REPOSITORY_URL=ftp://host/repo/path -PUSERNAME=uname -PPASSWORD=passwd
+ configuration = configurations.create('deployerJars')
+
+ beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+
+ pom.groupId = GROUP
+ pom.artifactId = POM_ARTIFACT_ID
+ pom.version = VERSION_NAME
+
+ repository(url: getReleaseRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+ snapshotRepository(url: getSnapshotRepositoryUrl()) {
+ authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
+ }
+
+ pom.whenConfigured { pom ->
+ pom.packaging = POM_PACKAGING
+ }
+
+ // Dependencies are only automatically included by the release plugin if the release
+ // variant is built. Since we've disabled the release variant to improve build
+ // times, we need to add the dependencies to the pom file explicitly.
+ if (isAndroidProject) {
+ pom.withXml {
+ def dependenciesNode = asNode().appendNode('dependencies')
+
+ project.configurations.implementation.allDependencies.each {
+ def groupId = it.group
+ def artifactId = it.name
+ // If we specify an artifact id that differs from the project name, it won't
+ // match. To avoid that, we look up the artifact id (and group) by property
+ // for any project dependencies.
+ // TODO: there must be a neater way to do this.
+ if (it instanceof ProjectDependency) {
+ def properties = it.getDependencyProject().getProperties()
+ groupId = properties.get("GROUP")
+ artifactId = properties.get("POM_ARTIFACT_ID")
+ }
+ def dependencyNode = dependenciesNode.appendNode('dependency')
+ dependencyNode.appendNode('groupId', groupId)
+ dependencyNode.appendNode('artifactId', artifactId)
+ dependencyNode.appendNode('version', it.version)
+ dependencyNode.appendNode('scope', 'compile')
+ }
+ }
+ }
+
+ pom.project {
+ name = POM_NAME
+ description = POM_DESCRIPTION
+ url = POM_URL
+
+ scm {
+ url POM_SCM_URL
+ connection POM_SCM_CONNECTION
+ developerConnection POM_SCM_DEV_CONNECTION
+ }
+
+ licenses {
+ license {
+ name = 'Simplified BSD License'
+ url = 'http://www.opensource.org/licenses/bsd-license'
+ distribution = 'repo'
+ }
+ license {
+ name = 'The Apache Software License, Version 2.0'
+ url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ distribution = 'repo'
+ }
+ }
+
+ developers {
+ developer {
+ id = POM_DEVELOPER_ID
+ name = POM_DEVELOPER_NAME
+ email = POM_DEVELOPER_EMAIL
+ }
+ }
+ }
+ }
+ }
+ }
+
+ signing {
+ required { isReleaseBuild() && gradle.taskGraph.hasTask('uploadArchives') }
+ sign configurations.archives
+ }
+
+ if (isAndroidProject) {
+ def variants = project.android.libraryVariants.findAll {
+ it.buildType.name.equalsIgnoreCase('debug')
+ }
+
+ def getAndroidSdkDirectory = project.android.sdkDirectory
+
+ def getAndroidJar = "${getAndroidSdkDirectory}/platforms/${project.android.compileSdkVersion}/android.jar"
+
+ task androidJavadocs(type: Javadoc, dependsOn: assembleDebug) {
+ source = variants.collect { it.getJavaCompileProvider().get().source }
+ classpath = files(
+ getAndroidJar,
+ project.file("build/intermediates/classes/debug")
+ )
+ doFirst {
+ classpath += files(variants.collect { it.javaCompile.classpath.files })
+ }
+ options {
+ links("http://docs.oracle.com/javase/7/docs/api/")
+ linksOffline("http://d.android.com/reference",
+ "${getAndroidSdkDirectory}/docs/reference")
+ }
+
+ exclude '**/R.java'
+ }
+
+ def cleanJavadocTask = task("cleanJavadocTask", type: Delete) {
+ delete androidJavadocs.destinationDir
+ } as Task
+ project.clean.dependsOn(cleanJavadocTask)
+
+ task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
+ archiveClassifier.set('javadoc')
+ from androidJavadocs.destinationDir
+ baseName "${JAR_PREFIX}${project.name}${JAR_POSTFIX}"
+ }
+
+ task androidSourcesJar(type: Jar) {
+ archiveClassifier.set('sources')
+ from project.android.sourceSets.main.java.source
+ baseName "${JAR_PREFIX}${project.name}${JAR_POSTFIX}"
+ }
+
+ task androidLibraryJar(type: Jar, dependsOn: compileDebugJavaWithJavac /* == variant.javaCompile */) {
+ from compileDebugJavaWithJavac.destinationDir
+ exclude '**/R.class'
+ exclude '**/R$*.class'
+ baseName "${JAR_PREFIX}${project.name}${JAR_POSTFIX}"
+ }
+
+ artifacts {
+ archives androidLibraryJar
+ archives androidSourcesJar
+ // This is unnecessary with a release variant because by default the release variant
+ // includes the release aar in archives. Since we've disabled our release variants and
+ // want to include an aar, we need to manually specify the task that produces the aar
+ // here.
+ archives project.tasks.bundleDebugAar
+ }
+ } else if (project.plugins.hasPlugin('java') || project.plugins.hasPlugin('java-library')) {
+ task sourcesJar(type: Jar, dependsOn: classes) {
+ archiveClassifier.set('sources')
+ from sourceSets.main.allSource
+ }
+
+ task javadocsJar(type: Jar, dependsOn: javadoc) {
+ archiveClassifier.set('javadoc')
+ from javadoc.destinationDir
+ }
+
+ artifacts {
+ archives sourcesJar
+ archives javadocsJar
+ }
+ }
+ logger.info("Published artifacts in ${configurations.archives}:")
+ configurations.archives.artifacts.files.files.each { logger.info("\t$it") }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..37a8a5b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+
+org.gradle.daemon=true
+org.gradle.configureondemand=false
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c763c7c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Sep 19 11:50:34 CEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/library-compiler/build.gradle b/library-compiler/build.gradle
new file mode 100644
index 0000000..37501ee
--- /dev/null
+++ b/library-compiler/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+ id 'java-library'
+}
+
+// This module will be used in Android projects, need to be compatible with Java 1.8
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':library-runtime')
+ implementation 'com.squareup:javapoet:1.13.0'
+ implementation 'com.google.guava:guava:24.1-jre'
+}
+
+apply from: '../buildSrc/upload.gradle'
\ No newline at end of file
diff --git a/library-compiler/gradle.properties b/library-compiler/gradle.properties
new file mode 100644
index 0000000..c9f305c
--- /dev/null
+++ b/library-compiler/gradle.properties
@@ -0,0 +1,23 @@
+VERSION_NAME=1.0.0
+GROUP=com.zeoflow
+
+POM_DESCRIPTION=A fast annotation processor that auto-generates the Parcelable methods without writing them.
+POM_URL=https://github.com/zeoflow/parcelled
+POM_SCM_URL=https://github.com/zeoflow/parcelled
+POM_SCM_CONNECTION=scm:git@github.com:zeoflow/parcelled.git
+POM_SCM_DEV_CONNECTION=scm:git@github.com:zeoflow/parcelled.git
+POM_DEVELOPER_ID=zeoflow
+POM_DEVELOPER_NAME=ZeoFlow
+POM_DEVELOPER_EMAIL=open-source@zeoflow.com
+
+POM_NAME=Parcelled
+
+POM_PACKAGING=jar
+
+POM_ARTIFACT_ID=parcelled-compiler
+
+NEXUS_USERNAME=
+NEXUS_PASSWORD=
+signing.keyId=
+signing.password=
+signing.secretKeyRingFile=../buildSrc/parcelled.gpg
\ No newline at end of file
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/AbortProcessingException.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/AbortProcessingException.java
new file mode 100644
index 0000000..ebd02a7
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/AbortProcessingException.java
@@ -0,0 +1,20 @@
+// 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.internal.codegen;
+
+class AbortProcessingException extends RuntimeException
+{
+
+}
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ErrorReporter.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ErrorReporter.java
new file mode 100644
index 0000000..f73905b
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ErrorReporter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 Google, Inc.
+ *
+ * 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.
+ */
+// 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.internal.codegen;
+
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+
+/**
+ * Handle error reporting for an annotation processor.
+ */
+class ErrorReporter
+{
+
+ private final Messager messager;
+
+ ErrorReporter(ProcessingEnvironment processingEnv)
+ {
+ this.messager = processingEnv.getMessager();
+ }
+
+ /**
+ * Issue a compilation note.
+ *
+ * @param msg the text of the note
+ * @param e the element to which it pertains
+ */
+ void reportNote(String msg, Element e)
+ {
+ messager.printMessage(Diagnostic.Kind.NOTE, msg, e);
+ }
+
+ /**
+ * Issue a compilation warning.
+ *
+ * @param msg the text of the warning
+ * @param e the element to which it pertains
+ */
+ void reportWarning(String msg, Element e)
+ {
+ messager.printMessage(Diagnostic.Kind.WARNING, msg, e);
+ }
+
+ /**
+ * Issue a compilation error. This method does not throw an exception, since we want to continue
+ * processing and perhaps report other errors. It is a good idea to introduce a test case in
+ * CompilationTest for any new call to reportError(...) to ensure that we continue correctly after
+ * an error.
+ *
+ * @param msg the text of the warning
+ * @param e the element to which it pertains
+ */
+ void reportError(String msg, Element e)
+ {
+ messager.printMessage(Diagnostic.Kind.ERROR, msg, e);
+ }
+
+ /**
+ * Issue a compilation error and abandon the processing of this class. This does not prevent the
+ * processing of other classes.
+ *
+ * @param msg the text of the error
+ * @param e the element to which it pertains
+ */
+ void abortWithError(String msg, Element e)
+ {
+ reportError(msg, e);
+ throw new AbortProcessingException();
+ }
+
+}
\ No newline at end of file
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/JavaScanner.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/JavaScanner.java
new file mode 100644
index 0000000..bc123c6
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/JavaScanner.java
@@ -0,0 +1,134 @@
+// 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.internal.codegen;
+
+/**
+ * A simplistic Java scanner. This scanner returns a sequence of tokens that can be used to
+ * reconstruct the source code. Since the source code is coming from a string, the scanner in fact
+ * just returns token boundaries rather than the tokens themselves.
+ *
+ *
We are not dealing with arbitrary user code so we can assume there are no exotic things like
+ * tabs or Unicode escapes that resolve into quotes. The purpose of the scanner here is to
+ * return a sequence of offsets that split the string up in a way that allows us to work with
+ * spaces without having to worry whether they are inside strings or comments. The particular
+ * properties we use are that every string and character literal and every comment is a single
+ * token; every newline plus all following indentation is a single token; and every other string
+ * of consecutive spaces outside a comment or literal is a single token. That means that we can
+ * safely compress a token that starts with a space into a single space, without falsely removing
+ * indentation or changing the contents of strings.
+ *
+ *
In addition to real Java syntax, this scanner recognizes tokens of the form
+ * {@code `text`}, which are used in the templates to wrap fully-qualified type names, so that they
+ * can be extracted and replaced by imported names if possible.
+ */
+// This scanner is different from the one in EclipseHackTokenizer (which is only needed for
+// EclipseHack). The needs of the two scanner are very different: EclipseHackTokenizer is only
+// needed to scan through an existing source file to find abstract method declarations, so it
+// can discard everything that isn't needed for that, including comments and string literals for
+// example. Meanwhile, this scanner needs to return a sequence of tokens that can be used to
+// reconstruct the source code. EclipseHackTokenizer also operates on a Reader (which in practice is
+// coming from a file), while here we already have the source code in a String, which means that we
+// can just return token boundaries rather than the tokens themselves.
+class JavaScanner
+{
+
+ private final String s;
+
+ JavaScanner(String s)
+ {
+ if (!s.endsWith("\n"))
+ {
+ s += "\n";
+ // This allows us to avoid checking for the end of the string in most cases.
+ }
+ this.s = s;
+ }
+
+ int tokenEnd(int start)
+ {
+ if (start >= s.length())
+ {
+ return s.length();
+ }
+ switch (s.charAt(start))
+ {
+ case ' ':
+ case '\n':
+ return spaceEnd(start);
+ case '/':
+ if (s.charAt(start + 1) == '*')
+ {
+ return blockCommentEnd(start);
+ } else if (s.charAt(start + 1) == '/')
+ {
+ return lineCommentEnd(start);
+ } else
+ {
+ return start + 1;
+ }
+ case '\'':
+ case '"':
+ case '`':
+ return quoteEnd(start);
+ default:
+ // Every other character is considered to be its own token.
+ return start + 1;
+ }
+ }
+
+ private int spaceEnd(int start)
+ {
+ assert s.charAt(start) == ' ' || s.charAt(start) == '\n';
+ int i;
+ for (i = start + 1; i < s.length() && s.charAt(i) == ' '; i++)
+ {
+ }
+ return i;
+ }
+
+ private int blockCommentEnd(int start)
+ {
+ assert s.charAt(start) == '/' && s.charAt(start + 1) == '*';
+ int i;
+ for (i = start + 2; s.charAt(i) != '*' || s.charAt(i + 1) != '/'; i++)
+ {
+ }
+ return i + 2;
+ }
+
+ private int lineCommentEnd(int start)
+ {
+ assert s.charAt(start) == '/' && s.charAt(start + 1) == '/';
+ int end = s.indexOf('\n', start + 2);
+ assert end > 0;
+ return end;
+ }
+
+ private int quoteEnd(int start)
+ {
+ char quote = s.charAt(start);
+ assert quote == '\'' || quote == '"' || quote == '`';
+ int i;
+ for (i = start + 1; s.charAt(i) != quote; i++)
+ {
+ if (s.charAt(i) == '\\')
+ {
+ i++;
+ }
+ }
+ return i + 1;
+ }
+
+}
\ No newline at end of file
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Parcelables.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Parcelables.java
new file mode 100644
index 0000000..0d75f09
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Parcelables.java
@@ -0,0 +1,402 @@
+// 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.internal.codegen;
+
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ArrayTypeName;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+
+import java.util.Set;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+/**
+ * This class implementation has been based and refactored from Parcelables auto-value
+ * extension implementation.
+ *
+ * https://github.com/rharter/auto-value-parcel/blob/master/auto-value-parcel/src/main/java/com/ryanharter/auto/value/parcel/Parcelables.java
+ */
+class Parcelables
+{
+
+ // Objects Type
+ private static final TypeName STRING = ClassName.get("java.lang", "String");
+ private static final TypeName MAP = ClassName.get("java.util", "Map");
+ private static final TypeName LIST = ClassName.get("java.util", "List");
+ private static final TypeName SPARSE_ARRAY = ClassName.get("android.util", "SparseArray");
+ private static final TypeName SPARSE_BOOLEAN_ARRAY = ClassName.get("android.util", "SparseBooleanArray");
+ private static final TypeName BUNDLE = ClassName.get("android.os", "Bundle");
+ private static final TypeName PARCELABLE = ClassName.get("android.os", "Parcelable");
+ private static final TypeName CHAR_SEQUENCE = ClassName.get("java.lang", "CharSequence");
+ private static final TypeName I_BINDER = ClassName.get("android.os", "IBinder");
+ private static final TypeName SERIALIZABLE = ClassName.get("java.io", "Serializable");
+ private static final TypeName PERSISTABLE_BUNDLE = ClassName.get("android.os", "PersistableBundle");
+ private static final TypeName SIZE = ClassName.get("android.util", "Size");
+ private static final TypeName SIZE_F = ClassName.get("android.util", "SizeF");
+ private static final TypeName TEXT_UTILS = ClassName.get("android.text", "TextUtils");
+ private static final TypeName ENUM = ClassName.get(Enum.class);
+
+ // Arrays of Object Type
+ private static final TypeName BOOLEAN_ARRAY = ArrayTypeName.of(boolean.class);
+ private static final TypeName BYTE_ARRAY = ArrayTypeName.of(byte.class);
+ private static final TypeName CHAR_ARRAY = ArrayTypeName.of(char.class);
+ private static final TypeName INT_ARRAY = ArrayTypeName.of(int.class);
+ private static final TypeName LONG_ARRAY = ArrayTypeName.of(long.class);
+ private static final TypeName STRING_ARRAY = ArrayTypeName.of(String.class);
+ private static final TypeName PARCELABLE_ARRAY = ArrayTypeName.of(PARCELABLE);
+ private static final TypeName OBJECT_ARRAY = ArrayTypeName.of(TypeName.OBJECT);
+
+ private static final Set VALID_TYPES = ImmutableSet.of(STRING, MAP, LIST, BOOLEAN_ARRAY,
+ BYTE_ARRAY, CHAR_ARRAY, INT_ARRAY, LONG_ARRAY, STRING_ARRAY, SPARSE_ARRAY, SPARSE_BOOLEAN_ARRAY,
+ BUNDLE, PARCELABLE, PARCELABLE_ARRAY, CHAR_SEQUENCE, I_BINDER, OBJECT_ARRAY,
+ SERIALIZABLE, PERSISTABLE_BUNDLE, SIZE, SIZE_F);
+
+ static void readValue(CodeBlock.Builder block, ParcelledProcessor.Property property, final TypeName parcelableType)
+ {
+
+ if (property.isNullable())
+ {
+ block.add("in.readInt() == 0 ? ");
+ }
+
+ if (parcelableType.equals(STRING))
+ {
+ block.add("in.readString()");
+ } else if (parcelableType.equals(TypeName.BYTE) || parcelableType.equals(TypeName.BYTE.box()))
+ {
+ block.add("in.readByte()");
+ } else if (parcelableType.equals(TypeName.INT) || parcelableType.equals(TypeName.INT.box()))
+ {
+ block.add("in.readInt()");
+ } else if (parcelableType.equals(TypeName.SHORT) || parcelableType.equals(TypeName.SHORT.box()))
+ {
+ block.add("(short) in.readInt()");
+ } else if (parcelableType.equals(TypeName.CHAR) || parcelableType.equals(TypeName.CHAR.box()))
+ {
+ block.add("(char) in.readInt()");
+ } else if (parcelableType.equals(TypeName.LONG) || parcelableType.equals(TypeName.LONG.box()))
+ {
+ block.add("in.readLong()");
+ } else if (parcelableType.equals(TypeName.FLOAT) || parcelableType.equals(TypeName.FLOAT.box()))
+ {
+ block.add("in.readFloat()");
+ } else if (parcelableType.equals(TypeName.DOUBLE) || parcelableType.equals(TypeName.DOUBLE.box()))
+ {
+ block.add("in.readDouble()");
+ } else if (parcelableType.equals(TypeName.BOOLEAN) || parcelableType.equals(TypeName.BOOLEAN.box()))
+ {
+ block.add("in.readInt() == 1");
+ } else if (parcelableType.equals(PARCELABLE))
+ {
+ if (property.typeName.equals(PARCELABLE))
+ {
+ block.add("in.readParcelable($T.class.getClassLoader())", parcelableType);
+ } else
+ {
+ block.add("($T) in.readParcelable($T.class.getClassLoader())", property.typeName, property.typeName);
+ }
+ } else if (parcelableType.equals(CHAR_SEQUENCE))
+ {
+ block.add("$T.CHAR_SEQUENCE_CREATOR.createFromParcel(in)", TEXT_UTILS);
+ } else if (parcelableType.equals(MAP))
+ {
+ block.add("($T) in.readHashMap($T.class.getClassLoader())", property.typeName, parcelableType);
+ } else if (parcelableType.equals(LIST))
+ {
+ block.add("($T) in.readArrayList($T.class.getClassLoader())", property.typeName, parcelableType);
+ } else if (parcelableType.equals(BOOLEAN_ARRAY))
+ {
+ block.add("in.createBooleanArray()");
+ } else if (parcelableType.equals(BYTE_ARRAY))
+ {
+ block.add("in.createByteArray()");
+ } else if (parcelableType.equals(CHAR_ARRAY))
+ {
+ block.add("in.createCharArray()");
+ } else if (parcelableType.equals(STRING_ARRAY))
+ {
+ block.add("in.readStringArray()");
+ } else if (parcelableType.equals(I_BINDER))
+ {
+ if (property.typeName.equals(I_BINDER))
+ {
+ block.add("in.readStrongBinder()");
+ } else
+ {
+ block.add("($T) in.readStrongBinder()", property.typeName);
+ }
+ } else if (parcelableType.equals(OBJECT_ARRAY))
+ {
+ block.add("in.readArray($T.class.getClassLoader())", parcelableType);
+ } else if (parcelableType.equals(INT_ARRAY))
+ {
+ block.add("in.createIntArray()");
+ } else if (parcelableType.equals(LONG_ARRAY))
+ {
+ block.add("in.createLongArray()");
+ } else if (parcelableType.equals(SERIALIZABLE))
+ {
+ if (property.typeName.equals(SERIALIZABLE))
+ {
+ block.add("in.readSerializable()");
+ } else
+ {
+ block.add("($T) in.readSerializable()", property.typeName);
+ }
+ } else if (parcelableType.equals(PARCELABLE_ARRAY))
+ {
+ ArrayTypeName atype = (ArrayTypeName) property.typeName;
+ if (atype.componentType.equals(PARCELABLE))
+ {
+ block.add("in.readParcelableArray($T.class.getClassLoader())", parcelableType);
+ } else
+ {
+ block.add("($T) in.readParcelableArray($T.class.getClassLoader())", atype, parcelableType);
+ }
+ } else if (parcelableType.equals(SPARSE_ARRAY))
+ {
+ block.add("in.readSparseArray($T.class.getClassLoader())", parcelableType);
+ } else if (parcelableType.equals(SPARSE_BOOLEAN_ARRAY))
+ {
+ block.add("in.readSparseBooleanArray()");
+ } else if (parcelableType.equals(BUNDLE))
+ {
+ block.add("in.readBundle($T.class.getClassLoader())", property.typeName);
+ } else if (parcelableType.equals(PERSISTABLE_BUNDLE))
+ {
+ block.add("in.readPersistableBundle($T.class.getClassLoader())", property.typeName);
+ } else if (parcelableType.equals(SIZE))
+ {
+ block.add("in.readSize()");
+ } else if (parcelableType.equals(SIZE_F))
+ {
+ block.add("in.readSizeF()");
+ } else if (parcelableType.equals(ENUM))
+ {
+ block.add("$T.valueOf(in.readString())", property.typeName);
+ } else
+ {
+ block.add("($T) in.readValue($T.class.getClassLoader())", property.typeName, parcelableType);
+ }
+
+ if (property.isNullable())
+ {
+ block.add(" : null");
+ }
+ }
+
+ public static void readValueWithTypeAdapter(CodeBlock.Builder block, ParcelledProcessor.Property property, final FieldSpec adapter)
+ {
+ if (property.isNullable())
+ {
+ block.add("in.readInt() == 0 ? ");
+ }
+ block.add("$N.fromParcel(in)", adapter);
+ if (property.isNullable())
+ {
+ block.add(" : null");
+ }
+ }
+
+ public static CodeBlock writeVersion(int version, ParameterSpec out)
+ {
+ CodeBlock.Builder block = CodeBlock.builder();
+
+ block.add("$N.writeInt( /* version */ " + version + ")", out);
+ block.add(";\n");
+
+ return block.build();
+ }
+
+ public static CodeBlock writeValue(ParcelledProcessor.Property property, ParameterSpec out, ParameterSpec flags, Types typeUtils)
+ {
+ CodeBlock.Builder block = CodeBlock.builder();
+
+ if (property.isNullable())
+ {
+ block.beginControlFlow("if ($N == null)", property.fieldName);
+ block.addStatement("$N.writeInt(1)", out);
+ block.nextControlFlow("else");
+ block.addStatement("$N.writeInt(0)", out);
+ }
+
+ TypeName type = getTypeNameFromProperty(property, typeUtils);
+
+ if (type.equals(STRING))
+ block.add("$N.writeString($N)", out, property.fieldName);
+ else if (type.equals(TypeName.BYTE) || type.equals(TypeName.BYTE.box()))
+ block.add("$N.writeInt($N)", out, property.fieldName);
+ else if (type.equals(TypeName.INT) || type.equals(TypeName.INT.box()))
+ block.add("$N.writeInt($N)", out, property.fieldName);
+ else if (type.equals(TypeName.SHORT))
+ block.add("$N.writeInt(((Short) $N).intValue())", out, property.fieldName);
+ else if (type.equals(TypeName.SHORT.box()))
+ block.add("$N.writeInt($N.intValue())", out, property.fieldName);
+ else if (type.equals(TypeName.CHAR) || type.equals(TypeName.CHAR.box()))
+ block.add("$N.writeInt($N)", out, property.fieldName);
+ else if (type.equals(TypeName.LONG) || type.equals(TypeName.LONG.box()))
+ block.add("$N.writeLong($N)", out, property.fieldName);
+ else if (type.equals(TypeName.FLOAT) || type.equals(TypeName.FLOAT.box()))
+ block.add("$N.writeFloat($N)", out, property.fieldName);
+ else if (type.equals(TypeName.DOUBLE) || type.equals(TypeName.DOUBLE.box()))
+ block.add("$N.writeDouble($N)", out, property.fieldName);
+ else if (type.equals(TypeName.BOOLEAN) || type.equals(TypeName.BOOLEAN.box()))
+ block.add("$N.writeInt($N ? 1 : 0)", out, property.fieldName);
+ else if (type.equals(PARCELABLE))
+ block.add("$N.writeParcelable($N, $N)", out, property.fieldName, flags);
+ else if (type.equals(CHAR_SEQUENCE))
+ block.add("$T.writeToParcel($N, $N, $N)", TEXT_UTILS, property.fieldName, out, flags);
+ else if (type.equals(MAP))
+ block.add("$N.writeMap($N)", out, property.fieldName);
+ else if (type.equals(LIST))
+ block.add("$N.writeList($N)", out, property.fieldName);
+ else if (type.equals(BOOLEAN_ARRAY))
+ block.add("$N.writeBooleanArray($N)", out, property.fieldName);
+ else if (type.equals(BYTE_ARRAY))
+ block.add("$N.writeByteArray($N)", out, property.fieldName);
+ else if (type.equals(CHAR_ARRAY))
+ block.add("$N.writeCharArray($N)", out, property.fieldName);
+ else if (type.equals(STRING_ARRAY))
+ block.add("$N.writeStringArray($N)", out, property.fieldName);
+ else if (type.equals(I_BINDER))
+ block.add("$N.writeStrongBinder($N)", out, property.fieldName);
+ else if (type.equals(OBJECT_ARRAY))
+ block.add("$N.writeArray($N)", out, property.fieldName);
+ else if (type.equals(INT_ARRAY))
+ block.add("$N.writeIntArray($N)", out, property.fieldName);
+ else if (type.equals(LONG_ARRAY))
+ block.add("$N.writeLongArray($N)", out, property.fieldName);
+ else if (type.equals(SERIALIZABLE))
+ block.add("$N.writeSerializable($N)", out, property.fieldName);
+ else if (type.equals(PARCELABLE_ARRAY))
+ block.add("$N.writeParcelableArray($N)", out, property.fieldName);
+ else if (type.equals(SPARSE_ARRAY))
+ block.add("$N.writeSparseArray($N)", out, property.fieldName);
+ else if (type.equals(SPARSE_BOOLEAN_ARRAY))
+ block.add("$N.writeSparseBooleanArray($N)", out, property.fieldName);
+ else if (type.equals(BUNDLE))
+ block.add("$N.writeBundle($N)", out, property.fieldName);
+ else if (type.equals(PERSISTABLE_BUNDLE))
+ block.add("$N.writePersistableBundle($N)", out, property.fieldName);
+ else if (type.equals(SIZE))
+ block.add("$N.writeSize($N)", out, property.fieldName);
+ else if (type.equals(SIZE_F))
+ block.add("$N.writeSizeF($N)", out, property.fieldName);
+ else if (type.equals(ENUM))
+ block.add("$N.writeString($N.name())", out, property.fieldName);
+ else
+ block.add("$N.writeValue($N)", out, property.fieldName);
+
+ block.add(";\n");
+
+ if (property.isNullable())
+ {
+ block.endControlFlow();
+ }
+ return block.build();
+ }
+
+ public static CodeBlock writeValueWithTypeAdapter(FieldSpec adapter, ParcelledProcessor.Property p, ParameterSpec out)
+ {
+ CodeBlock.Builder block = CodeBlock.builder();
+
+ if (p.isNullable())
+ {
+ block.beginControlFlow("if ($N == null)", p.fieldName);
+ block.addStatement("$N.writeInt(1)", out);
+ block.nextControlFlow("else");
+ block.addStatement("$N.writeInt(0)", out);
+ }
+
+ block.addStatement("$N.toParcel($N, $N)", adapter, p.fieldName, out);
+
+ if (p.isNullable())
+ {
+ block.endControlFlow();
+ }
+
+ return block.build();
+ }
+
+ static boolean isTypeRequiresSuppressWarnings(TypeName type)
+ {
+ return type.equals(LIST) ||
+ type.equals(MAP);
+ }
+
+ static TypeName getTypeNameFromProperty(ParcelledProcessor.Property property, Types types)
+ {
+ TypeMirror returnType = property.element.asType();
+ TypeElement element = (TypeElement) types.asElement(returnType);
+ if (element != null)
+ {
+ TypeName parcelableType = getParcelableType(types, element);
+ if (!PARCELABLE.equals(parcelableType) && element.getKind() == ElementKind.ENUM)
+ {
+ return ENUM;
+ }
+ return parcelableType;
+ }
+ return property.typeName;
+ }
+
+ public static TypeName getParcelableType(Types types, TypeElement type)
+ {
+ TypeMirror typeMirror = type.asType();
+ while (typeMirror.getKind() != TypeKind.NONE)
+ {
+
+ // first, check if the class is valid.
+ TypeName typeName = TypeName.get(typeMirror);
+ if (typeName instanceof ParameterizedTypeName)
+ {
+ typeName = ((ParameterizedTypeName) typeName).rawType;
+ }
+ if (isValidType(typeName))
+ {
+ return typeName;
+ }
+
+ // then check if it implements valid interfaces
+ for (TypeMirror iface : type.getInterfaces())
+ {
+ TypeName inherited = getParcelableType(types, (TypeElement) types.asElement(iface));
+ if (inherited != null)
+ {
+ return inherited;
+ }
+ }
+ // then move on
+ type = (TypeElement) types.asElement(typeMirror);
+ typeMirror = type.getSuperclass();
+ }
+ return null;
+ }
+
+ public static boolean isValidType(TypeName typeName)
+ {
+ return typeName.isPrimitive() || typeName.isBoxedPrimitive() || VALID_TYPES.contains(typeName);
+ }
+
+}
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
new file mode 100644
index 0000000..866dae6
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/ParcelledProcessor.java
@@ -0,0 +1,584 @@
+// 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.internal.codegen;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ArrayTypeName;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.NameAllocator;
+import com.squareup.javapoet.ParameterSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.zeoflow.parcelled.Parcelled;
+import com.zeoflow.parcelled.ParcelledAdapter;
+import com.zeoflow.parcelled.ParcelledVersion;
+import com.zeoflow.parcelled.internal.common.MoreElements;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypeException;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+
+@SupportedAnnotationTypes("com.zeoflow.parcelled.Parcelled")
+public final class ParcelledProcessor extends AbstractProcessor
+{
+
+ private ErrorReporter mErrorReporter;
+ private Types mTypeUtils;
+ private boolean requiresSuppressWarnings = false;
+ private static AnnotationSpec createSuppressUncheckedWarningAnnotation()
+ {
+ return AnnotationSpec.builder(SuppressWarnings.class)
+ .addMember("value", "\"unchecked\"")
+ .build();
+ }
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv)
+ {
+ super.init(processingEnv);
+ mErrorReporter = new ErrorReporter(processingEnv);
+ mTypeUtils = processingEnv.getTypeUtils();
+ }
+ @Override
+ public SourceVersion getSupportedSourceVersion()
+ {
+ return SourceVersion.latestSupported();
+ }
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment env)
+ {
+ Collection extends Element> annotatedElements =
+ env.getElementsAnnotatedWith(Parcelled.class);
+ List types = new ImmutableList.Builder()
+ .addAll(ElementFilter.typesIn(annotatedElements))
+ .build();
+
+ for (TypeElement type : types)
+ {
+ processType(type);
+ }
+
+ // We are the only ones handling Parcelled annotations
+ return true;
+ }
+ private void processType(TypeElement type)
+ {
+ Parcelled parcelled = type.getAnnotation(Parcelled.class);
+ if (parcelled == null)
+ {
+ mErrorReporter.abortWithError("annotation processor for @Parcelled was invoked with a" +
+ "type annotated differently; compiler bug? O_o", type);
+ }
+ if (type.getKind() != ElementKind.CLASS)
+ {
+ mErrorReporter.abortWithError("@" + Parcelled.class.getName() + " only applies to classes", type);
+ }
+ if (ancestorIsAutoParcel(type))
+ {
+ mErrorReporter.abortWithError("One @Parcelled class shall not extend another", type);
+ }
+
+ checkModifiersIfNested(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());
+ source = Reformatter.fixup(source);
+ writeSourceFile(fqClassName, source, type);
+
+ }
+ private void writeSourceFile(String className, String text, TypeElement originatingType)
+ {
+ try
+ {
+ JavaFileObject sourceFile =
+ processingEnv.getFiler().createSourceFile(className, originatingType);
+ try (Writer writer = sourceFile.openWriter())
+ {
+ writer.write(text);
+ }
+ } catch (IOException e)
+ {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
+ "Could not write generated class " + className + ": " + e);
+ }
+ }
+ private String generateClass(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);
+
+ // get the type adapters
+ ImmutableMap typeAdapters = getTypeAdapters(properties);
+
+ // get the parcel version
+ int version = type.getAnnotation(Parcelled.class).version();
+
+ // Generate the Parcelled_$ class
+ String pkg = TypeUtil.packageNameOf(type);
+ TypeName classTypeName = ClassName.get(pkg, className);
+ assert className != null;
+ // generate writeToParcel()
+ TypeSpec.Builder subClass = TypeSpec.classBuilder(className)
+ // Add the version
+ .addField(TypeName.INT, "version", PRIVATE)
+ // Class must be always final
+ .addModifiers(FINAL)
+ // extends from original abstract class
+ .superclass(ClassName.get(pkg, classToExtend))
+ // Add the DEFAULT constructor
+ .addMethod(generateConstructor(properties))
+ // Add the private constructor
+ .addMethod(generateConstructorFromParcel(processingEnv, properties, typeAdapters))
+ // overrides describeContents()
+ .addMethod(generateDescribeContents())
+ // static final CREATOR
+ .addField(generateCreator(classTypeName))
+ // overrides writeToParcel()
+ .addMethod(generateWriteToParcel(version, processingEnv, properties, typeAdapters));
+
+ if (!ancestoIsParcelable(processingEnv, type))
+ {
+ // Implement android.os.Parcelable if the ancestor does not do it.
+ subClass.addSuperinterface(ClassName.get("android.os", "Parcelable"));
+ }
+
+ if (!typeAdapters.isEmpty())
+ {
+ typeAdapters.values().forEach(subClass::addField);
+ }
+
+ JavaFile javaFile = JavaFile.builder(pkg, subClass.build()).build();
+ return javaFile.toString();
+ }
+
+ private ImmutableMap getTypeAdapters(ImmutableList properties)
+ {
+ Map typeAdapters = new LinkedHashMap<>();
+ NameAllocator nameAllocator = new NameAllocator();
+ nameAllocator.newName("CREATOR");
+ for (Property property : properties)
+ {
+ if (property.typeAdapter != null && !typeAdapters.containsKey(property.typeAdapter))
+ {
+ ClassName typeName = (ClassName) TypeName.get(property.typeAdapter);
+ String name = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, typeName.simpleName());
+ name = nameAllocator.newName(name, typeName);
+
+ typeAdapters.put(property.typeAdapter, FieldSpec.builder(
+ typeName, NameAllocator.toJavaIdentifier(name), PRIVATE, STATIC, FINAL)
+ .initializer("new $T()", typeName).build());
+ }
+ }
+ return ImmutableMap.copyOf(typeAdapters);
+ }
+ private ImmutableList buildProperties(List elements)
+ {
+ ImmutableList.Builder builder = ImmutableList.builder();
+ for (VariableElement element : elements)
+ {
+ builder.add(new Property(element.getSimpleName().toString(), element));
+ }
+
+ return builder.build();
+ }
+ /**
+ * This method returns a list of all non private fields. If any private fields is
+ * found, the method errors out
+ *
+ * @param type element
+ *
+ * @return list of all non-private fields
+ */
+ private List getParcelableFieldsOrError(TypeElement type)
+ {
+ List allFields = ElementFilter.fieldsIn(type.getEnclosedElements());
+ List nonPrivateFields = new ArrayList<>();
+
+ for (VariableElement field : allFields)
+ {
+ if (!field.getModifiers().contains(PRIVATE))
+ {
+ nonPrivateFields.add(field);
+ } else
+ {
+ // return error, PRIVATE fields are not allowed
+ mErrorReporter.abortWithError("getFieldsError error, PRIVATE fields not allowed", type);
+ }
+ }
+
+ return nonPrivateFields;
+ }
+ private MethodSpec generateConstructor(ImmutableList properties)
+ {
+
+ List params = Lists.newArrayListWithCapacity(properties.size());
+ for (Property property : properties)
+ {
+ params.add(ParameterSpec.builder(property.typeName, property.fieldName).build());
+ }
+
+ MethodSpec.Builder builder = MethodSpec.constructorBuilder()
+ .addParameters(params);
+ builder.addJavadoc("Class builder");
+ builder.addJavadoc("\n");
+ for (ParameterSpec param : params)
+ {
+ builder.addJavadoc("\n@param " + param.name + " {@link " + param.type + "}");
+ builder.addStatement("this.$N = $N", param.name, param.name);
+ }
+
+ return builder.build();
+ }
+ private MethodSpec generateConstructorFromParcel(
+ ProcessingEnvironment env,
+ ImmutableList properties,
+ ImmutableMap typeAdapters)
+ {
+
+ // Create the PRIVATE constructor from Parcel
+ MethodSpec.Builder builder = MethodSpec.constructorBuilder()
+ .addModifiers(PRIVATE) // private
+ .addParameter(ClassName.bestGuess("android.os.Parcel"), "in"); // input param
+
+ builder.addJavadoc("Parcelable builder");
+ builder.addJavadoc("\n");
+ builder.addJavadoc("\n@param in {@link " + ClassName.bestGuess("android.os.Parcel") + "}");
+
+ // get a code block builder
+ CodeBlock.Builder block = CodeBlock.builder();
+
+ // First thing is reading the Parcelable object version
+ block.add("this.version = in.readInt();\n");
+
+ // Now, iterate all properties, check the version initialize them
+ for (Property p : properties)
+ {
+
+ // get the property version
+ int aVersion = p.getAfterVersion();
+ int bVersion = p.getBeforeVersion();
+ if (aVersion > 0 && bVersion > 0)
+ {
+ block.beginControlFlow("if (this.version >= $L && this.version <= $L)", aVersion, bVersion);
+ } else if (aVersion > 0)
+ {
+ block.beginControlFlow("if (this.version >= $L)", aVersion);
+ } else if (bVersion > 0)
+ {
+ block.beginControlFlow("if (this.version <= $L)", bVersion);
+ }
+
+ block.add("this.$N = ", p.fieldName);
+
+ if (p.typeAdapter != null && typeAdapters.containsKey(p.typeAdapter))
+ {
+ Parcelables.readValueWithTypeAdapter(block, p, typeAdapters.get(p.typeAdapter));
+ } else
+ {
+ requiresSuppressWarnings |= Parcelables.isTypeRequiresSuppressWarnings(p.typeName);
+ TypeName parcelableType = Parcelables.getTypeNameFromProperty(p, env.getTypeUtils());
+ Parcelables.readValue(block, p, parcelableType);
+ }
+
+ block.add(";\n");
+
+ if (aVersion > 0 || bVersion > 0)
+ {
+ block.endControlFlow();
+ }
+ }
+
+ builder.addCode(block.build());
+
+ return builder.build();
+ }
+
+ private String generatedSubclassName(TypeElement type)
+ {
+ String classNameSuffix = "Parcelled_";
+ return generatedClassName(type, Strings.repeat("$", 0) + classNameSuffix);
+ }
+
+ private String generatedClassName(TypeElement type, String prefix)
+ {
+ StringBuilder name = new StringBuilder(type.getSimpleName().toString());
+ while (type.getEnclosingElement() instanceof TypeElement)
+ {
+ type = (TypeElement) type.getEnclosingElement();
+ name.insert(0, type.getSimpleName() + "_");
+ }
+ String pkg = TypeUtil.packageNameOf(type);
+ String dot = pkg.isEmpty() ? "" : ".";
+ return pkg + dot + prefix + name;
+ }
+
+ private MethodSpec generateWriteToParcel(
+ int version,
+ ProcessingEnvironment env,
+ ImmutableList properties,
+ ImmutableMap typeAdapters)
+ {
+ ParameterSpec dest = ParameterSpec
+ .builder(ClassName.get("android.os", "Parcel"), "dest")
+ .build();
+ ParameterSpec flags = ParameterSpec.builder(int.class, "flags").build();
+ MethodSpec.Builder builder = MethodSpec.methodBuilder("writeToParcel")
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .addParameter(dest)
+ .addParameter(flags);
+
+ // write first the parcelable object version...
+ builder.addCode(Parcelables.writeVersion(version, dest));
+
+ // ...then write all the properties
+ for (Property p : properties)
+ {
+ if (p.typeAdapter != null && typeAdapters.containsKey(p.typeAdapter))
+ {
+ FieldSpec typeAdapter = typeAdapters.get(p.typeAdapter);
+ builder.addCode(Parcelables.writeValueWithTypeAdapter(typeAdapter, p, dest));
+ } else
+ {
+ builder.addCode(Parcelables.writeValue(p, dest, flags, env.getTypeUtils()));
+ }
+ }
+
+ return builder.build();
+ }
+
+ private MethodSpec generateDescribeContents()
+ {
+ return MethodSpec.methodBuilder("describeContents")
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .returns(int.class)
+ .addStatement("return 0")
+ .build();
+ }
+
+ private FieldSpec generateCreator(TypeName type)
+ {
+ ClassName creator = ClassName.bestGuess("android.os.Parcelable.Creator");
+ TypeName creatorOfClass = ParameterizedTypeName.get(creator, type);
+
+ CodeBlock.Builder ctorCall = CodeBlock.builder();
+ ctorCall.add("return new $T(in);\n", type);
+
+ // Method createFromParcel()
+ MethodSpec.Builder createFromParcel = MethodSpec.methodBuilder("createFromParcel")
+ .addAnnotation(Override.class);
+ if (requiresSuppressWarnings)
+ {
+ createFromParcel.addAnnotation(createSuppressUncheckedWarningAnnotation());
+ }
+ createFromParcel
+ .addModifiers(PUBLIC)
+ .returns(type)
+ .addParameter(ClassName.bestGuess("android.os.Parcel"), "in");
+ createFromParcel.addCode(ctorCall.build());
+
+ TypeSpec creatorImpl = TypeSpec.anonymousClassBuilder("")
+ .superclass(creatorOfClass)
+ .addMethod(createFromParcel
+ .build())
+ .addMethod(MethodSpec.methodBuilder("newArray")
+ .addAnnotation(Override.class)
+ .addModifiers(PUBLIC)
+ .returns(ArrayTypeName.of(type))
+ .addParameter(int.class, "size")
+ .addStatement("return new $T[size]", type)
+ .build())
+ .build();
+
+ return FieldSpec
+ .builder(creatorOfClass, "CREATOR", PUBLIC, FINAL, STATIC)
+ .initializer("$L", creatorImpl)
+ .build();
+ }
+
+ private void checkModifiersIfNested(TypeElement type)
+ {
+ ElementKind enclosingKind = type.getEnclosingElement().getKind();
+ if (enclosingKind.isClass() || enclosingKind.isInterface())
+ {
+ if (type.getModifiers().contains(PRIVATE))
+ {
+ mErrorReporter.abortWithError("@Parcelled class must not be private", type);
+ }
+ if (!type.getModifiers().contains(STATIC))
+ {
+ mErrorReporter.abortWithError("Nested @Parcelled class must be static", type);
+ }
+ }
+ // In principle type.getEnclosingElement() could be an ExecutableElement (for a class
+ // declared inside a method), but since RoundEnvironment.getElementsAnnotatedWith doesn't
+ // return such classes we won't see them here.
+ }
+
+ private boolean ancestorIsAutoParcel(TypeElement type)
+ {
+ while (true)
+ {
+ TypeMirror parentMirror = type.getSuperclass();
+ if (parentMirror.getKind() == TypeKind.NONE)
+ {
+ return false;
+ }
+ TypeElement parentElement = (TypeElement) mTypeUtils.asElement(parentMirror);
+ if (MoreElements.isAnnotationPresent(parentElement, Parcelled.class))
+ {
+ return true;
+ }
+ type = parentElement;
+ }
+ }
+
+ private boolean ancestoIsParcelable(ProcessingEnvironment env, TypeElement type)
+ {
+ TypeMirror classType = type.asType();
+ TypeMirror parcelable = env.getElementUtils().getTypeElement("android.os.Parcelable").asType();
+ return TypeUtil.isClassOfType(env.getTypeUtils(), parcelable, classType);
+ }
+
+ static final class Property
+ {
+
+ final String fieldName;
+ final VariableElement element;
+ final TypeName typeName;
+ final ImmutableSet annotations;
+ final int version;
+ final int afterVersion;
+ final int beforeVersion;
+ TypeMirror typeAdapter;
+
+ Property(String fieldName, VariableElement element)
+ {
+ this.fieldName = fieldName;
+ this.element = element;
+ this.typeName = TypeName.get(element.asType());
+ this.annotations = getAnnotations(element);
+
+ // get the parcel adapter if any
+ ParcelledAdapter parcelledAdapter = element.getAnnotation(ParcelledAdapter.class);
+ if (parcelledAdapter != null)
+ {
+ try
+ {
+ parcelledAdapter.value();
+ } catch (MirroredTypeException e)
+ {
+ this.typeAdapter = e.getTypeMirror();
+ }
+
+ }
+
+ // get the element version, default 0
+ ParcelledVersion parcelledVersion = element.getAnnotation(ParcelledVersion.class);
+ this.version = parcelledVersion == null ? 0 : parcelledVersion.after();
+ this.afterVersion = parcelledVersion == null ? 0 : parcelledVersion.after();
+ this.beforeVersion = parcelledVersion == null ? 0 : parcelledVersion.before();
+ }
+
+ public boolean isNullable()
+ {
+ return this.annotations.contains("Nullable");
+ }
+
+ public int getAfterVersion()
+ {
+ return this.afterVersion;
+ }
+
+ public int getBeforeVersion()
+ {
+ return this.beforeVersion;
+ }
+
+ private ImmutableSet getAnnotations(VariableElement element)
+ {
+ ImmutableSet.Builder builder = ImmutableSet.builder();
+ for (AnnotationMirror annotation : element.getAnnotationMirrors())
+ {
+ builder.add(annotation.getAnnotationType().asElement().getSimpleName().toString());
+ }
+
+ return builder.build();
+ }
+
+ }
+
+}
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Reformatter.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Reformatter.java
new file mode 100644
index 0000000..b73e9e2
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/Reformatter.java
@@ -0,0 +1,140 @@
+// 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.internal.codegen;
+
+/**
+ * Postprocessor that runs over the output of the template engine in order to make it look nicer.
+ * Mostly, this involves removing surplus horizontal and vertical space.
+ */
+class Reformatter
+{
+
+ static String fixup(String s)
+ {
+ s = removeTrailingSpace(s);
+ s = compressBlankLines(s);
+ s = compressSpace(s);
+ return s;
+ }
+
+ private static String removeTrailingSpace(String s)
+ {
+ // Remove trailing space from all lines. This is mainly to make it easier to find
+ // blank lines later.
+ if (!s.endsWith("\n"))
+ {
+ s += '\n';
+ }
+ StringBuilder sb = new StringBuilder(s.length());
+ int start = 0;
+ while (start < s.length())
+ {
+ int nl = s.indexOf('\n', start);
+ int i = nl - 1;
+ while (i >= start && s.charAt(i) == ' ')
+ {
+ i--;
+ }
+ sb.append(s.substring(start, i + 1)).append('\n');
+ start = nl + 1;
+ }
+ return sb.toString();
+ }
+
+ private static String compressBlankLines(String s)
+ {
+ // Remove extra blank lines. An "extra" blank line is either a blank line where the previous
+ // line was also blank; or a blank line that appears inside parentheses or inside more than one
+ // set of braces. This means that we preserve blank lines inside our top-level class, but not
+ // within our generated methods.
+ StringBuilder sb = new StringBuilder(s.length());
+ int braces = 0;
+ int parens = 0;
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ switch (c)
+ {
+ case '(':
+ parens++;
+ break;
+ case ')':
+ parens--;
+ break;
+ case '{':
+ braces++;
+ break;
+ case '}':
+ braces--;
+ break;
+ case '\n':
+ int j = i + 1;
+ while (j < s.length() && s.charAt(j) == '\n')
+ {
+ j++;
+ }
+ if (j > i + 1)
+ {
+ if (parens == 0 && braces <= 1)
+ {
+ sb.append("\n");
+ }
+ i = j - 1;
+ }
+ break;
+ }
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ private static String compressSpace(String s)
+ {
+ // Remove extra spaces. An "extra" space is one that is not part of the indentation at the start
+ // of a line, and where the next character is also a space or a right paren or a semicolon
+ // or a dot or a comma, or the preceding character is a left paren.
+ // TODO(emcmanus): consider merging all three passes using this tokenization approach.
+ StringBuilder sb = new StringBuilder(s.length());
+ JavaScanner tokenizer = new JavaScanner(s);
+ int len = s.length();
+ int end;
+ for (int start = 0; start < len; start = end)
+ {
+ end = tokenizer.tokenEnd(start);
+ if (s.charAt(start) == ' ')
+ {
+ // Since we consider a newline plus following indentation to be a single token, we only
+ // see a token starting with ' ' if it is in the middle of a line.
+ if (sb.charAt(sb.length() - 1) == '(')
+ {
+ continue;
+ }
+ // Since we ensure that the tokenized string ends with \n, and a whitespace token stops
+ // at \n, it is safe to look at end.
+ char nextC = s.charAt(end);
+ if (".,;)".indexOf(nextC) >= 0)
+ {
+ continue;
+ }
+ sb.append(' ');
+ } else
+ {
+ sb.append(s.substring(start, end));
+ }
+ }
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/TypeUtil.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/TypeUtil.java
new file mode 100644
index 0000000..206f4ee
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/codegen/TypeUtil.java
@@ -0,0 +1,70 @@
+// 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.internal.codegen;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+class TypeUtil
+{
+
+ /**
+ * Returns the name of the package that the given type is in. If the type is in the default
+ * (unnamed) package then the name is the empty string.
+ *
+ * @param type given type
+ *
+ * @return package name
+ */
+ static String packageNameOf(TypeElement type)
+ {
+ while (true)
+ {
+ Element enclosing = type.getEnclosingElement();
+ if (enclosing instanceof PackageElement)
+ {
+ return ((PackageElement) enclosing).getQualifiedName().toString();
+ }
+ type = (TypeElement) enclosing;
+ }
+ }
+
+ /**
+ * Returns the simple class name once package is strip package.
+ *
+ * @param s fully qualified class name
+ *
+ * @return simple class name
+ */
+ static String simpleNameOf(String s)
+ {
+ if (s.contains("."))
+ {
+ return s.substring(s.lastIndexOf('.') + 1);
+ } else
+ {
+ return s;
+ }
+ }
+
+ static boolean isClassOfType(Types typeUtils, TypeMirror type, TypeMirror cls)
+ {
+ return type != null && typeUtils.isAssignable(cls, type);
+ }
+
+}
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreElements.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreElements.java
new file mode 100644
index 0000000..6e5e0a2
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreElements.java
@@ -0,0 +1,361 @@
+// 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.internal.common;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.SetMultimap;
+
+import java.lang.annotation.Annotation;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleElementVisitor6;
+
+import static javax.lang.model.element.ElementKind.PACKAGE;
+
+/**
+ * Static utility methods pertaining to {@link Element} instances.
+ */
+@Beta
+public final class MoreElements
+{
+
+ private static final ElementVisitor PACKAGE_ELEMENT_VISITOR =
+ new SimpleElementVisitor6()
+ {
+ @Override
+ protected PackageElement defaultAction(Element e, Void p)
+ {
+ throw new IllegalArgumentException();
+ }
+
+ @Override
+ public PackageElement visitPackage(PackageElement e, Void p)
+ {
+ return e;
+ }
+ };
+ private static final ElementVisitor TYPE_ELEMENT_VISITOR =
+ new SimpleElementVisitor6()
+ {
+ @Override
+ protected TypeElement defaultAction(Element e, Void p)
+ {
+ throw new IllegalArgumentException();
+ }
+
+ @Override
+ public TypeElement visitType(TypeElement e, Void p)
+ {
+ return e;
+ }
+ };
+ private static final ElementVisitor VARIABLE_ELEMENT_VISITOR =
+ new SimpleElementVisitor6()
+ {
+ @Override
+ protected VariableElement defaultAction(Element e, Void p)
+ {
+ throw new IllegalArgumentException();
+ }
+
+ @Override
+ public VariableElement visitVariable(VariableElement e, Void p)
+ {
+ return e;
+ }
+ };
+ private static final ElementVisitor EXECUTABLE_ELEMENT_VISITOR =
+ new SimpleElementVisitor6()
+ {
+ @Override
+ protected ExecutableElement defaultAction(Element e, Void p)
+ {
+ throw new IllegalArgumentException();
+ }
+
+ @Override
+ public ExecutableElement visitExecutable(ExecutableElement e, Void p)
+ {
+ return e;
+ }
+ };
+
+ private MoreElements()
+ {
+ }
+ /**
+ * An alternate implementation of {@link Elements#getPackageOf} that does not require an
+ * {@link Elements} instance.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ */
+ public static PackageElement getPackage(Element element)
+ {
+ while (element.getKind() != PACKAGE)
+ {
+ element = element.getEnclosingElement();
+ }
+ return (PackageElement) element;
+ }
+ /**
+ * Returns the given {@link Element} instance as {@link PackageElement}.
+ *
+ *
This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+ * always be used over that idiom as instructed in the documentation for {@link Element}.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ * @throws IllegalArgumentException if {@code element} isn't a {@link PackageElement}.
+ */
+ public static PackageElement asPackage(Element element)
+ {
+ return element.accept(PACKAGE_ELEMENT_VISITOR, null);
+ }
+ /**
+ * Returns true if the given {@link Element} instance is a {@link TypeElement}.
+ *
+ *
This method is functionally equivalent to an {@code instanceof} check, but should
+ * always be used over that idiom as instructed in the documentation for {@link Element}.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ */
+ public static boolean isType(Element element)
+ {
+ return element.getKind().isClass() || element.getKind().isInterface();
+ }
+ /**
+ * Returns the given {@link Element} instance as {@link TypeElement}.
+ *
+ *
This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+ * always be used over that idiom as instructed in the documentation for {@link Element}.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ * @throws IllegalArgumentException if {@code element} isn't a {@link TypeElement}.
+ */
+ public static TypeElement asType(Element element)
+ {
+ return element.accept(TYPE_ELEMENT_VISITOR, null);
+ }
+ /**
+ * Returns the given {@link Element} instance as {@link VariableElement}.
+ *
+ *
This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+ * always be used over that idiom as instructed in the documentation for {@link Element}.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ * @throws IllegalArgumentException if {@code element} isn't a {@link VariableElement}.
+ */
+ public static VariableElement asVariable(Element element)
+ {
+ return element.accept(VARIABLE_ELEMENT_VISITOR, null);
+ }
+ /**
+ * Returns the given {@link Element} instance as {@link ExecutableElement}.
+ *
+ *
This method is functionally equivalent to an {@code instanceof} check and a cast, but should
+ * always be used over that idiom as instructed in the documentation for {@link Element}.
+ *
+ * @throws NullPointerException if {@code element} is {@code null}
+ * @throws IllegalArgumentException if {@code element} isn't a {@link ExecutableElement}.
+ */
+ public static ExecutableElement asExecutable(Element element)
+ {
+ return element.accept(EXECUTABLE_ELEMENT_VISITOR, null);
+ }
+ /**
+ * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose
+ * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name
+ * as that of {@code annotationClass}. This method is a safer alternative to calling
+ * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
+ * annotation proxies.
+ */
+ public static boolean isAnnotationPresent(Element element,
+ Class extends Annotation> annotationClass)
+ {
+ return getAnnotationMirror(element, annotationClass).isPresent();
+ }
+ /**
+ * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on
+ * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a
+ * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
+ * annotation proxies.
+ */
+ public static Optional getAnnotationMirror(Element element,
+ Class extends Annotation> annotationClass)
+ {
+ String annotationClassName = annotationClass.getCanonicalName();
+ for (AnnotationMirror annotationMirror : element.getAnnotationMirrors())
+ {
+ TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());
+ if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName))
+ {
+ return Optional.of(annotationMirror);
+ }
+ }
+ return Optional.absent();
+ }
+ /**
+ * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
+ * The predicate returns {@code true} if the input {@link Element} has all of the given
+ * {@code modifiers}, perhaps in addition to others.
+ *
+ *
Here is an example how one could get a List of static methods of a class:
+ */
+ public static Predicate hasModifiers(Modifier... modifiers)
+ {
+ return hasModifiers(ImmutableSet.copyOf(modifiers));
+ }
+ /**
+ * Returns a {@link Predicate} that can be used to filter elements by {@link Modifier}.
+ * The predicate returns {@code true} if the input {@link Element} has all of the given
+ * {@code modifiers}, perhaps in addition to others.
+ *
+ *
Here is an example how one could get a List of methods with certain modifiers of a class:
+ */
+ public static Predicate hasModifiers(final Set modifiers)
+ {
+ return new Predicate()
+ {
+ @Override
+ public boolean apply(Element input)
+ {
+ return input.getModifiers().containsAll(modifiers);
+ }
+ };
+ }
+ /**
+ * Returns the set of all non-private methods from {@code type}, including methods that it
+ * inherits from its ancestors. Inherited methods that are overridden are not included in the
+ * result. So if {@code type} defines {@code public String toString()}, the returned set will
+ * contain that method, but not the {@code toString()} method defined by {@code Object}.
+ *
+ *
The returned set may contain more than one method with the same signature, if
+ * {@code type} inherits those methods from different ancestors. For example, if it
+ * inherits from unrelated interfaces {@code One} and {@code Two} which each define
+ * {@code void foo();}, and if it does not itself override the {@code foo()} method,
+ * then both {@code One.foo()} and {@code Two.foo()} will be in the returned set.
+ *
+ * @param type the type whose own and inherited methods are to be returned
+ * @param elementUtils an {@link Elements} object
+ */
+ public static ImmutableSet getLocalAndInheritedMethods(
+ TypeElement type, Elements elementUtils)
+ {
+
+ SetMultimap methodMap = LinkedHashMultimap.create();
+ getLocalAndInheritedMethods(getPackage(type), type, methodMap);
+ // Find methods that are overridden. We do this using `Elements.overrides`, which means
+ // that it is inherently a quadratic operation, since we have to compare every method against
+ // every other method. We reduce the performance impact by (a) grouping methods by name, since
+ // a method cannot override another method with a different name, and (b) making sure that
+ // methods in ancestor types precede those in descendant types, which means we only have to
+ // check a method against the ones that follow it in that order.
+ Set overridden = new LinkedHashSet();
+ for (String methodName : methodMap.keySet())
+ {
+ List methodList = ImmutableList.copyOf(methodMap.get(methodName));
+ for (int i = 0; i < methodList.size(); i++)
+ {
+ ExecutableElement methodI = methodList.get(i);
+ for (int j = i + 1; j < methodList.size(); j++)
+ {
+ ExecutableElement methodJ = methodList.get(j);
+ if (elementUtils.overrides(methodJ, methodI, type))
+ {
+ overridden.add(methodI);
+ }
+ }
+ }
+ }
+ Set methods = new LinkedHashSet(methodMap.values());
+ methods.removeAll(overridden);
+ return ImmutableSet.copyOf(methods);
+ }
+ // Add to `methods` the instance methods from `type` that are visible to code in the
+ // package `pkg`. This means all the instance methods from `type` itself and all instance methods
+ // it inherits from its ancestors, except private methods and package-private methods in other
+ // packages. This method does not take overriding into account, so it will add both an ancestor
+ // method and a descendant method that overrides it.
+ // `methods` is a multimap from a method name to all of the methods with that name, including
+ // methods that override or overload one another. Within those methods, those in ancestor types
+ // always precede those in descendant types.
+ private static void getLocalAndInheritedMethods(
+ PackageElement pkg, TypeElement type, SetMultimap methods)
+ {
+
+ for (TypeMirror superInterface : type.getInterfaces())
+ {
+ getLocalAndInheritedMethods(pkg, com.zeoflow.parcelled.internal.common.MoreTypes.asTypeElement(superInterface), methods);
+ }
+ if (type.getSuperclass().getKind() != TypeKind.NONE)
+ {
+ // Visit the superclass after superinterfaces so we will always see the implementation of a
+ // method after any interfaces that declared it.
+ getLocalAndInheritedMethods(pkg, MoreTypes.asTypeElement(type.getSuperclass()), methods);
+ }
+ for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements()))
+ {
+ if (!method.getModifiers().contains(Modifier.STATIC)
+ && methodVisibleFromPackage(method, pkg))
+ {
+ methods.put(method.getSimpleName().toString(), method);
+ }
+ }
+ }
+ private static boolean methodVisibleFromPackage(ExecutableElement method, PackageElement pkg)
+ {
+ // We use Visibility.ofElement rather than .effectiveVisibilityOfElement because it doesn't
+ // really matter whether the containing class is visible. If you inherit a public method
+ // then you have a public method, regardless of whether you inherit it from a public class.
+ com.zeoflow.parcelled.internal.common.Visibility visibility = Visibility.ofElement(method);
+ switch (visibility)
+ {
+ case PRIVATE:
+ return false;
+ case DEFAULT:
+ return getPackage(method).equals(pkg);
+ default:
+ return true;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreTypes.java b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreTypes.java
new file mode 100644
index 0000000..176b13e
--- /dev/null
+++ b/library-compiler/src/main/java/com/zeoflow/parcelled/internal/common/MoreTypes.java
@@ -0,0 +1,963 @@
+// 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.internal.common;
+
+import com.google.common.base.Equivalence;
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor6;
+import javax.lang.model.util.Types;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static javax.lang.model.type.TypeKind.ARRAY;
+import static javax.lang.model.type.TypeKind.DECLARED;
+import static javax.lang.model.type.TypeKind.EXECUTABLE;
+import static javax.lang.model.type.TypeKind.TYPEVAR;
+import static javax.lang.model.type.TypeKind.WILDCARD;
+
+/**
+ * Utilities related to {@link TypeMirror} instances.
+ */
+public final class MoreTypes
+{
+
+ private static final Class> INTERSECTION_TYPE;
+ private static final Method GET_BOUNDS;
+ private static final TypeVisitor EQUAL_VISITOR =
+ new SimpleTypeVisitor6()
+ {
+ @Override
+ protected Boolean defaultAction(TypeMirror a, EqualVisitorParam p)
+ {
+ return a.getKind().equals(p.type.getKind());
+ }
+
+ @Override
+ public Boolean visitArray(ArrayType a, EqualVisitorParam p)
+ {
+ if (p.type.getKind().equals(ARRAY))
+ {
+ ArrayType b = (ArrayType) p.type;
+ return equal(a.getComponentType(), b.getComponentType(), p.visiting);
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitDeclared(DeclaredType a, EqualVisitorParam p)
+ {
+ if (p.type.getKind().equals(DECLARED))
+ {
+ DeclaredType b = (DeclaredType) p.type;
+ Element aElement = a.asElement();
+ Element bElement = b.asElement();
+ Set newVisiting = visitingSetPlus(
+ p.visiting, aElement, a.getTypeArguments(), bElement, b.getTypeArguments());
+ if (newVisiting.equals(p.visiting))
+ {
+ // We're already visiting this pair of elements.
+ // This can happen for example with Enum in Enum>. Return a
+ // provisional true value since if the Elements are not in fact equal the original
+ // visitor of Enum will discover that. We have to check both Elements being compared
+ // though to avoid missing the fact that one of the types being compared
+ // differs at exactly this point.
+ return true;
+ }
+ return aElement.equals(bElement)
+ && equal(a.getEnclosingType(), a.getEnclosingType(), newVisiting)
+ && equalLists(a.getTypeArguments(), b.getTypeArguments(), newVisiting);
+
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitError(ErrorType a, EqualVisitorParam p)
+ {
+ return a.equals(p.type);
+ }
+
+ @Override
+ public Boolean visitExecutable(ExecutableType a, EqualVisitorParam p)
+ {
+ if (p.type.getKind().equals(EXECUTABLE))
+ {
+ ExecutableType b = (ExecutableType) p.type;
+ return equalLists(a.getParameterTypes(), b.getParameterTypes(), p.visiting)
+ && equal(a.getReturnType(), b.getReturnType(), p.visiting)
+ && equalLists(a.getThrownTypes(), b.getThrownTypes(), p.visiting)
+ && equalLists(a.getTypeVariables(), b.getTypeVariables(), p.visiting);
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitTypeVariable(TypeVariable a, EqualVisitorParam p)
+ {
+ if (p.type.getKind().equals(TYPEVAR))
+ {
+ TypeVariable b = (TypeVariable) p.type;
+ TypeParameterElement aElement = (TypeParameterElement) a.asElement();
+ TypeParameterElement bElement = (TypeParameterElement) b.asElement();
+ Set newVisiting = visitingSetPlus(p.visiting, aElement, bElement);
+ if (newVisiting.equals(p.visiting))
+ {
+ // We're already visiting this pair of elements.
+ // This can happen with our friend Eclipse when looking at >.
+ // It incorrectly reports the upper bound of T as T itself.
+ return true;
+ }
+ // We use aElement.getBounds() instead of a.getUpperBound() to avoid having to deal with
+ // the different way intersection types (like >) are
+ // represented before and after Java 8. We do have an issue that this code may consider
+ // that is different from , but it's very
+ // hard to avoid that, and not likely to be much of a problem in practice.
+ return equalLists(aElement.getBounds(), bElement.getBounds(), newVisiting)
+ && equal(a.getLowerBound(), b.getLowerBound(), newVisiting)
+ && a.asElement().getSimpleName().equals(b.asElement().getSimpleName());
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitWildcard(WildcardType a, EqualVisitorParam p)
+ {
+ if (p.type.getKind().equals(WILDCARD))
+ {
+ WildcardType b = (WildcardType) p.type;
+ return equal(a.getExtendsBound(), b.getExtendsBound(), p.visiting)
+ && equal(a.getSuperBound(), b.getSuperBound(), p.visiting);
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean visitUnknown(TypeMirror a, EqualVisitorParam p)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ private Set visitingSetPlus(
+ Set visiting, Element a, Element b)
+ {
+ ImmutableList noArguments = ImmutableList.of();
+ return visitingSetPlus(visiting, a, noArguments, b, noArguments);
+ }
+
+ private Set visitingSetPlus(
+ Set visiting,
+ Element a, List extends TypeMirror> aArguments,
+ Element b, List extends TypeMirror> bArguments)
+ {
+ ComparedElements comparedElements =
+ new ComparedElements(
+ a, ImmutableList.copyOf(aArguments),
+ b, ImmutableList.copyOf(bArguments));
+ Set newVisiting = new HashSet(visiting);
+ newVisiting.add(comparedElements);
+ return newVisiting;
+ }
+ };
+ private static final int HASH_SEED = 17;
+ private static final int HASH_MULTIPLIER = 31;
+ private static final TypeVisitor> HASH_VISITOR =
+ new SimpleTypeVisitor6>()
+ {
+ int hashKind(int seed, TypeMirror t)
+ {
+ int result = seed * HASH_MULTIPLIER;
+ result += t.getKind().hashCode();
+ return result;
+ }
+
+ @Override
+ protected Integer defaultAction(TypeMirror e, Set visiting)
+ {
+ return hashKind(HASH_SEED, e);
+ }
+
+ @Override
+ public Integer visitArray(ArrayType t, Set visiting)
+ {
+ int result = hashKind(HASH_SEED, t);
+ result *= HASH_MULTIPLIER;
+ result += t.getComponentType().accept(this, visiting);
+ return result;
+ }
+
+ @Override
+ public Integer visitDeclared(DeclaredType t, Set visiting)
+ {
+ Element element = t.asElement();
+ if (visiting.contains(element))
+ {
+ return 0;
+ }
+ Set newVisiting = new HashSet(visiting);
+ newVisiting.add(element);
+ int result = hashKind(HASH_SEED, t);
+ result *= HASH_MULTIPLIER;
+ result += t.asElement().hashCode();
+ result *= HASH_MULTIPLIER;
+ result += t.getEnclosingType().accept(this, newVisiting);
+ result *= HASH_MULTIPLIER;
+ result += hashList(t.getTypeArguments(), newVisiting);
+ return result;
+ }
+
+ @Override
+ public Integer visitExecutable(ExecutableType t, Set visiting)
+ {
+ int result = hashKind(HASH_SEED, t);
+ result *= HASH_MULTIPLIER;
+ result += hashList(t.getParameterTypes(), visiting);
+ result *= HASH_MULTIPLIER;
+ result += t.getReturnType().accept(this, visiting);
+ result *= HASH_MULTIPLIER;
+ result += hashList(t.getThrownTypes(), visiting);
+ result *= HASH_MULTIPLIER;
+ result += hashList(t.getTypeVariables(), visiting);
+ return result;
+ }
+
+ @Override
+ public Integer visitTypeVariable(TypeVariable t, Set visiting)
+ {
+ int result = hashKind(HASH_SEED, t);
+ result *= HASH_MULTIPLIER;
+ result += t.getLowerBound().accept(this, visiting);
+ TypeParameterElement element = (TypeParameterElement) t.asElement();
+ for (TypeMirror bound : element.getBounds())
+ {
+ result *= HASH_MULTIPLIER;
+ result += bound.accept(this, visiting);
+ }
+ return result;
+ }
+
+ @Override
+ public Integer visitWildcard(WildcardType t, Set visiting)
+ {
+ int result = hashKind(HASH_SEED, t);
+ result *= HASH_MULTIPLIER;
+ result +=
+ (t.getExtendsBound() == null) ? 0 : t.getExtendsBound().accept(this, visiting);
+ result *= HASH_MULTIPLIER;
+ result += (t.getSuperBound() == null) ? 0 : t.getSuperBound().accept(this, visiting);
+ return result;
+ }
+
+ @Override
+ public Integer visitUnknown(TypeMirror t, Set visiting)
+ {
+ throw new UnsupportedOperationException();
+ }
+ };
+ private static final Equivalence TYPE_EQUIVALENCE = new Equivalence()
+ {
+ @Override
+ protected boolean doEquivalent(TypeMirror a, TypeMirror b)
+ {
+ return MoreTypes.equal(a, b, ImmutableSet.of());
+ }
+
+ @Override
+ protected int doHash(TypeMirror t)
+ {
+ return MoreTypes.hash(t, ImmutableSet.of());
+ }
+ };
+ private static final TypeVisitor AS_ELEMENT_VISITOR =
+ new SimpleTypeVisitor6()
+ {
+ @Override
+ protected Element defaultAction(TypeMirror e, Void p)
+ {
+ throw new IllegalArgumentException(e + "cannot be converted to an Element");
+ }
+
+ @Override
+ public Element visitDeclared(DeclaredType t, Void p)
+ {
+ return t.asElement();
+ }
+
+ @Override
+ public Element visitError(ErrorType t, Void p)
+ {
+ return t.asElement();
+ }
+
+ @Override
+ public Element visitTypeVariable(TypeVariable t, Void p)
+ {
+ return t.asElement();
+ }
+ };
+ static
+ {
+ Class> c;
+ Method m;
+ try
+ {
+ c = Class.forName("javax.lang.model.type.IntersectionType");
+ m = c.getMethod("getBounds");
+ } catch (Exception e)
+ {
+ c = null;
+ m = null;
+ }
+ INTERSECTION_TYPE = c;
+ GET_BOUNDS = m;
+ }
+
+ private MoreTypes()
+ {
+ }
+ public static Equivalence equivalence()
+ {
+ return TYPE_EQUIVALENCE;
+ }
+ private static boolean equal(TypeMirror a, TypeMirror b, Set visiting)
+ {
+ // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can
+ // assume that if it does return true then the types are equal. This check also avoids getting
+ // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in
+ // > is a distinct but equal K.
+ // The javac implementation of ExecutableType, at least in some versions, does not take thrown
+ // exceptions into account in its equals implementation, so avoid this optimization for
+ // ExecutableType.
+ if (Objects.equal(a, b) && !(a instanceof ExecutableType))
+ {
+ return true;
+ }
+ EqualVisitorParam p = new EqualVisitorParam();
+ p.type = b;
+ p.visiting = visiting;
+ if (INTERSECTION_TYPE != null)
+ {
+ if (isIntersectionType(a))
+ {
+ return equalIntersectionTypes(a, b, visiting);
+ } else if (isIntersectionType(b))
+ {
+ return false;
+ }
+ }
+ return (a == b) || (a != null && b != null && a.accept(EQUAL_VISITOR, p));
+ }
+ private static boolean isIntersectionType(TypeMirror t)
+ {
+ return t != null && t.getKind().name().equals("INTERSECTION");
+ }
+ // The representation of an intersection type, as in >, changed
+ // between Java 7 and Java 8. In Java 7 it was modeled as a fake DeclaredType, and our logic
+ // for DeclaredType does the right thing. In Java 8 it is modeled as a new type IntersectionType.
+ // In order for our code to run on Java 7 (and Java 6) we can't even mention IntersectionType,
+ // so we can't override visitIntersectionType(IntersectionType). Instead, we discover through
+ // reflection whether IntersectionType exists, and if it does we extract the bounds of the
+ // intersection ((Number, Comparable) in the example) and compare them directly.
+ @SuppressWarnings("unchecked")
+ private static boolean equalIntersectionTypes(
+ TypeMirror a, TypeMirror b, Set visiting)
+ {
+ if (!isIntersectionType(b))
+ {
+ return false;
+ }
+ List extends TypeMirror> aBounds;
+ List extends TypeMirror> bBounds;
+ try
+ {
+ aBounds = (List extends TypeMirror>) GET_BOUNDS.invoke(a);
+ bBounds = (List extends TypeMirror>) GET_BOUNDS.invoke(b);
+ } catch (Exception e)
+ {
+ throw Throwables.propagate(e);
+ }
+ return equalLists(aBounds, bBounds, visiting);
+ }
+ private static boolean equalLists(
+ List extends TypeMirror> a, List extends TypeMirror> b,
+ Set visiting)
+ {
+ int size = a.size();
+ if (size != b.size())
+ {
+ return false;
+ }
+ // Use iterators in case the Lists aren't RandomAccess
+ Iterator extends TypeMirror> aIterator = a.iterator();
+ Iterator extends TypeMirror> bIterator = b.iterator();
+ while (aIterator.hasNext())
+ {
+ if (!bIterator.hasNext())
+ {
+ return false;
+ }
+ TypeMirror nextMirrorA = aIterator.next();
+ TypeMirror nextMirrorB = bIterator.next();
+ if (!equal(nextMirrorA, nextMirrorB, visiting))
+ {
+ return false;
+ }
+ }
+ return !aIterator.hasNext();
+ }
+ private static int hashList(List extends TypeMirror> mirrors, Set visiting)
+ {
+ int result = HASH_SEED;
+ for (TypeMirror mirror : mirrors)
+ {
+ result *= HASH_MULTIPLIER;
+ result += hash(mirror, visiting);
+ }
+ return result;
+ }
+
+ private static int hash(TypeMirror mirror, Set visiting)
+ {
+ return mirror == null ? 0 : mirror.accept(HASH_VISITOR, visiting);
+ }
+
+ /**
+ * Returns the set of {@linkplain TypeElement types} that are referenced by the given
+ * {@link TypeMirror}.
+ */
+ public static ImmutableSet referencedTypes(TypeMirror type)
+ {
+ checkNotNull(type);
+ ImmutableSet.Builder elements = ImmutableSet.builder();
+ type.accept(new SimpleTypeVisitor6>()
+ {
+ @Override
+ public Void visitArray(ArrayType t, ImmutableSet.Builder p)
+ {
+ t.getComponentType().accept(this, p);
+ return null;
+ }
+
+ @Override
+ public Void visitDeclared(DeclaredType t, ImmutableSet.Builder p)
+ {
+ p.add(MoreElements.asType(t.asElement()));
+ for (TypeMirror typeArgument : t.getTypeArguments())
+ {
+ typeArgument.accept(this, p);
+ }
+ return null;
+ }
+
+ @Override
+ public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder p)
+ {
+ t.getLowerBound().accept(this, p);
+ t.getUpperBound().accept(this, p);
+ return null;
+ }
+
+ @Override
+ public Void visitWildcard(WildcardType t, ImmutableSet.Builder p)
+ {
+ TypeMirror extendsBound = t.getExtendsBound();
+ if (extendsBound != null)
+ {
+ extendsBound.accept(this, p);
+ }
+ TypeMirror superBound = t.getSuperBound();
+ if (superBound != null)
+ {
+ superBound.accept(this, p);
+ }
+ return null;
+ }
+ }, elements);
+ return elements.build();
+ }
+
+ /**
+ * An alternate implementation of {@link Types#asElement} that does not require a {@link Types}
+ * instance with the notable difference that it will throw {@link IllegalArgumentException}
+ * instead of returning null if the {@link TypeMirror} can not be converted to an {@link Element}.
+ *
+ * @throws NullPointerException if {@code typeMirror} is {@code null}
+ * @throws IllegalArgumentException if {@code typeMirror} cannot be converted to an
+ * {@link Element}
+ */
+ public static Element asElement(TypeMirror typeMirror)
+ {
+ return typeMirror.accept(AS_ELEMENT_VISITOR, null);
+ }
+ // TODO(gak): consider removing these two methods as they're pretty trivial now
+ public static TypeElement asTypeElement(TypeMirror mirror)
+ {
+ return MoreElements.asType(asElement(mirror));
+ }
+ public static ImmutableSet asTypeElements(Iterable extends TypeMirror> mirrors)
+ {
+ checkNotNull(mirrors);
+ ImmutableSet.Builder builder = ImmutableSet.builder();
+ for (TypeMirror mirror : mirrors)
+ {
+ builder.add(asTypeElement(mirror));
+ }
+ return builder.build();
+ }
+ /**
+ * Returns a {@link ArrayType} if the {@link TypeMirror} represents a primitive array or
+ * throws an {@link IllegalArgumentException}.
+ */
+ public static ArrayType asArray(TypeMirror maybeArrayType)
+ {
+ return maybeArrayType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public ArrayType visitArray(ArrayType type, String ignore)
+ {
+ return type;
+ }
+ }, "primitive array");
+ }
+ /**
+ * Returns a {@link DeclaredType} if the {@link TypeMirror} represents a declared type such
+ * as a class, interface, union/compound, or enum or throws an {@link IllegalArgumentException}.
+ */
+ public static DeclaredType asDeclared(TypeMirror maybeDeclaredType)
+ {
+ return maybeDeclaredType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public DeclaredType visitDeclared(DeclaredType type, String ignored)
+ {
+ return type;
+ }
+ }, "declared type");
+ }
+ /**
+ * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
+ * as may result from missing code, or bad compiles or throws an {@link IllegalArgumentException}.
+ */
+ public static ErrorType asError(TypeMirror maybeErrorType)
+ {
+ return maybeErrorType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public ErrorType visitError(ErrorType type, String p)
+ {
+ return type;
+ }
+ }, "error type");
+ }
+ /**
+ * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
+ * as a method, constructor, or initializer or throws an {@link IllegalArgumentException}.
+ */
+ public static ExecutableType asExecutable(TypeMirror maybeExecutableType)
+ {
+ return maybeExecutableType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public ExecutableType visitExecutable(ExecutableType type, String p)
+ {
+ return type;
+ }
+ }, "executable type");
+ }
+ /**
+ * Returns a {@link NoType} if the {@link TypeMirror} represents an non-type such
+ * as void, or package, etc. or throws an {@link IllegalArgumentException}.
+ */
+ public static NoType asNoType(TypeMirror maybeNoType)
+ {
+ return maybeNoType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public NoType visitNoType(NoType noType, String p)
+ {
+ return noType;
+ }
+ }, "non-type");
+ }
+ /**
+ * Returns a {@link NullType} if the {@link TypeMirror} represents the null type
+ * or throws an {@link IllegalArgumentException}.
+ */
+ public static NullType asNullType(TypeMirror maybeNullType)
+ {
+ return maybeNullType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public NullType visitNull(NullType nullType, String p)
+ {
+ return nullType;
+ }
+ }, "null");
+ }
+ /**
+ * Returns a {@link PrimitiveType} if the {@link TypeMirror} represents a primitive type
+ * or throws an {@link IllegalArgumentException}.
+ */
+ public static PrimitiveType asPrimitiveType(TypeMirror maybePrimitiveType)
+ {
+ return maybePrimitiveType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public PrimitiveType visitPrimitive(PrimitiveType type, String p)
+ {
+ return type;
+ }
+ }, "primitive type");
+ }
+ /**
+ * Returns a {@link TypeVariable} if the {@link TypeMirror} represents a type variable
+ * or throws an {@link IllegalArgumentException}.
+ */
+ public static TypeVariable asTypeVariable(TypeMirror maybeTypeVariable)
+ {
+ return maybeTypeVariable.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public TypeVariable visitTypeVariable(TypeVariable type, String p)
+ {
+ return type;
+ }
+ }, "type variable");
+ }
+
+ //
+ // visitUnionType would go here, but it is a 1.7 API.
+ //
+ /**
+ * Returns a {@link WildcardType} if the {@link TypeMirror} represents a wildcard type
+ * or throws an {@link IllegalArgumentException}.
+ */
+ public static WildcardType asWildcard(WildcardType maybeWildcardType)
+ {
+ return maybeWildcardType.accept(new CastingTypeVisitor()
+ {
+ @Override
+ public WildcardType visitWildcard(WildcardType type, String p)
+ {
+ return type;
+ }
+ }, "wildcard type");
+ }
+ /**
+ * Returns true if the raw type underlying the given {@link TypeMirror} represents a type that can
+ * be referenced by a {@link Class}. If this returns true, then {@link #isTypeOf} is guaranteed to
+ * not throw.
+ */
+ public static boolean isType(TypeMirror type)
+ {
+ return type.accept(new SimpleTypeVisitor6()
+ {
+ @Override
+ protected Boolean defaultAction(TypeMirror type, Void ignored)
+ {
+ return false;
+ }
+
+ @Override
+ public Boolean visitNoType(NoType noType, Void p)
+ {
+ return noType.getKind().equals(TypeKind.VOID);
+ }
+
+ @Override
+ public Boolean visitPrimitive(PrimitiveType type, Void p)
+ {
+ return true;
+ }
+
+ @Override
+ public Boolean visitArray(ArrayType array, Void p)
+ {
+ return true;
+ }
+
+ @Override
+ public Boolean visitDeclared(DeclaredType type, Void ignored)
+ {
+ return MoreElements.isType(type.asElement());
+ }
+ }, null);
+ }
+ /**
+ * Returns true if the raw type underlying the given {@link TypeMirror} represents the
+ * same raw type as the given {@link Class} and throws an IllegalArgumentException if the
+ * {@link TypeMirror} does not represent a type that can be referenced by a {@link Class}
+ */
+ public static boolean isTypeOf(final Class> clazz, TypeMirror type)
+ {
+ checkNotNull(clazz);
+ return type.accept(new SimpleTypeVisitor6()
+ {
+ @Override
+ protected Boolean defaultAction(TypeMirror type, Void ignored)
+ {
+ throw new IllegalArgumentException(type + " cannot be represented as a Class>.");
+ }
+
+ @Override
+ public Boolean visitNoType(NoType noType, Void p)
+ {
+ if (noType.getKind().equals(TypeKind.VOID))
+ {
+ return clazz.equals(Void.TYPE);
+ }
+ throw new IllegalArgumentException(noType + " cannot be represented as a Class>.");
+ }
+
+ @Override
+ public Boolean visitPrimitive(PrimitiveType type, Void p)
+ {
+ switch (type.getKind())
+ {
+ case BOOLEAN:
+ return clazz.equals(Boolean.TYPE);
+ case BYTE:
+ return clazz.equals(Byte.TYPE);
+ case CHAR:
+ return clazz.equals(Character.TYPE);
+ case DOUBLE:
+ return clazz.equals(Double.TYPE);
+ case FLOAT:
+ return clazz.equals(Float.TYPE);
+ case INT:
+ return clazz.equals(Integer.TYPE);
+ case LONG:
+ return clazz.equals(Long.TYPE);
+ case SHORT:
+ return clazz.equals(Short.TYPE);
+ default:
+ throw new IllegalArgumentException(type + " cannot be represented as a Class>.");
+ }
+ }
+
+ @Override
+ public Boolean visitArray(ArrayType array, Void p)
+ {
+ return clazz.isArray()
+ && isTypeOf(clazz.getComponentType(), array.getComponentType());
+ }
+
+ @Override
+ public Boolean visitDeclared(DeclaredType type, Void ignored)
+ {
+ TypeElement typeElement;
+ try
+ {
+ typeElement = MoreElements.asType(type.asElement());
+ } catch (IllegalArgumentException iae)
+ {
+ throw new IllegalArgumentException(type + " does not represent a class or interface.");
+ }
+ return typeElement.getQualifiedName().contentEquals(clazz.getCanonicalName());
+ }
+ }, null);
+ }
+ /**
+ * Returns the non-object superclass of the type with the proper type parameters.
+ * An absent Optional is returned if there is no non-Object superclass.
+ */
+ public static Optional nonObjectSuperclass(final Types types, Elements elements,
+ DeclaredType type)
+ {
+ checkNotNull(types);
+ checkNotNull(elements);
+ checkNotNull(type);
+
+ final TypeMirror objectType =
+ elements.getTypeElement(Object.class.getCanonicalName()).asType();
+ // It's guaranteed there's only a single CLASS superclass because java doesn't have multiple
+ // class inheritance.
+ TypeMirror superclass = getOnlyElement(FluentIterable.from(types.directSupertypes(type))
+ .filter(new Predicate()
+ {
+ @Override
+ public boolean apply(TypeMirror input)
+ {
+ return input.getKind().equals(TypeKind.DECLARED)
+ && (MoreElements.asType(
+ MoreTypes.asDeclared(input).asElement())).getKind().equals(ElementKind.CLASS)
+ && !types.isSameType(objectType, input);
+ }
+ }), null);
+ return superclass != null
+ ? Optional.of(MoreTypes.asDeclared(superclass))
+ : Optional.absent();
+ }
+ /**
+ * Resolves a {@link VariableElement} parameter to a method or constructor based on the given
+ * container, or a member of a class. For parameters to a method or constructor, the variable's
+ * enclosing element must be a supertype of the container type. For example, given a
+ * {@code container} of type {@code Set}, and a variable corresponding to the {@code E e}
+ * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
+ */
+ public static TypeMirror asMemberOf(Types types, DeclaredType container,
+ VariableElement variable)
+ {
+ if (variable.getKind().equals(ElementKind.PARAMETER))
+ {
+ ExecutableElement methodOrConstructor =
+ MoreElements.asExecutable(variable.getEnclosingElement());
+ ExecutableType resolvedMethodOrConstructor = MoreTypes.asExecutable(
+ types.asMemberOf(container, methodOrConstructor));
+ List extends VariableElement> parameters = methodOrConstructor.getParameters();
+ List extends TypeMirror> parameterTypes =
+ resolvedMethodOrConstructor.getParameterTypes();
+ checkState(parameters.size() == parameterTypes.size());
+ for (int i = 0; i < parameters.size(); i++)
+ {
+ // We need to capture the parameter type of the variable we're concerned about,
+ // for later printing. This is the only way to do it since we can't use
+ // types.asMemberOf on variables of methods.
+ if (parameters.get(i).equals(variable))
+ {
+ return parameterTypes.get(i);
+ }
+ }
+ throw new IllegalStateException("Could not find variable: " + variable);
+ } else
+ {
+ return types.asMemberOf(container, variable);
+ }
+ }
+
+ // So EQUAL_VISITOR can be a singleton, we maintain visiting state, in particular which types
+ // have been seen already, in this object.
+ // The logic for handling recursive types like Comparable> is very tricky.
+ // If we're not careful we'll end up with an infinite recursion. So we record the types that
+ // we've already seen during the recursion, and if we see the same pair of types again we just
+ // return true provisionally. But "the same pair of types" is itself poorly-defined. We can't
+ // just say that it is an equal pair of TypeMirrors, because of course if we knew how to
+ // determine that then we wouldn't need the complicated type visitor at all. On the other hand,
+ // we can't say that it is an identical pair of TypeMirrors either, because there's no
+ // guarantee that the TypeMirrors for the two Ts in Comparable> will be
+ // represented by the same object, and indeed with the Eclipse compiler they aren't. We could
+ // compare the corresponding Elements, since equality is well-defined there, but that's not enough
+ // either, because the Element for Set