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.proxy;
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.attributes.Attributes;
027
028import org.microbean.bean.BeanTypeList;
029import org.microbean.bean.Id;
030
031import org.microbean.construct.Domain;
032
033import static org.microbean.bean.BeanTypes.proxiableBeanType;
034
035/**
036 * Information about a proxy.
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<Attributes> attributes;
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.attributes = id.attributes();
082    final BeanTypeList types = id.types();
083    final TypeMirror t = types.get(0); // putative superclass
084    if (t.getKind() != TypeKind.DECLARED || domain.javaLangObject(t) && types.size() == 1) {
085      throw new IllegalArgumentException("id: " + id);
086    } else if (((DeclaredType)t).asElement().getKind() == ElementKind.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      final int interfaceIndex = types.interfaceIndex();
094      this.interfaces = interfaceIndex < 0 ? List.of() : types.subList(interfaceIndex, types.size());
095    }
096    this.name = computeName(domain, this.sc, this.interfaces);
097  }
098
099
100  /*
101   * Instance methods.
102   */
103
104
105  @Override // Object
106  public boolean equals(final Object other) {
107    if (other == this) {
108      return true;
109    } else if (other != null && other.getClass() == this.getClass()) {
110      final ProxySpecification her = (ProxySpecification)other;
111      if (!this.domain.equals(her.domain)) {
112        return false;
113      }
114      if (!this.domain.sameType(this.superclass(), her.superclass())) {
115        return false;
116      }
117      final List<TypeMirror> interfaces = this.interfaces();
118      final List<TypeMirror> herInterfaces = her.interfaces();
119      final int size = interfaces.size();
120      if (herInterfaces.size() != size) {
121        return false;
122      }
123      for (int i = 0; i < size; i++) {
124        if (!this.domain.sameType(interfaces.get(i), herInterfaces.get(i))) {
125          return false;
126        }
127      }
128      return this.attributes().equals(her.attributes());
129    } else {
130      return false;
131    }
132  }
133
134  @Override // Object
135  public int hashCode() {
136    int hashCode = 31;
137    hashCode = 17 * hashCode + this.domain.hashCode();
138    hashCode = 17 * hashCode + this.superclass().hashCode();
139    hashCode = 17 * hashCode + this.interfaces().hashCode();
140    hashCode = 17 * hashCode + this.attributes().hashCode();
141    return hashCode;
142  }
143
144  /**
145   * Returns an immutable {@link List} of {@link Attributes} describing this {@link ProxySpecification}.
146   *
147   * @return a non-{@code null}, immutable {@link List} of {@link Attributes} instances
148   */
149  public final List<Attributes> attributes() {
150    return this.attributes;
151  }
152
153  /**
154   * Returns the interfaces the proxy should implement.
155   *
156   * @return a non-{@code null}, immutable {@link List} of {@link TypeMirror}s
157   */
158  public final List<TypeMirror> interfaces() {
159    return this.interfaces;
160  }
161
162  /**
163   * Returns the name the proxy class should have.
164   *
165   * @return a non-{@code null} {@link String}
166   */
167  public final String name() {
168    return this.name;
169  }
170
171  /**
172   * Returns the superclass the proxy should specialize.
173   *
174   * @return a non-{@code null} {@link DeclaredType}
175   */
176  public final DeclaredType superclass() {
177    return this.sc;
178  }
179
180
181  /*
182   * Static methods.
183   */
184
185
186  static final String computeName(final Domain domain, final DeclaredType superclass, final List<TypeMirror> interfaces) {
187
188    // TODO: there will absolutely be edge cases here and we know this is not complete.
189
190    if (superclass.getKind() != TypeKind.DECLARED) {
191      throw new IllegalArgumentException("superclass: " + superclass);
192    }
193    final DeclaredType proxyClassSibling;
194    if (domain.javaLangObject(superclass)) {
195      if (interfaces.isEmpty()) {
196        throw new IllegalArgumentException("interfaces.isEmpty(); superclass: java.lang.Object");
197      }
198      // Interface-only. There will be at least one and it will be the most specialized.
199      proxyClassSibling = (DeclaredType)interfaces.get(0);
200      if (proxyClassSibling.getKind() != TypeKind.DECLARED) {
201        throw new IllegalArgumentException("interfaces: " + interfaces);
202      }
203    } else {
204      proxyClassSibling = superclass;
205    }
206    return domain.toString(domain.binaryName((TypeElement)proxyClassSibling.asElement())) + "_Proxy";
207  }
208
209}