1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.incubator.code.dialect.java;
 27 
 28 import jdk.incubator.code.dialect.java.impl.JavaTypeUtils;
 29 import jdk.incubator.code.extern.ExternalizedTypeElement;
 30 
 31 import java.lang.constant.ClassDesc;
 32 import java.lang.invoke.MethodHandles.Lookup;
 33 import java.lang.reflect.ParameterizedType;
 34 import java.lang.reflect.Type;
 35 
 36 import java.util.ArrayList;
 37 import java.util.List;
 38 import java.util.Map;
 39 import java.util.Optional;
 40 
 41 /**
 42  * A class type.
 43  */
 44 public final class ClassType implements TypeVariableType.Owner, JavaType {
 45 
 46     // Enclosing class type (might be null)
 47     private final ClassType enclosing;
 48     // Fully qualified name
 49     private final ClassDesc type;
 50 
 51     private final List<JavaType> typeArguments;
 52 
 53     ClassType(ClassDesc type) {
 54         this(null, type);
 55     }
 56 
 57     ClassType(ClassType encl, ClassDesc type) {
 58         this(encl, type, List.of());
 59     }
 60 
 61     ClassType(ClassType encl, ClassDesc type, List<JavaType> typeArguments) {
 62         if (!type.isClassOrInterface()) {
 63             throw new IllegalArgumentException("Invalid base type: " + type);
 64         }
 65         this.enclosing = encl;
 66         this.type = type;
 67         this.typeArguments = List.copyOf(typeArguments);
 68     }
 69 
 70     @Override
 71     public Type resolve(Lookup lookup) throws ReflectiveOperationException {
 72         Class<?> baseType = type.resolveConstantDesc(lookup);
 73         List<Type> resolvedTypeArgs = new ArrayList<>();
 74         for (JavaType typearg : typeArguments) {
 75             resolvedTypeArgs.add(typearg.resolve(lookup));
 76         }
 77         Type encl = enclosing != null ?
 78                 enclosing.resolve(lookup) : null;
 79         return resolvedTypeArgs.isEmpty() ?
 80                 baseType :
 81                 makeReflectiveParameterizedType(baseType,
 82                         resolvedTypeArgs.toArray(new Type[0]), encl);
 83     }
 84 
 85     private static ParameterizedType makeReflectiveParameterizedType(Class<?> base, Type[] typeArgs, Type owner) {
 86         return sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(base, typeArgs, owner);
 87     }
 88 
 89     @Override
 90     public ExternalizedTypeElement externalize() {
 91         ExternalizedTypeElement exEnclosing = enclosing == null ?
 92                 VOID.externalize() : enclosing.externalize();
 93         String name = enclosing == null ?
 94                 toClassName() :
 95                 type.displayName().substring(enclosing.type.displayName().length() + 1);
 96         return JavaTypeUtils.classType(name, exEnclosing,
 97                 typeArguments().stream().map(JavaType::externalize).toList());
 98     }
 99 
100     @Override
101     public String toString() {
102         return JavaTypeUtils.toExternalTypeString(externalize());
103     }
104 
105     @Override
106     public boolean equals(Object o) {
107         if (this == o) return true;
108         if (o == null || getClass() != o.getClass()) return false;
109 
110         ClassType typeDesc = (ClassType) o;
111 
112         if (!type.equals(typeDesc.type)) return false;
113         return typeArguments.equals(typeDesc.typeArguments);
114     }
115 
116     @Override
117     public int hashCode() {
118         int result = type.hashCode();
119         result = 31 * result + typeArguments.hashCode();
120         return result;
121     }
122 
123     /**
124      * {@return the unboxed primitive type associated with this class type (if any)}
125      */
126     public Optional<PrimitiveType> unbox() {
127         class LazyHolder {
128             static final Map<ClassType, PrimitiveType> wrapperToPrimitive = Map.of(
129                     J_L_BYTE, BYTE,
130                     J_L_SHORT, SHORT,
131                     J_L_INTEGER, INT,
132                     J_L_LONG, LONG,
133                     J_L_FLOAT, FLOAT,
134                     J_L_DOUBLE, DOUBLE,
135                     J_L_CHARACTER, CHAR,
136                     J_L_BOOLEAN, BOOLEAN
137             );
138         }
139         return Optional.ofNullable(LazyHolder.wrapperToPrimitive.get(this));
140     }
141 
142     @Override
143     public JavaType erasure() {
144         return rawType();
145     }
146 
147     // Conversions
148 
149     /**
150      * {@return a class type whose base type is the same as this class type, but without any
151      * type arguments}
152      */
153     public ClassType rawType() {
154         return enclosing == null ?
155                 new ClassType(type) :
156                 new ClassType(enclosing.rawType(), type);
157     }
158 
159     /**
160      * {@return {@code true} if this class type has a non-empty type argument list}
161      * @see ClassType#typeArguments()
162      */
163     public boolean hasTypeArguments() {
164         return !typeArguments.isEmpty();
165     }
166 
167     /**
168      * {@return the type argument list associated with this class type}
169      */
170     public List<JavaType> typeArguments() {
171         return typeArguments;
172     }
173 
174     /**
175      * {@return the enclosing type associated with this class type (if any)}
176      */
177     public Optional<ClassType> enclosingType() {
178         return Optional.ofNullable(enclosing);
179     }
180 
181     @Override
182     public JavaType toBasicType() {
183         return JavaType.J_L_OBJECT;
184     }
185 
186     /**
187      * {@return a human-readable name for this class type}
188      */
189     public String toClassName() {
190         String pkg = type.packageName();
191         return pkg.isEmpty() ?
192                 type.displayName() :
193                 String.format("%s.%s", pkg, type.displayName());
194     }
195 
196     @Override
197     public ClassDesc toNominalDescriptor() {
198         return type;
199     }
200 }