[codegen] Support nested classes

Adds support for arbitrarily-nested @DataClasses
Only static ones are supported for now

See FileInfo for the main implementation piece

Fixes: 139833958
Test: . frameworks/base/tests/Codegen/runTest.sh
Change-Id: I31cd16969788c47003a7a15a3573a4bf623ab960
This commit is contained in:
Eugene Susla
2019-10-22 17:32:08 -07:00
parent 4a0b1750ef
commit 322e8b1772
19 changed files with 1162 additions and 363 deletions

View File

@@ -17,6 +17,7 @@ else
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java && \
(
cd $ANDROID_BUILD_TOP &&
header_and_eval mmma -j16 frameworks/base/tests/Codegen && \

View File

@@ -32,7 +32,7 @@ public class HierrarchicalDataClassBase implements Parcelable {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -98,11 +98,15 @@ public class HierrarchicalDataClassBase implements Parcelable {
};
@DataClass.Generated(
time = 1571258914826L,
codegenVersion = "1.0.9",
time = 1572630437620L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -46,7 +46,7 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -120,11 +120,15 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase {
};
@DataClass.Generated(
time = 1571258915848L,
codegenVersion = "1.0.9",
time = 1572630438646L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -52,7 +52,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -361,7 +361,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable {
}
/** Builds the instance. This builder should not be touched after calling this! */
public ParcelAllTheThingsDataClass build() {
public @NonNull ParcelAllTheThingsDataClass build() {
checkNotUsed();
mBuilderFieldsSet |= 0x100; // Mark builder used
@@ -410,11 +410,15 @@ public class ParcelAllTheThingsDataClass implements Parcelable {
}
@DataClass.Generated(
time = 1571258913802L,
codegenVersion = "1.0.9",
time = 1572630436563L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -342,7 +342,7 @@ public final class SampleDataClass implements Parcelable {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -1430,7 +1430,7 @@ public final class SampleDataClass implements Parcelable {
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static class Builder {
public static final class Builder {
private int mNum;
private int mNum2;
@@ -1793,7 +1793,7 @@ public final class SampleDataClass implements Parcelable {
}
/** Builds the instance. This builder should not be touched after calling this! */
public SampleDataClass build() {
public @NonNull SampleDataClass build() {
checkNotUsed();
mBuilderFieldsSet |= 0x100000; // Mark builder used
@@ -1872,11 +1872,15 @@ public final class SampleDataClass implements Parcelable {
}
@DataClass.Generated(
time = 1571258911688L,
codegenVersion = "1.0.9",
time = 1572630434434L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -243,6 +243,26 @@ public class SampleDataClassTest {
assertEquals(instance.toString(), unparceledInstance.toString());
}
@Test
public void testNestedDataClasses_notMangledWhenParceled() {
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass("1"),
SampleWithNestedDataClasses.NestedDataClass.CREATOR);
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass2("2"),
SampleWithNestedDataClasses.NestedDataClass2.CREATOR);
assertEqualsAfterParcelling(
new SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3(3),
SampleWithNestedDataClasses.NestedDataClass2.NestedDataClass3.CREATOR);
}
private static <T extends Parcelable> void assertEqualsAfterParcelling(
T p, Parcelable.Creator<T> creator) {
assertEquals(p, parcelAndUnparcel(p, creator));
}
private static <T extends Parcelable> T parcelAndUnparcel(
T original, Parcelable.Creator<T> creator) {
Parcel p = Parcel.obtain();

View File

@@ -85,7 +85,7 @@ public class SampleWithCustomBuilder implements Parcelable {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -224,7 +224,7 @@ public class SampleWithCustomBuilder implements Parcelable {
}
/** Builds the instance. This builder should not be touched after calling this! */
public SampleWithCustomBuilder build() {
public @NonNull SampleWithCustomBuilder build() {
checkNotUsed();
mBuilderFieldsSet |= 0x8; // Mark builder used
@@ -253,11 +253,15 @@ public class SampleWithCustomBuilder implements Parcelable {
}
@DataClass.Generated(
time = 1571258912752L,
codegenVersion = "1.0.9",
time = 1572630435484L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -0,0 +1,390 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.codegentest;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
/**
* An example of deeply nested data classes
*/
public class SampleWithNestedDataClasses {
int mFoo = 0;
@DataClass(genEqualsHashCode = true)
public static class NestedDataClass implements Parcelable {
@NonNull String mBar;
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@DataClass.Generated.Member
public NestedDataClass(
@NonNull String bar) {
this.mBar = bar;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBar);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public @NonNull String getBar() {
return mBar;
}
@Override
@DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(NestedDataClass other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
NestedDataClass that = (NestedDataClass) o;
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mBar, that.mBar);
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mBar);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeString(mBar);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected NestedDataClass(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
String bar = in.readString();
this.mBar = bar;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBar);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<NestedDataClass> CREATOR
= new Parcelable.Creator<NestedDataClass>() {
@Override
public NestedDataClass[] newArray(int size) {
return new NestedDataClass[size];
}
@Override
public NestedDataClass createFromParcel(@NonNull Parcel in) {
return new NestedDataClass(in);
}
};
@DataClass.Generated(
time = 1572630440713L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}
@DataClass(genEqualsHashCode = true)
public static class NestedDataClass2 implements Parcelable {
@NonNull String mBaz;
@DataClass(genEqualsHashCode = true)
public static class NestedDataClass3 implements Parcelable {
@NonNull long mBaz2;
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@DataClass.Generated.Member
public NestedDataClass3(
@NonNull long baz2) {
this.mBaz2 = baz2;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBaz2);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public @NonNull long getBaz2() {
return mBaz2;
}
@Override
@DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(NestedDataClass3 other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
NestedDataClass3 that = (NestedDataClass3) o;
//noinspection PointlessBooleanExpression
return true
&& mBaz2 == that.mBaz2;
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + Long.hashCode(mBaz2);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeLong(mBaz2);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected NestedDataClass3(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
long baz2 = in.readLong();
this.mBaz2 = baz2;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBaz2);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<NestedDataClass3> CREATOR
= new Parcelable.Creator<NestedDataClass3>() {
@Override
public NestedDataClass3[] newArray(int size) {
return new NestedDataClass3[size];
}
@Override
public NestedDataClass3 createFromParcel(@NonNull Parcel in) {
return new NestedDataClass3(in);
}
};
@DataClass.Generated(
time = 1572630440724L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@DataClass.Generated.Member
public NestedDataClass2(
@NonNull String baz) {
this.mBaz = baz;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBaz);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public @NonNull String getBaz() {
return mBaz;
}
@Override
@DataClass.Generated.Member
public boolean equals(@android.annotation.Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(NestedDataClass2 other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
NestedDataClass2 that = (NestedDataClass2) o;
//noinspection PointlessBooleanExpression
return true
&& java.util.Objects.equals(mBaz, that.mBaz);
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + java.util.Objects.hashCode(mBaz);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeString(mBaz);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected NestedDataClass2(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
String baz = in.readString();
this.mBaz = baz;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mBaz);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<NestedDataClass2> CREATOR
= new Parcelable.Creator<NestedDataClass2>() {
@Override
public NestedDataClass2[] newArray(int size) {
return new NestedDataClass2[size];
}
@Override
public NestedDataClass2 createFromParcel(@NonNull Parcel in) {
return new NestedDataClass2(in);
}
};
@DataClass.Generated(
time = 1572630440729L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}
void someCode() {}
}

View File

@@ -51,7 +51,7 @@ public class StaleDataclassDetectorFalsePositivesTest {
// Code below generated by codegen v1.0.9.
// Code below generated by codegen v1.0.11.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -65,11 +65,15 @@ public class StaleDataclassDetectorFalsePositivesTest {
@DataClass.Generated(
time = 1571258916868L,
codegenVersion = "1.0.9",
time = 1572630439617L,
codegenVersion = "1.0.11",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -1,47 +1,15 @@
package com.android.codegen
import com.github.javaparser.ParseProblemException
import com.github.javaparser.ParseResult
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
open class ClassInfo(val sourceLines: List<String>) {
open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) {
private val userSourceCode = (sourceLines + "}").joinToString("\n")
val fileAst: CompilationUnit = try {
JAVA_PARSER.parse(userSourceCode).throwIfFailed()
} catch (e: ParseProblemException) {
throw parseFailed(cause = e)
}
val fileAst = fileInfo.fileAst
fun <T> ParseResult<T>.throwIfFailed(): T {
if (problems.isNotEmpty()) {
throw parseFailed(
desc = this@throwIfFailed.problems.joinToString("\n"),
cause = this@throwIfFailed.problems.mapNotNull { it.cause.orElse(null) }.firstOrNull())
}
return result.get()
}
private fun parseFailed(cause: Throwable? = null, desc: String = ""): RuntimeException {
return RuntimeException("Failed to parse code:\n" +
userSourceCode
.lines()
.mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" }
.joinToString("\n") + "\n$desc",
cause)
}
val classAst = fileAst.types[0] as ClassOrInterfaceDeclaration
val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
val superInterfaces = (fileAst.types[0] as ClassOrInterfaceDeclaration)
.implementedTypes.map { it.asString() }
val superClass = run {
val superClasses = (fileAst.types[0] as ClassOrInterfaceDeclaration).extendedTypes
if (superClasses.isNonEmpty) superClasses[0] else null
}
val superInterfaces = classAst.implementedTypes.map { it.asString() }
val superClass = classAst.extendedTypes.getOrNull(0)
val ClassName = classAst.nameAsString
private val genericArgsAst = classAst.typeParameters

View File

@@ -11,36 +11,12 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType
* [ClassInfo] + utilities for printing out new class code with proper indentation and imports
*/
class ClassPrinter(
source: List<String>,
private val stringBuilder: StringBuilder,
var cliArgs: Array<String>
) : ClassInfo(source) {
classAst: ClassOrInterfaceDeclaration,
fileInfo: FileInfo
) : ClassInfo(classAst, fileInfo), Printer<ClassPrinter>, ImportsProvider {
val GENERATED_MEMBER_HEADER by lazy { "@$GeneratedMember" }
// Imports
val NonNull by lazy { classRef("android.annotation.NonNull") }
val NonEmpty by lazy { classRef("android.annotation.NonEmpty") }
val Nullable by lazy { classRef("android.annotation.Nullable") }
val TextUtils by lazy { classRef("android.text.TextUtils") }
val LinkedHashMap by lazy { classRef("java.util.LinkedHashMap") }
val Collections by lazy { classRef("java.util.Collections") }
val Preconditions by lazy { classRef("com.android.internal.util.Preconditions") }
val ArrayList by lazy { classRef("java.util.ArrayList") }
val DataClass by lazy { classRef("com.android.internal.util.DataClass") }
val DataClassEnum by lazy { classRef("com.android.internal.util.DataClass.Enum") }
val ParcelWith by lazy { classRef("com.android.internal.util.DataClass.ParcelWith") }
val PluralOf by lazy { classRef("com.android.internal.util.DataClass.PluralOf") }
val Each by lazy { classRef("com.android.internal.util.DataClass.Each") }
val DataClassGenerated by lazy { classRef("com.android.internal.util.DataClass.Generated") }
val DataClassSuppressConstDefs by lazy { classRef("com.android.internal.util.DataClass.SuppressConstDefsGeneration") }
val DataClassSuppress by lazy { classRef("com.android.internal.util.DataClass.Suppress") }
val GeneratedMember by lazy { classRef("com.android.internal.util.DataClass.Generated.Member") }
val Parcelling by lazy { classRef("com.android.internal.util.Parcelling") }
val Parcelable by lazy { classRef("android.os.Parcelable") }
val Parcel by lazy { classRef("android.os.Parcel") }
val UnsupportedAppUsage by lazy { classRef("android.annotation.UnsupportedAppUsage") }
init {
val fieldsWithMissingNullablity = fields.filter { field ->
!field.isPrimitive
@@ -60,49 +36,60 @@ class ClassPrinter(
}
}
/**
* Optionally shortens a class reference if there's a corresponding import present
*/
fun classRef(fullName: String): String {
if (cliArgs.contains(FLAG_NO_FULL_QUALIFIERS)) {
return fullName.split(".").dropWhile { it[0].isLowerCase() }.joinToString(".")
}
val cliArgs get() = fileInfo.cliArgs
val pkg = fullName.substringBeforeLast(".")
val simpleName = fullName.substringAfterLast(".")
if (fileAst.imports.any { imprt ->
imprt.nameAsString == fullName
|| (imprt.isAsterisk && imprt.nameAsString == pkg)
}) {
return simpleName
} else {
val outerClass = pkg.substringAfterLast(".", "")
if (outerClass.firstOrNull()?.isUpperCase() == true) {
return classRef(pkg) + "." + simpleName
}
fun print() {
currentIndent = fileInfo.sourceLines
.find { "class $ClassName" in it }!!
.takeWhile { it.isWhitespace() }
.plus(INDENT_SINGLE)
+fileInfo.generatedWarning
if (FeatureFlag.CONST_DEFS()) generateConstDefs()
if (FeatureFlag.CONSTRUCTOR()) {
generateConstructor("public")
} else if (FeatureFlag.BUILDER()
|| FeatureFlag.COPY_CONSTRUCTOR()
|| FeatureFlag.WITHERS()) {
generateConstructor("/* package-private */")
}
return fullName
if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.GETTERS()) generateGetters()
if (FeatureFlag.SETTERS()) generateSetters()
if (FeatureFlag.TO_STRING()) generateToString()
if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode()
if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
if (FeatureFlag.WITHERS()) generateWithers()
if (FeatureFlag.PARCELABLE()) generateParcelable()
if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon()
if (FeatureFlag.BUILDER()) generateBuilder()
if (FeatureFlag.AIDL()) fileInfo.generateAidl() //TODO guard against nested classes requesting aidl
generateMetadata(fileInfo.file)
+"""
//@formatter:on
$GENERATED_END
"""
rmEmptyLine()
}
/** @see classRef */
inline fun <reified T : Any> classRef(): String {
return classRef(T::class.java.name)
}
override var currentIndent: String
get() = fileInfo.currentIndent
set(value) { fileInfo.currentIndent = value }
override val stringBuilder get() = fileInfo.stringBuilder
/** @see classRef */
fun memberRef(fullName: String): String {
val className = fullName.substringBeforeLast(".")
val methodName = fullName.substringAfterLast(".")
return if (fileAst.imports.any {
it.isStatic
&& (it.nameAsString == fullName
|| (it.isAsterisk && it.nameAsString == className))
}) {
className.substringAfterLast(".") + "." + methodName
} else {
classRef(className) + "." + methodName
}
}
val dataClassAnnotationFeatures = classAst.annotations
.find { it.nameAsString == DataClass }
@@ -143,7 +130,7 @@ class ClassPrinter(
|| onByDefault
FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
FeatureFlag.AIDL -> FeatureFlag.PARCELABLE()
FeatureFlag.AIDL -> fileInfo.mainClass.nameAsString == ClassName && FeatureFlag.PARCELABLE()
FeatureFlag.IMPLICIT_NONNULL -> fields.any { it.isNullable }
&& fields.none { "@$NonNull" in it.annotations }
else -> onByDefault
@@ -163,162 +150,7 @@ class ClassPrinter(
}
}
var currentIndent = INDENT_SINGLE
private set
fun pushIndent() {
currentIndent += INDENT_SINGLE
}
fun popIndent() {
currentIndent = currentIndent.substring(0, currentIndent.length - INDENT_SINGLE.length)
}
fun backspace() = stringBuilder.setLength(stringBuilder.length - 1)
val lastChar get() = stringBuilder[stringBuilder.length - 1]
private fun appendRaw(s: String) {
stringBuilder.append(s)
}
fun append(s: String) {
if (s.isBlank() && s != "\n") {
appendRaw(s)
} else {
appendRaw(s.lines().map { line ->
if (line.startsWith(" *")) line else line.trimStart()
}.joinToString("\n$currentIndent"))
}
}
fun appendSameLine(s: String) {
while (lastChar.isWhitespace() || lastChar.isNewline()) {
backspace()
}
appendRaw(s)
}
fun rmEmptyLine() {
while (lastChar.isWhitespaceNonNewline()) backspace()
if (lastChar.isNewline()) backspace()
}
/**
* Syntactic sugar for:
* ```
* +"code()";
* ```
* to append the given string plus a newline
*/
operator fun String.unaryPlus() = append("$this\n")
/**
* Syntactic sugar for:
* ```
* !"code()";
* ```
* to append the given string without a newline
*/
operator fun String.not() = append(this)
/**
* Syntactic sugar for:
* ```
* ... {
* ...
* }+";"
* ```
* to append a ';' on same line after a block, and a newline afterwards
*/
operator fun Unit.plus(s: String) {
appendSameLine(s)
+""
}
/**
* A multi-purpose syntactic sugar for appending the given string plus anything generated in
* the given [block], the latter with the appropriate deeper indent,
* and resetting the indent back to original at the end
*
* Usage examples:
*
* ```
* "if (...)" {
* ...
* }
* ```
* to append a corresponding if block appropriate indentation
*
* ```
* "void foo(...)" {
* ...
* }
* ```
* similar to the previous one, plus an extra empty line after the function body
*
* ```
* "void foo(" {
* <args code>
* }
* ```
* to use proper indentation for args code and close the bracket on same line at end
*
* ```
* "..." {
* ...
* }
* to use the correct indentation for inner code, resetting it at the end
*/
inline operator fun String.invoke(block: ClassPrinter.() -> Unit) {
if (this == " {") {
appendSameLine(this)
} else {
append(this)
}
when {
endsWith("(") -> {
indentedBy(2, block)
appendSameLine(")")
}
endsWith("{") || endsWith(")") -> {
if (!endsWith("{")) appendSameLine(" {")
indentedBy(1, block)
+"}"
if ((endsWith(") {") || endsWith(")") || this == " {")
&& !startsWith("synchronized")
&& !startsWith("switch")
&& !startsWith("if ")
&& !contains(" else ")
&& !contains("new ")
&& !contains("return ")) {
+"" // extra line after function definitions
}
}
else -> indentedBy(2, block)
}
}
inline fun indentedBy(level: Int, block: ClassPrinter.() -> Unit) {
append("\n")
level times {
append(INDENT_SINGLE)
pushIndent()
}
block()
level times { popIndent() }
rmEmptyLine()
+""
}
inline fun Iterable<FieldInfo>.forEachTrimmingTrailingComma(b: FieldInfo.() -> Unit) {
forEachApply {
b()
if (isLast) {
while (lastChar == ' ' || lastChar == '\n') backspace()
if (lastChar == ',') backspace()
}
}
}
inline operator fun <R> invoke(f: ClassPrinter.() -> R): R = run(f)
@@ -381,10 +213,10 @@ class ClassPrinter(
BuilderClass = (builderFactoryOverride.type as ClassOrInterfaceType).nameAsString
BuilderType = builderFactoryOverride.type.asString()
} else {
val builderExtension = (fileAst.types
+ classAst.childNodes.filterIsInstance(TypeDeclaration::class.java)).find {
it.nameAsString == CANONICAL_BUILDER_CLASS
}
val builderExtension = classAst
.childNodes
.filterIsInstance(TypeDeclaration::class.java)
.find { it.nameAsString == CANONICAL_BUILDER_CLASS }
if (builderExtension != null) {
BuilderClass = BASE_BUILDER_CLASS
val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters

View File

@@ -1,5 +1,6 @@
package com.android.codegen
import com.github.javaparser.JavaParser
import com.github.javaparser.ast.body.FieldDeclaration
import com.github.javaparser.ast.expr.ClassExpr
import com.github.javaparser.ast.expr.Name
@@ -111,11 +112,12 @@ data class FieldInfo(
val annotations by lazy {
if (FieldClass in BUILTIN_SPECIAL_PARCELLINGS) {
classPrinter {
fieldAst.addAnnotation(SingleMemberAnnotationExpr(
Name(ParcelWith),
ClassExpr(JAVA_PARSER
.parseClassOrInterfaceType("$Parcelling.BuiltIn.For$FieldClass")
.throwIfFailed())))
fileInfo.apply {
fieldAst.addAnnotation(SingleMemberAnnotationExpr(
Name(ParcelWith),
ClassExpr(parseJava(JavaParser::parseClassOrInterfaceType,
"$Parcelling.BuiltIn.For$FieldClass"))))
}
}
}
fieldAst.annotations.map { it.removeComment().toString() }

View File

@@ -0,0 +1,289 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.codegen
import com.github.javaparser.JavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.TypeDeclaration
import java.io.File
/**
* File-level parsing & printing logic
*
* @see [main] entrypoint
*/
class FileInfo(
val sourceLines: List<String>,
val cliArgs: Array<String>,
val file: File)
: Printer<FileInfo>, ImportsProvider {
override val fileAst: CompilationUnit
= parseJava(JavaParser::parse, sourceLines.joinToString("\n"))
override val stringBuilder = StringBuilder()
override var currentIndent = INDENT_SINGLE
val generatedWarning = run {
val fileEscaped = file.absolutePath.replace(
System.getenv("ANDROID_BUILD_TOP"), "\$ANDROID_BUILD_TOP")
"""
// $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ $THIS_SCRIPT_LOCATION$CODEGEN_NAME ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
"""
}
private val generatedWarningNumPrecedingEmptyLines
= generatedWarning.lines().takeWhile { it.isBlank() }.size
val classes = fileAst.types
.filterIsInstance<ClassOrInterfaceDeclaration>()
.flatMap { it.plusNested() }
.filterNot { it.isInterface }
val mainClass = classes.find { it.nameAsString == file.nameWithoutExtension }!!
// Parse stage 1
val classBounds: List<ClassBounds> = classes.map { ast ->
ClassBounds(ast, fileInfo = this)
}.apply {
forEachApply {
if (ast.isNestedType) {
val parent = find {
it.name == (ast.parentNode.get()!! as TypeDeclaration<*>).nameAsString
}!!
parent.nested.add(this)
nestedIn = parent
}
}
}
// Parse Stage 2
var codeChunks = buildList<CodeChunk> {
val mainClassBounds = classBounds.find { it.nestedIn == null }!!
add(CodeChunk.FileHeader(
mainClassBounds.fileInfo.sourceLines.subList(0, mainClassBounds.range.start)))
add(CodeChunk.DataClass.parse(mainClassBounds))
}
// Output stage
fun main() {
codeChunks.forEach { print(it) }
}
fun print(chunk: CodeChunk) {
when(chunk) {
is CodeChunk.GeneratedCode -> {
// Re-parse class code, discarding generated code and nested dataclasses
val ast = chunk.owner.chunks
.filter {
it.javaClass == CodeChunk.Code::class.java
|| it.javaClass == CodeChunk.ClosingBrace::class.java
}
.flatMap { (it as CodeChunk.Code).lines }
.joinToString("\n")
.let {
parseJava(JavaParser::parseTypeDeclaration, it)
as ClassOrInterfaceDeclaration
}
// Write new generated code
ClassPrinter(ast, fileInfo = this).print()
}
is CodeChunk.ClosingBrace -> {
// Special case - print closing brace with -1 indent
rmEmptyLine()
popIndent()
+"\n}"
}
// Print general code as-is
is CodeChunk.Code -> chunk.lines.forEach { stringBuilder.appendln(it) }
// Recursively render data classes
is CodeChunk.DataClass -> chunk.chunks.forEach { print(it) }
}
}
/**
* Output of stage 1 of parsing a file:
* Recursively nested ranges of code line numbers containing nested classes
*/
data class ClassBounds(
val ast: ClassOrInterfaceDeclaration,
val fileInfo: FileInfo,
val name: String = ast.nameAsString,
val range: ClosedRange<Int> = ast.range.get()!!.let { rng -> rng.begin.line-1..rng.end.line-1 },
val nested: MutableList<ClassBounds> = mutableListOf(),
var nestedIn: ClassBounds? = null) {
val nestedDataClasses: List<ClassBounds> by lazy {
nested.filter { it.isDataclass }.sortedBy { it.range.start }
}
val isDataclass = ast.annotations.any { it.nameAsString.endsWith("DataClass") }
val baseIndentLength = fileInfo.sourceLines.find { "class $name" in it }!!.takeWhile { it == ' ' }.length
val baseIndent = buildString { repeat(baseIndentLength) { append(' ') } }
val sourceNoPrefix = fileInfo.sourceLines.drop(range.start)
val generatedCodeRange = sourceNoPrefix
.indexOfFirst { it.startsWith("$baseIndent$INDENT_SINGLE// $GENERATED_WARNING_PREFIX") }
.let { start ->
if (start < 0) {
null
} else {
var endInclusive = sourceNoPrefix.indexOfFirst {
it.startsWith("$baseIndent$INDENT_SINGLE$GENERATED_END")
}
if (endInclusive == -1) {
// Legacy generated code doesn't have end markers
endInclusive = fileInfo.sourceLines.size - 2
}
IntRange(
range.start + start - fileInfo.generatedWarningNumPrecedingEmptyLines,
range.start + endInclusive)
}
}
/** Debug info */
override fun toString(): String {
return buildString {
appendln("class $name $range")
nested.forEach {
appendln(it)
}
appendln("end $name")
}
}
}
/**
* Output of stage 2 of parsing a file
*/
sealed class CodeChunk {
/** General code */
open class Code(val lines: List<String>): CodeChunk() {}
/** Copyright + package + imports + main javadoc */
class FileHeader(lines: List<String>): Code(lines)
/** Code to be discarded and refreshed */
open class GeneratedCode(lines: List<String>): Code(lines) {
lateinit var owner: DataClass
class Placeholder: GeneratedCode(emptyList())
}
object ClosingBrace: Code(listOf("}"))
data class DataClass(
val ast: ClassOrInterfaceDeclaration,
val chunks: List<CodeChunk>,
val generatedCode: GeneratedCode?): CodeChunk() {
companion object {
fun parse(classBounds: ClassBounds): DataClass {
val initial = Code(lines = classBounds.fileInfo.sourceLines.subList(
fromIndex = classBounds.range.start,
toIndex = findLowerBound(
thisClass = classBounds,
nextNestedClass = classBounds.nestedDataClasses.getOrNull(0))))
val chunks = mutableListOf<CodeChunk>(initial)
classBounds.nestedDataClasses.forEachSequentialPair {
nestedDataClass, nextNestedDataClass ->
chunks += DataClass.parse(nestedDataClass)
chunks += Code(lines = classBounds.fileInfo.sourceLines.subList(
fromIndex = nestedDataClass.range.endInclusive + 1,
toIndex = findLowerBound(
thisClass = classBounds,
nextNestedClass = nextNestedDataClass)))
}
var generatedCode = classBounds.generatedCodeRange?.let { rng ->
GeneratedCode(classBounds.fileInfo.sourceLines.subList(
rng.start, rng.endInclusive+1))
}
if (generatedCode != null) {
chunks += generatedCode
chunks += ClosingBrace
} else if (classBounds.isDataclass) {
// Insert placeholder for generated code to be inserted for the 1st time
chunks.last = (chunks.last as Code)
.lines
.dropLastWhile { it.isBlank() }
.run {
if (last().dropWhile { it.isWhitespace() }.startsWith("}")) {
dropLast(1)
} else {
this
}
}.let { Code(it) }
generatedCode = GeneratedCode.Placeholder()
chunks += generatedCode
chunks += ClosingBrace
} else {
// Outer class may be not a @DataClass but contain ones
// so just skip generated code for them
}
return DataClass(classBounds.ast, chunks, generatedCode).also {
generatedCode?.owner = it
}
}
private fun findLowerBound(thisClass: ClassBounds, nextNestedClass: ClassBounds?): Int {
return nextNestedClass?.range?.start
?: thisClass.generatedCodeRange?.start
?: thisClass.range.endInclusive + 1
}
}
}
/** Debug info */
fun summary(): String = when(this) {
is Code -> "${javaClass.simpleName}(${lines.size} lines): ${lines.getOrNull(0)?.take(70) ?: ""}..."
is DataClass -> "DataClass ${ast.nameAsString}:\n" +
chunks.joinToString("\n") { it.summary() } +
"\n//end ${ast.nameAsString}"
}
}
private fun ClassOrInterfaceDeclaration.plusNested(): List<ClassOrInterfaceDeclaration> {
return mutableListOf<ClassOrInterfaceDeclaration>().apply {
add(this@plusNested)
childNodes.filterIsInstance<ClassOrInterfaceDeclaration>()
.flatMap { it.plusNested() }
.let { addAll(it) }
}
}
}

View File

@@ -119,14 +119,14 @@ fun ClassPrinter.generateConstDef(consts: List<Pair<VariableDeclarator, FieldDec
}
}
fun ClassPrinter.generateAidl(javaFile: File) {
val aidl = File(javaFile.path.substringBeforeLast(".java") + ".aidl")
fun FileInfo.generateAidl() {
val aidl = File(file.path.substringBeforeLast(".java") + ".aidl")
if (aidl.exists()) return
aidl.writeText(buildString {
sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach {
appendln(it)
}
append("\nparcelable $ClassName;\n")
append("\nparcelable ${mainClass.nameAsString};\n")
})
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.codegen
import com.github.javaparser.ast.CompilationUnit
/**
* Mixin for optionally shortening references based on existing imports
*/
interface ImportsProvider {
abstract val fileAst: CompilationUnit
val NonNull: String get() { return classRef("android.annotation.NonNull") }
val NonEmpty: String get() { return classRef("android.annotation.NonEmpty") }
val Nullable: String get() { return classRef("android.annotation.Nullable") }
val TextUtils: String get() { return classRef("android.text.TextUtils") }
val LinkedHashMap: String get() { return classRef("java.util.LinkedHashMap") }
val Collections: String get() { return classRef("java.util.Collections") }
val Preconditions: String get() { return classRef("com.android.internal.util.Preconditions") }
val ArrayList: String get() { return classRef("java.util.ArrayList") }
val DataClass: String get() { return classRef("com.android.internal.util.DataClass") }
val DataClassEnum: String get() { return classRef("com.android.internal.util.DataClass.Enum") }
val ParcelWith: String get() { return classRef("com.android.internal.util.DataClass.ParcelWith") }
val PluralOf: String get() { return classRef("com.android.internal.util.DataClass.PluralOf") }
val Each: String get() { return classRef("com.android.internal.util.DataClass.Each") }
val DataClassGenerated: String get() { return classRef("com.android.internal.util.DataClass.Generated") }
val DataClassSuppressConstDefs: String get() { return classRef("com.android.internal.util.DataClass.SuppressConstDefsGeneration") }
val DataClassSuppress: String get() { return classRef("com.android.internal.util.DataClass.Suppress") }
val GeneratedMember: String get() { return classRef("com.android.internal.util.DataClass.Generated.Member") }
val Parcelling: String get() { return classRef("com.android.internal.util.Parcelling") }
val Parcelable: String get() { return classRef("android.os.Parcelable") }
val Parcel: String get() { return classRef("android.os.Parcel") }
val UnsupportedAppUsage: String get() { return classRef("android.annotation.UnsupportedAppUsage") }
/**
* Optionally shortens a class reference if there's a corresponding import present
*/
fun classRef(fullName: String): String {
val pkg = fullName.substringBeforeLast(".")
val simpleName = fullName.substringAfterLast(".")
if (fileAst.imports.any { imprt ->
imprt.nameAsString == fullName
|| (imprt.isAsterisk && imprt.nameAsString == pkg)
}) {
return simpleName
} else {
val outerClass = pkg.substringAfterLast(".", "")
if (outerClass.firstOrNull()?.isUpperCase() == true) {
return classRef(pkg) + "." + simpleName
}
}
return fullName
}
/** @see classRef */
fun memberRef(fullName: String): String {
val className = fullName.substringBeforeLast(".")
val methodName = fullName.substringAfterLast(".")
return if (fileAst.imports.any {
it.isStatic
&& (it.nameAsString == fullName
|| (it.isAsterisk && it.nameAsString == className))
}) {
className.substringAfterLast(".") + "." + methodName
} else {
classRef(className) + "." + methodName
}
}
}
/** @see classRef */
inline fun <reified T : Any> ImportsProvider.classRef(): String {
return classRef(T::class.java.name)
}

View File

@@ -6,6 +6,7 @@ import java.io.File
const val THIS_SCRIPT_LOCATION = ""
const val GENERATED_WARNING_PREFIX = "Code below generated by $CODEGEN_NAME"
const val GENERATED_END = "// End of generated code"
const val INDENT_SINGLE = " "
val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
@@ -115,81 +116,15 @@ fun main(args: Array<String>) {
System.exit(0)
}
val file = File(args.last()).absoluteFile
val sourceLinesNoClosingBrace = file.readLines().dropLastWhile {
val sourceLisnesOriginal = file.readLines()
val sourceLinesNoClosingBrace = sourceLisnesOriginal.dropLastWhile {
it.startsWith("}") || it.all(Char::isWhitespace)
}
val cliArgs = handleUpdateFlag(args, sourceLinesNoClosingBrace)
val sourceLinesAsIs = discardGeneratedCode(sourceLinesNoClosingBrace)
val sourceLines = sourceLinesAsIs
.filterNot { it.trim().startsWith("//") }
.map { it.trimEnd().dropWhile { it == '\n' } }
val stringBuilder = StringBuilder(sourceLinesAsIs.joinToString("\n"))
ClassPrinter(sourceLines, stringBuilder, cliArgs).run {
val cliExecutable = "$THIS_SCRIPT_LOCATION$CODEGEN_NAME"
val fileEscaped = file.absolutePath.replace(
System.getenv("ANDROID_BUILD_TOP"), "\$ANDROID_BUILD_TOP")
+"""
// $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
"""
if (FeatureFlag.CONST_DEFS()) generateConstDefs()
if (FeatureFlag.CONSTRUCTOR()) {
generateConstructor("public")
} else if (FeatureFlag.BUILDER()
|| FeatureFlag.COPY_CONSTRUCTOR()
|| FeatureFlag.WITHERS()) {
generateConstructor("/* package-private */")
}
if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.GETTERS()) generateGetters()
if (FeatureFlag.SETTERS()) generateSetters()
if (FeatureFlag.TO_STRING()) generateToString()
if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode()
if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
if (FeatureFlag.WITHERS()) generateWithers()
if (FeatureFlag.PARCELABLE()) generateParcelable()
if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon()
if (FeatureFlag.BUILDER()) generateBuilder()
if (FeatureFlag.AIDL()) generateAidl(file)
generateMetadata(file)
rmEmptyLine()
}
stringBuilder.append("\n}\n")
file.writeText(stringBuilder.toString().mapLines { trimEnd() })
}
internal fun discardGeneratedCode(sourceLinesNoClosingBrace: List<String>): List<String> {
return sourceLinesNoClosingBrace
.takeWhile { GENERATED_WARNING_PREFIX !in it }
.dropLastWhile(String::isBlank)
val fileInfo = FileInfo(sourceLisnesOriginal, cliArgs, file)
fileInfo.main()
file.writeText(fileInfo.stringBuilder.toString().mapLines { trimEnd() })
}
private fun handleUpdateFlag(cliArgs: Array<String>, sourceLines: List<String>): Array<String> {

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.android.codegen
/**
* Mixin for syntactic sugar around indent-aware printing into [stringBuilder]
*/
interface Printer<SELF: Printer<SELF>> {
val stringBuilder: StringBuilder
var currentIndent: String
fun pushIndent() {
currentIndent += INDENT_SINGLE
}
fun popIndent() {
currentIndent = if (currentIndent.length >= INDENT_SINGLE.length) {
currentIndent.substring(0, currentIndent.length - INDENT_SINGLE.length)
} else {
""
}
}
fun backspace() = stringBuilder.setLength(stringBuilder.length - 1)
val lastChar get() = stringBuilder[stringBuilder.length - 1]
private fun appendRaw(s: String) {
stringBuilder.append(s)
}
fun append(s: String) {
if (s.isBlank() && s != "\n") {
appendRaw(s)
} else {
appendRaw(s.lines().map { line ->
if (line.startsWith(" *")) line else line.trimStart()
}.joinToString("\n$currentIndent"))
}
}
fun appendSameLine(s: String) {
while (lastChar.isWhitespace() || lastChar.isNewline()) {
backspace()
}
appendRaw(s)
}
fun rmEmptyLine() {
while (lastChar.isWhitespaceNonNewline()) backspace()
if (lastChar.isNewline()) backspace()
}
/**
* Syntactic sugar for:
* ```
* +"code()";
* ```
* to append the given string plus a newline
*/
operator fun String.unaryPlus() = append("$this\n")
/**
* Syntactic sugar for:
* ```
* !"code()";
* ```
* to append the given string without a newline
*/
operator fun String.not() = append(this)
/**
* Syntactic sugar for:
* ```
* ... {
* ...
* }+";"
* ```
* to append a ';' on same line after a block, and a newline afterwards
*/
operator fun Unit.plus(s: String) {
appendSameLine(s)
+""
}
/**
* A multi-purpose syntactic sugar for appending the given string plus anything generated in
* the given [block], the latter with the appropriate deeper indent,
* and resetting the indent back to original at the end
*
* Usage examples:
*
* ```
* "if (...)" {
* ...
* }
* ```
* to append a corresponding if block appropriate indentation
*
* ```
* "void foo(...)" {
* ...
* }
* ```
* similar to the previous one, plus an extra empty line after the function body
*
* ```
* "void foo(" {
* <args code>
* }
* ```
* to use proper indentation for args code and close the bracket on same line at end
*
* ```
* "..." {
* ...
* }
* to use the correct indentation for inner code, resetting it at the end
*/
operator fun String.invoke(block: SELF.() -> Unit) {
if (this == " {") {
appendSameLine(this)
} else {
append(this)
}
when {
endsWith("(") -> {
indentedBy(2, block)
appendSameLine(")")
}
endsWith("{") || endsWith(")") -> {
if (!endsWith("{")) appendSameLine(" {")
indentedBy(1, block)
+"}"
if ((endsWith(") {") || endsWith(")") || this == " {")
&& !startsWith("synchronized")
&& !startsWith("switch")
&& !startsWith("if ")
&& !contains(" else ")
&& !contains("new ")
&& !contains("return ")) {
+"" // extra line after function definitions
}
}
else -> indentedBy(2, block)
}
}
fun indentedBy(level: Int, block: SELF.() -> Unit) {
append("\n")
level times {
append(INDENT_SINGLE)
pushIndent()
}
(this as SELF).block()
level times { popIndent() }
rmEmptyLine()
+""
}
fun Iterable<FieldInfo>.forEachTrimmingTrailingComma(b: FieldInfo.() -> Unit) {
forEachApply {
b()
if (isLast) {
while (lastChar == ' ' || lastChar == '\n') backspace()
if (lastChar == ',') backspace()
}
}
}
}

View File

@@ -1,5 +1,11 @@
package com.android.codegen
import com.github.javaparser.JavaParser
import com.github.javaparser.ParseProblemException
import com.github.javaparser.ParseResult
import com.github.javaparser.ast.Node
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.TypeDeclaration
import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import java.time.Instant
@@ -92,3 +98,49 @@ val AnnotationExpr.args: Map<String, Expression> get() = when (this) {
is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap()
else -> throw IllegalArgumentException("Unknown annotation expression: $this")
}
val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDeclaration<*>>()
val TypeDeclaration<*>.nestedDataClasses get()
= nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>()
.filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } }
val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line
inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) {
forEachIndexed { index, t ->
action(t, getOrNull(index + 1))
}
}
fun <T: Node> parseJava(fn: JavaParser.(String) -> ParseResult<T>, source: String): T = try {
val parse = JAVA_PARSER.fn(source)
if (parse.problems.isNotEmpty()) {
throw parseFailed(
source,
desc = parse.problems.joinToString("\n"),
cause = parse.problems.mapNotNull { it.cause.orElse(null) }.firstOrNull())
}
parse.result.get()
} catch (e: ParseProblemException) {
throw parseFailed(source, cause = e)
}
private fun parseFailed(source: String, cause: Throwable? = null, desc: String = ""): RuntimeException {
return RuntimeException("Failed to parse code:\n" +
source
.lines()
.mapIndexed { lnNum, ln -> "/*$lnNum*/$ln" }
.joinToString("\n") + "\n$desc",
cause)
}
var <T> MutableList<T>.last
get() = last()
set(value) {
if (isEmpty()) {
add(value)
} else {
this[size - 1] = value
}
}
inline fun <T> buildList(init: MutableList<T>.() -> Unit) = mutableListOf<T>().apply(init)

View File

@@ -208,7 +208,16 @@ class StaleDataclassProcessor: AbstractProcessor() {
val refreshCmd = if (file != null) {
"$CODEGEN_NAME $file"
} else {
"find \$ANDROID_BUILD_TOP -path */${clazz.replace('.', '/')}.java -exec $CODEGEN_NAME {} \\;"
var gotTopLevelCalssName = false
val filePath = clazz.split(".")
.takeWhile { word ->
if (!gotTopLevelCalssName && word[0].isUpperCase()) {
gotTopLevelCalssName = true
return@takeWhile true
}
!gotTopLevelCalssName
}.joinToString("/")
"find \$ANDROID_BUILD_TOP -path */$filePath.java -exec $CODEGEN_NAME {} \\;"
}
}