001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–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.event;
015
016import java.util.Iterator;
017import java.util.List;
018
019import javax.lang.model.element.AnnotationMirror;
020import javax.lang.model.element.Element;
021
022import javax.lang.model.type.TypeMirror;
023
024import org.microbean.assign.Annotated;
025
026import org.microbean.bean.Qualifiers;
027import org.microbean.bean.ReferencesSelector;
028
029import org.microbean.construct.Domain;
030
031import org.microbean.construct.type.UniversalType;
032
033import static java.util.Objects.requireNonNull;
034
035/**
036 * A utility class for working with <dfn>events</dfn>.
037 *
038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
039 */
040// Deliberately not final.
041public class Events {
042
043  private final EventTypes eventTypes;
044
045  private final org.microbean.assign.Qualifiers aq;
046
047  private final EventTypeMatcher eventTypeMatcher;
048
049  private final EventQualifiersMatcher eventQualifiersMatcher;
050
051  private final Annotated<TypeMirror> eventListenerAnnotated;
052
053  /**
054   * Creates a new {@link Events}.
055   *
056   * @param domain a non-{@code null} Domain
057   *
058   * @param eventTypes a non-{@code null} {@link EventTypes}
059   *
060   * @param aq a non-{@code null} {@link org.microbean.assign.Qualifiers}
061   *
062   * @param bq a non-{@code null} {@link Qualifiers}
063   *
064   * @param eventTypeMatcher a non-{@code null} {@link EventTypeMatcher}
065   *
066   * @param eventQualifiersMatcher a non-{@code null} {@link EventQualifiersMatcher}
067   *
068   * @exception NullPointerException if any argument is {@code null}
069   */
070  public Events(final Domain domain,
071                final EventTypes eventTypes,
072                final org.microbean.assign.Qualifiers aq,
073                final Qualifiers bq,
074                final EventTypeMatcher eventTypeMatcher,
075                final EventQualifiersMatcher eventQualifiersMatcher) {
076    super();
077    this.eventTypes = requireNonNull(eventTypes, "eventTypes");
078    this.aq = requireNonNull(aq, "aq");
079    this.eventTypeMatcher = requireNonNull(eventTypeMatcher, "eventTypeMatcher");
080    this.eventQualifiersMatcher = requireNonNull(eventQualifiersMatcher, "eventQualifiersMatcher");
081    this.eventListenerAnnotated =
082      Annotated.of(new UniversalType(bq.anyQualifiers(),
083                                     domain.declaredType(domain.typeElement(EventListener.class.getCanonicalName()),
084                                                         domain.wildcardType(),
085                                                         domain.wildcardType()),
086                                     domain));
087  }
088
089  /**
090   * Delivers ("fires") the supplied {@code event} to <dfn>suitable</dfn> {@link EventListener}s.
091   *
092   * @param typeArgumentSource handwave here about the specified type and type argument substitution
093   *
094   * @param annotations a {@link List} of {@link AnnotationMirror}s qualifying the event; must not be {@code null}
095   *
096   * @param event the event; must not be {@code null}
097   *
098   * @param rs a {@link ReferencesSelector}; used to find {@link EventListener EventListener&lt;?, ?&gt;} references;
099   * must not be {@code null}
100   *
101   * @exception NullPointerException if any argument is {@code null}
102   *
103   * @exception IllegalArgumentException if {@code typeArgumentSource} is unsuitable
104   *
105   * @see #fire(EventListener, Object, ReferencesSelector)
106   *
107   * @see EventTypes#eventTypes(TypeMirror, Object)
108   */
109  // Deliberately final.
110  public final void fire(final TypeMirror typeArgumentSource,
111                         final List<AnnotationMirror> annotations,
112                         final Object event,
113                         final ReferencesSelector rs) {
114    final EventTypeList eventTypes = this.eventTypes.eventTypes(typeArgumentSource, event);
115    final List<AnnotationMirror> eventQualifiers = this.aq.qualifiers(annotations);
116    final Iterator<? extends EventListener<?, ? super Object>> i =
117      rs.<EventListener<?, ? super Object>>references(this.eventListenerAnnotated).iterator();
118    while (i.hasNext()) {
119      final EventListener<?, ? super Object> el = i.next();
120      try {
121        final Annotated<? extends Element> slot = el.eventDependency();
122        if (slot == null ||
123            !this.eventQualifiersMatcher.test(this.aq.qualifiers(slot.annotations()), eventQualifiers)) {
124          continue;
125        }
126        final TypeMirror slotType = slot.annotated().asType();
127        for (final TypeMirror eventType : eventTypes) {
128          if (this.eventTypeMatcher.test(slotType, eventType)) {
129            // This level of indirection permits asynchronous notification.
130            this.fire(el, event, rs);
131            break;
132          }
133        }
134      } finally {
135        i.remove(); // if the EventListener is in a scope where it can be removed, do so, otherwise no-op
136      }
137    }
138  }
139
140  /**
141   * Delivers ("Fires") the supplied {@code event} to the supplied {@link EventListener} via its {@link
142   * EventListener#eventReceived(Object, ReferencesSelector)} method.
143   *
144   * <p>The default implementation of this method behaves as if its body were exactly {@link EventListener
145   * el}<code>.</code>{@link EventListener#eventReceived(Object, ReferencesSelector) eventReceived(event, rs)}.</p>
146   *
147   * <p>When this method is invoked by the {@link #fire(TypeMirror, List, Object, ReferencesSelector)} method, it is
148   * guaranteed that the supplied {@link EventListener} is a suitable one for the supplied {@code event}.</p>
149   *
150   * <p>Overrides of this method must not call the {@link #fire(TypeMirror, List, Object, ReferencesSelector)} method,
151   * or an infinite loop may result.</p>
152   *
153   * @param el an {@link EventListener} that has been determined to be suitable for the supplied {@code event}; must
154   * not be {@code null}
155   *
156   * @param event the event to deliver; must not be {@code null}
157   *
158   * @param rs a {@link ReferencesSelector}; must not be {@code null}
159   *
160   * @exception NullPointerException if any argument is {@code null}
161   */
162  protected void fire(final EventListener<?, ? super Object> el,
163                      final Object event,
164                      final ReferencesSelector rs) {
165    el.eventReceived(event, rs);
166  }
167
168}