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