[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:
@@ -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 && \
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() }
|
||||
|
||||
289
tools/codegen/src/com/android/codegen/FileInfo.kt
Normal file
289
tools/codegen/src/com/android/codegen/FileInfo.kt
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
91
tools/codegen/src/com/android/codegen/ImportsProvider.kt
Normal file
91
tools/codegen/src/com/android/codegen/ImportsProvider.kt
Normal 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)
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
186
tools/codegen/src/com/android/codegen/Printer.kt
Normal file
186
tools/codegen/src/com/android/codegen/Printer.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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 {} \\;"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user