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.core;
17
18 import java.util.Arrays;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.Optional;
22 import java.util.function.BiConsumer;
23 import java.util.function.Function;
24 import java.util.function.Supplier;
25 import software.amazon.awssdk.annotations.SdkProtectedApi;
26 import software.amazon.awssdk.core.protocol.MarshallLocation;
27 import software.amazon.awssdk.core.protocol.MarshallingType;
28 import software.amazon.awssdk.core.traits.DefaultValueTrait;
29 import software.amazon.awssdk.core.traits.LocationTrait;
30 import software.amazon.awssdk.core.traits.Trait;
31
32 /**
33  * Metadata about a member in an {@link SdkPojo}. Contains information about how to marshall/unmarshall.
34  *
35  * @param <TypeT> Java Type of member.
36  */

37 @SdkProtectedApi
38 public final class SdkField<TypeT> {
39
40     private final MarshallingType<? super TypeT> marshallingType;
41     private final MarshallLocation location;
42     private final String locationName;
43     private final String unmarshallLocationName;
44     private final Supplier<SdkPojo> constructor;
45     private final BiConsumer<Object, TypeT> setter;
46     private final Function<Object, TypeT> getter;
47     private final Map<Class<? extends Trait>, Trait> traits;
48
49     private SdkField(Builder<TypeT> builder) {
50         this.marshallingType = builder.marshallingType;
51         this.traits = new HashMap<>(builder.traits);
52         this.constructor = builder.constructor;
53         this.setter = builder.setter;
54         this.getter = builder.getter;
55
56         // Eagerly dereference location trait since it's so commonly used.
57         LocationTrait locationTrait = getTrait(LocationTrait.class);
58         this.location = locationTrait.location();
59         this.locationName = locationTrait.locationName();
60         this.unmarshallLocationName = locationTrait.unmarshallLocationName();
61     }
62
63     /**
64      * @return MarshallingType of member. Used primarily for marshaller/unmarshaller lookups.
65      */

66     public MarshallingType<? super TypeT> marshallingType() {
67         return marshallingType;
68     }
69
70     /**
71      * @return Location the member should be marshalled into (i.e. headers/query/path/payload).
72      */

73     public MarshallLocation location() {
74         return location;
75     }
76
77     /**
78      * @return The location name to use when marshalling. I.E. the field name of the JSON document, or the header name, etc.
79      */

80     public String locationName() {
81         return locationName;
82     }
83
84     /**
85      * @return The location name to use when unmarshalling. This is only needed for AWS/Query or EC2 services. All
86      * other services should use {@link #locationName} for both marshalling and unmarshalling.
87      */

88     public String unmarshallLocationName() {
89         return unmarshallLocationName;
90     }
91
92     public Supplier<SdkPojo> constructor() {
93         return constructor;
94     }
95
96     /**
97      * Gets the trait of the specified class if available.
98      *
99      * @param clzz Trait class to get.
100      * @param <T> Type of trait.
101      * @return Trait instance or null if trait is not present.
102      */

103     @SuppressWarnings("unchecked")
104     public <T extends Trait> T getTrait(Class<T> clzz) {
105         return (T) traits.get(clzz);
106     }
107
108     /**
109      * Gets the trait of the specified class if available.
110      *
111      * @param clzz Trait class to get.
112      * @param <T> Type of trait.
113      * @return Optional of trait instance.
114      */

115     @SuppressWarnings("unchecked")
116     public <T extends Trait> Optional<T> getOptionalTrait(Class<T> clzz) {
117         return Optional.ofNullable((T) traits.get(clzz));
118     }
119
120     /**
121      * Checks if a given {@link Trait} is present on the field.
122      *
123      * @param clzz Trait class to check.
124      * @return True if trait is present, false if not.
125      */

126     public boolean containsTrait(Class<? extends Trait> clzz) {
127         return traits.containsKey(clzz);
128     }
129
130     /**
131      * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}.
132      *
133      * @param pojo POJO to retrieve value from.
134      * @return Current value of 'this' field in the POJO.
135      */

136     private TypeT get(Object pojo) {
137         return getter.apply(pojo);
138     }
139
140     /**
141      * Retrieves the current value of 'this' field from the given POJO. Uses the getter passed into the {@link Builder}. If the
142      * current value is null this method will look for the {@link DefaultValueTrait} on the field and attempt to resolve a default
143      * value. If the {@link DefaultValueTrait} is not present this just returns null.
144      *
145      * @param pojo POJO to retrieve value from.
146      * @return Current value of 'this' field in the POJO or default value if current value is null.
147      */

148     public TypeT getValueOrDefault(Object pojo) {
149         TypeT val = this.get(pojo);
150         DefaultValueTrait trait = getTrait(DefaultValueTrait.class);
151         return (trait == null ? val : (TypeT) trait.resolveValue(val));
152     }
153
154     /**
155      * Sets the given value on the POJO via the setter passed into the {@link Builder}.
156      *
157      * @param pojo POJO containing field to set.
158      * @param val Value of field.
159      */

160     @SuppressWarnings("unchecked")
161     public void set(Object pojo, Object val) {
162         setter.accept(pojo, (TypeT) val);
163     }
164
165     /**
166      * Creates a new instance of {@link Builder} bound to the specified type.
167      *
168      * @param marshallingType Type of field.
169      * @param <TypeT> Type of field. Must be a subtype of the {@link MarshallingType} type param.
170      * @return New builder instance.
171      */

172     public static <TypeT> Builder<TypeT> builder(MarshallingType<? super TypeT> marshallingType) {
173         return new Builder<>(marshallingType);
174     }
175
176     /**
177      * Builder for {@link SdkField}.
178      *
179      * @param <TypeT> Java type of field.
180      */

181     public static final class Builder<TypeT> {
182
183         private final MarshallingType<? super TypeT> marshallingType;
184         private Supplier<SdkPojo> constructor;
185         private BiConsumer<Object, TypeT> setter;
186         private Function<Object, TypeT> getter;
187         private final Map<Class<? extends Trait>, Trait> traits = new HashMap<>();
188
189         private Builder(MarshallingType<? super TypeT> marshallingType) {
190             this.marshallingType = marshallingType;
191         }
192
193         /**
194          * Sets a {@link Supplier} which will create a new <b>MUTABLE</b> instance of the POJO. I.E. this will
195          * create the Builder for a given POJO and not the immutable POJO itself.
196          *
197          * @param constructor Supplier method to create the mutable POJO.
198          * @return This object for method chaining.
199          */

200         public Builder<TypeT> constructor(Supplier<SdkPojo> constructor) {
201             this.constructor = constructor;
202             return this;
203         }
204
205         /**
206          * Sets the {@link BiConsumer} which will accept an object and a value and set that value on the appropriate
207          * member of the object. This requires a <b>MUTABLE</b> pojo so thus this setter will be on the Builder
208          * for the given POJO.
209          *
210          * @param setter Setter method.
211          * @return This object for method chaining.
212          */

213         public Builder<TypeT> setter(BiConsumer<Object, TypeT> setter) {
214             this.setter = setter;
215             return this;
216         }
217
218         /**
219          * Sets the {@link Function} that will accept an object and return the current value of 'this' field on that object.
220          * This will typically be a getter on the immutable representation of the POJO and is used mostly during marshalling.
221          *
222          * @param getter Getter method.
223          * @return This object for method chaining.
224          */

225         public Builder<TypeT> getter(Function<Object, TypeT> getter) {
226             this.getter = getter;
227             return this;
228         }
229
230         /**
231          * Attaches one or more traits to the {@link SdkField}. Traits can have additional metadata and behavior that
232          * influence how a field is marshalled/unmarshalled.
233          *
234          * @param traits Traits to attach.
235          * @return This object for method chaining.
236          */

237         public Builder<TypeT> traits(Trait... traits) {
238             Arrays.stream(traits).forEach(t -> this.traits.put(t.getClass(), t));
239             return this;
240         }
241
242         /**
243          * @return An immutable {@link SdkField}.
244          */

245         public SdkField<TypeT> build() {
246             return new SdkField<>(this);
247         }
248     }
249 }
250