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