001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024–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.construct.element;
015
016import java.util.ArrayList;
017import java.util.Collection;
018import java.util.Collections;
019import java.util.List;
020import java.util.Objects;
021
022import java.util.function.Supplier;
023
024import javax.lang.model.element.ModuleElement.Directive;
025import javax.lang.model.element.ModuleElement.DirectiveKind;
026import javax.lang.model.element.ModuleElement.DirectiveVisitor;
027import javax.lang.model.element.ModuleElement.ExportsDirective;
028import javax.lang.model.element.ModuleElement.OpensDirective;
029import javax.lang.model.element.ModuleElement.ProvidesDirective;
030import javax.lang.model.element.ModuleElement.RequiresDirective;
031import javax.lang.model.element.ModuleElement.UsesDirective;
032
033import org.microbean.construct.Domain;
034
035/**
036 * A {@link Directive} implementation.
037 *
038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
039 *
040 * @see Directive#getKind()
041 */
042public final class UniversalDirective
043  implements ExportsDirective, OpensDirective, ProvidesDirective, RequiresDirective, UsesDirective {
044
045  private final Domain domain;
046
047  // volatile not needed
048  private Supplier<? extends Directive> delegateSupplier;
049
050  /**
051   * Creates a new {@link UniversalDirective}.
052   *
053   * @param delegate a {@link Directive} to which operations will be delegated; must not be {@code null}
054   *
055   * @param domain a {@link Domain} from which the supplied {@code delegate} is presumed to have originated; must not be
056   * {@code null}
057   *
058   * @exception NullPointerException if either argument is {@code null}
059   */
060  @SuppressWarnings("try")
061  public UniversalDirective(final Directive delegate, final Domain domain) {
062    super();
063    this.domain = Objects.requireNonNull(domain, "domain");
064    final Directive unwrappedDelegate = unwrap(Objects.requireNonNull(delegate, "delegate"));
065    final Runnable symbolCompleter = unwrappedDelegate::getKind;
066    this.delegateSupplier = () -> {
067      try (var lock = domain.lock()) {
068        symbolCompleter.run();
069        this.delegateSupplier = () -> unwrappedDelegate;
070      }
071      return unwrappedDelegate;
072    };
073  }
074
075  @Override // Directive
076  public final <R, P> R accept(final DirectiveVisitor<R, P> v, final P p) {
077    return switch (this.getKind()) {
078    case EXPORTS -> v.visitExports(this, p);
079    case OPENS -> v.visitOpens(this, p);
080    case PROVIDES -> v.visitProvides(this, p);
081    case REQUIRES -> v.visitRequires(this, p);
082    case USES -> v.visitUses(this, p);
083    };
084  }
085
086  /**
087   * Returns the delegate to which operations are delegated.
088   *
089   * @return a non-{@code null} delegate
090   *
091   * @see Directive
092   */
093  public final Directive delegate() {
094    return this.delegateSupplier.get();
095  }
096
097  @Override // Object
098  public final boolean equals(final Object other) {
099    return other == this || switch (other) {
100    case null -> false;
101    case UniversalDirective her when this.getClass() == her.getClass() -> this.delegate().equals(her.delegate());
102    default -> false;
103    };
104  }
105
106  @Override // RequiresDirective
107  public final UniversalElement getDependency() {
108    return switch(this.getKind()) {
109    case REQUIRES -> UniversalElement.of(((RequiresDirective)this.delegate()).getDependency(), this.domain);
110    case EXPORTS, OPENS, PROVIDES, USES -> null;
111    };
112  }
113
114  @Override // ProvidesDirective
115  public final List<? extends UniversalElement> getImplementations() {
116    return switch (this.getKind()) {
117    case PROVIDES -> UniversalElement.of(((ProvidesDirective)this.delegate()).getImplementations(), this.domain);
118    case EXPORTS, OPENS, REQUIRES, USES -> List.of();
119    };
120  }
121
122  @Override // Directive
123  public final DirectiveKind getKind() {
124    return this.delegate().getKind();
125  }
126
127  @Override // ExportsDirective, OpensDirective
128  public final UniversalElement getPackage() {
129    return switch (this.getKind()) {
130    case EXPORTS -> UniversalElement.of(((ExportsDirective)this.delegate()).getPackage(), this.domain);
131    case OPENS -> UniversalElement.of(((OpensDirective)this.delegate()).getPackage(), this.domain);
132    case PROVIDES, REQUIRES, USES -> null;
133    };
134  }
135
136  @Override // ProvidesDirective
137  public final UniversalElement getService() {
138    return switch (this.getKind()) {
139    case PROVIDES -> UniversalElement.of(((ProvidesDirective)this.delegate()).getService(), this.domain);
140    case USES -> UniversalElement.of(((UsesDirective)this.delegate()).getService(), this.domain);
141    case EXPORTS, OPENS, REQUIRES -> null;
142    };
143  }
144
145  @Override // ExportsDirective, OpensDirective
146  public final List<? extends UniversalElement> getTargetModules() {
147    return switch (this.getKind()) {
148    case EXPORTS -> UniversalElement.of(((ExportsDirective)this.delegate()).getTargetModules(), this.domain);
149    case OPENS -> UniversalElement.of(((OpensDirective)this.delegate()).getTargetModules(), this.domain);
150    default -> List.of();
151    };
152  }
153
154  @Override // Object
155  public final int hashCode() {
156    return this.delegate().hashCode();
157  }
158
159  @Override // RequiresDirective
160  public final boolean isStatic() {
161    return switch (this.getKind()) {
162    case REQUIRES -> ((RequiresDirective)this.delegate()).isStatic();
163    case EXPORTS, OPENS, PROVIDES, USES -> false;
164    };
165  }
166
167  @Override // RequiresDirective
168  public final boolean isTransitive() {
169    return switch (this.getKind()) {
170    case REQUIRES -> ((RequiresDirective)this.delegate()).isTransitive();
171    case EXPORTS, OPENS, PROVIDES, USES -> false;
172    };
173  }
174
175  @Override // Object
176  @SuppressWarnings("try")
177  public final String toString() {
178    try (var lock = this.domain.lock()) {
179      return this.delegate().toString();
180    }
181  }
182
183
184  /*
185   * Static methods.
186   */
187
188
189  /**
190   * Returns a {@link UniversalDirective} that is either the supplied {@link Directive} (if it itself is {@code null} or is
191   * a {@link UniversalDirective}) or one that wraps it.
192   *
193   * @param d an {@link Directive}; may be {@code null} in which case {@code null} will be returned
194   *
195   * @param domain a {@link Domain}; must not be {@code null}
196   *
197   * @return a {@link UniversalDirective}, or {@code null} (if {@code e} is {@code null})
198   *
199   * @exception NullPointerException if {@code domain} is {@code null}
200   *
201   * @see #UniversalDirective(Directive, Domain)
202   */
203  public static final UniversalDirective of(final Directive d, final Domain domain) {
204    return switch (d) {
205    case null -> null;
206    case UniversalDirective ud -> ud;
207    default -> new UniversalDirective(d, domain);
208    };
209  }
210
211  /**
212   * Returns a non-{@code null}, immutable {@link List} of {@link UniversalDirective}s whose elements wrap the supplied
213   * {@link List}'s elements.
214   *
215   * @param es a {@link Collection} of {@link Directive}s; must not be {@code null}
216   *
217   * @param domain a {@link Domain}; must not be {@code null}
218   *
219   * @return a non-{@code null}, immutable {@link List} of {@link UniversalDirective}s
220   *
221   * @exception NullPointerException if either argument is {@code null}
222   */
223  public static final List<? extends UniversalDirective> of(final Collection<? extends Directive> es, final Domain domain) {
224    if (es.isEmpty()) {
225      return List.of();
226    }
227    final List<UniversalDirective> newEs = new ArrayList<>(es.size());
228    for (final Directive e : es) {
229      newEs.add(of(e, domain));
230    }
231    return Collections.unmodifiableList(newEs);
232  }
233
234  /**
235   * <dfn>Unwraps</dfn> the supplied {@link Directive} implementation such that the returned value is not an
236   * instance of {@link UniversalDirective}.
237   *
238   * @param <T> a {@link Directive} subtype
239   *
240   * @param t a {@link Directive}; may be {@code null}
241   *
242   * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalDirective}
243   *
244   * @see #delegate()
245   */
246  @SuppressWarnings("unchecked")
247  public static final <T extends Directive> T unwrap(T t) {
248    while (t instanceof UniversalDirective ud) {
249      t = (T)ud.delegate();
250    }
251    return t;
252  }
253
254}