1 /* 2 * Copyright 2015 The gRPC Authors 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 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.base.Objects; 22 import java.util.Collections; 23 import java.util.IdentityHashMap; 24 import java.util.Map; 25 import java.util.Set; 26 import javax.annotation.Nullable; 27 import javax.annotation.concurrent.Immutable; 28 29 /** 30 * An immutable type-safe container of attributes. 31 * 32 * <h3>Annotation semantics</h3> 33 * 34 * <p>As a convention, annotations such as {@link Grpc.TransportAttr} is defined to associate 35 * attribute {@link Key}s and their propagation paths. The annotation may be applied to a {@code 36 * Key} definition field, a method that returns {@link Attributes}, or a variable of type {@link 37 * Attributes}, to indicate that the annotated {@link Attributes} objects may contain the annotated 38 * {@code Key}. 39 * 40 * <p>Javadoc users may click "USE" on the navigation bars of the annotation's javadoc page to view 41 * references of such annotation. 42 * 43 * @since 1.13.0 44 */ 45 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1764") 46 @Immutable 47 public final class Attributes { 48 49 private final IdentityHashMap<Key<?>, Object> data; 50 51 private static final IdentityHashMap<Key<?>, Object> EMPTY_MAP = 52 new IdentityHashMap<Key<?>, Object>(); 53 public static final Attributes EMPTY = new Attributes(EMPTY_MAP); 54 Attributes(IdentityHashMap<Key<?>, Object> data)55 private Attributes(IdentityHashMap<Key<?>, Object> data) { 56 assert data != null; 57 this.data = data; 58 } 59 60 /** 61 * Gets the value for the key, or {@code null} if it's not present. 62 */ 63 @SuppressWarnings("unchecked") 64 @Nullable get(Key<T> key)65 public <T> T get(Key<T> key) { 66 return (T) data.get(key); 67 } 68 69 /** 70 * Returns set of keys stored in container. 71 * 72 * @return Set of Key objects. 73 * @deprecated This method is being considered for removal, if you feel this method is needed 74 * please reach out on this Github issue: 75 * <a href="https://github.com/grpc/grpc-java/issues/1764">grpc-java/issues/1764</a>. 76 */ 77 @Deprecated keys()78 public Set<Key<?>> keys() { 79 return Collections.unmodifiableSet(data.keySet()); 80 } 81 keysForTest()82 Set<Key<?>> keysForTest() { 83 return Collections.unmodifiableSet(data.keySet()); 84 } 85 86 /** 87 * Create a new builder that is pre-populated with the content from a given container. 88 * @deprecated Use {@link Attributes#toBuilder()} on the {@link Attributes} instance instead. 89 * This method will be removed in the future. 90 */ 91 @Deprecated newBuilder(Attributes base)92 public static Builder newBuilder(Attributes base) { 93 checkNotNull(base, "base"); 94 return new Builder(base); 95 } 96 97 /** 98 * Create a new builder. 99 */ newBuilder()100 public static Builder newBuilder() { 101 return new Builder(EMPTY); 102 } 103 104 /** 105 * Creates a new builder that is pre-populated with the content of this container. 106 * @return a new builder. 107 */ toBuilder()108 public Builder toBuilder() { 109 return new Builder(this); 110 } 111 112 /** 113 * Key for an key-value pair. Uses reference equality. 114 * 115 * @param <T> type of the value in the key-value pair 116 */ 117 @Immutable 118 @SuppressWarnings("UnusedTypeParameter") 119 public static final class Key<T> { 120 private final String debugString; 121 Key(String debugString)122 private Key(String debugString) { 123 this.debugString = debugString; 124 } 125 126 @Override toString()127 public String toString() { 128 return debugString; 129 } 130 131 /** 132 * Factory method for creating instances of {@link Key}. 133 * 134 * @param debugString a string used to describe the key, used for debugging. 135 * @param <T> Key type 136 * @return Key object 137 * @deprecated use {@link #create} instead. This method will be removed in the future. 138 */ 139 @Deprecated of(String debugString)140 public static <T> Key<T> of(String debugString) { 141 return new Key<>(debugString); 142 } 143 144 /** 145 * Factory method for creating instances of {@link Key}. 146 * 147 * @param debugString a string used to describe the key, used for debugging. 148 * @param <T> Key type 149 * @return Key object 150 */ create(String debugString)151 public static <T> Key<T> create(String debugString) { 152 return new Key<>(debugString); 153 } 154 } 155 156 @Override toString()157 public String toString() { 158 return data.toString(); 159 } 160 161 /** 162 * Returns true if the given object is also a {@link Attributes} with an equal attribute values. 163 * 164 * <p>Note that if a stored values are mutable, it is possible for two objects to be considered 165 * equal at one point in time and not equal at another (due to concurrent mutation of attribute 166 * values). 167 * 168 * <p>This method is not implemented efficiently and is meant for testing. 169 * 170 * @param o an object. 171 * @return true if the given object is a {@link Attributes} equal attributes. 172 */ 173 @Override equals(Object o)174 public boolean equals(Object o) { 175 if (this == o) { 176 return true; 177 } 178 if (o == null || getClass() != o.getClass()) { 179 return false; 180 } 181 Attributes that = (Attributes) o; 182 if (data.size() != that.data.size()) { 183 return false; 184 } 185 for (Map.Entry<Key<?>, Object> e : data.entrySet()) { 186 if (!that.data.containsKey(e.getKey())) { 187 return false; 188 } 189 if (!Objects.equal(e.getValue(), that.data.get(e.getKey()))) { 190 return false; 191 } 192 } 193 return true; 194 } 195 196 /** 197 * Returns a hash code for the attributes. 198 * 199 * <p>Note that if a stored values are mutable, it is possible for two objects to be considered 200 * equal at one point in time and not equal at another (due to concurrent mutation of attribute 201 * values). 202 * 203 * @return a hash code for the attributes map. 204 */ 205 @Override hashCode()206 public int hashCode() { 207 int hashCode = 0; 208 for (Map.Entry<Key<?>, Object> e : data.entrySet()) { 209 hashCode += Objects.hashCode(e.getKey(), e.getValue()); 210 } 211 return hashCode; 212 } 213 214 /** 215 * The helper class to build an Attributes instance. 216 */ 217 public static final class Builder { 218 private Attributes base; 219 private IdentityHashMap<Key<?>, Object> newdata; 220 Builder(Attributes base)221 private Builder(Attributes base) { 222 assert base != null; 223 this.base = base; 224 } 225 data(int size)226 private IdentityHashMap<Key<?>, Object> data(int size) { 227 if (newdata == null) { 228 newdata = new IdentityHashMap<>(size); 229 } 230 return newdata; 231 } 232 set(Key<T> key, T value)233 public <T> Builder set(Key<T> key, T value) { 234 data(1).put(key, value); 235 return this; 236 } 237 238 /** 239 * Removes the key and associated value from the attribtues. 240 * 241 * @since 1.22.0 242 * @param key The key to remove 243 * @return this 244 */ 245 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/5777") discard(Key<T> key)246 public <T> Builder discard(Key<T> key) { 247 if (base.data.containsKey(key)) { 248 IdentityHashMap<Key<?>, Object> newBaseData = new IdentityHashMap<>(base.data); 249 newBaseData.remove(key); 250 base = new Attributes(newBaseData); 251 } 252 if (newdata != null) { 253 newdata.remove(key); 254 } 255 return this; 256 } 257 setAll(Attributes other)258 public Builder setAll(Attributes other) { 259 data(other.data.size()).putAll(other.data); 260 return this; 261 } 262 263 /** 264 * Build the attributes. 265 */ build()266 public Attributes build() { 267 if (newdata != null) { 268 for (Map.Entry<Key<?>, Object> entry : base.data.entrySet()) { 269 if (!newdata.containsKey(entry.getKey())) { 270 newdata.put(entry.getKey(), entry.getValue()); 271 } 272 } 273 base = new Attributes(newdata); 274 newdata = null; 275 } 276 return base; 277 } 278 } 279 } 280