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}