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}