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.Lookup;
017
018import java.lang.reflect.InvocationHandler;
019import java.lang.reflect.Method;
020
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024
025import java.util.concurrent.ConcurrentHashMap;
026
027import java.util.function.Function;
028import java.util.function.Supplier;
029
030import javax.lang.model.type.DeclaredType;
031import javax.lang.model.type.TypeMirror;
032
033import javax.lang.model.element.TypeElement;
034
035import org.microbean.bean.Id;
036
037import org.microbean.construct.Domain;
038
039import org.microbean.proxy.AbstractReflectiveProxier;
040import org.microbean.proxy.Proxy;
041import org.microbean.proxy.ProxySpecification;
042
043import static java.lang.invoke.MethodHandles.publicLookup;
044
045import static java.lang.reflect.Proxy.newProxyInstance;
046
047/**
048 * An {@link AbstractReflectiveProxier} implementation that uses {@link java.lang.reflect.Proxy java.lang.reflect.Proxy}
049 * machinery.
050 *
051 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
052 */
053public class ReflectiveClientProxier extends AbstractReflectiveProxier<ProxySpecification> implements ClientProxier {
054
055  private static final Lookup lookup = publicLookup();
056
057  private static final Map<ProxySpecification, Object> proxyInstances = new ConcurrentHashMap<>();
058
059  /**
060   * Creates a new {@link ReflectiveClientProxier}.
061   *
062   * @param domain a {@link Domain}; must not be {@code null}
063   *
064   * @exception NullPointerException if {@code domain} is {@code null}
065   */
066  public ReflectiveClientProxier(final Domain domain) {
067    super(domain);
068  }
069
070  @Override // ClientProxier
071  public <R> R clientProxy(final Id id, final Supplier<? extends R> instanceSupplier) {
072    return this.proxy(new ProxySpecification(this.domain(), id), instanceSupplier).$cast();
073  }
074
075  /**
076   * Returns a {@link Proxy} appropriate for the supplied {@linkplain ProxySpecification specification} and {@link
077   * Supplier} of contextual instances.
078   *
079   * @param <R> the contextual instance type
080   *
081   * @param ps an appropriate {@linkplain ProxySpecification proxy specification}; must not be {@code null}
082   *
083   * @param interfaces the interfaces to implement; every element is guaranteed to {@linkplain Class#isInterface() be an
084   * interface}
085   *
086   * @param instanceSupplier a {@link Supplier} of contextual instances; must not be {@code null}; may or may not create
087   * a new contextual instance each time it is invoked; may or may not be invoked multiple times depending on the
088   * subclass implementation
089   *
090   * @return a non-{@code null} {@link Proxy}
091   *
092   * @exception NullPointerException if any argument is {@code null}
093   */
094  @Override // AbstractReflectiveProxier
095  @SuppressWarnings("unchecked")
096  protected final <R> Proxy<R> proxy(final ProxySpecification ps,
097                                     final Class<?>[] interfaces,
098                                     final Supplier<? extends R> instanceSupplier) {
099    return
100      (Proxy<R>)proxyInstances
101      .computeIfAbsent(ps,
102                       ps0 -> newProxyInstance(this.classLoader(),
103                                               interfaces,
104                                               new InvocationHandler() {
105                                                 @Override // InvocationHandler
106                                                 public final Object invoke(final Object p,
107                                                                            final Method m,
108                                                                            final Object[] a)
109                                                   throws Throwable {
110                                                   return switch (m) {
111                                                   case Method x when equalsMethod(x) -> p == a[0];
112                                                   case Method x when hashCodeMethod(x) -> System.identityHashCode(p);
113                                                   default -> m.invoke(instanceSupplier.get(), a);
114                                                   };
115                                                 }
116                                               }));
117  }
118
119}