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}