001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2025–2026 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.util.List;
017
018import javax.lang.model.element.AnnotationMirror;
019import javax.lang.model.element.TypeElement;
020
021import javax.lang.model.type.DeclaredType;
022import javax.lang.model.type.TypeMirror;
023
024import org.microbean.bean.BeanTypeList;
025import org.microbean.bean.Id;
026
027import org.microbean.construct.Domain;
028
029import static javax.lang.model.element.ElementKind.INTERFACE;
030
031import static javax.lang.model.type.TypeKind.DECLARED;
032
033import static org.microbean.bean.BeanTypes.proxiableBeanType;
034
035/**
036 * Information about what requirements a {@link Proxy} must fulfil.
037 *
038 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
039 */
040// Deliberately not final.
041public class ProxySpecification {
042
043
044  /*
045   * Instance fields.
046   */
047
048
049  private final Domain domain;
050
051  private final DeclaredType sc;
052
053  private final List<TypeMirror> interfaces;
054
055  private final List<AnnotationMirror> annotations;
056
057  private final String name;
058
059
060  /*
061   * Constructors.
062   */
063
064
065  /**
066   * Creates a new {@link ProxySpecification}.
067   *
068   * @param domain a {@link Domain}; must not be {@code null}
069   *
070   * @param id an {@link Id}; must not be {@code null}
071   *
072   * @exception NullPointerException if any argument is {@code null}
073   *
074   * @exception IllegalArgumentException if {@code types} does not represent a type that can be proxied
075   *
076   * @see org.microbean.bean.BeanTypes#proxiableBeanType(TypeMirror)
077   */
078  public ProxySpecification(final Domain domain, final Id id) {
079    super();
080    this.domain = domain; // not nullable
081    this.annotations = id.annotations();
082    final BeanTypeList types = id.types();
083    final TypeMirror t = types.get(0); // putative superclass
084    if (t.getKind() != DECLARED || domain.javaLangObject(t) && types.size() == 1) {
085      throw new IllegalArgumentException("id: " + id);
086    } else if (((DeclaredType)t).asElement().getKind() == INTERFACE) {
087      this.sc = (DeclaredType)domain.javaLangObject().asType();
088      this.interfaces = types;
089    } else if (!proxiableBeanType(t)) {
090      throw new IllegalArgumentException("id: " + id);
091    } else {
092      this.sc = (DeclaredType)t;
093      this.interfaces = types.interfaces();
094    }
095    this.name = computeName(domain, this.sc, this.interfaces);
096  }
097
098
099  /*
100   * Instance methods.
101   */
102
103
104  @Override // Object
105  public boolean equals(final Object other) {
106    if (other == this) {
107      return true;
108    } else if (other != null && other.getClass() == this.getClass()) {
109      final ProxySpecification her = (ProxySpecification)other;
110      if (!this.domain.equals(her.domain)) {
111        return false;
112      }
113      if (!this.domain.sameType(this.superclass(), her.superclass())) {
114        return false;
115      }
116      final List<TypeMirror> interfaces = this.interfaces();
117      final List<TypeMirror> herInterfaces = her.interfaces();
118      final int size = interfaces.size();
119      if (herInterfaces.size() != size) {
120        return false;
121      }
122      for (int i = 0; i < size; i++) {
123        if (!this.domain.sameType(interfaces.get(i), herInterfaces.get(i))) {
124          return false;
125        }
126      }
127      return this.annotations().equals(her.annotations());
128    } else {
129      return false;
130    }
131  }
132
133  @Override // Object
134  public int hashCode() {
135    int hashCode = 31;
136    hashCode = 17 * hashCode + this.domain.hashCode();
137    hashCode = 17 * hashCode + this.superclass().hashCode();
138    hashCode = 17 * hashCode + this.interfaces().hashCode();
139    hashCode = 17 * hashCode + this.annotations().hashCode();
140    return hashCode;
141  }
142
143  /**
144   * Returns an immutable {@link List} of {@link AnnotationMirror}s describing this {@link ProxySpecification}.
145   *
146   * @return a non-{@code null}, immutable {@link List} of {@link AnnotationMirror} instances
147   */
148  public final List<AnnotationMirror> annotations() {
149    return this.annotations;
150  }
151
152  /**
153   * Returns the interfaces the proxy should implement.
154   *
155   * @return a non-{@code null}, immutable {@link List} of {@link TypeMirror}s
156   */
157  public final List<TypeMirror> interfaces() {
158    return this.interfaces;
159  }
160
161  /**
162   * Returns the name the proxy class should have.
163   *
164   * @return a non-{@code null} {@link String}
165   */
166  public final String name() {
167    return this.name;
168  }
169
170  /**
171   * Returns the superclass the proxy should specialize.
172   *
173   * @return a non-{@code null} {@link DeclaredType}
174   */
175  public final DeclaredType superclass() {
176    return this.sc;
177  }
178
179
180  /*
181   * Static methods.
182   */
183
184
185  static final String computeName(final Domain domain, final DeclaredType superclass, final List<TypeMirror> interfaces) {
186
187    // TODO: there will absolutely be edge cases here and we know this is not complete.
188
189    if (superclass.getKind() != DECLARED) {
190      throw new IllegalArgumentException("superclass: " + superclass);
191    }
192    final DeclaredType proxyClassSibling;
193    if (domain.javaLangObject(superclass)) {
194      if (interfaces.isEmpty()) {
195        throw new IllegalArgumentException("interfaces.isEmpty(); superclass: java.lang.Object");
196      }
197      // Interface-only. There will be at least one and it will be the most specialized.
198      proxyClassSibling = (DeclaredType)interfaces.get(0);
199      if (proxyClassSibling.getKind() != DECLARED) {
200        throw new IllegalArgumentException("interfaces: " + interfaces);
201      }
202    } else {
203      proxyClassSibling = superclass;
204    }
205    return domain.toString(domain.binaryName((TypeElement)proxyClassSibling.asElement())) + "_Proxy";
206  }
207
208}