001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2026 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.attributes; 015 016import java.util.List; 017import java.util.Map; 018 019import java.util.concurrent.ConcurrentHashMap; 020 021/** 022 * A simple, mutable, concurrent, {@link Attributed}-aware cache of objects. 023 * 024 * @param <T> the type of object to be normalized 025 * 026 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 027 * 028 * @see #normalize(Object) 029 */ 030public final class Normalizer<T> { 031 032 private final Map<Key<? extends T>, T> cache; 033 034 /** 035 * Creates a new {@link Normalizer}. 036 */ 037 public Normalizer() { 038 super(); 039 this.cache = new ConcurrentHashMap<>(); 040 } 041 042 /** 043 * <dfn>Normalizes</dfn> the supplied {@code element} such that if this {@link Normalizer} already has an {@linkplain 044 * Object#equals(Object) equivalent} cached element, the cached element is returned, and, if it does not, the supplied 045 * {@code element} is cached indefinitely and returned. 046 * 047 * <p><strong>Note:</strong> if the supplied {@code element} is an {@link Attributed} implementation, then, in 048 * accordance with the {@link Attributed} contract, it must not include its {@linkplain Attributed#attributes() 049 * attributes} in its equality or {@linkplain Attributed#hashCode() hashcode} computations, or undefined behavior may 050 * result.</p> 051 * 052 * @param element the element to normalize; may be {@code null} in which case {@code null} is returned 053 * 054 * @return the supplied {@code element} or a previously cached {@linkplain Object#equals(Object) equivalent} element 055 */ 056 public final T normalize(final T element) { 057 return element instanceof Attributed a ? this.cache.computeIfAbsent(new Key<>(a.attributes(), element), k -> k.element()) : element; 058 } 059 060 private static final record Key<T>(List<Attributes> attributes, T element) implements Attributed {} 061 062}