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