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.reference; 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.bean.BeanTypeList; 027 028import org.microbean.construct.Domain; 029 030import static org.microbean.bean.BeanTypes.proxiableBeanType; 031 032/** 033 * Information about a client proxy. 034 * 035 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 036 */ 037public final class ProxySpecification { 038 039 private final Domain domain; 040 041 private final DeclaredType sc; 042 043 private final List<TypeMirror> interfaces; 044 045 private final String name; 046 047 048 /* 049 * Constructors. 050 */ 051 052 053 ProxySpecification(final Domain domain, final BeanTypeList types) { 054 super(); 055 this.domain = Objects.requireNonNull(domain, "domain"); 056 final TypeMirror t = types.get(0); 057 if (t.getKind() != TypeKind.DECLARED || domain.javaLangObject(t) && types.size() == 1) { 058 throw new IllegalArgumentException("types: " + types); 059 } else if (((DeclaredType)t).asElement().getKind() == ElementKind.INTERFACE) { 060 this.sc = (DeclaredType)domain.javaLangObject().asType(); 061 this.interfaces = types; 062 } else if (!proxiableBeanType(t)) { 063 throw new IllegalArgumentException("types: " + types); 064 } else { 065 this.sc = (DeclaredType)t; 066 final int interfaceIndex = types.interfaceIndex(); 067 this.interfaces = interfaceIndex < 0 ? List.of() : types.subList(interfaceIndex, types.size()); 068 } 069 this.name = computeName(domain, this.sc, this.interfaces); 070 } 071 072 073 /* 074 * Instance methods. 075 */ 076 077 078 @Override // Object 079 public final boolean equals(final Object other) { 080 if (other == this) { 081 return true; 082 } else if (other != null && other.getClass() == this.getClass()) { 083 final ProxySpecification her = (ProxySpecification)other; 084 if (!this.domain.equals(her.domain)) { 085 return false; 086 } 087 if (!this.domain.sameType(this.superclass(), her.superclass())) { 088 return false; 089 } 090 final List<TypeMirror> interfaces = this.interfaces(); 091 final List<TypeMirror> herInterfaces = her.interfaces(); 092 final int size = interfaces.size(); 093 if (herInterfaces.size() != size) { 094 return false; 095 } 096 for (int i = 0; i < size; i++) { 097 if (!this.domain.sameType(interfaces.get(i), herInterfaces.get(i))) { 098 return false; 099 } 100 } 101 return true; 102 } else { 103 return false; 104 } 105 } 106 107 @Override // Object 108 public final int hashCode() { 109 int hashCode = 31; 110 hashCode = 17 * hashCode + this.domain.hashCode(); 111 hashCode = 17 * hashCode + this.sc.hashCode(); 112 hashCode = 17 * hashCode + this.interfaces.hashCode(); 113 return hashCode; 114 } 115 116 /** 117 * Returns the interfaces the proxy should implement. 118 * 119 * @return a non-{@code null}, immutable {@link List} of {@link TypeMirror}s 120 */ 121 public final List<TypeMirror> interfaces() { 122 return this.interfaces; 123 } 124 125 /** 126 * Returns the name the proxy class should have. 127 * 128 * @return a non-{@code null} {@link String} 129 */ 130 public final String name() { 131 return this.name; 132 } 133 134 /** 135 * Returns the superclass the proxy should specialize. 136 * 137 * @return a non-{@code null} {@link DeclaredType} 138 */ 139 public final DeclaredType superclass() { 140 return this.sc; 141 } 142 143 144 /* 145 * Static methods. 146 */ 147 148 149 static final String computeName(final Domain domain, final DeclaredType superclass, final List<TypeMirror> interfaces) { 150 151 // TODO: there will absolutely be edge cases here and we know this is not complete. 152 153 if (superclass.getKind() != TypeKind.DECLARED) { 154 throw new IllegalArgumentException("superclass: " + superclass); 155 } 156 final DeclaredType proxyClassSibling; 157 if (domain.javaLangObject(superclass)) { 158 if (interfaces.isEmpty()) { 159 throw new IllegalArgumentException("interfaces.isEmpty(); superclass: java.lang.Object"); 160 } 161 // Interface-only. There will be at least one and it will be the most specialized. 162 proxyClassSibling = (DeclaredType)interfaces.get(0); 163 if (proxyClassSibling.getKind() != TypeKind.DECLARED) { 164 throw new IllegalArgumentException("interfaces: " + interfaces); 165 } 166 } else { 167 proxyClassSibling = superclass; 168 } 169 return domain.toString(domain.binaryName((TypeElement)proxyClassSibling.asElement())) + "_Proxy"; 170 } 171 172}