blob: 8b94ff59ec398ed4812912108a8d827194f81fd5 [file] [log] [blame]
/*
* 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;
}
}
}