/* | |
* 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; | |
} | |
} | |
} |