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}