001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.qualifier; 018 019import java.lang.constant.MethodHandleDesc; 020import java.lang.constant.MethodTypeDesc; 021 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Objects; 026import java.util.TreeSet; 027 028import java.util.function.Function; 029 030import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC; 031 032import static org.microbean.constant.ConstantDescs.CD_Iterable; 033 034import static org.microbean.qualifier.ConstantDescs.CD_Qualifiers; 035 036/** 037 * An immutable {@link Bindings} containing {@link Qualifier} 038 * instances. 039 * 040 * <p>This is a <a 041 * href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html">value-based</a> 042 * class.</p> 043 * 044 * @param <V> the type of a {@link Qualifier}'s {@linkplain 045 * Qualifier#attributes() attribute values} 046 * 047 * @author <a href="https://about.me/lairdnelson" 048 * target="_parent">Laird Nelson</a> 049 * 050 * @see Bindings 051 */ 052public final class Qualifiers<V> extends Bindings<V, Qualifier<V>> { 053 054 055 /* 056 * Static fields. 057 */ 058 059 060 private static final Qualifiers<?> EMPTY = new Qualifiers<>(List.of()); 061 062 063 /* 064 * Constructors. 065 */ 066 067 068 private Qualifiers(final Iterable<? extends Qualifier<V>> qualifiers) { 069 super(qualifiers); 070 } 071 072 073 /* 074 * Instance methods. 075 */ 076 077 078 /** 079 * Returns a <strong>usually new</strong> {@link Qualifiers} with 080 * this {@link Qualifiers}' entries and an additional entry 081 * consisting of the supplied {@link Qualifier}. 082 * 083 * <p>The returned {@link Qualifiers} <strong>will be new</strong> 084 * unless {@code qualifier} is {@code null}, in which case {@code 085 * this} will be returned.</p> 086 * 087 * @param qualifier a {@link Qualifier}; may be {@code null} in 088 * which case {@code this} will be returned 089 * 090 * @return a {@link Qualifiers} with this {@link Qualifiers}' 091 * entries and an additional entry consisting of the supplied {@link 092 * Qualifier} 093 * 094 * @nullability This method never returns {@code null}. 095 * 096 * @idempotency This method is idempotent and deterministic. 097 * 098 * @threadsafety This method is safe for concurrent use by multiple 099 * threads. 100 * 101 * @see #plus(Iterable) 102 */ 103 public final Qualifiers<V> plus(final Qualifier<V> qualifier) { 104 if (qualifier == null) { 105 return this; 106 } else if (this.isEmpty()) { 107 return of(qualifier); 108 } else { 109 return this.plus(List.of(qualifier)); 110 } 111 } 112 113 /** 114 * Returns a <strong>usually new</strong> {@link Qualifiers} with 115 * this {@link Qualifiers}' entries and additional entries 116 * represented by the supplied {@code qualifiers}. 117 * 118 * <p>The returned {@link Qualifiers} <strong>will be new</strong> 119 * unless {@code qualifier} is {@code null}, in which case {@code 120 * this} will be returned.</p> 121 * 122 * @param qualifiers additional {@link Qualifier}s; may be {@code 123 * null} in which case {@code this} will be returned 124 * 125 * @return a {@link Qualifiers} with this {@link Qualifiers}' 126 * entries and additional entries represented by the supplied {@code 127 * qualifiers} 128 * 129 * @nullability This method never returns {@code null}. 130 * 131 * @idempotency This method is idempotent and deterministic. 132 * 133 * @threadsafety This method is safe for concurrent use by multiple 134 * threads. 135 */ 136 public final Qualifiers<V> plus(final Iterable<? extends Qualifier<V>> qualifiers) { 137 if (qualifiers == null) { 138 return this; 139 } else if (this.isEmpty()) { 140 return of(qualifiers); 141 } 142 final Collection<Qualifier<V>> newQualifiers = new TreeSet<>(); 143 for (final Qualifier<V> q : this) { 144 newQualifiers.add(q); 145 } 146 for (final Qualifier<V> q : qualifiers) { 147 newQualifiers.add(q); 148 } 149 return of(newQualifiers); 150 } 151 152 /** 153 * Returns a <strong>usually new</strong> {@link Qualifiers} whose 154 * {@link Qualifier}s' {@linkplain Qualifier#attributes() attribute 155 * keys} are prefixed with the supplied {@code prefix}. 156 * 157 * <p>If this {@link Qualifiers} is {@linkplain #isEmpty() empty}, 158 * then {@code this} is returned.</p> 159 * 160 * @param prefix a prefix; if {@code null} then {@code this} will be returned 161 * 162 * @return a <strong>usually new</strong> {@link Qualifiers} whose 163 * {@link Qualifier}s' {@linkplain Qualifier#attributes() attribute 164 * keys} are prefixed with the supplied {@code prefix} 165 * 166 * @nullability This method never returns {@code null}. 167 * 168 * @idempotency This method is idempotent and deterministic, 169 * assuming the supplied {@link Function} is. 170 * 171 * @threadsafety This method is safe for concurrent use by multiple 172 * threads 173 */ 174 public final Qualifiers<V> withPrefix(final String prefix) { 175 if (prefix == null || this.isEmpty()) { 176 return this; 177 } 178 return this.withPrefix(q -> prefix + q.name()); 179 } 180 181 /** 182 * Returns a <strong>usually new</strong> {@link Qualifiers} whose 183 * {@link Qualifier}s' {@linkplain Qualifier#attributes() attribute 184 * keys} are produced by the supplied {@link Function}, which is 185 * expected to prepend a prefix to the original key and return the 186 * result. 187 * 188 * <p>If this {@link Qualifiers} is {@linkplain #isEmpty() empty}, 189 * then {@code this} is returned.</p> 190 * 191 * @param f a deterministic, idempotent {@link Function} that 192 * accepts keys drawn from this {@link Qualifiers}' {@link 193 * Qualifier}s' {@linkplain Qualifier#attributes() attribute keys} 194 * and returns a non-{@code null} prefixed version of that key; may 195 * be {@code null} in which case {@code this} will be returned 196 * 197 * @return a <strong>usually new</strong> {@link Qualifiers} whose 198 * {@link Qualifier}s' {@linkplain Qualifier#attributes() attribute 199 * keys} have been prefixed by the actions of the supplied {@link 200 * Function} 201 * 202 * @nullability This method never returns {@code null}. 203 * 204 * @idempotency This method is idempotent and deterministic, 205 * assuming the supplied {@link Function} is. 206 * 207 * @threadsafety This method is safe for concurrent use by multiple 208 * threads, assuming the supplied {@link Function} is 209 */ 210 public final Qualifiers<V> withPrefix(final Function<? super Qualifier<V>, ? extends String> f) { 211 if (f == null || this.isEmpty()) { 212 return this; 213 } 214 final Collection<Qualifier<V>> newQualifiers = new TreeSet<>(); 215 for (final Qualifier<V> q : this) { 216 newQualifiers.add(Qualifier.of(f.apply(q), 217 q.value(), 218 q.attributes(), 219 q.info())); 220 } 221 return of(newQualifiers); 222 } 223 224 /** 225 * Returns a {@link MethodHandleDesc} describing the constructor or 226 * {@code static} method that will be used to create a <a 227 * href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/package-summary.html#condycon">dynamic 228 * constant</a> representing this {@link Qualifiers}. 229 * 230 * @return a {@link MethodHandleDesc} describing the constructor or 231 * {@code static} method that will be used to create a <a 232 * href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/package-summary.html#condycon">dynamic 233 * constant</a> representing this {@link Qualifiers} 234 * 235 * @nullability This method does not, and its overrides must not, 236 * return {@code null}. 237 * 238 * @idempotency This method is, and its overrides must be, 239 * idempotent and deterministic. 240 * 241 * @threadsafety This method is, and its overrides must be, safe for 242 * concurrent use by multiple threads. 243 */ 244 @Override // Bindings<V, Qualifier<V>> 245 protected final MethodHandleDesc describeConstructor() { 246 return 247 MethodHandleDesc.ofMethod(STATIC, 248 CD_Qualifiers, 249 "of", 250 MethodTypeDesc.of(CD_Qualifiers, CD_Iterable)); 251 } 252 253 254 /* 255 * Static methods. 256 */ 257 258 259 /** 260 * Returns a {@link Qualifiers}, which may or may not be newly 261 * created, whose {@link #isEmpty() isEmpty()} method will return 262 * {@code true}. 263 * 264 * @param <V> the type of the {@link Qualifier}'s {@linkplain 265 * Qualifier#attributes() attribute values} 266 * 267 * @return a {@link Qualifiers} 268 * 269 * @nullability This method never returns {@code null}. 270 * 271 * @idempotency This method is idempotent and deterministic. 272 * 273 * @threadsafety This method is safe for concurrent use by multiple 274 * threads. 275 */ 276 @SuppressWarnings("unchecked") 277 public static final <V> Qualifiers<V> of() { 278 return (Qualifiers<V>)EMPTY; 279 } 280 281 /** 282 * Returns a {@link Qualifiers}, which may or may not be newly 283 * created, representing the supplied arguments. 284 * 285 * @param <V> the type of the {@link Qualifier}'s {@linkplain 286 * Qualifier#attributes() attribute values} 287 * 288 * @param qualifier the sole {@link Qualifier} the {@link 289 * Qualifiers} will contain; must not be {@code null} 290 * 291 * @return a {@link Qualifiers} 292 * 293 * @exception NullPointerException if {@code qualifier} is {@code 294 * null} 295 * 296 * @nullability This method never returns {@code null}. 297 * 298 * @idempotency This method is idempotent and deterministic. 299 * 300 * @threadsafety This method is safe for concurrent use by multiple 301 * threads. 302 */ 303 public static final <V> Qualifiers<V> of(final Qualifier<V> qualifier) { 304 return of(List.of(Objects.requireNonNull(qualifier, "qualifier"))); 305 } 306 307 /** 308 * Returns a {@link Qualifiers}, which may or may not be newly 309 * created, representing the supplied arguments. 310 * 311 * @param <V> the type of the {@link Qualifier}'s {@linkplain 312 * Qualifier#attributes() attribute values} 313 * 314 * @param qualifier0 the first {@link Qualifier} the {@link 315 * Qualifiers} will contain; must not be {@code null} 316 * 317 * @param qualifier1 the second {@link Qualifier} the {@link 318 * Qualifiers} will contain; must not be {@code null} 319 * 320 * @return a {@link Qualifiers} 321 * 322 * @exception NullPointerException if {@code qualifier} is {@code 323 * null} 324 * 325 * @nullability This method never returns {@code null}. 326 * 327 * @idempotency This method is idempotent and deterministic. 328 * 329 * @threadsafety This method is safe for concurrent use by multiple 330 * threads. 331 */ 332 public static final <V> Qualifiers<V> of(final Qualifier<V> qualifier0, final Qualifier<V> qualifier1) { 333 return of(List.of(qualifier0, qualifier1)); 334 } 335 336 /** 337 * Returns a {@link Qualifiers}, which may or may not be newly 338 * created, representing the supplied arguments. 339 * 340 * @param <V> the type of the {@link Qualifier}'s {@linkplain 341 * Qualifier#attributes() attribute values} 342 * 343 * @param qualifiers an {@link Iterable} representing {@link 344 * Qualifier} instances the {@link Qualifiers} will contain; may be 345 * {@code null} 346 * 347 * @return a {@link Qualifiers} 348 * 349 * @nullability This method never returns {@code null}. 350 * 351 * @idempotency This method is idempotent and deterministic. 352 * 353 * @threadsafety This method is safe for concurrent use by multiple 354 * threads. 355 */ 356 public static final <V> Qualifiers<V> of(final Iterable<? extends Qualifier<V>> qualifiers) { 357 if (qualifiers == null) { 358 return of(); 359 } 360 final Iterator<? extends Qualifier<V>> i = qualifiers.iterator(); 361 if (i.hasNext()) { 362 final Collection<Qualifier<V>> newQualifiers = new TreeSet<>(); 363 newQualifiers.add(i.next()); 364 while (i.hasNext()) { 365 newQualifiers.add(i.next()); 366 } 367 return new Qualifiers<>(newQualifiers); 368 } 369 return of(); 370 } 371 372 /** 373 * Returns a {@link Qualifiers}, which may or may not be newly 374 * created, representing the supplied arguments. 375 * 376 * @param qualifier0 the first {@link Qualifier} the {@link 377 * Qualifiers} will contain; must not be {@code null} 378 * 379 * @param qualifier1 the second {@link Qualifier} the {@link 380 * Qualifiers} will contain; must not be {@code null} 381 * 382 * @return a {@link Qualifiers} 383 * 384 * @exception NullPointerException if {@code qualifier} is {@code 385 * null} 386 * 387 * @nullability This method never returns {@code null}. 388 * 389 * @idempotency This method is idempotent and deterministic. 390 * 391 * @threadsafety This method is safe for concurrent use by multiple 392 * threads. 393 */ 394 public static final Qualifiers<?> ofDisparate(final Qualifier<?> qualifier0, final Qualifier<?> qualifier1) { 395 return ofDisparate(List.of(qualifier0, qualifier1)); 396 } 397 398 399 /** 400 * Returns a {@link Qualifiers}, which may or may not be newly 401 * created, representing the supplied arguments. 402 * 403 * @param qualifiers an {@link Iterable} representing {@link 404 * Qualifier} instances the {@link Qualifiers} will contain; may be 405 * {@code null} 406 * 407 * @return a {@link Qualifiers} 408 * 409 * @nullability This method never returns {@code null}. 410 * 411 * @idempotency This method is idempotent and deterministic. 412 * 413 * @threadsafety This method is safe for concurrent use by multiple 414 * threads. 415 */ 416 @SuppressWarnings("unchecked") 417 public static final Qualifiers<?> ofDisparate(final Iterable<? extends Qualifier<?>> qualifiers) { 418 if (qualifiers == null) { 419 return of(); 420 } 421 final Iterator<? extends Qualifier<?>> i = qualifiers.iterator(); 422 if (i.hasNext()) { 423 final Collection<Qualifier<Object>> newQualifiers = new TreeSet<>(); 424 newQualifiers.add((Qualifier<Object>)i.next()); 425 while (i.hasNext()) { 426 newQualifiers.add((Qualifier<Object>)i.next()); 427 } 428 return new Qualifiers<>(newQualifiers); 429 } 430 return of(); 431 } 432 433}