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.accessor;
27
28 import hat.ifacemapper.MapperUtil;
29 //import jdk.internal.ValueBased;
30
31 import java.lang.foreign.GroupLayout;
32 import java.lang.foreign.MemoryLayout;
33 import java.lang.foreign.MemorySegment;
34 import java.lang.foreign.SequenceLayout;
35 import java.lang.foreign.ValueLayout;
36 import java.lang.reflect.Method;
37 import java.lang.reflect.Modifier;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Objects;
45 import java.util.Optional;
46 import java.util.Set;
47 import java.util.function.Function;
48 import java.util.function.Predicate;
49 import java.util.stream.Collectors;
50 import java.util.stream.Stream;
51
52 /**
53 * This class is used to create MethodInfo objects (which hold extensive additional information
54 * for a method) and to organize them in a way they can easily be retrieved.
55 */
56 //@ValueBased
57 public final class Accessors {
58
59 private final Map<AccessorInfo.Key, List<AccessorInfo>> keyToAccessorMap;
60 private final Map<Method, AccessorInfo> methodToAccessorMap;
61
62 private Accessors(Map<AccessorInfo.Key, List<AccessorInfo>> keyToAccessorMap) {
63 this.keyToAccessorMap = keyToAccessorMap;
64 this.methodToAccessorMap = keyToAccessorMap.values().stream()
65 .flatMap(Collection::stream)
66 .collect(Collectors.toMap(AccessorInfo::method, Function.identity()));
67 }
68
69 public boolean isEmpty() {
70 return keyToAccessorMap.isEmpty();
71 }
72
73 public Optional<AccessorInfo> get(Method method) {
74 return Optional.ofNullable(methodToAccessorMap.get(method));
75 }
76
77 public AccessorInfo getOrThrow(Method method) {
78 AccessorInfo accessorInfo = methodToAccessorMap.get(method);
79 if (accessorInfo == null) {
80 throw new IllegalArgumentException("There is no method: " + method);
81 }
82 return accessorInfo;
83 }
84
85 public List<AccessorInfo> get(AccessorInfo.Key key) {
86 return keyToAccessorMap.getOrDefault(key, Collections.emptyList());
87 }
88
89 public Stream<AccessorInfo> stream(Set<AccessorInfo.Key> set) {
90 return set.stream()
91 .map(keyToAccessorMap::get)
92 .filter(Objects::nonNull)
93 .flatMap(Collection::stream);
94 }
95
96 public Stream<AccessorInfo> stream(AccessorInfo.AccessorType accessorType) {
97 return stream(key -> key.accessorType() == accessorType);
98 }
99
100 public Stream<AccessorInfo> stream(Predicate<AccessorInfo.Key> condition) {
101 return Arrays.stream(AccessorInfo.Key.values())
102 .filter(condition)
103 .map(keyToAccessorMap::get)
104 .filter(Objects::nonNull)
105 .flatMap(Collection::stream);
106 }
107
108 public Stream<AccessorInfo> stream() {
109 return keyToAccessorMap.values().stream()
110 .flatMap(Collection::stream);
111 }
112
113 // The order of methods should conform to the order in which names
114 // appears in the layout
115 public static Accessors ofInterface(Class<?> type, GroupLayout layout) {
116 Map<String, List<Method>> methods = Arrays.stream(type.getMethods())
117 .filter(m -> !MapperUtil.isSegmentMapperDiscoverable(type, m))
118 .collect(Collectors.groupingBy(Method::getName));
119
120 return new Accessors(layout.memberLayouts().stream()
121 .map(MemoryLayout::name)
122 .filter(Optional::isPresent)
123 // Get the name of the element layout
124 .map(Optional::get)
125 // Lookup class methods using the element name
126 .map(methods::get)
127 // Ignore unmapped elements
128 .filter(Objects::nonNull)
129 // Flatten the list of Methods (e.g. getters and setters)
130 .flatMap(Collection::stream)
131 // Only consider abstract methods (e.g. ignore default methods)
132 .filter(m -> Modifier.isAbstract(m.getModifiers()))
133 .map(m -> accessorInfo(type, layout, m))
134 // Retain insertion order
135 .collect(Collectors.groupingBy(AccessorInfo::key, LinkedHashMap::new, Collectors.toList())));
136 }
137
138 private static AccessorInfo accessorInfo(Class<?> type, GroupLayout layout, Method method) {
139 AccessorInfo.AccessorType accessorType = isGetter(method)
140 ? AccessorInfo.AccessorType.GETTER
141 : AccessorInfo.AccessorType.SETTER;
142 return accessorInfo(type, layout, method, accessorType);
143 }
144
145
146 private static AccessorInfo accessorInfo(Class<?> type,
147 GroupLayout layout,
148 Method method,
149 AccessorInfo.AccessorType accessorType) {
150
151
152 Class<?> targetType = (accessorType == AccessorInfo.AccessorType.GETTER)
153 ? method.getReturnType()
154 : setterType(method);
155
156 ValueType valueType = valueTypeFor(method, targetType);
157
158 var elementPath = MemoryLayout.PathElement.groupElement(method.getName());
159 MemoryLayout element;
160 try {
161 element = layout.select(elementPath);
162 } catch (IllegalArgumentException iae) {
163 throw new IllegalArgumentException("Unable to resolve '" + method + "' in " + layout, iae);
164 }
165 var offset = layout.byteOffset(elementPath);
166
167 return switch (element) {
168 case ValueLayout vl -> {
169 if (!targetType.equals(vl.carrier())) {
170 throw new IllegalArgumentException("The type " + targetType + " for method " + method +
171 "does not match " + element);
172 }
173 yield new AccessorInfo(AccessorInfo.Key.of(Cardinality.SCALAR, valueType, accessorType),
174 method, targetType, LayoutInfo.of(vl), offset);
175 }
176 case GroupLayout gl -> new AccessorInfo(AccessorInfo.Key.of(Cardinality.SCALAR, valueType, accessorType),
177 method, targetType, LayoutInfo.of(gl), offset);
178 case SequenceLayout sl -> {
179 AccessorInfo info = new AccessorInfo(AccessorInfo.Key.of(Cardinality.ARRAY, valueType, accessorType)
180 , method, targetType, LayoutInfo.of(sl), offset);
181
182 // This is an interface mapper so, check the array access parameter count matches
183 int noDimensions = info.layoutInfo().arrayInfo().orElseThrow().dimensions().size();
184 // The last parameter for a setter is the new value
185 int expectedParameterIndexCount = method.getParameterCount() - (accessorType == AccessorInfo.AccessorType.SETTER ? 1 : 0);
186 if (expectedParameterIndexCount != noDimensions) {
187 throw new IllegalArgumentException(
188 "Sequence layout has a dimension of " + noDimensions +
189 " and so, the method parameter count does not" +
190 " match for: " + method);
191 }
192
193 yield info;
194 }
195 default -> throw new IllegalArgumentException("Cannot map " + element + " for " + type);
196 };
197 }
198
199 private static ValueType valueTypeFor(Method method, Class<?> targetType) {
200 if (targetType.isArray()) {
201 return valueTypeFor(method, targetType.getComponentType());
202 }
203 ValueType valueType;
204 if (targetType.isPrimitive() || targetType.equals(MemorySegment.class)) {
205 valueType = ValueType.VALUE;
206 } else if (targetType.isInterface()) {
207 valueType = ValueType.INTERFACE;
208 } else {
209 throw new IllegalArgumentException("Type " + targetType + " is neither a primitive value or an interface: " + method);
210 }
211 return valueType;
212 }
213
214 private static Class<?> setterType(Method method) {
215 if (method.getParameterCount() == 0) {
216 throw new IllegalArgumentException("A setter must take at least one argument: " + method);
217 }
218 return method.getParameterTypes()[method.getParameterCount() - 1];
219 }
220
221 private static boolean isGetter(Method method) {
222 return method.getReturnType() != void.class;
223 }
224
225 }