1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.enterprise.connectedapps.processor;
17 
18 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_CLASSNAME;
19 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_TYPE_CLASSNAME;
20 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.INVALID_PROTOCOL_BUFFER_EXCEPTION_CLASSNAME;
21 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCELABLE_CLASSNAME;
22 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.PARCEL_CLASSNAME;
23 import static com.google.android.enterprise.connectedapps.processor.containers.ParcelableWrapper.PARCELABLE_WRAPPER_PACKAGE;
24 import static com.google.common.base.Preconditions.checkNotNull;
25 
26 import com.google.android.enterprise.connectedapps.processor.containers.GeneratorContext;
27 import com.google.android.enterprise.connectedapps.processor.containers.ParcelableWrapper;
28 import com.squareup.javapoet.ClassName;
29 import com.squareup.javapoet.FieldSpec;
30 import com.squareup.javapoet.MethodSpec;
31 import com.squareup.javapoet.TypeSpec;
32 import javax.lang.model.element.Modifier;
33 import javax.lang.model.type.TypeMirror;
34 
35 /**
36  * Generate the Parcelable Wrapper for a single Proto.
37  *
38  * <p>This is intended to be initialised and used once, which will generate all needed code.
39  *
40  * <p>This must only be used once. It should be used after {@link EarlyValidator} has been used to
41  * validate that the annotated code is correct.
42  */
43 public final class ProtoParcelableWrapperGenerator {
44 
45   private static final String GENERATED_PARCELABLE_WRAPPER_PACKAGE =
46       PARCELABLE_WRAPPER_PACKAGE + ".generated";
47 
48   private boolean generated = false;
49   private final GeneratorContext generatorContext;
50   private final GeneratorUtilities generatorUtilities;
51   private final ParcelableWrapper parcelableWrapper;
52 
ProtoParcelableWrapperGenerator( GeneratorContext generatorContext, ParcelableWrapper parcelableWrapper)53   ProtoParcelableWrapperGenerator(
54       GeneratorContext generatorContext, ParcelableWrapper parcelableWrapper) {
55     this.generatorContext = checkNotNull(generatorContext);
56     this.generatorUtilities = new GeneratorUtilities(generatorContext);
57     this.parcelableWrapper = checkNotNull(parcelableWrapper);
58   }
59 
generate()60   void generate() {
61     if (generated) {
62       throw new IllegalStateException(
63           "ProtoParcelableWrapperGenerator#generate can only be called once");
64     }
65     generated = true;
66 
67     generateProtoParcelableWrapper();
68   }
69 
generateProtoParcelableWrapper()70   private void generateProtoParcelableWrapper() {
71     ClassName wrapperClassName = parcelableWrapper.wrapperClassName();
72 
73     if (generatorContext.elements().getTypeElement(wrapperClassName.toString()) != null) {
74       // We don't generate things which already exist
75       return;
76     }
77 
78     TypeSpec.Builder classBuilder =
79         TypeSpec.classBuilder(wrapperClassName)
80             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
81             .addSuperinterface(PARCELABLE_CLASSNAME)
82             .addJavadoc(
83                 "Wrapper for reading & writing {@link $T} instances to and from {@link $T}"
84                     + " instances.",
85                 parcelableWrapper.wrappedType(),
86                 PARCEL_CLASSNAME);
87 
88     classBuilder.addField(
89         FieldSpec.builder(ClassName.get(parcelableWrapper.wrappedType()), "proto", Modifier.PRIVATE)
90             .build());
91 
92     classBuilder.addField(
93         FieldSpec.builder(int.class, "NULL_SIZE")
94             .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC)
95             .initializer("-1")
96             .build());
97 
98     classBuilder.addMethod(
99         MethodSpec.methodBuilder("of")
100             .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
101             .addJavadoc(
102                 "Create a wrapper for the given {@link $T}.\n", parcelableWrapper.wrappedType())
103             .returns(parcelableWrapper.wrapperClassName())
104             .addParameter(BUNDLER_CLASSNAME, "bundler")
105             .addParameter(BUNDLER_TYPE_CLASSNAME, "type")
106             .addParameter(ClassName.get(parcelableWrapper.wrappedType()), "proto")
107             .addStatement(
108                 "return new $T(bundler, type, proto)", parcelableWrapper.wrapperClassName())
109             .build());
110 
111     classBuilder.addMethod(
112         MethodSpec.methodBuilder("get")
113             .addModifiers(Modifier.PUBLIC)
114             .returns(ClassName.get(parcelableWrapper.wrappedType()))
115             .addStatement("return proto")
116             .build());
117 
118     classBuilder.addMethod(
119         MethodSpec.constructorBuilder()
120             .addModifiers(Modifier.PRIVATE)
121             .addParameter(BUNDLER_CLASSNAME, "bundler")
122             .addParameter(BUNDLER_TYPE_CLASSNAME, "type")
123             .addParameter(ClassName.get(parcelableWrapper.wrappedType()), "proto")
124             .addStatement("this.proto = proto")
125             .build());
126 
127     classBuilder.addMethod(
128         MethodSpec.constructorBuilder()
129             .addModifiers(Modifier.PRIVATE)
130             .addParameter(PARCEL_CLASSNAME, "in")
131             .addStatement("int size = in.readInt()")
132             .beginControlFlow("if (size == NULL_SIZE)")
133             .addStatement("proto = null")
134             .addStatement("return")
135             .endControlFlow()
136             .addStatement("byte[] protoBytes = new byte[size]")
137             .addStatement("in.readByteArray(protoBytes)")
138             .beginControlFlow("try")
139             .addStatement("proto = $T.parseFrom(protoBytes)", parcelableWrapper.wrappedType())
140             .nextControlFlow("catch ($T e)", INVALID_PROTOCOL_BUFFER_EXCEPTION_CLASSNAME)
141             .addComment("TODO: Deal with exception")
142             .endControlFlow()
143             .build());
144 
145     classBuilder.addMethod(
146         MethodSpec.methodBuilder("writeToParcel")
147             .addAnnotation(Override.class)
148             .addModifiers(Modifier.PUBLIC)
149             .addParameter(PARCEL_CLASSNAME, "dest")
150             .addParameter(int.class, "flags")
151             .beginControlFlow("if (proto == null)")
152             .addStatement("dest.writeInt(NULL_SIZE)")
153             .addStatement("return")
154             .endControlFlow()
155             .addStatement("byte[] protoBytes = proto.toByteArray()")
156             .addStatement("dest.writeInt(protoBytes.length)")
157             .addStatement("dest.writeByteArray(protoBytes)")
158             .build());
159 
160     generatorUtilities.addDefaultParcelableMethods(
161         classBuilder, parcelableWrapper.wrapperClassName());
162 
163     generatorUtilities.writeClassToFile(
164         parcelableWrapper.wrapperClassName().packageName(), classBuilder);
165   }
166 
getGeneratedProtoWrapperClassName(TypeMirror type)167   public static ClassName getGeneratedProtoWrapperClassName(TypeMirror type) {
168     String simpleName = type.toString().substring(type.toString().lastIndexOf(".") + 1);
169     return ClassName.get(GENERATED_PARCELABLE_WRAPPER_PACKAGE, simpleName + "Wrapper");
170   }
171 }
172