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 org.microbean.construct.Domain;
039
040/**
041 * An {@link AbstractProxier} that helps subclasses create {@link Proxy proxies} using the {@link
042 * java.lang.reflect.Proxy java.lang.reflect.Proxy} machinery present in the Java Development Kit.
043 *
044 * <p>This class also contains various {@code protected} utility methods that help with converting reflective {@link
045 * Type}s and {@link Executable}s to their {@link TypeMirror} and {@link ExecutableElement} counterparts.</p>
046 *
047 * @param <PS> the {@link ProxySpecification} type
048 *
049 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
050 *
051 * @see #proxy(ProxySpecification, Supplier)
052 */
053public abstract non-sealed class AbstractReflectiveProxier<PS extends ProxySpecification> extends AbstractProxier<PS> {
054
055  private static final TypeMirror[] EMPTY_TYPE_MIRROR_ARRAY = new TypeMirror[0];
056
057  /**
058   * Creates a new {@link AbstractReflectiveProxier} implementation.
059   *
060   * @param domain a {@link Domain}; must not be {@code null}
061   *
062   * @exception NullPointerException if {@code domain} is {@code null}
063   */
064  protected AbstractReflectiveProxier(final Domain domain) {
065    super(domain);
066  }
067
068  /**
069   * A convenience method that returns {@code true} if the supplied {@link Method} is the {@link Object#equals(Object)
070   * java.lang.Object#equals(java.lang.Object)} method.
071   *
072   * @param m a {@link Method}; must not be {@code null}
073   *
074   * @return {@code true} if the supplied {@link Method} is the {@link Object#equals(Object)
075   * java.lang.Object#equals(java.lang.Object)} method
076   *
077   * @exception NullPointerException if {@code m} is {@code null}
078   */
079  // (Convenience.)
080  protected final boolean equalsMethod(final Method m) {
081    return
082      m.getDeclaringClass() == Object.class &&
083      m.getReturnType() == boolean.class &&
084      m.getParameterCount() == 1 &&
085      m.getParameterTypes()[0] == Object.class &&
086      m.getName().equals("equals");
087  }
088
089  /**
090   * A convenience method that returns {@code true} if the supplied {@link Method} is the {@link Object#hashCode()
091   * java.lang.Object#hashCode()} method.
092   *
093   * @param m a {@link Method}; must not be {@code null}
094   *
095   * @return {@code true} if the supplied {@link Method} is the {@link Object#hashCode() java.lang.Object#hashCode()}
096   * method
097   *
098   * @exception NullPointerException if {@code m} is {@code null}
099   */
100  // (Convenience.)
101  protected final boolean hashCodeMethod(final Method m) {
102    return
103      m.getDeclaringClass() == Object.class &&
104      m.getReturnType() == int.class &&
105      m.getParameterCount() == 0 &&
106      m.getName().equals("hashCode");
107  }
108
109  /**
110   * Returns a {@link Proxy} appropriate for the supplied specification and {@link Supplier} of instances.
111   *
112   * @param ps an appropriate proxy specification; must not be {@code null}
113   *
114   * @param instanceSupplier a {@link Supplier} of contextual instances; must not be {@code null}; may or may not create
115   * a new contextual instance each time it is invoked; may or may not be invoked multiple times depending on the
116   * subclass implementation
117   *
118   * @return a non-{@code null} {@link Proxy}
119   *
120   * @exception NullPointerException if any argument is {@code null}
121   *
122   * @exception IllegalArgumentException if the {@linkplain ProxySpecification#superclass() superclass} is not {@link
123   * Object java.lang.Object} (only interfaces may be proxied reflectively)
124   *
125   * @see #proxy(ProxySpecification, Class[], Supplier)
126   *
127   * @see #classLoader()
128   */
129  @Override // AbstractProxier<PS>
130  public final <R> Proxy<R> proxy(final PS ps, final Supplier<? extends R> instanceSupplier) {
131    final Domain domain = this.domain();
132    if (!domain.javaLangObject(ps.superclass())) {
133      throw new IllegalArgumentException("ps: " + ps);
134    }
135    final List<? extends TypeMirror> interfaceTypeMirrors = ps.interfaces();
136    final int size = interfaceTypeMirrors.size();
137    final Class<?>[] interfaces = new Class<?>[size];
138    final ClassLoader classLoader = this.classLoader();
139    try {
140      for (int i = 0; i < size; i++) {
141        final TypeElement e = (TypeElement)((DeclaredType)interfaceTypeMirrors.get(i)).asElement();
142        final String binaryName = domain.toString(domain.binaryName(e));
143        interfaces[i] = Class.forName(binaryName, false, classLoader);
144      }
145    } catch (final ClassNotFoundException cnfe) {
146      throw new IllegalArgumentException("ps: " + ps, cnfe);
147    }
148    return this.proxy(ps, interfaces, instanceSupplier);
149  }
150
151  /**
152   * Returns a {@link Proxy} appropriate for the supplied specification and {@link Supplier} of contextual instances.
153   *
154   * @param <R> the contextual instance type
155   *
156   * @param ps an appropriate proxy specification; must not be {@code null}
157   *
158   * @param interfaces the interfaces to implement; every element is guaranteed to {@linkplain Class#isInterface() be an interface}
159   *
160   * @param instanceSupplier a {@link Supplier} of contextual instances; must not be {@code null}; may or may not create
161   * a new contextual instance each time it is invoked; may or may not be invoked multiple times depending on the
162   * subclass implementation
163   *
164   * @return a non-{@code null} {@link Proxy}
165   *
166   * @exception NullPointerException if any argument is {@code null}
167   */
168  protected abstract <R> Proxy<R> proxy(final PS ps,
169                                        final Class<?>[] interfaces,
170                                        final Supplier<? extends R> instanceSupplier);
171
172}