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;
017
018import javax.lang.model.element.TypeElement;
019
020import javax.lang.model.type.DeclaredType;
021import javax.lang.model.type.TypeMirror;
022
023import org.microbean.attributes.Attributes;
024
025import org.microbean.bean.BeanTypeList;
026import org.microbean.bean.Id;
027
028import org.microbean.construct.Domain;
029
030import static javax.lang.model.element.ElementKind.INTERFACE;
031
032import static javax.lang.model.type.TypeKind.DECLARED;
033
034import static org.microbean.bean.BeanTypes.proxiableBeanType;
035
036/**
037 * Information about a proxy.
038 *
039 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
040 */
041// Deliberately not final.
042public class ProxySpecification {
043
044
045  /*
046   * Instance fields.
047   */
048
049
050  private final Domain domain;
051
052  private final DeclaredType sc;
053
054  private final List<TypeMirror> interfaces;
055
056  private final List<Attributes> attributes;
057
058  private final String name;
059
060
061  /*
062   * Constructors.
063   */
064
065
066  /**
067   * Creates a new {@link ProxySpecification}.
068   *
069   * @param domain a {@link Domain}; must not be {@code null}
070   *
071   * @param id an {@link Id}; must not be {@code null}
072   *
073   * @exception NullPointerException if any argument is {@code null}
074   *
075   * @exception IllegalArgumentException if {@code types} does not represent a type that can be proxied
076   *
077   * @see org.microbean.bean.BeanTypes#proxiableBeanType(TypeMirror)
078   */
079  public ProxySpecification(final Domain domain, final Id id) {
080    super();
081    this.domain = domain; // not nullable
082    this.attributes = id.attributes();
083    final BeanTypeList types = id.types();
084    final TypeMirror t = types.get(0); // putative superclass
085    if (t.getKind() != DECLARED || domain.javaLangObject(t) && types.size() == 1) {
086      throw new IllegalArgumentException("id: " + id);
087    } else if (((DeclaredType)t).asElement().getKind() == INTERFACE) {
088      this.sc = (DeclaredType)domain.javaLangObject().asType();
089      this.interfaces = types;
090    } else if (!proxiableBeanType(t)) {
091      throw new IllegalArgumentException("id: " + id);
092    } else {
093      this.sc = (DeclaredType)t;
094      this.interfaces = types.interfaces();
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() != 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() != 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}