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