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.reference;
015
016import java.lang.invoke.MethodHandles;
017import java.lang.invoke.MethodHandles.Lookup;
018
019import java.lang.reflect.InvocationHandler;
020import java.lang.reflect.Method;
021
022import java.util.List;
023import java.util.Objects;
024
025import java.util.function.Function;
026import java.util.function.Supplier;
027
028import javax.lang.model.type.DeclaredType;
029import javax.lang.model.type.TypeMirror;
030
031import javax.lang.model.element.TypeElement;
032
033import org.microbean.construct.Domain;
034
035import static java.lang.reflect.Proxy.newProxyInstance;
036
037/**
038 * An {@link AbstractClientProxier} implementation that uses {@link java.lang.reflect.Proxy java.lang.reflect.Proxy}
039 * machinery.
040 *
041 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
042 */
043public class ReflectiveClientProxier extends AbstractClientProxier<Class<?>> {
044
045  private static final Lookup lookup = MethodHandles.publicLookup();
046
047  private final Function<? super List<? extends Class<?>>, ? extends ClassLoader> clf;
048
049  /**
050   * Creates a new {@link ReflectiveClientProxier}.
051   *
052   * @param domain a {@link Domain}; must not be {@code null}
053   *
054   * @exception NullPointerException if {@code domain} is {@code null}
055   *
056   * @see #ReflectiveClientProxier(Domain, Function)
057   */
058  public ReflectiveClientProxier(final Domain domain) {
059    this(domain, i -> Thread.currentThread().getContextClassLoader());
060  }
061
062  /**
063   * Creates a new {@link ReflectiveClientProxier}.
064   *
065   * @param domain a {@link Domain}; must not be {@code null}
066   *
067   * @param clf a {@link Function} that returns a {@link ClassLoader} appropriate for a {@link List} of {@link Class}
068   * instances; must not be {@code null}
069   *
070   * @exception NullPointerException if any argument is {@code null}
071   */
072  public ReflectiveClientProxier(final Domain domain,
073                                 final Function<? super List<? extends Class<?>>, ? extends ClassLoader> clf) {
074    super(domain);
075    this.clf = Objects.requireNonNull(clf, "clf");
076  }
077
078  @Override // AbstractClientProxier<Class<?>>
079  @SuppressWarnings("unchecked")
080  protected <R> ClientProxy<R> instantiate(final ProxySpecification ps, final Supplier<? extends R> instanceSupplier) {
081    final Domain domain = this.domain();
082    if (!domain.javaLangObject(ps.superclass())) {
083      throw new IllegalArgumentException("ps: " + ps);
084    }
085    final List<? extends TypeMirror> interfaceTypeMirrors = ps.interfaces();
086    final int size = interfaceTypeMirrors.size();
087    final Class<?>[] interfaces = new Class<?>[size];
088    try {
089      for (int i = 0; i < size; i++) {
090        final TypeElement e = (TypeElement)((DeclaredType)interfaceTypeMirrors.get(i)).asElement();
091        final String binaryName = domain.toString(domain.binaryName(e));
092        interfaces[i] = Class.forName(binaryName, false, this.classLoader(e));
093      }
094    } catch (final ClassNotFoundException cnfe) {
095      throw new IllegalArgumentException("ps: " + ps, cnfe);
096    }
097    return (ClientProxy<R>)newProxyInstance(this.clf.apply(List.of(interfaces)), interfaces, new InvocationHandler() {
098        @Override // InvocationHandler
099        public final Object invoke(final Object p, final Method m, final Object[] a) throws Throwable {
100          return switch (m) {
101          case null -> throw new NullPointerException("m");
102          case Method x when
103            x.getDeclaringClass() == Object.class &&
104            x.getReturnType() == boolean.class &&
105            x.getParameterCount() == 1 &&
106            x.getParameterTypes()[0] == Object.class &&
107            x.getName().equals("equals") -> p == a[0];
108          case Method x when
109            x.getDeclaringClass() == Object.class &&
110            x.getReturnType() == int.class &&
111            x.getParameterCount() == 0 &&
112            x.getName().equals("hashCode") -> System.identityHashCode(p);
113          default -> m.invoke(instanceSupplier.get(), a);
114          };
115        }
116      });
117  }
118
119  @Override // AbstractClientProxier<Void>
120  protected Lookup lookup(final Class<?> c) {
121    return lookup;
122  }
123
124}