unused-saved/src/org/apache/uima/cas/impl/FeatureStructureClassGen.java (345 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.uima.cas.impl; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.AASTORE; import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ASM5; import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.BALOAD; import static org.objectweb.asm.Opcodes.BASTORE; import static org.objectweb.asm.Opcodes.DALOAD; import static org.objectweb.asm.Opcodes.DASTORE; import static org.objectweb.asm.Opcodes.DLOAD; import static org.objectweb.asm.Opcodes.DRETURN; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.FALOAD; import static org.objectweb.asm.Opcodes.FASTORE; import static org.objectweb.asm.Opcodes.FLOAD; import static org.objectweb.asm.Opcodes.FRETURN; import static org.objectweb.asm.Opcodes.F_SAME; import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.IALOAD; import static org.objectweb.asm.Opcodes.IASTORE; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.IRETURN; import static org.objectweb.asm.Opcodes.LALOAD; import static org.objectweb.asm.Opcodes.LASTORE; import static org.objectweb.asm.Opcodes.LLOAD; import static org.objectweb.asm.Opcodes.LRETURN; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.PUTSTATIC; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.SALOAD; import static org.objectweb.asm.Opcodes.SASTORE; import static org.objectweb.asm.Opcodes.V1_8; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.uima.cas.impl.FeatureImpl; import org.apache.uima.cas.impl.TypeImpl; import org.apache.uima.cas.impl.TypeSystemImpl; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.FrameNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.LookupSwitchInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; /** * Support for creating Feature Structure Classes * * In v3, Feature Structures are represented as instances of Java classes * * See the package-info for a description of the structure of these classes. * * The class name corresponds to the UIMA Type name including the package. * - some exceptions for internal types * * These may be loaded under a PEAR class loader. * * Each typesystem has one definition * made from merged typesystem info and * the 1st found customization class (if any) * * Pears occurring in a pipeline that uses the above * typesystem may have a different definition, loaded * under the Pear's classloader. * - this means the instances cannot be cast among the * different (but same-named) classes * * Main operation: * locate the customization (if any). * If found, read the compiled code, using ASM: * visit fields: * validate range type OK * at end: insert fields not already present * visit methods: * validate method impl OK * at end: insert methods not already present * * at end, load result using appropriate class loader, * store in typeSystem (perhaps under Pear classpath) * */ public class FeatureStructureClassGen { private final static boolean GET = true; private final static boolean SET = false; private final static boolean[] GET_SET = {GET, SET}; private final static int JAVA_CLASS_VERSION = V1_8; // correspond to Java 8 private static final String CAS_RUN_EX = "org/apache/uima/cas/CASRuntimeException"; // shared state for the type private TypeImpl type; private ClassNode cn; /** * x/y/z form */ private String typeJavaClassName; // in x/y/z format private String typeJavaDescriptor; // shared state for a feature private FeatureImpl fi; private String rangeJavaDescriptor; private String rangeArrayElementJavaDescriptor; private String featureFieldName; /** * Create - no customization case * not used for TOP or other built-in predefined types * @return the class as a byte array */ byte[] createJCasCoverClass(TypeImpl type) { this.type = type; typeJavaDescriptor = type.getJavaDescriptor(); typeJavaClassName = type.getName().replace('.', '/'); cn = new ClassNode(ASM5); // java 8 cn.version = JAVA_CLASS_VERSION; cn.access = ACC_PUBLIC + ACC_SUPER; cn.name = typeJavaClassName; cn.superName = type.getSuperType().getName().replace('.', '/'); // cn.interfaces = typeImpl.getInterfaceNamesArray(); // TODO // add the "_typeImpl" field - this has a ref to the TypeImpl for this class cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "_typeImpl", "Lorg/apache/uima/type_system/impl/TypeImpl;", null, null)); // add field declares, and getters and setters, and special getters/setters for array things type.getMergedStaticFeaturesIntroducedByThisType().stream() .forEach(this::addFeatureFieldGetSet); addStaticInitAndConstructors(); createSwitchGettersAndSetters(); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cn.accept(cw); return cw.toByteArray(); } /** * Not called for built-in types * @param type the type to generate * @return the bytecode for that type */ byte[] createJCas_TypeCoverClass(TypeImpl type) { this.type = type; typeJavaDescriptor = type.getJavaDescriptor(); typeJavaClassName = type.getName().replace('.', '/') + "_Type"; cn = new ClassNode(ASM5); // java 8 cn.version = JAVA_CLASS_VERSION; cn.access = ACC_PUBLIC + ACC_SUPER; cn.name = typeJavaClassName; cn.superName = type.getSuperType().getName().replace('.', '/') + "_Type"; // TODO ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cn.accept(cw); return cw.toByteArray(); } private void addFeatureFieldGetSet(FeatureImpl fi) { // compute shared info for this feature this.fi = fi; featureFieldName = getFeatureFieldName(fi); rangeJavaDescriptor = fi.getRangeAsJavaDescriptor(); addFeatureField(); // add declares for fields addFeatureGetSet(); TypeImpl range = (TypeImpl) fi.getRange(); if (range.isArray()) { rangeArrayElementJavaDescriptor = fi.getRangeArrayElementAsJavaDescriptor(); addArrayFeatureGetSet(); } } private void addFeatureGetSet() { for (boolean isGet : GET_SET) { MethodNode mn = new MethodNode(ASM5, ACC_PUBLIC, // Get for non-array value fi.getGetterSetterName(isGet), isGet ? ("()" + rangeJavaDescriptor) : "(" + rangeJavaDescriptor + ")V", null, null); InsnList il = mn.instructions; il.add(new VarInsnNode(ALOAD, 0)); if (isGet) { il.add(new FieldInsnNode(GETFIELD, typeJavaClassName, featureFieldName, rangeJavaDescriptor)); il.add(new InsnNode(getReturnInst(fi))); } else { il.add(new VarInsnNode(getLoadInst(fi), 1)); // load ref, or primitive value il.add(new FieldInsnNode(PUTFIELD, typeJavaClassName, featureFieldName, rangeJavaDescriptor)); il.add(new InsnNode(RETURN)); } final boolean is2slotValue = ((TypeImpl) fi.getRange()).isLongOrDouble(); mn.maxStack = isGet ? 1 : is2slotValue ? 3 : 2; mn.maxLocals = isGet ? 1 : is2slotValue ? 3 : 2; cn.methods.add(mn); } } private void addArrayFeatureGetSet() { for (boolean isGet : GET_SET) { MethodNode mn = new MethodNode(ASM5, ACC_PUBLIC, fi.getGetterSetterName(isGet), isGet ? "(I)" + rangeArrayElementJavaDescriptor : "(I" + rangeArrayElementJavaDescriptor + ")V", null, null); InsnList il = mn.instructions; il.add(new VarInsnNode(ALOAD, 0)); il.add(new FieldInsnNode(GETFIELD, typeJavaClassName, featureFieldName, rangeJavaDescriptor)); il.add(new VarInsnNode(ILOAD, 1)); if (isGet) { il.add(new InsnNode(getArrayLoadInst(fi))); il.add(new InsnNode(getReturnInst(fi))); } else { il.add(new VarInsnNode(getArrayLoadInst(fi), 2)); // load the value to be set into the array slot il.add(new InsnNode(getArrayStoreInst(fi))); il.add(new InsnNode(RETURN)); } final boolean is2slotValue = ((TypeImpl) fi.getRange()).isLongOrDouble(); mn.maxStack = isGet ? 2 : (is2slotValue ? 4 : 3); mn.maxLocals = isGet ? 2 : (is2slotValue ? 4 : 3); cn.methods.add(mn); } } private void addFeatureField() { cn.fields.add(new FieldNode(ACC_PRIVATE, featureFieldName, rangeJavaDescriptor, null, null)); } /** * To support get/set by Feature instead of by name (a form of indirection), * generate a method which returns an array of UIMA MethodReferences * (see Functional Interfaces in org.apache.uima.jcas.impl package) * which can be used to get / set the value of the feature; * @see methods in {@link FeatureStructureImpl} * * The method created is */ private void createSwitchGettersAndSetters() { Arrays.stream(allTypes).sequential().forEach(this::makeTypedSwitchGetterSetter); } private void makeTypedSwitchGetterSetter(TypeImpl featureRangeType) { List<FeatureImpl> features = type.getFeaturesSharingRange(featureRangeType); if (null == features || features.size() == 0) { return; } final int nbrFeats = features.size(); String rangeName = featureRangeType.isArray() ? ((TypeImplArray) featureRangeType).getComponentType().getName() + "Array" : featureRangeType.getShortName(); String javaDesc = featureRangeType.getJavaDescriptor(); // these two are for the method calls to the actual getters/setters String getterJavaDesc = "()" + javaDesc; String setterJavaDesc = "(" + javaDesc + ")V"; LabelNode[] labelNodesGet = new LabelNode[nbrFeats]; LabelNode[] labelNodesSet = new LabelNode[nbrFeats]; int[] keys = new int[nbrFeats]; for (int i = 0; i < nbrFeats; i++) { labelNodesGet[i] = new LabelNode(new Label()); labelNodesSet[i] = new LabelNode(new Label()); keys[i] = features.get(i).getOffsetForGenerics(); } Arrays.sort(keys); LabelNode defaultLabelNodeGet = new LabelNode(new Label()); LabelNode defaultLabelNodeSet = new LabelNode(new Label()); for (boolean isGet : GET_SET) { MethodNode mn = new MethodNode(ASM5, ACC_PUBLIC, "_" + (isGet ? "get" : "set") + rangeName, // _ avoids name collision with other getters/setters isGet ? ("(I)" + javaDesc) : ("(I" + javaDesc + ")V"), null, null); InsnList il = mn.instructions; il.add(new VarInsnNode(ILOAD, 1)); // load the switch int il.add(new LookupSwitchInsnNode(isGet ? defaultLabelNodeGet : defaultLabelNodeSet, keys, isGet ? labelNodesGet : labelNodesSet)); for (int i = 0; i < nbrFeats; i++) { final FeatureImpl fi = features.get(i); il.add((isGet ? labelNodesGet : labelNodesSet)[i]); il.add(new FrameNode(F_SAME, 0, null, 0, null)); il.add(new VarInsnNode(ALOAD, 0)); // load this if (isGet) { il.add(new MethodInsnNode(INVOKEVIRTUAL, typeJavaClassName, fi.getGetterSetterName(GET), getterJavaDesc, false)); il.add(new InsnNode(getReturnInst(fi))); } else { // setter - here we might insert code to do index corruption fixup il.add(new VarInsnNode(getLoadInst(fi), 2)); // load the value or value ref il.add(new MethodInsnNode(INVOKEVIRTUAL, typeJavaClassName, fi.getGetterSetterName(SET), setterJavaDesc, false)); il.add(new InsnNode(RETURN)); } } // default - throw il.add(isGet? defaultLabelNodeGet : defaultLabelNodeSet); il.add(new FrameNode(F_SAME, 0, null, 0, null)); il.add(new TypeInsnNode(NEW, CAS_RUN_EX)); il.add(new InsnNode(DUP)); il.add(new LdcInsnNode("INAPPROP_FEAT_X")); il.add(new MethodInsnNode(INVOKESPECIAL, CAS_RUN_EX, "<init>", "(Ljava/lang/String;)V", false)); il.add(new InsnNode(ATHROW)); boolean is2slotValue = featureRangeType.isLongOrDouble(); mn.maxStack = 3; // for throw mn.maxLocals = isGet ? 2 : (is2slotValue ? 4 : 3); cn.methods.add(mn); } } /** * Used to avoid collisions between field names for features and other things * @param feature the feature * @return - */ private String getFeatureFieldName(FeatureImpl feature) { return feature.getShortName(); } private void addStaticInitAndConstructors() { // class init - // instance init method MethodNode mn = new MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null); InsnList il = mn.instructions; il.add(new MethodInsnNode(INVOKESTATIC, "org/apache/uima/type_system/impl/TypeSystemImpl", "getTypeImplBeingLoaded", "()Lorg/apache/uima/type_system/impl/TypeImpl;", false)); il.add(new FieldInsnNode(PUTSTATIC, "pkg/sample/name/SeeSample", "_typeImpl", "Lorg/apache/uima/type_system/impl/TypeImpl;")); il.add(new InsnNode(RETURN)); mn.maxStack = 1; mn.maxLocals = 0; cn.methods.add(mn); // instance constructors method mn = new MethodNode(ACC_PUBLIC, "<init>", "(ILorg/apache/uima/jcas/cas/TOP_Type;)V", null, null); il = mn.instructions; il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ILOAD, 1)); il.add(new VarInsnNode(ALOAD, 2)); il.add(new MethodInsnNode(INVOKESPECIAL, "org/apache/uima/jcas/tcas/Annotation", "<init>", "(ILorg/apache/uima/jcas/cas/TOP_Type;)V", false)); il.add(new InsnNode(RETURN)); mn.maxStack = 3; mn.maxLocals = 3; cn.methods.add(mn); mn = new MethodNode(ACC_PUBLIC, "<init>", "(Lorg/apache/uima/jcas/JCas;)V", null, null); il = mn.instructions; il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ALOAD, 1)); il.add(new MethodInsnNode(INVOKESPECIAL, "org/apache/uima/jcas/tcas/Annotation", "<init>", "(Lorg/apache/uima/jcas/JCas;)V", false)); il.add(new InsnNode(RETURN)); mn.maxStack = 2; mn.maxLocals = 2; cn.methods.add(mn); // constructor for annotation if (type.isAnnotation) { mn = new MethodNode(ACC_PUBLIC, "<init>", "(Lorg/apache/uima/jcas/JCas;II)V", null, null); il = mn.instructions; il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ALOAD, 1)); il.add(new MethodInsnNode(INVOKESPECIAL, "org/apache/uima/jcas/tcas/Annotation", "<init>", "(Lorg/apache/uima/jcas/JCas;)V", false)); il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ILOAD, 2)); il.add(new MethodInsnNode(INVOKEVIRTUAL, "org/apache/uima/tutorial/RoomNumberv3", "setBegin", "(I)V", false)); il.add(new VarInsnNode(ALOAD, 0)); il.add(new VarInsnNode(ILOAD, 3)); il.add(new MethodInsnNode(INVOKEVIRTUAL, "org/apache/uima/tutorial/RoomNumberv3", "setEnd", "(I)V", false)); il.add(new InsnNode(RETURN)); mn.maxStack = 2; mn.maxLocals = 4; cn.methods.add(mn); } } private int getReturnInst(FeatureImpl feature) { switch (((TypeImpl) feature.getRange()).getCode()) { case TypeSystemImpl.booleanTypeCode : return IRETURN; case TypeSystemImpl.byteTypeCode : return IRETURN; case TypeSystemImpl.shortTypeCode : return IRETURN; case TypeSystemImpl.intTypeCode : return IRETURN; case TypeSystemImpl.longTypeCode : return LRETURN; case TypeSystemImpl.floatTypeCode : return FRETURN; case TypeSystemImpl.doubleTypeCode : return DRETURN; default : return ARETURN; } } private int getLoadInst(FeatureImpl fi) { // load from local variable return getLoadInst(((TypeImpl) fi.getRange()).getCode()); } private int getLoadInst(int typeCode) { switch (typeCode) { case TypeSystemImpl.booleanTypeCode : return ILOAD; case TypeSystemImpl.byteTypeCode : return ILOAD; case TypeSystemImpl.shortTypeCode : return ILOAD; case TypeSystemImpl.intTypeCode : return ILOAD; case TypeSystemImpl.longTypeCode : return LLOAD; case TypeSystemImpl.floatTypeCode : return FLOAD; case TypeSystemImpl.doubleTypeCode : return DLOAD; default : return ALOAD; } } private int getArrayElementLoadInst(FeatureImpl fi) { return getLoadInst(((TypeImpl) ((TypeImplArray) fi.getRange()).getComponentType()).getCode()); } private int getArrayLoadInst(FeatureImpl feature) { // load from array switch (((TypeImpl) feature.getRange()).getCode()) { case TypeSystemImpl.booleanTypeCode : return BALOAD; case TypeSystemImpl.byteTypeCode : return BALOAD; case TypeSystemImpl.shortTypeCode : return SALOAD; case TypeSystemImpl.intTypeCode : return IALOAD; case TypeSystemImpl.longTypeCode : return LALOAD; case TypeSystemImpl.floatTypeCode : return FALOAD; case TypeSystemImpl.doubleTypeCode : return DALOAD; default : return AALOAD; } } private int getArrayStoreInst(FeatureImpl feature) { // store into array switch (((TypeImpl) feature.getRange()).getCode()) { case TypeSystemImpl.booleanTypeCode : return BASTORE; case TypeSystemImpl.byteTypeCode : return BASTORE; case TypeSystemImpl.shortTypeCode : return SASTORE; case TypeSystemImpl.intTypeCode : return IASTORE; case TypeSystemImpl.longTypeCode : return LASTORE; case TypeSystemImpl.floatTypeCode : return FASTORE; case TypeSystemImpl.doubleTypeCode : return DASTORE; default : return AASTORE; } } }