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