blob: 7db860c28a834f122adca625dcceca08fc87b239 [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 java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodType.methodType;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.uima.UIMAFramework;
import org.apache.uima.UIMARuntimeException;
import org.apache.uima.UIMA_IllegalStateException;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASException;
import org.apache.uima.cas.CASRuntimeException;
import org.apache.uima.internal.util.Misc;
import org.apache.uima.internal.util.UIMAClassLoader;
import org.apache.uima.internal.util.WeakIdentityMap;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.util.Level;
import org.apache.uima.util.Logger;
// @formatter:off
/**
* There is one **class** instance of this per UIMA core class loader.
* The class loader is the loader for the UIMA core classes, not any UIMA extension class loader
* - **Builtin** JCas Types are loaded and shared among all type systems, once, when this class is loaded.
*
* There are no instances of this class.
* - The type system impl instances at commit time initialize parts of their Impl from data in this class
* - Some of the data kept in this class in static values, is constructed when the type system is committed
*
* The class instance is shared
* - by multiple type systems
* - by multiple CASes (in a CAS pool, for instance, when these CASes are sharing the same type system).
* - by all views of those CASes.
* - by multiple different pipelines, built using the same merged type system instance
* - by non-built-in JCas classes, loaded under possibly different extension class loaders
*
* PEAR support
* Multiple PEAR contexts can be used.
* - hierarchy (each is parent of kind below
* -- UIMA core class loader (built-ins, not redefinable by user JCas classes)
* --- a new limitation of UIMA V3 to allow sharing of built-in JCas classes, which also
* have custom impl, and don't fit the model used for PEAR Trampolines
* -- outer (non Pear) class loader (optional, known as base extension class loader)
* --- possible multiple, for different AE pipelines
* -- Within PEAR class loader
* - when running within a PEAR, operations which return Feature Structures potentially return
* JCas instances of classes loaded from the Pear's class loader.
* - These instances share the same int[] and Object[] and _typeImpl and _casView refs with the outer class loader's FS
*
* Timing / life cycle
* Built-in classes loaded and initialized at first type system commit time.
* non-pear classes loaded and initialized at type system commit time (if not already loaded)
* - special checks for conformability if some types loaded later, due to requirements for computing feature offsets at load time
* pear classes loaded and initialized at first entry to Pear, for a given type system and class loader.
*
*
* At typeSystemCommit time, this class is created and initialized:
* - The built-in JCas types are loaded
*
* - The user-defined non-PEAR JCas classes are loaded (not lazy, but eager), provided the type system is a new one.
* (If the type system is "equal" to an existing committed one, that one is used instead).
*
* -- User classes defined with the name of UIMA types, but which are not JCas definitions, are not used as
* JCas types. This permits uses cases where users define a class which (perhaps at a later integration time)
* has the same name as a UIMA type, but is not a JCas class.
* -- These classes, once loaded, remain loaded because of Java's design, unless the ClassLoader
* used to load them is Garbage Collected.
* --- The ClassLoader used is the CAS's JCasClassLoader, set from the UIMA Extension class loader if specified.
*
* Assigning slots for features:
* - each type being loaded runs static final initializers to set for (a subset of) all features the offset
* in the int or ref storage arrays for those values.
* - These call a static method in JCasRegistry: register[Int/Ref]Feature, which assigns the next available slot
* via accessing/updating a thread local instance of TypeSystemImpl.SlotAllocate.
*/
// @formatter:on
public abstract class FSClassRegistry { // abstract to prevent instantiating; this class only has
// static methods
static final String RECORD_JCAS_CLASSLOADERS = "uima.record_jcas_classloaders";
static final boolean IS_RECORD_JCAS_CLASSLOADERS = Misc
.getNoValueSystemProperty(RECORD_JCAS_CLASSLOADERS);
static final String LOG_JCAS_CLASSLOADERS_ON_SHUTDOWN = "uima.log_jcas_classloaders_on_shutdown";
static final boolean IS_LOG_JCAS_CLASSLOADERS_ON_SHUTDOWN = Misc
.getNoValueSystemProperty(LOG_JCAS_CLASSLOADERS_ON_SHUTDOWN);
// private static final boolean IS_TRACE_AUGMENT_TS = false;
// private static final boolean IS_TIME_AUGMENT_FEATURES = false;
/* ========================================================= */
/* This class has only static methods and fields */
/* To allow multi-threaded use, some fields are */
/* kept in a thread local */
/* ========================================================= */
/* ==========================++++++=== */
/* Static final, not in thread local */
/* not sync-ed */
/* ==========================++++++=== */
// Used for the built-ins.
private static final Lookup defaultLookup = MethodHandles.lookup();
private static final MethodType findConstructorJCasCoverType = methodType(void.class,
TypeImpl.class, CASImpl.class);
// private static final MethodType findConstructorJCasCoverTypeArray = methodType(void.class,
// TypeImpl.class, CASImpl.class, int.class);
/**
* The callsite has the return type, followed by capture arguments
*/
private static final MethodType callsiteFsGenerator = methodType(FsGenerator3.class);
// private static final MethodType callsiteFsGeneratorArray = methodType(FsGeneratorArray.class);
// // NO LONGER USED
private static final MethodType fsGeneratorType = methodType(TOP.class, TypeImpl.class,
CASImpl.class);
// private static final MethodType fsGeneratorArrayType = methodType(TOP.class, TypeImpl.class,
// CASImpl.class, int.class); // NO LONGER USED
// @formatter:off
/**
* precomputed generators for built-in types
* These instances are shared for all type systems
* Key = index = typecode
*/
// @formatter:on
private static final JCasClassInfo[] jcasClassesInfoForBuiltins;
/* =================================== */
/* not in thread local, sync-ed */
/* =================================== */
/** a cache for constant int method handles */
private static final List<MethodHandle> methodHandlesForInt = new ArrayList<>();
// @formatter:off
/**
* Map from class loaders used to load JCas Classes, both PEAR and non-Pear cases, to JCasClassInfo for that loaded JCas class instance.
* key is the class loader
* value is a plain HashMapmap from string form of typenames to JCasClassInfo corresponding to the JCas class covering that type
* (which may be a supertype of the type name).
*
* Key is JCas fully qualified name (not UIMA type name).
* Is a String, since different type systems may use the same JCas classes.
* value is the JCasClassInfo for that class
* - this may be for that actual JCas class, if one exists for that UIMA type name
* - or it is null, signalling that there is no JCas for this type, and a supertype should be used
*
* Cache of FsGenerator[]s kept in TypeSystemImpl instance, since it depends on type codes.
* Current FsGenerator[] kept in CASImpl shared view data, switched as needed for PEARs.
* <p>
* <b>NOTE:</b> Access this map in a thread-safe way only via {@link #get_className_to_jcci} which
* synchronizes on the map object.
*/
// @formatter:on
private static final WeakIdentityMap<ClassLoader, Map<String, JCasClassInfo>> cl_to_type2JCas = WeakIdentityMap
.newHashMap(); // identity: key is classloader
private static final WeakIdentityMap<ClassLoader, StackTraceElement[]> cl_to_type2JCasStacks;
// private static final Map<ClassLoader, Map<String, JCasClassInfo>> cl_4pears_to_type2JCas =
// Collections.synchronizedMap(new IdentityHashMap<>()); // identity: key is classloader
static private class ErrorReport {
final Exception e;
final boolean doThrow;
ErrorReport(Exception e, boolean doThrow) {
this.e = e;
this.doThrow = doThrow;
}
}
// must precede first (static) use
static private ThreadLocal<List<ErrorReport>> errorSet = new ThreadLocal<>();
// /**
// * Map (per class loader) from JCas Classes, to all callSites in that JCas class
// */
// public static final Map<Class<? extends TOP>, ArrayList<Entry<String, MutableCallSite>>>
// callSites_all_JCasClasses = new HashMap<>();
// @formatter:off
/**
* One instance per JCas class defined for it, per class loader
* - per class loader, because different JCas class definitions for the same name are possible, per class loader
*
* Kept in maps, per class loader.
* Created when potentially loading JCas classes.
*
* Entries kept in potentially multiple global static hashmaps, with key = the string form of the typename
* - string form of key allows sharing the same named JCas definition among different type system type-impls.
* - one hashmap per classloader
* Entries reused potentially by multiple type systems.
*
* Info used for
* - identifying the target of a "new" operator - could be generator for superclass.
* - remembering the results of getting all the features this JCas class defines
* One entry per defined JCas class per classloader; no instance if no JCas class is defined.
*/
// @formatter:on
public static class JCasClassInfo {
final FsGenerator3 generator;
/**
* The corresponding loaded JCas Class for this UIMA type, may be a JCas class associated with a
* UIMA supertype if no JCas class is found for this type.
*/
final Class<? extends TOP> jcasClass;
/**
* NOT the TypeCode, but instead, the unique int assigned to the JCas class when it gets loaded
* Might be -1 if the JCasClassInfo instance is for a non-JCas instantiable type
*/
final int jcasType;
final JCasClassFeatureInfo[] features;
JCasClassInfo(Class<? extends TOP> jcasClass, FsGenerator3 generator, int jcasType) {
this.generator = generator;
this.jcasClass = jcasClass;
this.jcasType = jcasType; // typeId for jcas class, **NOT Typecode**
this.features = getJCasClassFeatureInfo(jcasClass);
// System.out.println("debug create jcci, class = " + jcasClass.getName() + ", typeint = " +
// jcasType);
}
boolean isCopydown(TypeImpl ti) {
return isCopydown(ti.getJCasClassName());
}
boolean isCopydown(String jcasClassName) {
return !jcasClass.getCanonicalName().equals(jcasClassName);
}
boolean isPearOverride(TypeSystemImpl tsi) {
JCasClassInfo baseJcci = tsi.getJcci(jcasClass.getName());
return baseJcci == null
|| !jcasClass.getClassLoader().equals(baseJcci.jcasClass.getClassLoader());
}
TypeImpl getUimaType(TypeSystemImpl tsi) {
return tsi.getType(Misc.javaClassName2UimaTypeName(jcasClass.getName()));
}
}
/**
* Information about all features this JCas class defines Used to expand the type system when the
* JCas defines more features than the type system declares.
*/
static class JCasClassFeatureInfo {
final String shortName;
// rangename is byte.class, etc
// or x.y.z.JCasClassName
final String uimaRangeName;
JCasClassFeatureInfo(String shortName, String uimaRangeName) {
this.shortName = shortName;
this.uimaRangeName = uimaRangeName;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String.format("JCasClassFeatureInfo feature: %s, range: %s",
(shortName == null) ? "<null>" : shortName,
(uimaRangeName == null) ? "<null>" : uimaRangeName);
}
}
static {
TypeSystemImpl tsi = TypeSystemImpl.staticTsi;
jcasClassesInfoForBuiltins = new JCasClassInfo[tsi.getTypeArraySize()];
// lookup = defaultLookup;
// walk in subsumption order, supertype before subtype
// Class loader used for builtins is the UIMA framework's class loader
ArrayList<MutableCallSite> callSites_toSync = new ArrayList<>();
ClassLoader cl = tsi.getClass().getClassLoader();
loadBuiltins(tsi.topType, cl, get_className_to_jcci(cl, false), callSites_toSync);
MutableCallSite[] sync = callSites_toSync.toArray(new MutableCallSite[callSites_toSync.size()]);
MutableCallSite.syncAll(sync);
reportErrors();
if (IS_LOG_JCAS_CLASSLOADERS_ON_SHUTDOWN || IS_RECORD_JCAS_CLASSLOADERS) {
cl_to_type2JCasStacks = WeakIdentityMap.newHashMap();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
log_registered_classloaders(Level.WARN);
}
});
} else {
cl_to_type2JCasStacks = null;
}
}
static int clToType2JCasSize() {
return cl_to_type2JCas.size();
}
private static void loadBuiltins(TypeImpl ti, ClassLoader cl,
Map<String, JCasClassInfo> type2jcci, ArrayList<MutableCallSite> callSites_toSync) {
String typeName = ti.getName();
if (BuiltinTypeKinds.creatableBuiltinJCas.contains(typeName)
|| typeName.equals(CAS.TYPE_NAME_SOFA)) {
JCasClassInfo jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, defaultLookup);
assert jcci != null;
// done while beginning to commit the staticTsi (before committed flag is set), for builtins
updateOrValidateAllCallSitesForJCasClass(jcci.jcasClass, ti, callSites_toSync);
jcasClassesInfoForBuiltins[ti.getCode()] = jcci;
// Class<?> builtinClass = maybeLoadJCas(ti, cl);
// assert (builtinClass != null); // builtin types must be present
// // copy down to subtypes, if needed, done later
// int jcasType = Misc.getStaticIntFieldNoInherit(builtinClass, "typeIndexID");
// JCasClassInfo jcasClassInfo = createJCasClassInfo(builtinClass, ti, jcasType);
// jcasClassesInfoForBuiltins[ti.getCode()] = jcasClassInfo;
}
for (TypeImpl subType : ti.getDirectSubtypes()) {
loadBuiltins(subType, cl, type2jcci, callSites_toSync);
}
}
/**
* Load JCas types for some combination of class loader and type system Some of these classes may
* have already been loaded for this type system Some of these classes may have already been
* loaded (perhaps for another type system)
*
* @param ts
* the type system
* @param isDoUserJCasLoading
* always true, left in for experimentation in the future with dynamic generation of JCas
* classes
* @param cl
* the class loader. For Pears, is the pear class loader
*/
private static synchronized void loadJCasForTSandClassLoader(TypeSystemImpl ts,
boolean isDoUserJCasLoading, ClassLoader cl, Map<String, JCasClassInfo> type2jcci) {
// boolean alreadyPartiallyLoaded = false; // true if any JCas types for this class loader have
// previously been loaded
// synchronized (cl2t2j) {
// type2jcci = cl2t2j.get(cl);
//
// if (null == type2jcci) {
// type2jcci = new HashMap<>();
// cl2t2j.put(cl, type2jcci);
// } else {
// alreadyPartiallyLoaded = true;
// }
// }
// @formatter:off
/**
* copy in built-ins
* update t2jcci (if not already loaded) with load info for type
* update type system's map from unique JCasID to the type in this type system
*/
// @formatter:on
/* ============================== */
/* BUILT-INS */
/* ============================== */
for (int typecode = 1; typecode < jcasClassesInfoForBuiltins.length; typecode++) {
JCasClassInfo jcci = jcasClassesInfoForBuiltins[typecode];
if (jcci != null) {
Class<? extends TOP> jcasClass = jcci.jcasClass;
type2jcci.putIfAbsent(jcasClass.getCanonicalName(), jcci);
setTypeFromJCasIDforBuiltIns(jcci, ts, typecode);
// update call sites not called, was called when
// jcci was created.
// updateAllCallSitesForJCasClass(jcasClass, ts.getTypeForCode(typecode));
}
}
/* ========================================================= */
/* Add all user-defined JCas Types, in subsumption order */
/* ========================================================= */
if (isDoUserJCasLoading) {
// @formatter:off
/**
* Two passes are needed loading is needed.
* - The first one loads the JCas Cover Classes initializes everything
* -- some of the classes might already be loaded (including the builtins which are loaded once per class loader)
* - The second pass performs the conformance checks between the loaded JCas cover classes, and the current type system.
* This depends on having the TypeImpl's javaClass field be accurate (reflect any loaded JCas types)
*/
// @formatter:on
// this is this here rather than in a static initializer, because
// we want to use the "cl" parameter to get a version of the
// getMethodHandlesLookup that will have the right (maybe more local) permissions checking.
// This is done by having the UIMA Class loader notice that the class being loaded is
// MHLC, and then dynamically loading in that class loader a copy of the byte code
// for that class.
Lookup lookup = getLookup(cl);
ArrayList<MutableCallSite> callSites_toSync = new ArrayList<>();
maybeLoadJCasAndSubtypes(ts, ts.topType, type2jcci.get(TOP.class.getCanonicalName()), cl,
type2jcci, callSites_toSync, lookup);
MutableCallSite[] sync = callSites_toSync
.toArray(new MutableCallSite[callSites_toSync.size()]);
MutableCallSite.syncAll(sync);
checkConformance(ts, ts.topType, type2jcci);
}
reportErrors();
}
private static void setTypeFromJCasIDforBuiltIns(JCasClassInfo jcci, TypeSystemImpl tsi,
int typeCode) {
int v = jcci.jcasType;
// v is negative if not found, which is the case for types like FloatList (these can't be
// instantiated)
if (v >= 0) {
tsi.setJCasRegisteredType(v, tsi.getTypeForCode(typeCode));
}
}
// @formatter:off
/**
* Called for all the types, including the built-ins, but the built-ins have already been set up by the caller.
* Saves the results in two places
* type system independent spot: JCasClassInfo instance indexed by JCasClassName
* type system spot: the JCasIndexID -> type table in the type system
*
* Looks up by classname to see if there is an associated JCas class for this type.
* - all types of that name (perhaps from different loaded type systems) will share that one JCas class
* - copyDowns are excluded from this requirement - because there are no JCas class definitions
* for this type (in that case).
*
* @param tsi
* the type system
* @param ti
* the type to process
* @param copyDownDefault_jcasClassInfo
* @param cl
* the loader used to load, and to save the results under the key of the class loader the
* results
* @param type2JCas
* map holding the results of loading JCas classes
*/
// @formatter:on
private static void maybeLoadJCasAndSubtypes(TypeSystemImpl tsi, TypeImpl ti,
JCasClassInfo copyDownDefault_jcasClassInfo, ClassLoader cl,
Map<String, JCasClassInfo> type2jcci, ArrayList<MutableCallSite> callSites_toSync,
Lookup lookup) {
JCasClassInfo jcci = getOrCreateJCasClassInfo(ti, cl, type2jcci, lookup);
if (null != jcci && tsi.isCommitted()) {
updateOrValidateAllCallSitesForJCasClass(jcci.jcasClass, ti, callSites_toSync);
}
// String t2jcciKey = Misc.typeName2ClassName(ti.getName());
// JCasClassInfo jcci = type2jcci.get(t2jcciKey);
//
// if (jcci == null) {
//
// // first time encountering this typename. Attempt to load a jcas class for this
// // - if none, the next call returns null.
// jcci = createJCasClassInfo(ti, cl, callSites_toSync, lookup); // does update of callsites if
// was able find JCas class
//
// if (null != jcci) {
// validateSuperClass(jcci, ti);
// type2jcci.put(t2jcciKey, jcci);
// tsi.setJCasRegisteredType(jcci.jcasType, ti);
// }
//
//// // not yet recorded as loaded under this class loader.
////
//// Class<?> clazz = maybeLoadJCas(ti, cl);
//// if (null != clazz && TOP.class.isAssignableFrom(clazz)) {
////
//// int jcasType = -1;
//// if (!Modifier.isAbstract(clazz.getModifiers())) { // skip next for abstract classes
//// jcasType = Misc.getStaticIntFieldNoInherit(clazz, "typeIndexID");
//// // if jcasType is negative, this means there's no value for this field
//// assert(jcasType >= 0);
//// }
//// jcci = createJCasClassInfo(clazz, ti, jcasType);
//// isCopyDown = false;
//// // don't do this call, caller will call conformance which has a weaker
//// // test - passes if there is a shared
//// // https://issues.apache.org/jira/browse/UIMA-5660
////// if (clazz != TOP.class) { // TOP has no super class
////// validateSuperClass(jcci, ti);
////// }
//// } else {
//// jcci = copyDownDefault_jcasClassInfo;
//// }
//
//// type2jcci.put(t2jcciKey, jcci);
//
// } else {
// // have previously set up a jcci for this type name
// // maybe for a different type instance (of the same name)
// // next may be redundant?
// if (ti.getTypeSystem().isCommitted()) {
// updateOrValidateAllCallSitesForJCasClass(jcci.jcasClass, ti, callSites_toSync);
// }
//
// }
// // this UIMA type was set up (maybe loaded, maybe defaulted to a copy-down) previously
// isCopyDown = jcci.isCopydown(t2jcciKey);
//
// if (isCopyDown) {
// // the "stored" version might have the wrong super class for this type system
// type2jcci.put(t2jcciKey, jcci = copyDownDefault_jcasClassInfo);
//
// } else if (!ti.isTopType()) {
// // strong test for non-copy-down case: supertype must match, with 2 exceptions
// // removed https://issues.apache.org/jira/browse/UIMA-5660
//// validateSuperClass(jcci, ti);
// }
// }
// this is done even after the class is first loaded, in case the type system changed.
// don't set anything if copy down - otherwise was setting the copyed-down typeId ref to the
// new ti
// System.out.println("debug set jcas regisered type " + jcci.jcasType + ", type = " +
// ti.getName());
JCasClassInfo jcci_or_copyDown = (jcci == null) ? copyDownDefault_jcasClassInfo : jcci;
if (!ti.isPrimitive()) { // bypass this for primitives because the jcasClassInfo is the
// "inherited one" of TOP
/**
* Note: this value sets into the shared TypeImpl (maybe shared among many JCas impls) the
* "latest" jcasClass It is "read" by the conformance testing, while still under the type
* system lock. Other uses of this may get an arbitrary (the latest) version of the class
* Currently the only other use is in backwards compatibility with low level type system
* "switching" an existing type.
*/
ti.setJavaClass(jcci_or_copyDown.jcasClass);
}
for (TypeImpl subtype : ti.getDirectSubtypes()) {
maybeLoadJCasAndSubtypes(tsi, subtype, jcci_or_copyDown, cl, type2jcci, callSites_toSync,
lookup);
}
}
// @formatter:off
/**
* For a particular type name, get the JCasClassInfo
* - by fetching the cached value
* - by loading the class
* - return null if no JCas class for this name
* only called for non-Pear callers
* @param ti -
* @param cl -
* @param type2jcci -
* @param lookup -
* @return - jcci or null, if no JCas class for this type was able to be loaded
*/
// @formatter:on
public static JCasClassInfo getOrCreateJCasClassInfo(TypeImpl ti, ClassLoader cl,
Map<String, JCasClassInfo> type2jcci, Lookup lookup) {
JCasClassInfo jcci = type2jcci.get(ti.getJCasClassName());
if (jcci == null) {
jcci = maybeCreateJCasClassInfo(ti, cl, type2jcci, lookup);
}
// Due to initialization order, it could be that we created the JCCI before the static fields in
// the JCas class have been initialized. In particular, the jcasType typeIndexID might still
// have been uninitialized (0) when we crated the JCCI. To work fix that case, check if the
// typeIndexID has changed and if so update the JCCI
if (jcci != null && jcci.jcasType == 0) {
if (!Modifier.isAbstract(jcci.jcasClass.getModifiers())) { // skip next for abstract classes
int jcasType = Misc.getStaticIntFieldNoInherit(jcci.jcasClass, "typeIndexID");
if (jcasType != jcci.jcasType) {
jcci = new JCasClassInfo(jcci.jcasClass, jcci.generator, jcasType);
type2jcci.put(ti.getJCasClassName(), jcci);
}
}
}
// do this setup for new type systems using previously loaded jcci, as well as
// for new jccis
if (jcci != null && jcci.jcasType >= 0) {
ti.getTypeSystem().setJCasRegisteredType(jcci.jcasType, ti);
}
return jcci;
}
static JCasClassInfo maybeCreateJCasClassInfo(TypeImpl ti, ClassLoader cl,
Map<String, JCasClassInfo> type2jcci, Lookup lookup) {
JCasClassInfo jcci = createJCasClassInfo(ti, cl, lookup); // does update of callsites if was
// able find JCas class
if (null != jcci) {
type2jcci.put(ti.getJCasClassName(), jcci);
// non-creatable JCas types (e.g. FSList) do not have a valid jcasType
}
return jcci;
}
public static JCasClassInfo createJCasClassInfo(TypeImpl ti, ClassLoader cl, Lookup lookup) {
Class<? extends TOP> clazz = maybeLoadJCas(ti, cl);
if (null == clazz || !TOP.class.isAssignableFrom(clazz)) {
return null;
}
int jcasType = -1;
if (!Modifier.isAbstract(clazz.getModifiers())) { // skip next for abstract classes
jcasType = Misc.getStaticIntFieldNoInherit(clazz, "typeIndexID");
// if jcasType is negative, this means there's no value for this field
if (jcasType == -1) {
add2errors(errorSet,
/**
* The Class "{0}" matches a UIMA Type, and is a subtype of uima.cas.TOP, but is
* missing the JCas typeIndexId.
*/
new CASRuntimeException(CASRuntimeException.JCAS_MISSING_TYPEINDEX,
clazz.getName()),
false); // not a fatal error
return null;
}
}
return createJCasClassInfo(clazz, ti, jcasType, lookup);
}
// static AtomicLong time = IS_TIME_AUGMENT_FEATURES ? new AtomicLong(0) : null;
//
// static {
// if (IS_TIME_AUGMENT_FEATURES) {
// Runtime.getRuntime().addShutdownHook(new Thread(null, () -> {
// System.out.format("Augment features from JCas time: %,d ms%n",
// time.get() / 1000000L);
// }, "show augment feat from jcas time"));
// }
// }
//
// static void augmentFeaturesFromJCas(
// TypeImpl type,
// ClassLoader cl,
// TypeSystemImpl tsi,
// Map<String, JCasClassInfo> type2jcci,
// Lookup lookup) {
//
// long startTime = 0;
// if (type.isTopType()) {
// if (IS_TIME_AUGMENT_FEATURES) {
// startTime = System.nanoTime();
// }
// } else {
// /**************************************************************************************
// * N O T E : *
// * fixup the ordering of staticMergedFeatures: *
// * - supers, then features introduced by this type. *
// * - order may be "bad" if later feature merge introduced an additional feature *
// **************************************************************************************/
// // skip for top level; no features there, but no super type either
// type.getFeatureImpls(); // done for side effect of computingcomputeStaticMergedFeaturesList();
// }
//
// if ( //false && // debugging
// ! type.isBuiltIn) {
//
// if (IS_TRACE_AUGMENT_TS) System.out.println("trace Augment TS from JCas, for type " +
// type.getName());
//
//
// TypeSystemImpl.typeBeingLoadedThreadLocal.set(type); // only for supporting previous version of
// v3 jcas
//
// JCasClassInfo jcci = getOrCreateJCasClassInfo(type, cl, type2jcci, lookup); // no call site
// sync
// if (jcci != null) {
//
// if (IS_TRACE_AUGMENT_TS) System.out.println(" trace Augment TS from JCas, adding features: " +
// Misc.ppList(Arrays.asList(jcci.features)));
//
// type.jcci = jcci;
// // also recurse for supertypes to load jcci's (in case some don't have uima type)
// // recursion stops when have jcci already
// jcci = type2jcci.get(jcci.jcasClass.getSuperclass());
// if (null == jcci) {
//
// }
//
//// for (JCasClassFeatureInfo f : jcci.features) {
//// FeatureImpl fi = type.getFeatureByBaseName(f.shortName);
//// if (fi == null) {
////
//// /* *********************************************************************************
//// * feature is missing in the type, a pseudo feature for it *
//// * *********************************************************************************/
////
//// /* Range is either one of the uima primitives, or *
//// * a fs reference. FS References could be to "unknown" types in this type system. *
//// * If so, use TOP */
//// TypeImpl rangeType = tsi.getType(f.uimaRangeName);
//// if (rangeType == null) {
//// rangeType = tsi.topType;
//// }
////
//// /** Can't add feature to type "{0}" since it is feature final. */
//// if (type.isFeatureFinal()) {
//// throw new CASAdminException(CASAdminException.TYPE_IS_FEATURE_FINAL, type.getName());
//// }
////
//// if (IS_TRACE_AUGMENT_TS) System.out.println(" trace Augment TS from JCas, for feature: " +
// f.shortName );
////
//// if (tsi.isInInt(rangeType)) {
//// type.jcas_added_int_slots.add(new FeatureImpl_jcas_only(f.shortName, rangeType));
//// } else {
//// type.jcas_added_ref_slots.add(new FeatureImpl_jcas_only(f.shortName, rangeType));
//// }
//// }
//// }
// }
// }
//
// if (IS_TRACE_AUGMENT_TS) System.out.println("trace Augment TS from JCas, for subtypes of type "
// + type.getName() + ", " + Misc.ppList(type.getDirectSubtypes()));
// for (TypeImpl subti : type.getDirectSubtypes()) {
// augmentFeaturesFromJCas(subti, cl, tsi, type2jcci, lookup);
// }
//
// if (IS_TIME_AUGMENT_FEATURES && type.isTopType()) {
// time.addAndGet(System.nanoTime() - startTime);
// }
// }
// private void setTypeJcci(TypeImpl type, ClassLoader cl, Lookup lookup, Map<String,
// JCasClassInfo> type2jcci) {
// if (IS_TRACE_AUGMENT_TS) System.out.println("trace Augment TS from JCas, for type " +
// type.getName());
//
// TypeSystemImpl.typeBeingLoadedThreadLocal.set(type); // only for supporting previous version of
// v3 jcas
//
// JCasClassInfo jcci = getOrCreateJCasClassInfo(type, cl, type2jcci, lookup); // no call site
// sync
// if (jcci != null) {
//
// if (IS_TRACE_AUGMENT_TS) System.out.println(" trace Augment TS from JCas, adding features: " +
// Misc.ppList(Arrays.asList(jcci.features)));
//
// type.jcci = jcci;
// // also recurse for supertypes to load jcci's (in case some don't have uima type)
// // recursion stops when have jcci already
// Class<?> superClass = jcci.jcasClass.getSuperclass();
// String superClassName = superClass.getName();
// jcci = type2jcci.get(superClassName);
//
// if (null == jcci) {
// TypeSystemImpl tsi = type.getTypeSystem();
// TypeImpl ti = tsi.getType(Misc.javaClassName2UimaTypeName(superClassName));
//
//
// setTypeJcci()
// }
//
// }
// private static String superTypeJCasName(TypeImpl ti) {
// return Misc.typeName2ClassName(ti.getSuperType().getName());
// }
//
private static boolean compare_C_T(Class<?> clazz, TypeImpl ti) {
return ti.getJCasClassName().equals(clazz.getName());
}
/**
* Changed https://issues.apache.org/jira/browse/UIMA-5660 to allow insertions of extra types/
* classes into the superchain. verify that the supertype class chain matches the type
*
* @param clazz
* The JCas class, always below TOP
* @param ti
* -
*/
private static void validateSuperClass(JCasClassInfo jcci, TypeImpl ti) {
final Class<?> superClass = jcci.jcasClass.getSuperclass();
final TypeImpl superType = ti.getSuperType();
if (compare_C_T(superClass, superType)) {
return;
}
for (TypeImpl st : ti.getAllSuperTypes()) {
if (compare_C_T(superClass, st)) {
return;
}
}
for (Class<?> sc = superClass.getSuperclass(); sc != Object.class
&& sc != FeatureStructureImplC.class; sc = sc.getSuperclass()) {
if (compare_C_T(sc, superType)) {
return;
}
}
/**
* The JCas class: "{0}" has supertypes: "{1}" which do not match the UIMA type "{2}"''s
* supertypes "{3}".
*/
throw new CASRuntimeException(CASRuntimeException.JCAS_MISMATCH_SUPERTYPE,
jcci.jcasClass.getName(), getAllSuperclassNames(jcci.jcasClass), ti.getName(),
getAllSuperTypeNames(ti));
}
private static String getAllSuperclassNames(Class<?> clazz) {
StringBuilder sb = new StringBuilder();
for (Class<?> sc = clazz.getSuperclass(); sc != null
&& sc != FeatureStructureImplC.class; sc = sc.getSuperclass()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(sc.getName());
}
return sb.toString();
}
private static String getAllSuperTypeNames(TypeImpl ti) {
StringBuilder sb = new StringBuilder();
for (TypeImpl st = ti.getSuperType(); st.getCode() != TypeSystemConstants.topTypeCode; st = st
.getSuperType()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(st.getName());
}
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("uima.cas.TOP");
return sb.toString();
}
//
//
// if (! clazz.getSuperclass().getCanonicalName().equals(superTypeJCasName(ti))) {
// /** Special case exceptions */
// TypeImpl superti = ti.getSuperType();
// TypeSystemImpl tsi = ti.getTypeSystem();
// if (superti == tsi.arrayBaseType ||
// superti == tsi.listBaseType) return;
// /** The JCas class: "{0}" has supertype: "{1}" which doesn''t match the UIMA type "{2}"''s
// supertype "{3}". */
// throw new CASRuntimeException(CASRuntimeException.JCAS_MISMATCH_SUPERTYPE,
// clazz.getCanonicalName(),
// clazz.getSuperclass().getCanonicalName(),
// ti.getName(),
// ti.getSuperType().getName());
// }
//
// }
// @formatter:off
/**
* Called to load (if possible) a corresponding JCas class for a UIMA type.
* Called at Class Init time for built-in types
* Called at TypeSystemCommit for non-built-in types
* Runs the static initializers in the loaded JCas classes - doing resolve
*
* Synchronization: done outside this class
*
* @param typeName
* -
* @param cl
* the class loader to use
* @return the loaded / resolved class
*/
// @formatter:on
private static Class<? extends TOP> maybeLoadJCas(TypeImpl ti, ClassLoader cl) {
Class<? extends TOP> clazz = null;
String className = ti.getJCasClassName();
try {
clazz = (Class<? extends TOP>) Class.forName(className, true, cl);
} catch (ClassNotFoundException e) {
// Class not found is normal, if there is no JCas for this class
return clazz;
} catch (ExceptionInInitializerError e) {
throw new RuntimeException("Exception while loading " + className, e);
}
return clazz;
}
// SYNCHRONIZED
static synchronized MethodHandle getConstantIntMethodHandle(int i) {
MethodHandle mh = Misc.getWithExpand(methodHandlesForInt, i);
if (mh == null) {
methodHandlesForInt.set(i, mh = MethodHandles.constant(int.class, i));
}
return mh;
}
/**
* Return a Functional Interface for a generator for creating instances of a type. Function takes
* a casImpl arg, and returning an instance of the JCas type.
*
* @param jcasClass
* the class of the JCas type to construct
* @param typeImpl
* the UIMA type
* @return a Functional Interface whose createFS method takes a casImpl and when subsequently
* invoked, returns a new instance of the class
*/
private static FsGenerator3 createGenerator(Class<?> jcasClass, Lookup lookup) {
try {
MethodHandle mh = lookup.findConstructor(jcasClass, findConstructorJCasCoverType);
MethodType mtThisGenerator = methodType(jcasClass, TypeImpl.class, CASImpl.class);
CallSite callSite = LambdaMetafactory.metafactory(lookup, // lookup context for the
// constructor
"createFS", // name of the method in the Function Interface
callsiteFsGenerator, // signature of callsite, return type is functional interface,
// args are captured args if any
fsGeneratorType, // samMethodType signature and return type of method impl by function
// object
mh, // method handle to constructor
mtThisGenerator);
return (FsGenerator3) callSite.getTarget().invokeExact();
} catch (Throwable e) {
if (e instanceof NoSuchMethodException) {
String classname = jcasClass.getName();
add2errors(errorSet,
new CASRuntimeException(e, CASRuntimeException.JCAS_CAS_NOT_V3, classname,
jcasClass.getClassLoader()
.getResource(classname.replace('.', '/') + ".class").toString()));
return null;
}
/**
* An internal error occurred, please report to the Apache UIMA project; nested exception if
* present: {0}
*/
throw new UIMARuntimeException(e, UIMARuntimeException.INTERNAL_ERROR);
}
}
// /**
// * Return a Functional Interface for a getter for getting the value of a feature,
// * called by APIs using the non-JCas style of access via features,
// * but accessing the values via the JCas cover class getter methods.
// *
// * The caller of these methods is the FeatureStructureImplC methods.
// *
// * There are these return values:
// * boolean, byte, short, int, long, float, double, String, FeatureStructure
// *
// */
// // static for setting up builtin values
// private static Object createGetterOrSetter(Class<?> jcasClass, FeatureImpl fi, boolean
// isGetter, boolean ncnj) {
//
// TypeImpl range = fi.getRangeImpl();
// String name = ncnj ? ("_" + fi.getGetterSetterName(isGetter) + "NcNj")
// : fi.getGetterSetterName(isGetter);
//
// try {
// /* get an early-bound getter
// /* Instead of findSpecial, we use findVirtual, in case the method is overridden by a subtype
// loaded later */
// MethodHandle mh = lookup.findVirtual(
// jcasClass, // class having the method code for the getter
// name, // the name of the method for the getter or setter
// isGetter ? methodType(range.javaClass)
// : methodType(void.class, range.javaClass) // return value, e.g. int.class, xyz.class,
// FeatureStructureImplC.class
// );
//
// // getter methodtype is return_type, FeatureStructure.class
// // return_type is int, byte, etc. primitive (except string/substring), or
// // object (to correspond with erasure)
// // setter methodtype is void.class, FeatureStructure.class, javaclass
// MethodType mhMt = isGetter ? methodType(range.getJavaPrimitiveClassOrObject(),
// FeatureStructureImplC.class)
// : methodType(void.class, FeatureStructureImplC.class, range.getJavaPrimitiveClassOrObject());
// MethodType iMt = isGetter ? methodType(range.javaClass, jcasClass)
// : methodType(void.class, jcasClass, range.javaClass);
//
//// System.out.format("mh method type for %s method %s is %s%n",
//// jcasClass.getSimpleName(),
//// fi.getGetterSetterName(isGetter),
//// mhMt);
//
// CallSite callSite = LambdaMetafactory.metafactory(
// lookup, // lookup context for the getter
// isGetter ? "get" : "set", // name of the method in the Function Interface
// methodType(isGetter ? range.getter_funct_intfc_class : range.setter_funct_intfc_class), //
// callsite signature = just the functional interface return value
// mhMt, // samMethodType signature and return type of method impl by function object
// mh, // method handle to constructor
// iMt);
//
// if (range.getJavaClass() == boolean.class) {
// return isGetter ? (JCas_getter_boolean) callSite.getTarget().invokeExact()
// : (JCas_setter_boolean) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == byte.class) {
// return isGetter ? (JCas_getter_byte) callSite.getTarget().invokeExact()
// : (JCas_setter_byte) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == short.class) {
// return isGetter ? (JCas_getter_short) callSite.getTarget().invokeExact()
// : (JCas_setter_short) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == int.class) {
// return isGetter ? (JCas_getter_int) callSite.getTarget().invokeExact()
// : (JCas_setter_int) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == long.class) {
// return isGetter ? (JCas_getter_long) callSite.getTarget().invokeExact()
// : (JCas_setter_long) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == float.class) {
// return isGetter ? (JCas_getter_float) callSite.getTarget().invokeExact()
// : (JCas_setter_float) callSite.getTarget().invokeExact();
// } else if (range.getJavaClass() == double.class) {
// return isGetter ? (JCas_getter_double) callSite.getTarget().invokeExact()
// : (JCas_setter_double) callSite.getTarget().invokeExact();
// } else {
// return isGetter ? (JCas_getter_generic<?>) callSite.getTarget().invokeExact()
// : (JCas_setter_generic<?>) callSite.getTarget().invokeExact();
// }
// } catch (NoSuchMethodException e) {
// if ((jcasClass == Sofa.class && !isGetter) ||
// (jcasClass == AnnotationBase.class && !isGetter)) {
// return null;
// }
// // report missing setter or getter
// /* Unable to find required {0} method for JCAS type {1} with {2} type of {3}. */
// CASException casEx = new CASException(CASException.JCAS_GETTER_SETTER_MISSING,
// name,
// jcasClass.getName(),
// isGetter ? "return" : "argument",
// range.javaClass.getName()
// );
// ArrayList<Exception> es = errorSet.get();
// if (es == null) {
// es = new ArrayList<Exception>();
// errorSet.set(es);
// }
// es.add(casEx);
// return null;
// } catch (Throwable e) {
// throw new UIMARuntimeException(e, UIMARuntimeException.INTERNAL_ERROR);
// }
// }
// GetterSetter getGetterSetter(int typecode, String featShortName) {
// return jcasClassesInfo[typecode].gettersAndSetters.get(featShortName);
// }
// static for setting up static builtin values
// @formatter:off
/**
* Called after succeeding at loading, once per load for an exact matching JCas Class
* - class was already checked to insure is of proper type for JCas
* - skips creating-generator-for-Sofa - since "new Sofa(...)" is not a valid way to create a sofa
*
* @param jcasClass
* the JCas class that corresponds to the type
* @param ti
* the type
* @return the info for this JCas that is shared across all type systems under this class loader
*/
// @formatter:on
private static JCasClassInfo createJCasClassInfo(Class<? extends TOP> jcasClass, TypeImpl ti,
int jcasType, Lookup lookup) {
boolean noGenerator = ti.getCode() == TypeSystemConstants.sofaTypeCode
|| Modifier.isAbstract(jcasClass.getModifiers()) || ti.isArray();
FsGenerator3 generator = noGenerator ? null : createGenerator(jcasClass, lookup);
JCasClassInfo jcasClassInfo = new JCasClassInfo(jcasClass, generator, jcasType);
// System.out.println("debug creating jcci, classname = " + jcasClass.getName() + ",
// jcasTypeNumber: " + jcasType);
return jcasClassInfo;
}
private static JCasClassFeatureInfo[] getJCasClassFeatureInfo(Class<?> jcasClass) {
ArrayList<JCasClassFeatureInfo> features = new ArrayList<>();
try {
for (Field f : jcasClass.getDeclaredFields()) {
String fname = f.getName();
if (fname.length() <= 5 || !fname.startsWith("_FC_")) {
continue;
}
String featName = fname.substring(4);
// compute range by looking at get method
String getterName = "get" + Character.toUpperCase(featName.charAt(0))
+ featName.substring(1);
Method m;
try {
m = jcasClass.getDeclaredMethod(getterName); // get the getter with no args
} catch (NoSuchMethodException e) {
/**
* Cas class {0} with feature {1} but is mssing a 0 argument getter. This feature will not
* be used to maybe expand the type's feature set.
*/
Logger logger = UIMAFramework.getLogger(FSClassRegistry.class);
logger.warn(() -> logger.rb_ue(CASRuntimeException.JCAS_MISSING_GETTER,
jcasClass.getName(), featName));
continue; // skip this one
}
String rangeClassName = m.getReturnType().getName();
String uimaRangeName = Misc.javaClassName2UimaTypeName(rangeClassName);
features.add(new JCasClassFeatureInfo(featName, uimaRangeName));
} // end of for loop
JCasClassFeatureInfo[] r = new JCasClassFeatureInfo[features.size()];
return features.toArray(r);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
// static boolean isFieldInClass(Feature feat, Class<?> clazz) {
// try {
// return null != clazz.getDeclaredField("_FC_" + feat.getShortName());
// } catch (NoSuchFieldException e) {
// return false;
// }
// }
static void checkConformance(ClassLoader cl, TypeSystemImpl ts) {
Map<String, JCasClassInfo> type2jcci = get_className_to_jcci(cl, false);
checkConformance(ts, ts.topType, type2jcci);
}
private static void checkConformance(TypeSystemImpl ts, TypeImpl ti,
Map<String, JCasClassInfo> type2jcci) {
if (ti.isPrimitive()) {
return;
}
JCasClassInfo jcci = type2jcci.get(ti.getJCasClassName());
// if (null == jcci) {
// if (!skipCheck && ti.isBuiltIn && jcci.isAlreadyCheckedBuiltIn) {
// skipCheck = true;
// }
if (null != jcci && // skip if the UIMA class has an abstract (non-creatable) JCas class)
!(ti.isBuiltIn)) { // skip if builtin
checkConformance(jcci.jcasClass, ts, ti, type2jcci);
}
for (TypeImpl subtype : ti.getDirectSubtypes()) {
checkConformance(ts, subtype, type2jcci);
}
}
// @formatter:off
/**
* Inner check
*
* Never called for "built-ins", or for uima types not having a JCas loaded class
*
* Checks that a JCas class definition conforms to the current type in the current type system.
* Checks that the superclass chain contains some match to the super type chain.
* Checks that the return value for the getters for features matches the feature's range.
* Checks that static _FC_xxx values from the JCas class == the adjusted feature offsets in the type system
*
* @param clazz
* - the JCas class to check
* @param tsi
* -
* @param ti
* -
*/
// @formatter:on
private static void checkConformance(Class<?> clazz, TypeSystemImpl tsi, TypeImpl ti,
Map<String, JCasClassInfo> type2jcci) {
// // skip the test if the jcasClassInfo is being inherited
// // because that has already been checked
// if (!clazz.getName().equals(ti.getJCasClassName())) {
// System.out.println("debug should never print");
// return;
// }
// check supertype
validateSuperClass(type2jcci.get(ti.getJCasClassName()), ti);
// // This is done by validateSuperClass, when JCasClass is loaded or looked up for a particular
// type system
// // one of the supertypes must match a superclass of the class
// // (both of these should be OK)
//
// // class: X -> XS -> Annotation -> AnnotationBase -> TOP -> FeatureStructureImplC
// // type: X -------> Annotation -> AnnotationBase -> TOP
// // (if XS getters/setters used, have runtime error; if never used, OK)
// //
// // class: X --------> Annotation -> AnnotationBase -> TOP -> FeatureStructureImplC
// // type: X -> XS -> Annotation -> AnnotationBase -> TOP
// boolean isOk = false;
// List<Class<?>> superClasses = new ArrayList<>();
// boolean isCheckImmediateSuper = true;
// Class<?> superClass = clazz.getSuperclass();
//
// outer:
// for (TypeImpl uimaSuperType : ti.getAllSuperTypes()) { // iterate uimaSuperTypes
// JCasClassInfo jcci = type2jcci.get(uimaSuperType.getJCasClassName());
// if (jcci != null) {
// if (isCheckImmediateSuper) {
// if (jcci.jcasClass != superClass) {
// /** The JCas class: "{0}" has supertype: "{1}" which doesn''t match the UIMA type "{2}"''s
// supertype "{3}". */
// add2errors(errorSet,
// new CASRuntimeException(CASRuntimeException.JCAS_MISMATCH_SUPERTYPE,
// clazz.getCanonicalName(),
// clazz.getSuperclass().getCanonicalName(),
// ti.getName(),
// ti.getSuperType().getName()),
// false); // not a throwable error, just a warning
// }
// }
//
// superClasses.add(superClass);
// while (superClass != FeatureStructureImplC.class && superClass != Object.class) {
// if (jcci.jcasClass == superClass) {
// isOk = true;
// break outer;
// }
// superClass = superClass.getSuperclass();
// superClasses.add(superClass);
// }
// }
//
// isCheckImmediateSuper = false;
// }
//
// // This error only happens if the JCas type chain doesn't go thru "TOP" - so it isn't really
// a JCas class!
//
// if (!isOk && superClasses.size() > 0) {
// /** JCas Class's supertypes for "{0}", "{1}" and the corresponding UIMA Supertypes for "{2}",
// "{3}" don't have an intersection. */
// add2errors(errorSet,
// new CASRuntimeException(CASRuntimeException.JCAS_CAS_MISMATCH_SUPERTYPE,
// clazz.getName(), Misc.ppList(superClasses), ti.getName(),
// Misc.ppList(Arrays.asList(ti.getAllSuperTypes()))),
// true); // throwable error
// }
// the range of all the features must match the getters
for (Method m : clazz.getDeclaredMethods()) {
String mname = m.getName();
if (mname.length() <= 3 || !mname.startsWith("get")) {
continue;
}
String suffix = (mname.length() == 4) ? "" : mname.substring(4); // one char past 1st letter
// of feature
String fname = Character.toLowerCase(mname.charAt(3)) + suffix; // entire name, with first
// letter lower cased
FeatureImpl fi = ti.getFeatureByBaseName(fname);
if (fi == null) {
fname = mname.charAt(3) + suffix; // no feature, but look for one with captialized first
// letter
fi = ti.getFeatureByBaseName(fname);
if (fi == null) {
continue;
}
}
// some users are writing getFeat(some other args) as additional signatures - skip checking
// these
// https://issues.apache.org/jira/projects/UIMA/issues/UIMA-5557
Parameter[] p = m.getParameters();
TypeImpl range = fi.getRangeImpl();
if (p.length > 1) {
continue; // not a getter, which has either 0 or 1 arg(the index int for arrays)
}
if (p.length == 1 && (!range.isArray() || p[0].getType() != int.class)) {
continue; // has 1 arg, but is not an array or the arg is not an int
}
// have the feature, check the range
Class<?> returnClass = m.getReturnType(); // for primitive, is int.class, etc.
Class<?> rangeClass = range.getJavaClass();
if (range.isArray()) {
if (p.length == 1 && p[0].getType() == int.class) {
rangeClass = range.getComponentType().getJavaClass();
}
}
if (!rangeClass.isAssignableFrom(returnClass)) { // can return subclass of TOP, OK if range is
// TOP
if (rangeClass.getName().equals("org.apache.uima.jcas.cas.Sofa") && // exception: for
// backwards compat
// reasons, sofaRef
// returns SofaFS, not
// Sofa.
returnClass.getName().equals("org.apache.uima.cas.SofaFS")) {
// empty
} else {
/**
* CAS type system type "{0}" defines field "{1}" with range "{2}", but JCas getter method
* is returning "{3}" which is not a subtype of the declared range.
*/
add2errors(errorSet, new CASRuntimeException(CASRuntimeException.JCAS_TYPE_RANGE_MISMATCH,
ti.getName(), fi.getShortName(), rangeClass, returnClass), false); // should
// throw, but
// some code
// breaks!
}
}
} // end of checking methods
try {
for (Field f : clazz.getDeclaredFields()) {
String fname = f.getName();
if (fname.length() <= 5 || !fname.startsWith("_FC_")) {
continue;
}
String featName = fname.substring(4);
FeatureImpl fi = ti.getFeatureByBaseName(featName);
if (fi == null) {
add2errors(errorSet,
/**
* JCAS class "{0}" defines a UIMA field "{1}" but the UIMA type doesn''t define
* that field.
*/
new CASRuntimeException(CASRuntimeException.JCAS_FIELD_MISSING_IN_TYPE_SYSTEM,
clazz.getName(), featName),
false); // don't throw on this error, field is still set up
// //debug
// System.out.format("debug JCAS field not in ts: type: %s, field: %s %n%s%n",
// clazz.getName(), featName, Misc.getCallers(1, 30));
} else {
Field mhf = clazz.getDeclaredField("_FH_" + featName);
mhf.setAccessible(true);
MethodHandle mh = (MethodHandle) mhf.get(null);
int staticOffsetInClass = (int) mh.invokeExact();
if (fi.getAdjustedOffset() != staticOffsetInClass) {
/**
* In JCAS class "{0}", UIMA field "{1}" was set up when this class was previously
* loaded and initialized, to have an adjusted offset of "{2}" but now the feature has a
* different adjusted offset of "{3}"; this may be due to something else other than type
* system commit actions loading and initializing the JCas class, or to having a
* different non-compatible type system for this class, trying to use a common JCas
* cover class, which is not supported.
*/
add2errors(errorSet,
new CASRuntimeException(CASRuntimeException.JCAS_FIELD_ADJ_OFFSET_CHANGED,
clazz.getName(), fi.getName(), staticOffsetInClass,
fi.getAdjustedOffset()),
staticOffsetInClass != -1); // throw unless static offset is -1, in that case, a
// runtime error will occur if it is used
} // end of offset changed
} // end of feature check
} // end of for loop
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private static void add2errors(ThreadLocal<List<ErrorReport>> errors, Exception e) {
add2errors(errors, e, true);
}
private static void add2errors(ThreadLocal<List<ErrorReport>> errors, Exception e,
boolean doThrow) {
List<ErrorReport> es = errors.get();
if (es == null) {
es = new ArrayList<>();
errors.set(es);
}
es.add(new ErrorReport(e, doThrow));
}
private static void reportErrors() {
boolean throwWhenDone = false;
List<ErrorReport> es = errorSet.get();
if (es != null) {
StringBuilder msg = new StringBuilder(100);
// msg.append('\n'); // makes a break in the message at the beginning, unneeded
for (ErrorReport f : es) {
msg.append(f.e.getMessage());
throwWhenDone = throwWhenDone || f.doThrow;
msg.append('\n');
}
errorSet.set(null); // reset after reporting
if (throwWhenDone) {
throw new CASRuntimeException(CASException.JCAS_INIT_ERROR, "\n" + msg);
} else {
Logger logger = UIMAFramework.getLogger();
if (null == logger) {
throw new CASRuntimeException(CASException.JCAS_INIT_ERROR, "\n" + msg);
} else {
logger.log(Level.WARNING, msg.toString());
}
}
}
}
// @formatter:off
/**
* called infrequently to set up cache
* Only called when a type system has not had generators for a particular class loader.
*
* For PEAR generators:
* Populates only for those classes the PEAR has overriding implementations
* - other entries are null; this serves as a boolean indicator that no pear override exists for that type
* and therefore no trampoline is needed
*
* @param cl
* identifies which set of jcas cover classes
* @param isPear
* true for pear case
* @param tsi
* the type system being used
* @return the generators for that set, as an array indexed by type code
*/
// @formatter:on
static FsGenerator3[] getGeneratorsForClassLoader(ClassLoader cl, boolean isPear,
TypeSystemImpl tsi) {
Map<String, JCasClassInfo> type2jcci = get_className_to_jcci(cl, isPear);
// final Map<ClassLoader, Map<String, JCasClassInfo>> cl2t2j = isPear ? cl_4pears_to_type2JCas :
// cl_to_type2JCas;
// synchronized(cl2t2j) {
// //debug
// System.out.format("debug loading JCas for type System %s ClassLoader %s, isPear: %b%n",
// tsi.hashCode(), cl, isPear);
// This is the first time this class loader is being used - load the classes for this type
// system, or
// This is the first time this class loader is being used with this particular type system
loadJCasForTSandClassLoader(tsi, true, cl, type2jcci);
FsGenerator3[] r = new FsGenerator3[tsi.getTypeArraySize()];
// Map<String, JCasClassInfo> t2jcci = cl2t2j.get(cl);
// can't use values alone because many types have the same value (due to copy-down)
// cannot iterate over type2jcci - that map only has types with found JCas classes
getGeneratorsForTypeAndSubtypes(tsi.topType, type2jcci, isPear, cl, r, tsi);
// for (Entry<String, JCasClassInfo> e : type2jcci.entrySet()) {
// TypeImpl ti = tsi.getType(Misc.javaClassName2UimaTypeName(e.getKey()));
// if (null == ti) {
// continue; // JCas loaded some type in the past, but it's not in this type system
// }
// JCasClassInfo jcci = e.getValue();
//
// // skip entering a generator in the result if
// // in a pear setup, and this cl is not the cl that loaded the JCas class.
// // See method comment for why.
// if (!isPear || jcci.isPearOverride(cl)) {
// r[ti.getCode()] = (FsGenerator3) jcci.generator;
// }
// }
return r;
}
private static void getGeneratorsForTypeAndSubtypes(TypeImpl ti,
Map<String, JCasClassInfo> t2jcci, boolean isPear, ClassLoader cl, FsGenerator3[] r,
TypeSystemImpl tsi) {
TypeImpl jti = ti;
JCasClassInfo jcci = t2jcci.get(jti.getJCasClassName());
while (jcci == null) {
jti = jti.getSuperType();
jcci = t2jcci.get(jti.getJCasClassName());
}
// skip entering a generator in the result if
// in a pear setup, and this cl is not the cl that loaded the JCas class.
// See method comment getGeneratorsForClassLoader(...) in for why.
if (!isPear || jcci.isPearOverride(tsi)) {
r[ti.getCode()] = (FsGenerator3) jcci.generator;
}
for (TypeImpl subtype : ti.getDirectSubtypes()) {
getGeneratorsForTypeAndSubtypes(subtype, t2jcci, isPear, cl, r, tsi);
}
}
private static boolean isAllNull(FsGenerator3[] r) {
for (FsGenerator3 v : r) {
if (v != null) {
return false;
}
}
return true;
}
// @formatter:off
/**
* Called once when the JCasClassInfo is created.
* Once set, the offsets are never changed (although they could be...)
*
* New type systems are checked for conformance to existing settings in the JCas class.
* Type System types are augmented by features defined in the JCas
* but missing in the type, before this routine is called.
*
* Iterate over all fields named _FC_ followed by a feature name.
* If that feature doesn't exist in this type system - skip init, will cause runtime error if used
* Else, set the callSite's method Handle to one that returns the int constant for type system's offset of that feature.
* If already set, check that the offset didn't change.
*
*
* @param clazz
* -
* @param type
* -
*/
// @formatter:on
private static void updateOrValidateAllCallSitesForJCasClass(Class<? extends TOP> clazz,
TypeImpl type, ArrayList<MutableCallSite> callSites_toSync) {
try {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
if (fieldName.startsWith("_FC_")) {
// //debug
// System.out.println("debug " + fieldName);
String featureName = fieldName.substring("_FC_".length());
final int index = TypeSystemImpl.getAdjustedFeatureOffset(type, featureName);
// //debug
// if (type.getShortName().equals("Split") && featureName.equals("splits")
// ) {
// System.out.println("debug attempting to set offset for splits in Splits to " + index);
// System.out.println(type.toString(2));
// System.out.println(Misc.getCallers(1, 32));
// }
if (index == -1) {
continue; // a feature defined in the JCas class doesn't exist in the currently loaded
// type
} // skip setting it. If code uses this, a runtime error will happen.
// only happens for pear-loaded lazyily JCas classes
// "Normal" loaded JCas classes (at start of type system commit)
// have any extra features added to the type system
// https://issues.apache.org/jira/browse/UIMA-5698
MutableCallSite c;
field.setAccessible(true);
c = (MutableCallSite) field.get(null);
if (c == null) { // happens when first load of TypeSystemImpl is from JCas class ref
continue; // will be set later when type system is committed.
}
int prev = (int) c.getTarget().invokeExact();
if (prev == -1) { // the static method in JCas classes, TypeSystemImpl.createCallSite,
// initializes the call site with a method handle that returns -1
MethodHandle mh_constant = getConstantIntMethodHandle(index);
c.setTarget(mh_constant);
callSites_toSync.add(c);
} else if (prev != index) {
// This is one of two errors.
// It could also be caused by the range type switching from ref array to the int array
checkConformance(clazz.getClassLoader(), type.getTypeSystem());
reportErrors();
// //debug
// System.err.format(
// "Debug incompat offset jcas, class = %s, type= %s, classIndex = %d, type index:
// %d%n",
// clazz.getName(), type.getName(), prev, index);
// System.err.flush();
throw new UIMA_IllegalStateException(
UIMA_IllegalStateException.JCAS_INCOMPATIBLE_TYPE_SYSTEMS,
new Object[] { type.getName(), featureName });
}
}
}
} catch (Throwable e) {
Misc.internalError(e); // never happen
}
}
/**
* For internal use only!
*/
public static void unregister_jcci_classloader(ClassLoader cl) {
synchronized (cl_to_type2JCas) {
cl_to_type2JCas.remove(cl);
if (cl_to_type2JCasStacks != null) {
cl_to_type2JCasStacks.remove(cl);
}
}
}
/**
* For internal use only!
*/
public static void log_registered_classloaders(Level aLogLevel) {
Logger log = UIMAFramework.getLogger(lookup().lookupClass());
if (cl_to_type2JCasStacks == null) {
log.warn(
"log_registered_classloaders called but classLoader registration stack logging "
+ "is not turned on. Define the system property [{}] to enable it.",
LOG_JCAS_CLASSLOADERS_ON_SHUTDOWN);
return;
}
Map<ClassLoader, StackTraceElement[]> clToLog = new LinkedHashMap<>();
synchronized (cl_to_type2JCas) {
Iterator<ClassLoader> i = cl_to_type2JCas.keyIterator();
while (i.hasNext()) {
ClassLoader cl = i.next();
if (cl == TypeSystemImpl.staticTsi.getClass().getClassLoader()) {
// This is usually the default/system classloader and is registered when the
// TypeSystemImpl class is statically intialized. It is not interesting for leak
// detection.
continue;
}
StackTraceElement[] stack = cl_to_type2JCasStacks.get(cl);
if (stack == null) {
continue;
}
clToLog.put(cl, stack);
}
}
if (clToLog.isEmpty()) {
log.log(aLogLevel, "No classloaders except the system classloader registered.");
return;
}
StringBuilder buf = new StringBuilder();
if (aLogLevel.isGreaterOrEqual(Level.WARN)) {
buf.append("On shutdown, there were still " + clToLog.size()
+ " classloaders registered in the FSClassRegistry. Not destroying "
+ "ResourceManagers after usage can cause memory leaks.");
} else {
buf.append(
"There are " + clToLog.size() + " classloaders registered in the FSClassRegistry:");
}
int i = 1;
for (Entry<ClassLoader, StackTraceElement[]> e : clToLog.entrySet()) {
buf.append("[" + i + "] " + e.getKey() + " registered through:\n");
for (StackTraceElement s : e.getValue()) {
buf.append(" " + s + "\n");
}
i++;
}
log.log(aLogLevel, buf.toString());
}
static Map<String, JCasClassInfo> get_className_to_jcci(ClassLoader cl, boolean is_pear) {
synchronized (cl_to_type2JCas) {
// This was used before switching from the normal synchronized map to the weak map
// and is part of a whole bunch of commented out code with special handling for the PEAR
// case. Not sure if this commented out code was kept for debugging or for historical
// reasons - so let's keep this for the moment as a comment here.
// REC - 2020-10-17
// final Map<ClassLoader, Map<String, JCasClassInfo>> cl2t2j = cl_to_type2JCas; /*is_pear ?
// cl_4pears_to_type2JCas :*/
Map<String, JCasClassInfo> cl_to_jcci = cl_to_type2JCas.get(cl);
if (cl_to_jcci == null) {
cl_to_jcci = new HashMap<>();
cl_to_type2JCas.put(cl, cl_to_jcci);
if (cl_to_type2JCasStacks != null) {
cl_to_type2JCasStacks.put(cl, new RuntimeException().getStackTrace());
}
}
return cl_to_jcci;
}
}
static Lookup getLookup(ClassLoader cl) {
Lookup lookup = null;
try {
Class<?> clazz = Class.forName(UIMAClassLoader.MHLC, true, cl);
Method m = clazz.getMethod("getMethodHandlesLookup");
lookup = (Lookup) m.invoke(null);
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new UIMARuntimeException(e, UIMARuntimeException.INTERNAL_ERROR);
}
return lookup;
}
}