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 }