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.proxy; 015 016import java.lang.reflect.Constructor; 017import java.lang.reflect.Executable; 018import java.lang.reflect.GenericArrayType; 019import java.lang.reflect.GenericDeclaration; 020import java.lang.reflect.Method; 021import java.lang.reflect.ParameterizedType; 022import java.lang.reflect.Type; 023import java.lang.reflect.TypeVariable; 024import java.lang.reflect.WildcardType; 025 026import java.util.List; 027 028import java.util.function.Supplier; 029 030import javax.lang.model.element.ExecutableElement; 031import javax.lang.model.element.Parameterizable; 032import javax.lang.model.element.TypeElement; 033 034import javax.lang.model.type.DeclaredType; 035import javax.lang.model.type.TypeKind; 036import javax.lang.model.type.TypeMirror; 037 038import java.util.function.Supplier; 039 040import org.microbean.construct.Domain; 041 042/** 043 * An {@link AbstractProxier} that helps subclasses create {@link Proxy proxies} using the {@link 044 * java.lang.reflect.Proxy java.lang.reflect.Proxy} machinery present in the Java Development Kit. 045 * 046 * <p>This class also contains various {@code protected} utility methods that help with converting reflective {@link 047 * Type}s and {@link Executable}s to their {@link TypeMirror} and {@link ExecutableElement} counterparts.</p> 048 * 049 * @param <PS> the {@link ProxySpecification} type 050 * 051 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 052 * 053 * @see #proxy(ProxySpecification, Supplier) 054 * 055 * @see #type(Type) 056 * 057 * @see #executableElement(Executable) 058 */ 059public abstract non-sealed class AbstractReflectiveProxier<PS extends ProxySpecification> extends AbstractProxier<PS> { 060 061 private static final TypeMirror[] EMPTY_TYPE_MIRROR_ARRAY = new TypeMirror[0]; 062 063 /** 064 * Creates a new {@link AbstractReflectiveProxier} implementation. 065 * 066 * @param domain a {@link Domain}; must not be {@code null} 067 * 068 * @exception NullPointerException if {@code domain} is {@code null} 069 */ 070 protected AbstractReflectiveProxier(final Domain domain) { 071 super(domain); 072 } 073 074 /** 075 * Returns {@code true} if the supplied {@link Method} is the {@link Object#equals(Object) 076 * java.lang.Object#equals(java.lang.Object)} method. 077 * 078 * @param m a {@link Method}; must not be {@code null} 079 * 080 * @return {@code true} if the supplied {@link Method} is the {@link Object#equals(Object) 081 * java.lang.Object#equals(java.lang.Object)} method 082 * 083 * @exception NullPointerException if {@code m} is {@code null} 084 */ 085 protected final boolean equalsMethod(final Method m) { 086 return 087 m.getDeclaringClass() == Object.class && 088 m.getReturnType() == boolean.class && 089 m.getParameterCount() == 1 && 090 m.getParameterTypes()[0] == Object.class && 091 m.getName().equals("equals"); 092 } 093 094 /** 095 * Returns an {@link ExecutableElement} corresponding to the supplied {@link Executable}. 096 * 097 * @param e an {@link Executable}; must not be {@code null} 098 * 099 * @return an {@link ExecutableElement} corresponding to the supplied {@link Executable}; never {@code null} 100 * 101 * @exception NullPointerException if {@code e} is {@code null} 102 * 103 * @exception IllegalArgumentException if somehow {@code e} is neither a {@link Constructor} nor a {@link Method} 104 */ 105 protected final ExecutableElement executableElement(final Executable e) { 106 final Domain domain = domain(); 107 return switch (e) { 108 case null -> throw new NullPointerException("e"); 109 case Constructor<?> c -> 110 domain.executableElement(domain.typeElement(c.getDeclaringClass().getCanonicalName()), 111 domain.noType(TypeKind.VOID), 112 "<init>", 113 this.types(c.getParameterTypes())); 114 case Method m -> 115 domain.executableElement(domain.typeElement(m.getDeclaringClass().getCanonicalName()), 116 this.type(m.getReturnType()), 117 m.getName(), 118 this.types(m.getParameterTypes())); 119 default -> throw new IllegalArgumentException("e: " + e); 120 }; 121 } 122 123 /** 124 * Returns {@code true} if the supplied {@link Method} is the {@link Object#hashCode() java.lang.Object#hashCode()} 125 * method. 126 * 127 * @param m a {@link Method}; must not be {@code null} 128 * 129 * @return {@code true} if the supplied {@link Method} is the {@link Object#hashCode() java.lang.Object#hashCode()} 130 * method 131 * 132 * @exception NullPointerException if {@code m} is {@code null} 133 */ 134 protected final boolean hashCodeMethod(final Method m) { 135 return 136 m.getDeclaringClass() == Object.class && 137 m.getReturnType() == int.class && 138 m.getParameterCount() == 0 && 139 m.getName().equals("hashCode"); 140 } 141 142 /** 143 * Returns a {@link Parameterizable} corresponding to the supplied {@link GenericDeclaration}. 144 * 145 * @param gd a {@link GenericDeclaration}; must not be {@code null} 146 * 147 * @return a {@link Parameterizable} corresponding to the supplied {@link GenericDeclaration}; never {@code null} 148 * 149 * @exception NullPointerException if {@code gd} is {@code null} 150 * 151 * @exception IllegalArgumentException if {@code gd} is neither a {@link Class} nor an {@link Executable} 152 */ 153 protected final Parameterizable parameterizable(final GenericDeclaration gd) { 154 final Domain domain = this.domain(); 155 return switch (gd) { 156 case null -> throw new NullPointerException("gd"); 157 case Class<?> c -> domain.typeElement(c.getCanonicalName()); 158 case Executable e -> this.executableElement(e); 159 default -> throw new IllegalArgumentException("gd: " + gd); 160 }; 161 } 162 163 /** 164 * Returns a {@link Proxy} appropriate for the supplied specification and {@link Supplier} of instances. 165 * 166 * @param ps an appropriate proxy specification; must not be {@code null} 167 * 168 * @param instanceSupplier a {@link Supplier} of contextual instances; must not be {@code null}; may or may not create 169 * a new contextual instance each time it is invoked; may or may not be invoked multiple times depending on the 170 * subclass implementation 171 * 172 * @return a non-{@code null} {@link Proxy} 173 * 174 * @exception NullPointerException if any argument is {@code null} 175 * 176 * @exception IllegalArgumentException if the {@linkplain ProxySpecification#superclass() superclass} is not {@link 177 * Object java.lang.Object} (only interfaces may be proxied reflectively) 178 * 179 * @see #proxy(ProxySpecification, Class[], Supplier) 180 * 181 * @see #classLoader() 182 */ 183 @Override // AbstractProxier<PS> 184 public final <R> Proxy<R> proxy(final PS ps, final Supplier<? extends R> instanceSupplier) { 185 final Domain domain = this.domain(); 186 if (!domain.javaLangObject(ps.superclass())) { 187 throw new IllegalArgumentException("ps: " + ps); 188 } 189 final List<? extends TypeMirror> interfaceTypeMirrors = ps.interfaces(); 190 final int size = interfaceTypeMirrors.size(); 191 final Class<?>[] interfaces = new Class<?>[size]; 192 final ClassLoader classLoader = this.classLoader(); 193 try { 194 for (int i = 0; i < size; i++) { 195 final TypeElement e = (TypeElement)((DeclaredType)interfaceTypeMirrors.get(i)).asElement(); 196 final String binaryName = domain.toString(domain.binaryName(e)); 197 interfaces[i] = Class.forName(binaryName, false, classLoader); 198 } 199 } catch (final ClassNotFoundException cnfe) { 200 throw new IllegalArgumentException("ps: " + ps, cnfe); 201 } 202 return this.proxy(ps, interfaces, instanceSupplier); 203 } 204 205 /** 206 * Returns a {@link Proxy} appropriate for the supplied specification and {@link Supplier} of contextual instances. 207 * 208 * @param <R> the contextual instance type 209 * 210 * @param ps an appropriate proxy specification; must not be {@code null} 211 * 212 * @param interfaces the interfaces to implement; every element is guaranteed to {@linkplain Class#isInterface() be an interface} 213 * 214 * @param instanceSupplier a {@link Supplier} of contextual instances; must not be {@code null}; may or may not create 215 * a new contextual instance each time it is invoked; may or may not be invoked multiple times depending on the 216 * subclass implementation 217 * 218 * @return a non-{@code null} {@link Proxy} 219 * 220 * @exception NullPointerException if any argument is {@code null} 221 */ 222 protected abstract <R> Proxy<R> proxy(final PS ps, 223 final Class<?>[] interfaces, 224 final Supplier<? extends R> instanceSupplier); 225 226 /** 227 * Returns the {@link TypeMirror} corresponding to the supplied {@link Type}. 228 * 229 * @param t a {@link Type}; must not be {@code null} 230 * 231 * @return the {@link TypeMirror} corresponding to the supplied {@link Type}; never {@code null} 232 * 233 * @exception NullPointerException if {@code t} is {@code null} 234 * 235 * @exception IllegalArgumentException if {@code t} is not a {@link Class}, {@link GenericArrayType}, {@link 236 * ParameterizedType}, {@link TypeVariable} or {@link WildcardType} 237 */ 238 protected final TypeMirror type(final Type t) { 239 // TODO: anywhere there is domain.declaredType(), consider passing 240 // domain.moduleElement(this.getClass().getModule().getName()) as the first argument. Not sure how this works 241 // exactly but I think it might be necessary. 242 final Domain domain = this.domain(); 243 return switch (t) { 244 case null -> throw new NullPointerException("t"); 245 case Class<?> c when t == boolean.class -> domain.primitiveType(TypeKind.BOOLEAN); 246 case Class<?> c when t == byte.class -> domain.primitiveType(TypeKind.BYTE); 247 case Class<?> c when t == char.class -> domain.primitiveType(TypeKind.CHAR); 248 case Class<?> c when t == double.class -> domain.primitiveType(TypeKind.DOUBLE); 249 case Class<?> c when t == float.class -> domain.primitiveType(TypeKind.FLOAT); 250 case Class<?> c when t == int.class -> domain.primitiveType(TypeKind.INT); 251 case Class<?> c when t == long.class -> domain.primitiveType(TypeKind.LONG); 252 case Class<?> c when t == short.class -> domain.primitiveType(TypeKind.SHORT); 253 case Class<?> c when t == void.class -> domain.noType(TypeKind.VOID); 254 case Class<?> c when t == Object.class -> domain.javaLangObject().asType(); // cheap and easy optimization 255 case Class<?> c when c.isArray() -> domain.arrayTypeOf(this.type(c.getComponentType())); 256 case Class<?> c -> domain.declaredType(c.getCanonicalName()); 257 case GenericArrayType g -> domain.arrayTypeOf(this.type(g.getGenericComponentType())); 258 case ParameterizedType pt when pt.getOwnerType() == null -> 259 domain.declaredType(domain.typeElement(((Class<?>)pt.getRawType()).getCanonicalName()), 260 this.types(pt.getActualTypeArguments())); 261 case ParameterizedType pt -> 262 domain.declaredType((DeclaredType)this.type(pt.getOwnerType()), 263 domain.typeElement(((Class<?>)pt.getRawType()).getCanonicalName()), 264 this.types(pt.getActualTypeArguments())); 265 case TypeVariable<?> tv -> domain.typeVariable(this.parameterizable(tv.getGenericDeclaration()), tv.getName()); 266 case WildcardType w when w.getLowerBounds().length <= 0 -> domain.wildcardType(this.type(w.getUpperBounds()[0]), null); 267 case WildcardType w -> domain.wildcardType(null, this.type(w.getLowerBounds()[0])); 268 default -> throw new IllegalArgumentException("t: " + t); 269 }; 270 } 271 272 /** 273 * Returns an array of {@link TypeMirror}s whose elements correspond to the elements in the supplied {@link Type} array. 274 * 275 * @param ts an array of {@link Type}s; must not be {@code null} 276 * 277 * @return an array of {@link TypeMirror}s whose elements correspond to the elements in the supplied {@link Type} 278 * array; never {@code null} 279 * 280 * @exception NullPointerException if {@code ts} is {@code null} or contains {@code null} elements 281 * 282 * @exception IllegalArgumentException if any element of {@code ts} is deemed illegal by the {@link #type(Type)} 283 * method 284 * 285 * @see #type(Type) 286 */ 287 protected final TypeMirror[] types(final Type[] ts) { 288 if (ts.length <= 0) { 289 return EMPTY_TYPE_MIRROR_ARRAY; 290 } else if (ts.length == 1) { 291 // cheap and easy optimization 292 return new TypeMirror[] { type(ts[0]) }; 293 } 294 final TypeMirror[] rv = new TypeMirror[ts.length]; 295 for (int i = 0; i < ts.length; i++) { 296 rv[i] = type(ts[i]); 297 } 298 return rv; 299 } 300 301}