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}