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.op; 27 28 import java.lang.annotation.ElementType; 29 import java.lang.annotation.Retention; 30 import java.lang.annotation.RetentionPolicy; 31 import java.lang.annotation.Target; 32 import java.lang.invoke.MethodHandle; 33 import java.lang.invoke.MethodHandles; 34 import java.lang.reflect.Constructor; 35 import java.lang.reflect.Method; 36 import java.lang.reflect.Modifier; 37 import jdk.incubator.code.Op; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.function.Function; 41 42 /** 43 * An operation factory for constructing an {@link Op operation} from its 44 * {@link ExternalizableOp.ExternalizedOp external content}. 45 */ 46 @FunctionalInterface 47 public interface OpFactory { 48 49 /** 50 * An operation declaration annotation. 51 * <p> 52 * This annotation may be declared on a concrete class implementing an {@link Op operation} whose name is a constant 53 * that can be declared as this attribute's value. 54 * <p> 55 * Tooling can process declarations of this annotation to build a factory for constructing operations from their name. 56 */ 57 @Retention(RetentionPolicy.RUNTIME) 58 @Target(ElementType.TYPE) 59 @interface OpDeclaration { 60 /** 61 * {@return the operation name} 62 */ 63 String value(); 64 } 65 66 /** 67 * A class value for lazily computing an operation factory for {@link Op operation} classes 68 * annotated with {@link OpDeclaration} and enclosed within a given class to compute over. 69 * <p> 70 * Each enclosed class annotated with {@code OpDeclaration} must declare a public static method named {@code create} 71 * with one parameter type of {@link ExternalizableOp.ExternalizedOp} and return type that is the concrete class type. 72 * Alternatively, the concrete class must declare public constructor with one parameter type of 73 * {@link ExternalizableOp.ExternalizedOp}. 74 */ 75 ClassValue<OpFactory> OP_FACTORY = new ClassValue<>() { 76 @Override 77 protected OpFactory computeValue(Class<?> c) { 78 // @@@ See https://bugs.openjdk.org/browse/JDK-8321207 79 final Map<String, Class<? extends Op>> opMapping = createOpMapping(c); 80 81 return def -> { 82 var opClass = opMapping.get(def.name()); 83 if (opClass == null) { 84 return null; 85 } 86 87 return constructOp(opClass, def); 88 }; 89 } 90 }; 91 92 /** 93 * Constructs an {@link Op operation} from its external content. 94 * <p> 95 * If there is no mapping from the operation's name to a concrete 96 * class of an {@code Op} then this method returns null. 97 * 98 * @param def the operation's external content 99 * @return the operation, otherwise null 100 */ 101 Op constructOp(ExternalizableOp.ExternalizedOp def); 102 103 /** 104 * Constructs an {@link Op operation} from its external content. 105 * <p> 106 * If there is no mapping from the operation's name to a concrete 107 * class of an {@code Op} then this method throws UnsupportedOperationException. 108 * 109 * @param def the operation's external content 110 * @return the operation, otherwise null 111 * @throws UnsupportedOperationException if there is no mapping from the operation's 112 * name to a concrete class of an {@code Op} 113 */ 114 default Op constructOpOrFail(ExternalizableOp.ExternalizedOp def) { 115 Op op = constructOp(def); 116 if (op == null) { 117 throw new UnsupportedOperationException("Unsupported operation: " + def.name()); 118 } 119 120 return op; 121 } 122 123 /** 124 * Compose this operation factory with another operation factory. 125 * <p> 126 * If there is no mapping in this operation factory then the result 127 * of the other operation factory is returned. 128 * 129 * @param after the other operation factory. 130 * @return the composed operation factory. 131 */ 132 default OpFactory andThen(OpFactory after) { 133 return def -> { 134 Op op = constructOp(def); 135 return op != null ? op : after.constructOp(def); 136 }; 137 } 138 139 private static Map<String, Class<? extends Op>> createOpMapping(Class<?> opClasses) { 140 Map<String, Class<? extends Op>> mapping = new HashMap<>(); 141 for (Class<?> opClass : opClasses.getNestMembers()) { 142 if (opClass.isAnnotationPresent(OpDeclaration.class)) { 143 if (!Modifier.isPublic(opClass.getModifiers())) { 144 throw new InternalError("Operation class not public: " + opClass.getName()); 145 } 146 147 if (!Op.class.isAssignableFrom(opClass)) { 148 throw new InternalError("Operation class is not assignable to Op: " + opClass); 149 } 150 151 MethodHandle handle = getOpConstructorMethodHandle(opClass); 152 if (handle == null) { 153 throw new InternalError("Operation constructor for operation class not found: " + opClass.getName()); 154 } 155 156 if (!Op.class.isAssignableFrom(handle.type().returnType())) { 157 throw new InternalError("Operation constructor does not return an Op: " + handle); 158 } 159 160 String opName = opClass.getAnnotation(OpDeclaration.class).value(); 161 @SuppressWarnings("unchecked") 162 var opClassCast = (Class<Op>) opClass; 163 mapping.put(opName, opClassCast); 164 } 165 } 166 return mapping; 167 } 168 169 private static MethodHandle getOpConstructorMethodHandle(Class<?> opClass) { 170 Method method = null; 171 try { 172 method = opClass.getMethod("create", ExternalizableOp.ExternalizedOp.class); 173 } catch (NoSuchMethodException e) { 174 } 175 176 if (method != null) { 177 if (!Modifier.isStatic(method.getModifiers())) { 178 throw new InternalError("Operation constructor is not a static method: " + method); 179 } 180 181 try { 182 /*__return MethodHandles.lookup().unreflect(method);__*/ return MethodHandles.publicLookup().unreflect(method); 183 } catch (IllegalAccessException e) { 184 throw new InternalError("Inaccessible operation constructor for operation: " + 185 method); 186 } 187 } 188 189 Constructor<?> constructor; 190 try { 191 constructor = opClass.getConstructor(ExternalizableOp.ExternalizedOp.class); 192 } catch (NoSuchMethodException e) { 193 return null; 194 } 195 196 try { 197 /*__return MethodHandles.lookup().unreflectConstructor(constructor);__*/ return MethodHandles.publicLookup().unreflectConstructor(constructor); 198 } catch (IllegalAccessException e) { 199 throw new InternalError("Inaccessible operation constructor for operation: " + 200 constructor); 201 } 202 } 203 204 private static Op constructOp(Class<? extends Op> opClass, ExternalizableOp.ExternalizedOp opDef) { 205 class Enclosed { 206 private static final ClassValue<Function<ExternalizableOp.ExternalizedOp, Op>> OP_CONSTRUCTOR = new ClassValue<>() { 207 @Override 208 protected Function<ExternalizableOp.ExternalizedOp, Op> computeValue(Class<?> opClass) { 209 final MethodHandle opConstructorMH = getOpConstructorMethodHandle(opClass); 210 assert opConstructorMH != null; 211 212 return operationDefinition -> { 213 try { 214 return (Op) opConstructorMH.invoke(operationDefinition); 215 } catch (RuntimeException | Error e) { 216 throw e; 217 } catch (Throwable t) { 218 throw new RuntimeException(t); 219 } 220 }; 221 } 222 }; 223 } 224 return Enclosed.OP_CONSTRUCTOR.get(opClass).apply(opDef); 225 } 226 }