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