• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.internal.http;
17 
18 import static software.amazon.awssdk.utils.CollectionUtils.deepCopyMap;
19 
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.TreeMap;
24 import java.util.function.Supplier;
25 import software.amazon.awssdk.annotations.NotThreadSafe;
26 import software.amazon.awssdk.annotations.SdkInternalApi;
27 import software.amazon.awssdk.annotations.ThreadSafe;
28 import software.amazon.awssdk.http.SdkHttpRequest;
29 import software.amazon.awssdk.http.SdkHttpResponse;
30 import software.amazon.awssdk.utils.CollectionUtils;
31 import software.amazon.awssdk.utils.Lazy;
32 
33 /**
34  * A {@code Map<String, List<String>>} for headers and query strings in {@link SdkHttpRequest} and {@link SdkHttpResponse} that
35  * avoids copying data during conversion between builders and buildables, unless data is modified.
36  *
37  * This is created via {@link #emptyHeaders()} or {@link #emptyQueryParameters()}.
38  */
39 @SdkInternalApi
40 public final class LowCopyListMap {
LowCopyListMap()41     private LowCopyListMap() {
42     }
43 
44     /**
45      * Create an empty {@link LowCopyListMap.ForBuilder} for header storage.
46      */
emptyHeaders()47     public static LowCopyListMap.ForBuilder emptyHeaders() {
48         return new LowCopyListMap.ForBuilder(() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER));
49     }
50 
51     /**
52      * Create an empty {@link LowCopyListMap.ForBuilder} for query parameter storage.
53      */
emptyQueryParameters()54     public static LowCopyListMap.ForBuilder emptyQueryParameters() {
55         return new LowCopyListMap.ForBuilder(LinkedHashMap::new);
56     }
57 
58     @NotThreadSafe
59     public static final class ForBuilder {
60         /**
61          * The constructor that can be used to create new, empty maps.
62          */
63         private final Supplier<Map<String, List<String>>> mapConstructor;
64 
65         /**
66          * Whether {@link #map} has been shared with a {@link ForBuildable}. If this is true, we need to make sure to copy the
67          * map before we mutate it.
68          */
69         private boolean mapIsShared = false;
70 
71         /**
72          * The data stored in this low-copy list-map.
73          */
74         private Map<String, List<String>> map;
75 
76         /**
77          * Created from {@link LowCopyListMap#emptyHeaders()} or {@link LowCopyListMap#emptyQueryParameters()}.
78          */
ForBuilder(Supplier<Map<String, List<String>>> mapConstructor)79         private ForBuilder(Supplier<Map<String, List<String>>> mapConstructor) {
80             this.mapConstructor = mapConstructor;
81             this.map = mapConstructor.get();
82         }
83 
84         /**
85          * Created from {@link LowCopyListMap.ForBuildable#forBuilder()}.
86          */
ForBuilder(ForBuildable forBuildable)87         private ForBuilder(ForBuildable forBuildable) {
88             this.mapConstructor = forBuildable.mapConstructor;
89             this.map = forBuildable.map;
90             this.mapIsShared = true;
91         }
92 
clear()93         public void clear() {
94             this.map = mapConstructor.get();
95             this.mapIsShared = false;
96         }
97 
setFromExternal(Map<String, List<String>> map)98         public void setFromExternal(Map<String, List<String>> map) {
99             this.map = deepCopyMap(map, mapConstructor);
100             this.mapIsShared = false;
101         }
102 
forInternalWrite()103         public Map<String, List<String>> forInternalWrite() {
104             if (mapIsShared) {
105                 this.map = deepCopyMap(map, mapConstructor);
106                 this.mapIsShared = false;
107             }
108             return this.map;
109         }
110 
forInternalRead()111         public Map<String, List<String>> forInternalRead() {
112             return this.map;
113         }
114 
forBuildable()115         public ForBuildable forBuildable() {
116             this.mapIsShared = true;
117             return new ForBuildable(this);
118         }
119 
120         @Override
equals(Object o)121         public boolean equals(Object o) {
122             if (this == o) {
123                 return true;
124             }
125             if (o == null || getClass() != o.getClass()) {
126                 return false;
127             }
128 
129             ForBuilder that = (ForBuilder) o;
130 
131             return map.equals(that.map);
132         }
133 
134         @Override
hashCode()135         public int hashCode() {
136             return map.hashCode();
137         }
138     }
139 
140 
141     @ThreadSafe
142     public static final class ForBuildable {
143         /**
144          * The constructor that can be used to create new, empty maps.
145          */
146         private final Supplier<Map<String, List<String>>> mapConstructor;
147 
148         /**
149          * An unmodifiable copy of {@link #map}, which is lazily initialized only when it is needed.
150          */
151         private final Lazy<Map<String, List<String>>> deeplyUnmodifiableMap;
152 
153         /**
154          * The data stored in this low-copy list-map.
155          */
156         private final Map<String, List<String>> map;
157 
ForBuildable(ForBuilder forBuilder)158         private ForBuildable(ForBuilder forBuilder) {
159             this.mapConstructor = forBuilder.mapConstructor;
160             this.map = forBuilder.map;
161             this.deeplyUnmodifiableMap = new Lazy<>(() -> CollectionUtils.deepUnmodifiableMap(this.map, this.mapConstructor));
162         }
163 
forExternalRead()164         public Map<String, List<String>> forExternalRead() {
165             return deeplyUnmodifiableMap.getValue();
166         }
167 
forInternalRead()168         public Map<String, List<String>> forInternalRead() {
169             return map;
170         }
171 
forBuilder()172         public ForBuilder forBuilder() {
173             return new ForBuilder(this);
174         }
175 
176         @Override
equals(Object o)177         public boolean equals(Object o) {
178             if (this == o) {
179                 return true;
180             }
181             if (o == null || getClass() != o.getClass()) {
182                 return false;
183             }
184 
185             ForBuildable that = (ForBuildable) o;
186 
187             return map.equals(that.map);
188         }
189 
190         @Override
hashCode()191         public int hashCode() {
192             return map.hashCode();
193         }
194     }
195 }
196