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 }