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.util.List;
017import java.util.Objects;
018
019import javax.lang.model.element.ElementKind;
020import javax.lang.model.element.TypeElement;
021
022import javax.lang.model.type.DeclaredType;
023import javax.lang.model.type.TypeKind;
024import javax.lang.model.type.TypeMirror;
025
026import org.microbean.bean.BeanTypeList;
027
028import org.microbean.construct.Domain;
029
030import static org.microbean.bean.BeanTypes.proxiableBeanType;
031
032/**
033 * Information about a client proxy.
034 *
035 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
036 */
037public final class ProxySpecification {
038
039  private final Domain domain;
040
041  private final DeclaredType sc;
042
043  private final List<TypeMirror> interfaces;
044
045  private final String name;
046
047
048  /*
049   * Constructors.
050   */
051
052
053  ProxySpecification(final Domain domain, final BeanTypeList types) {
054    super();
055    this.domain = Objects.requireNonNull(domain, "domain");
056    final TypeMirror t = types.get(0);
057    if (t.getKind() != TypeKind.DECLARED || domain.javaLangObject(t) && types.size() == 1) {
058      throw new IllegalArgumentException("types: " + types);
059    } else if (((DeclaredType)t).asElement().getKind() == ElementKind.INTERFACE) {
060      this.sc = (DeclaredType)domain.javaLangObject().asType();
061      this.interfaces = types;
062    } else if (!proxiableBeanType(t)) {
063      throw new IllegalArgumentException("types: " + types);
064    } else {
065      this.sc = (DeclaredType)t;
066      final int interfaceIndex = types.interfaceIndex();
067      this.interfaces = interfaceIndex < 0 ? List.of() : types.subList(interfaceIndex, types.size());
068    }
069    this.name = computeName(domain, this.sc, this.interfaces);
070  }
071
072
073  /*
074   * Instance methods.
075   */
076
077
078  @Override // Object
079  public final boolean equals(final Object other) {
080    if (other == this) {
081      return true;
082    } else if (other != null && other.getClass() == this.getClass()) {
083      final ProxySpecification her = (ProxySpecification)other;
084      if (!this.domain.equals(her.domain)) {
085        return false;
086      }
087      if (!this.domain.sameType(this.superclass(), her.superclass())) {
088        return false;
089      }
090      final List<TypeMirror> interfaces = this.interfaces();
091      final List<TypeMirror> herInterfaces = her.interfaces();
092      final int size = interfaces.size();
093      if (herInterfaces.size() != size) {
094        return false;
095      }
096      for (int i = 0; i < size; i++) {
097        if (!this.domain.sameType(interfaces.get(i), herInterfaces.get(i))) {
098          return false;
099        }
100      }
101      return true;
102    } else {
103      return false;
104    }
105  }
106
107  @Override // Object
108  public final int hashCode() {
109    int hashCode = 31;
110    hashCode = 17 * hashCode + this.domain.hashCode();
111    hashCode = 17 * hashCode + this.sc.hashCode();
112    hashCode = 17 * hashCode + this.interfaces.hashCode();
113    return hashCode;
114  }
115
116  /**
117   * Returns the interfaces the proxy should implement.
118   *
119   * @return a non-{@code null}, immutable {@link List} of {@link TypeMirror}s
120   */
121  public final List<TypeMirror> interfaces() {
122    return this.interfaces;
123  }
124
125  /**
126   * Returns the name the proxy class should have.
127   *
128   * @return a non-{@code null} {@link String}
129   */
130  public final String name() {
131    return this.name;
132  }
133
134  /**
135   * Returns the superclass the proxy should specialize.
136   *
137   * @return a non-{@code null} {@link DeclaredType}
138   */
139  public final DeclaredType superclass() {
140    return this.sc;
141  }
142
143
144  /*
145   * Static methods.
146   */
147
148
149  static final String computeName(final Domain domain, final DeclaredType superclass, final List<TypeMirror> interfaces) {
150
151    // TODO: there will absolutely be edge cases here and we know this is not complete.
152
153    if (superclass.getKind() != TypeKind.DECLARED) {
154      throw new IllegalArgumentException("superclass: " + superclass);
155    }
156    final DeclaredType proxyClassSibling;
157    if (domain.javaLangObject(superclass)) {
158      if (interfaces.isEmpty()) {
159        throw new IllegalArgumentException("interfaces.isEmpty(); superclass: java.lang.Object");
160      }
161      // Interface-only. There will be at least one and it will be the most specialized.
162      proxyClassSibling = (DeclaredType)interfaces.get(0);
163      if (proxyClassSibling.getKind() != TypeKind.DECLARED) {
164        throw new IllegalArgumentException("interfaces: " + interfaces);
165      }
166    } else {
167      proxyClassSibling = superclass;
168    }
169    return domain.toString(domain.binaryName((TypeElement)proxyClassSibling.asElement())) + "_Proxy";
170  }
171
172}