001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2024 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 -> this.delegate().equals(her.delegate());
102    case Directive her -> this.delegate().equals(her);
103    default -> false;
104    };
105  }
106
107  @Override // RequiresDirective
108  public final UniversalElement getDependency() {
109    return switch(this.getKind()) {
110    case REQUIRES -> UniversalElement.of(((RequiresDirective)this.delegate()).getDependency(), this.domain);
111    case EXPORTS, OPENS, PROVIDES, USES -> null;
112    };
113  }
114
115  @Override // ProvidesDirective
116  public final List<? extends UniversalElement> getImplementations() {
117    return switch (this.getKind()) {
118    case PROVIDES -> UniversalElement.of(((ProvidesDirective)this.delegate()).getImplementations(), this.domain);
119    case EXPORTS, OPENS, REQUIRES, USES -> List.of();
120    };
121  }
122
123  @Override // Directive
124  public final DirectiveKind getKind() {
125    return this.delegate().getKind();
126  }
127
128  @Override // ExportsDirective, OpensDirective
129  public final UniversalElement getPackage() {
130    return switch (this.getKind()) {
131    case EXPORTS -> UniversalElement.of(((ExportsDirective)this.delegate()).getPackage(), this.domain);
132    case OPENS -> UniversalElement.of(((OpensDirective)this.delegate()).getPackage(), this.domain);
133    case PROVIDES, REQUIRES, USES -> null;
134    };
135  }
136
137  @Override // ProvidesDirective
138  public final UniversalElement getService() {
139    return switch (this.getKind()) {
140    case PROVIDES -> UniversalElement.of(((ProvidesDirective)this.delegate()).getService(), this.domain);
141    case USES -> UniversalElement.of(((UsesDirective)this.delegate()).getService(), this.domain);
142    case EXPORTS, OPENS, REQUIRES -> null;
143    };
144  }
145
146  @Override // ExportsDirective, OpensDirective
147  public final List<? extends UniversalElement> getTargetModules() {
148    return switch (this.getKind()) {
149    case EXPORTS -> UniversalElement.of(((ExportsDirective)this.delegate()).getTargetModules(), this.domain);
150    case OPENS -> UniversalElement.of(((OpensDirective)this.delegate()).getTargetModules(), this.domain);
151    default -> List.of();
152    };
153  }
154
155  @Override // Object
156  public final int hashCode() {
157    return this.delegate().hashCode();
158  }
159
160  @Override // RequiresDirective
161  public final boolean isStatic() {
162    return switch (this.getKind()) {
163    case REQUIRES -> ((RequiresDirective)this.delegate()).isStatic();
164    case EXPORTS, OPENS, PROVIDES, USES -> false;
165    };
166  }
167
168  @Override // RequiresDirective
169  public final boolean isTransitive() {
170    return switch (this.getKind()) {
171    case REQUIRES -> ((RequiresDirective)this.delegate()).isTransitive();
172    case EXPORTS, OPENS, PROVIDES, USES -> false;
173    };
174  }
175
176  @Override // Object
177  @SuppressWarnings("try")
178  public final String toString() {
179    try (var lock = this.domain.lock()) {
180      return this.delegate().toString();
181    }
182  }
183
184
185  /*
186   * Static methods.
187   */
188
189
190  /**
191   * Returns a {@link UniversalDirective} that is either the supplied {@link Directive} (if it itself is {@code null} or is
192   * a {@link UniversalDirective}) or one that wraps it.
193   *
194   * @param d an {@link Directive}; may be {@code null} in which case {@code null} will be returned
195   *
196   * @param domain a {@link Domain}; must not be {@code null}
197   *
198   * @return a {@link UniversalDirective}, or {@code null} (if {@code e} is {@code null})
199   *
200   * @exception NullPointerException if {@code domain} is {@code null}
201   *
202   * @see #UniversalDirective(Directive, Domain)
203   */
204  public static final UniversalDirective of(final Directive d, final Domain domain) {
205    return switch (d) {
206    case null -> null;
207    case UniversalDirective ud -> ud;
208    default -> new UniversalDirective(d, domain);
209    };
210  }
211
212  /**
213   * Returns a non-{@code null}, immutable {@link List} of {@link UniversalDirective}s whose elements wrap the supplied
214   * {@link List}'s elements.
215   *
216   * @param es a {@link Collection} of {@link Directive}s; must not be {@code null}
217   *
218   * @param domain a {@link Domain}; must not be {@code null}
219   *
220   * @return a non-{@code null}, immutable {@link List} of {@link UniversalDirective}s
221   *
222   * @exception NullPointerException if either argument is {@code null}
223   */
224  public static final List<? extends UniversalDirective> of(final Collection<? extends Directive> es, final Domain domain) {
225    if (es.isEmpty()) {
226      return List.of();
227    }
228    final List<UniversalDirective> newEs = new ArrayList<>(es.size());
229    for (final Directive e : es) {
230      newEs.add(of(e, domain));
231    }
232    return Collections.unmodifiableList(newEs);
233  }
234
235  /**
236   * <dfn>Unwraps</dfn> the supplied {@link Directive} implementation such that the returned value is not an
237   * instance of {@link UniversalDirective}.
238   *
239   * @param <T> a {@link Directive} subtype
240   *
241   * @param t a {@link Directive}; may be {@code null}
242   *
243   * @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalDirective}
244   *
245   * @see #delegate()
246   */
247  @SuppressWarnings("unchecked")
248  public static final <T extends Directive> T unwrap(T t) {
249    while (t instanceof UniversalDirective ud) {
250      t = (T)ud.delegate();
251    }
252    return t;
253  }
254
255}