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.enhanced.dynamodb.update;
17 
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24 import software.amazon.awssdk.annotations.SdkPublicApi;
25 
26 /**
27  * Contains sets of {@link UpdateAction} that represent the four DynamoDB update actions: SET, ADD, REMOVE and DELETE.
28  * <p>
29  * Use this class to build an immutable UpdateExpression with one or more UpdateAction. An UpdateExpression may be merged
30  * with another. When two UpdateExpression are merged, the actions of each group of UpdateAction, should they exist, are
31  * combined; all SET actions from each expression are concatenated, all REMOVE actions etc.
32  * <p>
33  * DynamoDb Enhanced will convert the UpdateExpression to a format readable by DynamoDb,
34  * <p>
35  * Example:-
36  * <pre>
37  * {@code
38  * RemoveUpdateAction removeAction = ...
39  * SetUpdateAction setAction = ...
40  * UpdateExpression.builder()
41  *                 .addAction(removeAction)
42  *                 .addAction(setAction)
43  *                 .build();
44  * }
45  * </pre>
46  *
47  * See respective subtype of {@link UpdateAction}, for example {@link SetAction}, for details on creating that action.
48  */
49 @SdkPublicApi
50 public final class UpdateExpression {
51 
52     private final List<RemoveAction> removeActions;
53     private final List<SetAction> setActions;
54     private final List<DeleteAction> deleteActions;
55     private final List<AddAction> addActions;
56 
UpdateExpression(Builder builder)57     private UpdateExpression(Builder builder) {
58         this.removeActions = builder.removeActions;
59         this.setActions = builder.setActions;
60         this.deleteActions = builder.deleteActions;
61         this.addActions = builder.addActions;
62     }
63 
64     /**
65      * Constructs a new builder for {@link UpdateExpression}.
66      *
67      * @return a new builder.
68      */
builder()69     public static Builder builder() {
70         return new Builder();
71     }
72 
removeActions()73     public List<RemoveAction> removeActions() {
74         return Collections.unmodifiableList(new ArrayList<>(removeActions));
75     }
76 
setActions()77     public List<SetAction> setActions() {
78         return Collections.unmodifiableList(new ArrayList<>(setActions));
79     }
80 
deleteActions()81     public List<DeleteAction> deleteActions() {
82         return Collections.unmodifiableList(new ArrayList<>(deleteActions));
83     }
84 
addActions()85     public List<AddAction> addActions() {
86         return Collections.unmodifiableList(new ArrayList<>(addActions));
87     }
88 
89     /**
90      * Merges two UpdateExpression, returning a new
91      */
mergeExpressions(UpdateExpression expression1, UpdateExpression expression2)92     public static UpdateExpression mergeExpressions(UpdateExpression expression1, UpdateExpression expression2) {
93         if (expression1 == null) {
94             return expression2;
95         }
96         if (expression2 == null) {
97             return expression1;
98         }
99         Builder builder = builder();
100         builder.removeActions = Stream.concat(expression1.removeActions.stream(),
101                                               expression2.removeActions.stream()).collect(Collectors.toList());
102         builder.setActions = Stream.concat(expression1.setActions.stream(),
103                                            expression2.setActions.stream()).collect(Collectors.toList());
104         builder.deleteActions = Stream.concat(expression1.deleteActions.stream(),
105                                               expression2.deleteActions.stream()).collect(Collectors.toList());
106         builder.addActions = Stream.concat(expression1.addActions.stream(),
107                                            expression2.addActions.stream()).collect(Collectors.toList());
108         return builder.build();
109     }
110 
111     @Override
equals(Object o)112     public boolean equals(Object o) {
113         if (this == o) {
114             return true;
115         }
116         if (o == null || getClass() != o.getClass()) {
117             return false;
118         }
119 
120         UpdateExpression that = (UpdateExpression) o;
121 
122         if (removeActions != null ? ! removeActions.equals(that.removeActions) : that.removeActions != null) {
123             return false;
124         }
125         if (setActions != null ? ! setActions.equals(that.setActions) : that.setActions != null) {
126             return false;
127         }
128         if (deleteActions != null ? ! deleteActions.equals(that.deleteActions) : that.deleteActions != null) {
129             return false;
130         }
131         return addActions != null ? addActions.equals(that.addActions) : that.addActions == null;
132     }
133 
134     @Override
hashCode()135     public int hashCode() {
136         int result = removeActions != null ? removeActions.hashCode() : 0;
137         result = 31 * result + (setActions != null ? setActions.hashCode() : 0);
138         result = 31 * result + (deleteActions != null ? deleteActions.hashCode() : 0);
139         result = 31 * result + (addActions != null ? addActions.hashCode() : 0);
140         return result;
141     }
142 
143     /**
144      * A builder for {@link UpdateExpression}
145      */
146     public static final class Builder {
147 
148         private List<RemoveAction> removeActions = new ArrayList<>();
149         private List<SetAction> setActions = new ArrayList<>();
150         private List<DeleteAction> deleteActions = new ArrayList<>();
151         private List<AddAction> addActions = new ArrayList<>();
152 
Builder()153         private Builder() {
154         }
155 
156         /**
157          * Add an action of type {@link RemoveAction}
158          */
addAction(RemoveAction action)159         public Builder addAction(RemoveAction action) {
160             removeActions.add(action);
161             return this;
162         }
163 
164         /**
165          * Add an action of type {@link SetAction}
166          */
addAction(SetAction action)167         public Builder addAction(SetAction action) {
168             setActions.add(action);
169             return this;
170         }
171 
172         /**
173          * Add an action of type {@link DeleteAction}
174          */
addAction(DeleteAction action)175         public Builder addAction(DeleteAction action) {
176             deleteActions.add(action);
177             return this;
178         }
179 
180         /**
181          * Add an action of type {@link AddAction}
182          */
addAction(AddAction action)183         public Builder addAction(AddAction action) {
184             addActions.add(action);
185             return this;
186         }
187 
188         /**
189          * Adds a list of {@link UpdateAction} of any subtype to the builder, overwriting any previous values.
190          */
actions(List<? extends UpdateAction> actions)191         public Builder actions(List<? extends UpdateAction> actions) {
192             replaceActions(actions);
193             return this;
194         }
195 
196         /**
197          * Adds a list of {@link UpdateAction} of any subtype to the builder, overwriting any previous values.
198          */
actions(UpdateAction... actions)199         public Builder actions(UpdateAction... actions) {
200             actions(Arrays.asList(actions));
201             return this;
202         }
203 
204         /**
205          * Builds an {@link UpdateExpression} based on the values stored in this builder.
206          */
build()207         public UpdateExpression build() {
208             return new UpdateExpression(this);
209         }
210 
replaceActions(List<? extends UpdateAction> actions)211         private void replaceActions(List<? extends UpdateAction> actions) {
212             if (actions != null) {
213                 this.removeActions = new ArrayList<>();
214                 this.setActions = new ArrayList<>();
215                 this.deleteActions = new ArrayList<>();
216                 this.addActions = new ArrayList<>();
217                 actions.forEach(this::assignAction);
218             }
219         }
220 
assignAction(UpdateAction action)221         private void assignAction(UpdateAction action) {
222             if (action instanceof RemoveAction) {
223                 addAction((RemoveAction) action);
224             } else if (action instanceof SetAction) {
225                 addAction((SetAction) action);
226             } else if (action instanceof DeleteAction) {
227                 addAction((DeleteAction) action);
228             } else if (action instanceof AddAction) {
229                 addAction((AddAction) action);
230             } else {
231                 throw new IllegalArgumentException(
232                     String.format("Do not recognize UpdateAction: %s", action.getClass()));
233             }
234         }
235     }
236 }
237