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 hat.ifacemapper;
27
28 import hat.buffer.Buffer;
29 import hat.ifacemapper.accessor.AccessorInfo;
30 import hat.ifacemapper.accessor.Accessors;
31 import hat.util.StreamMutable;
32
33 import java.lang.constant.ClassDesc;
34 import java.lang.foreign.GroupLayout;
35 import java.lang.foreign.MemoryLayout;
36 import java.lang.foreign.MemorySegment;
37 import java.lang.reflect.Method;
38 import java.lang.reflect.Modifier;
39 import java.util.Arrays;
40 import java.util.List;
41 import java.util.Objects;
42 import java.util.Optional;
43 import java.util.Set;
44 import java.util.function.Function;
45 import java.util.function.Predicate;
46 import java.util.stream.Collectors;
47
48 import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
49 import static java.lang.foreign.ValueLayout.JAVA_BYTE;
50 import static java.lang.foreign.ValueLayout.JAVA_CHAR;
51 import static java.lang.foreign.ValueLayout.JAVA_DOUBLE;
52 import static java.lang.foreign.ValueLayout.JAVA_FLOAT;
53 import static java.lang.foreign.ValueLayout.JAVA_INT;
54 import static java.lang.foreign.ValueLayout.JAVA_LONG;
55 import static java.lang.foreign.ValueLayout.JAVA_SHORT;
56
57 public final class MapperUtil {
58
59 private MapperUtil() {
60 }
61
62 private static final boolean DEBUG =
63 Boolean.getBoolean("hat.ifacemapper.debug");
64
65 public static final String SECRET_SEGMENT_METHOD_NAME = "$_$_$sEgMeNt$_$_$";
66 public static final String SECRET_LAYOUT_METHOD_NAME = "$_$_$lAyOuT$_$_$";
67 public static final String SECRET_BOUND_SCHEMA_METHOD_NAME = "$_$_$bOuNdScHeMa$_$_$";
68 public static final String SECRET_OFFSET_METHOD_NAME = "$_$_$oFfSeT$_$_$";
69
70 public static boolean isDebug() {
71 return DEBUG;
72 }
73
74 public static <T> Class<T> requireImplementableInterfaceType(Class<T> type) {
75 Objects.requireNonNull(type);
76 if (!type.isInterface()) {
77 throw newIae(type, "not an interface");
78 }
79 if (type.isHidden()) {
80 throw newIae(type, "a hidden interface");
81 }
82 if (type.isSealed()) {
83 throw newIae(type, "a sealed interface");
84 }
85 assertNotDeclaringTypeParameters(type);
86 return type;
87 }
88
89 private static void assertNotDeclaringTypeParameters(Class<?> type) {
90 if (type.getTypeParameters().length != 0) {
91 throw newIae(type, "directly declaring type parameters: " + type.toGenericString());
92 }
93 }
94
95 static IllegalArgumentException newIae(Class<?> type, String trailingInfo) {
96 return new IllegalArgumentException(type.getName() + " is " + trailingInfo);
97 }
98
99 public static ClassDesc desc(Class<?> clazz) {
100 return clazz.describeConstable()
101 .orElseThrow();
102 }
103
104 public static boolean isSegmentMapperDiscoverable(Class<?> type, Method method) {
105 return SegmentMapper.Discoverable.class.isAssignableFrom(type) &&
106 method.getParameterCount() == 0 &&
107 (method.getReturnType() == MemorySegment.class && method.getName().equals("segment") ||
108 method.getReturnType() == MemoryLayout.class && method.getName().equals("layout") ||
109 method.getReturnType() == BoundSchema.class && method.getName().equals("boundSchema") ||
110 method.getReturnType() == long.class && method.getName().equals("offset"));
111 }
112
113 static void assertMappingsCorrectAndTotal(Class<?> type,
114 GroupLayout layout,
115 Accessors accessors) {
116
117 var nameMappingCounts = layout.memberLayouts().stream()
118 .map(MemoryLayout::name)
119 .flatMap(Optional::stream)
120 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
121
122 List<AccessorInfo> allMethods = accessors.stream().toList();
123
124 // Make sure we have all components distinctly mapped
125 for (AccessorInfo component : allMethods) {
126 String name = component.method().getName();
127 switch (nameMappingCounts.getOrDefault(name, 0L).intValue()) {
128 case 0 -> throw new IllegalArgumentException("No mapping for " +
129 type.getName() + "." + name +
130 " in layout " + layout);
131 case 1 -> { /* Happy path */ }
132 default -> throw new IllegalArgumentException("Duplicate mappings for " +
133 type.getName() + "." + name +
134 " in layout " + layout);
135 }
136 }
137
138 // Make sure all methods of the type are mapped (totality)
139 Set<Method> accessorMethods = allMethods.stream()
140 .map(AccessorInfo::method)
141 .collect(Collectors.toSet());
142
143 var typeMethods = Arrays.stream(type.getMethods())
144 .filter(m -> !MapperUtil.isSegmentMapperDiscoverable(type, m))
145 .filter(m -> Modifier.isAbstract(m.getModifiers()))
146 .toList();
147
148 var missing = typeMethods.stream()
149 .filter(Predicate.not(accessorMethods::contains))
150 .toList();
151
152 if (!missing.isEmpty()) {
153 throw new IllegalArgumentException("Unable to map methods: " + missing);
154 }
155
156 }
157
158 public static MemoryLayout primitiveToLayout(Class<?> type) {
159 if (type == Integer.TYPE) {
160 return JAVA_INT;
161 } else if (type == Float.TYPE) {
162 return JAVA_FLOAT;
163 } else if (type == Long.TYPE) {
164 return JAVA_LONG;
165 } else if (type == Double.TYPE) {
166 return JAVA_DOUBLE;
167 } else if (type == Short.TYPE) {
168 return JAVA_SHORT;
169 } else if (type == Character.TYPE) {
170 return JAVA_CHAR;
171 } else if (type == Byte.TYPE) {
172 return JAVA_BYTE;
173 } else if (type == Boolean.TYPE) {
174 return JAVA_BOOLEAN;
175 } else {
176 throw new IllegalStateException("Expecting primitive " + type);
177 }
178 }
179
180
181 public static boolean isMappableIface(Class<?> clazz) {
182 return MappableIface.class.isAssignableFrom(clazz);
183 }
184 static boolean isBuffer(Class<?> clazz) {
185 return Buffer.class.isAssignableFrom(clazz);
186 }
187
188 static boolean isStruct(Class<?> clazz) {
189 return Buffer.Struct.class.isAssignableFrom(clazz);
190 }
191 public static boolean isMemorySegment(Class<?> clazz) {
192 return MemorySegment.class.isAssignableFrom(clazz);
193 }
194
195 static boolean isStructOrBuffer(Class<?> clazz) {
196 return (isBuffer(clazz) || isStruct(clazz));
197 }
198
199 static boolean isUnion(Class<?> clazz) {
200 return Buffer.Union.class.isAssignableFrom(clazz);
201 }
202
203 static boolean isPrimitive(Class<?> clazz) {
204 return clazz.isPrimitive();
205 }
206
207 static boolean isVoid(Class<?> clazz) {
208 return clazz == Void.TYPE;
209 }
210
211 static boolean isLong(Class<?> clazz) {
212 return clazz == Long.TYPE;
213 }
214
215 /*
216 * From the iface mapper
217 * T foo() getter iface|primitive 0 args , return T returnType T
218 * T foo(long) arraygetter iface|primitive arg[0]==long , return T returnType T
219 * void foo(T) setter primitive arg[0]==T , return void returnType T
220 * void foo(long, T) arraysetter primitive arg[0]==long, arg[1]==T , return void returnType T
221 */
222 static Class<?> typeOf(Class<?> iface, String name) {
223 var methods = iface.getDeclaredMethods();
224 var typeStreamMutable = StreamMutable.of(null);
225 Arrays.stream(methods).filter(method -> method.getName().equals(name)).forEach(matchingMethod -> {
226 Class<?> returnType = matchingMethod.getReturnType();
227 Class<?>[] paramTypes = matchingMethod.getParameterTypes();
228 Class<?> thisType = null;
229 if (paramTypes.length == 0 && (returnType.isInterface() || returnType.isPrimitive())) {
230 thisType = returnType;
231 } else if (paramTypes.length == 1 && paramTypes[0].isPrimitive() && returnType == Void.TYPE) {
232 thisType = paramTypes[0];
233 } else if (paramTypes.length == 1 && isMemorySegment(paramTypes[0]) && returnType == Void.TYPE) {
234 thisType = paramTypes[0];
235 } else if (paramTypes.length == 1 && paramTypes[0] == Long.TYPE && (returnType.isInterface() || returnType.isPrimitive())) {
236 thisType = returnType;
237 } else if (returnType == Void.TYPE && paramTypes.length == 2 &&
238 paramTypes[0] == Long.TYPE && paramTypes[1].isPrimitive()) {
239 thisType = paramTypes[1];
240 } else {
241 throw new IllegalStateException("Can't determine iface mapping type for " + matchingMethod);
242 }
243 if (typeStreamMutable.get()== null ) {
244 typeStreamMutable.set(thisType);
245 }else if (!typeStreamMutable.get().equals(thisType)) {
246 throw new IllegalStateException("type mismatch for " + name);
247 }
248 });
249 if (typeStreamMutable.get()==null) {
250 throw new IllegalStateException("No type mapping for " + iface + " " + name);
251 }
252 return (Class<?>)typeStreamMutable.get();
253 }
254 }