001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023–2025 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.bean;
015
016import java.util.ArrayList;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022
023import org.microbean.attributes.Attributes;
024import org.microbean.attributes.StringValue;
025
026/**
027 * A utility class providing methods that work with interceptor bindings.
028 *
029 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
030 */
031public final class InterceptorBindings {
032
033  private static final Attributes INTERCEPTOR_BINDING = Attributes.of("InterceptorBinding");
034
035  private static final List<Attributes> INTERCEPTOR_BINDING_LIST = List.of(INTERCEPTOR_BINDING);
036
037  private static final Attributes ANY_INTERCEPTOR_BINDING = Attributes.of("Any", INTERCEPTOR_BINDING_LIST);
038
039  private InterceptorBindings() {
040    super();
041  }
042
043  /**
044   * Returns a {@link Attributes} representing the <dfn>any</dfn> interceptor binding.
045   *
046   * @return a {@link Attributes} representing the <dfn>any</dfn> interceptor binding; never {@code null}
047   */
048  public static final Attributes anyInterceptorBinding() {
049    return ANY_INTERCEPTOR_BINDING;
050  }
051
052  /**
053   * Returns {@code true} if and only if the supplied {@link Attributes} represents the <dfn>any</dfn>
054   * interceptor binding.
055   *
056   * @param a a {@link Attributes}; may be {@code null} in which case {@code false} will be returned
057   *
058   * @return {@code true} if and only if the supplied {@link Attributes} represents the <dfn>any</dfn>
059   * interceptor binding
060   *
061   * @see #anyInterceptorBinding()
062   */
063  public static final boolean anyInterceptorBinding(final Attributes a) {
064    return ANY_INTERCEPTOR_BINDING.equals(a) && interceptorBinding(a);
065  }
066
067  /**
068   * Returns a {@link Attributes} representing the <dfn>interceptor binding</dfn> (meta-) interceptor binding.
069   *
070   * @return a {@link Attributes} representing the <dfn>interceptor binding</dfn> (meta-) interceptor binding;
071   * never {@code null}
072   */
073  public static final Attributes interceptorBinding() {
074    return INTERCEPTOR_BINDING;
075  }
076
077  /**
078   * Returns {@code true} if and only if the supplied {@link Attributes} is itself a {@link Attributes} that can be used
079   * to designate other {@link Attributes} instances as interceptor bindings, or a {@link Attributes} so designated.
080   *
081   * @param a a {@link Attributes}; may be {@code null} in which case {@code false} will be returned
082   *
083   * @return {@code true} if and only if the supplied {@link Attributes} is itself a {@link Attributes}
084   * that can be used to designate other {@link Attributes} instances as interceptor bindings, or a {@link
085   * Attributes} so designated
086   *
087   * @see #interceptorBinding()
088   */
089  public static final boolean interceptorBinding(final Attributes a) {
090    return a != null && interceptorBinding(a.attributes(a.name()));
091  }
092
093  private static final boolean interceptorBinding(final Iterable<? extends Attributes> mds) {
094    for (final Attributes md : mds) {
095      if (md.equals(INTERCEPTOR_BINDING) && md.attributes().isEmpty() || interceptorBinding(md)) {
096        return true;
097      }
098    }
099    return false;
100  }
101
102  /**
103   * Given a {@link Collection} of {@link Attributes}s, returns an immutable {@link List} consisting of those
104   * {@link Attributes} instances that are {@linkplain #interceptorBinding() deemed to be interceptor bindings}.
105   *
106   * @param c a {@link Collection}; must not be {@code null}
107   *
108   * @return a {@link List} of interceptor bindings
109   *
110   * @exception NullPointerException if {@code c} is {@code null}
111   */
112  public static final List<Attributes> interceptorBindings(final Collection<? extends Attributes> c) {
113    if (c.isEmpty()) {
114      return List.of();
115    }
116    final ArrayList<Attributes> list = new ArrayList<>(c.size());
117    for (final Attributes a : c) {
118      if (interceptorBinding(a)) {
119        list.add(a);
120      }
121    }
122    list.trimToSize();
123    return Collections.unmodifiableList(list);
124  }
125
126  /**
127   * Returns a {@link Attributes} representing a <dfn>target class</dfn> interceptor binding.
128   *
129   * @param type the target class name; must not be {@code null}
130   *
131   * @return a {@link Attributes} representing a <dfn>target class</dfn> interceptor binding; never {@code null}
132   *
133   * @exception NullPointerException if {@code type} is {@code null}
134   */
135  public static final Attributes targetClassInterceptorBinding(final String type) {
136    return Attributes.of("TargetClass", Map.of("class", StringValue.of(type)), Map.of(), Map.of("TargetClass", INTERCEPTOR_BINDING_LIST));
137  }
138
139  /**
140   * Returns {@code true} if and only if the supplied {@link Attributes} is a <dfn>target class</dfn> interceptor
141   * binding.
142   *
143   * @param a a {@link Attributes}; must not be {@code null}
144   *
145   * @return {@code true} if and only if the supplied {@link Attributes} is a <dfn>target class</dfn> interceptor
146   * binding
147   *
148   * @exception NullPointerException if {@code a} is {@code null}
149   */
150  // Is a a TargetClass interceptor binding?
151  public static final boolean targetClassInterceptorBinding(final Attributes a) {
152    return a.name().equals("TargetClass") && interceptorBinding(a);
153  }
154
155}