1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.codegen.poet.model;
17 
18 import com.squareup.javapoet.ClassName;
19 import com.squareup.javapoet.FieldSpec;
20 import com.squareup.javapoet.ParameterizedTypeName;
21 import com.squareup.javapoet.TypeName;
22 import com.squareup.javapoet.WildcardTypeName;
23 import java.io.InputStream;
24 import java.math.BigDecimal;
25 import java.math.BigInteger;
26 import java.nio.ByteBuffer;
27 import java.time.Instant;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.stream.Stream;
33 import javax.lang.model.element.Modifier;
34 import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
35 import software.amazon.awssdk.codegen.model.intermediate.MapModel;
36 import software.amazon.awssdk.codegen.model.intermediate.MemberModel;
37 import software.amazon.awssdk.codegen.poet.PoetExtension;
38 import software.amazon.awssdk.core.SdkBytes;
39 import software.amazon.awssdk.core.document.Document;
40 
41 /**
42  * Helper class for resolving Poet {@link TypeName}s for use in model classes.
43  */
44 public class TypeProvider {
45     private final IntermediateModel intermediateModel;
46     private final PoetExtension poetExtensions;
47 
TypeProvider(IntermediateModel intermediateModel)48     public TypeProvider(IntermediateModel intermediateModel) {
49         this.intermediateModel = intermediateModel;
50         this.poetExtensions = new PoetExtension(this.intermediateModel);
51     }
52 
listImplClassName()53     public ClassName listImplClassName() {
54         return ClassName.get(ArrayList.class);
55     }
56 
enumReturnType(MemberModel memberModel)57     public TypeName enumReturnType(MemberModel memberModel) {
58         return typeName(memberModel, new TypeNameOptions().useEnumTypes(true));
59     }
60 
returnType(MemberModel memberModel)61     public TypeName returnType(MemberModel memberModel) {
62         return typeName(memberModel, new TypeNameOptions().useEnumTypes(false));
63     }
64 
fieldType(MemberModel memberModel)65     public TypeName fieldType(MemberModel memberModel) {
66         return typeName(memberModel, new TypeNameOptions().useEnumTypes(false));
67     }
68 
parameterType(MemberModel memberModel)69     public TypeName parameterType(MemberModel memberModel) {
70         return parameterType(memberModel, false);
71     }
72 
parameterType(MemberModel memberModel, boolean preserveEnum)73     public TypeName parameterType(MemberModel memberModel, boolean preserveEnum) {
74         return typeName(memberModel, new TypeNameOptions().useCollectionForList(true)
75                                                           .useSubtypeWildcardsForCollections(true)
76                                                           .useEnumTypes(preserveEnum));
77     }
78 
mapEntryWithConcreteTypes(MapModel mapModel)79     public TypeName mapEntryWithConcreteTypes(MapModel mapModel) {
80         TypeName keyType = fieldType(mapModel.getKeyModel());
81         TypeName valueType = fieldType(mapModel.getValueModel());
82         return ParameterizedTypeName.get(ClassName.get(Map.Entry.class), keyType, valueType);
83     }
84 
getTypeNameForSimpleType(String simpleType)85     public TypeName getTypeNameForSimpleType(String simpleType) {
86         return Stream.of(String.class,
87                 Boolean.class,
88                 Integer.class,
89                 Long.class,
90                 Short.class,
91                 Byte.class,
92                 BigInteger.class,
93                 Double.class,
94                 Float.class,
95                 BigDecimal.class,
96                 SdkBytes.class,
97                 InputStream.class,
98                 Instant.class,
99                 Document.class)
100                 .filter(cls -> cls.getName().equals(simpleType) || cls.getSimpleName().equals(simpleType))
101                 .map(ClassName::get)
102                 .findFirst()
103                 .orElseThrow(() -> new RuntimeException("Unsupported simple fieldType " + simpleType));
104     }
105 
asField(MemberModel memberModel, Modifier... modifiers)106     public FieldSpec asField(MemberModel memberModel, Modifier... modifiers) {
107         FieldSpec.Builder builder = FieldSpec.builder(fieldType(memberModel),
108                 memberModel.getVariable().getVariableName());
109 
110         if (modifiers != null) {
111             builder.addModifiers(modifiers);
112         }
113 
114         return builder.build();
115     }
116 
isContainerType(MemberModel m)117     private static boolean isContainerType(MemberModel m) {
118         return m.isList() || m.isMap();
119     }
120 
typeName(MemberModel model)121     public TypeName typeName(MemberModel model) {
122         return typeName(model, new TypeNameOptions());
123     }
124 
typeName(MemberModel model, TypeNameOptions options)125     public TypeName typeName(MemberModel model, TypeNameOptions options) {
126         if (model.isSdkBytesType() && options.useByteBufferTypes) {
127             return ClassName.get(ByteBuffer.class);
128         }
129 
130         if (model.getEnumType() != null && options.useEnumTypes) {
131             return poetExtensions.getModelClass(model.getEnumType());
132         }
133 
134         if (model.isSimple()) {
135             return getTypeNameForSimpleType(model.getVariable().getVariableType());
136         }
137 
138         if (model.isList()) {
139             MemberModel entryModel = model.getListModel().getListMemberModel();
140             TypeName entryType = typeName(entryModel, options);
141 
142             if (options.useSubtypeWildcardsForCollections && isContainerType(entryModel) ||
143                 options.useSubtypeWildcardsForBuilders && entryModel.hasBuilder()) {
144                 entryType = WildcardTypeName.subtypeOf(entryType);
145             }
146 
147             Class<?> collectionType = options.useCollectionForList ? Collection.class : List.class;
148 
149             return ParameterizedTypeName.get(ClassName.get(collectionType), entryType);
150         }
151 
152         if (model.isMap()) {
153             MemberModel keyModel = model.getMapModel().getKeyModel();
154             MemberModel valueModel = model.getMapModel().getValueModel();
155             TypeName keyType = typeName(keyModel, options);
156             TypeName valueType = typeName(valueModel, options);
157 
158             if (options.useSubtypeWildcardsForCollections && isContainerType(keyModel) ||
159                 options.useSubtypeWildcardsForBuilders && keyModel.hasBuilder()) {
160                 keyType = WildcardTypeName.subtypeOf(keyType);
161             }
162 
163             if (options.useSubtypeWildcardsForCollections && isContainerType(valueModel) ||
164                 options.useSubtypeWildcardsForBuilders && valueModel.hasBuilder()) {
165                 valueType = WildcardTypeName.subtypeOf(valueType);
166             }
167 
168             return ParameterizedTypeName.get(ClassName.get(Map.class), keyType, valueType);
169         }
170 
171         if (model.hasBuilder()) {
172             ClassName shapeClass = poetExtensions.getModelClass(model.getC2jShape());
173             switch (options.shapeTransformation) {
174                 case NONE: return shapeClass;
175                 case USE_BUILDER: return shapeClass.nestedClass("Builder");
176                 case USE_BUILDER_IMPL: return shapeClass.nestedClass("BuilderImpl");
177                 default: throw new IllegalStateException();
178             }
179         }
180 
181         throw new IllegalArgumentException("Unsupported member model: " + model);
182     }
183 
184     public enum ShapeTransformation {
185         USE_BUILDER,
186         USE_BUILDER_IMPL,
187         NONE
188     }
189 
190     public static final class TypeNameOptions {
191         private ShapeTransformation shapeTransformation = ShapeTransformation.NONE;
192         private boolean useCollectionForList = false;
193         private boolean useSubtypeWildcardsForCollections = false;
194         private boolean useByteBufferTypes = false;
195         private boolean useEnumTypes = false;
196         private boolean useSubtypeWildcardsForBuilders = false;
197 
shapeTransformation(ShapeTransformation shapeTransformation)198         public TypeNameOptions shapeTransformation(ShapeTransformation shapeTransformation) {
199             this.shapeTransformation = shapeTransformation;
200             return this;
201         }
202 
useCollectionForList(boolean useCollectionForList)203         public TypeNameOptions useCollectionForList(boolean useCollectionForList) {
204             this.useCollectionForList = useCollectionForList;
205             return this;
206         }
207 
useSubtypeWildcardsForCollections(boolean useSubtypeWildcardsForCollections)208         public TypeNameOptions useSubtypeWildcardsForCollections(boolean useSubtypeWildcardsForCollections) {
209             this.useSubtypeWildcardsForCollections = useSubtypeWildcardsForCollections;
210             return this;
211         }
212 
useSubtypeWildcardsForBuilders(boolean useSubtypeWildcardsForBuilders)213         public TypeNameOptions useSubtypeWildcardsForBuilders(boolean useSubtypeWildcardsForBuilders) {
214             this.useSubtypeWildcardsForBuilders = useSubtypeWildcardsForBuilders;
215             return this;
216         }
217 
useEnumTypes(boolean useEnumTypes)218         public TypeNameOptions useEnumTypes(boolean useEnumTypes) {
219             this.useEnumTypes = useEnumTypes;
220             return this;
221         }
222 
useByteBufferTypes(boolean useByteBufferTypes)223         public TypeNameOptions useByteBufferTypes(boolean useByteBufferTypes) {
224             this.useByteBufferTypes = useByteBufferTypes;
225             return this;
226         }
227     }
228 }
229