| /* |
| * 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.util; |
| |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.apache.uima.UIMARuntimeException; |
| import org.apache.uima.UimaSerializable; |
| import org.apache.uima.cas.CAS; |
| import org.apache.uima.cas.CASRuntimeException; |
| import org.apache.uima.cas.CommonArrayFS; |
| import org.apache.uima.cas.FSIterator; |
| import org.apache.uima.cas.FeatureStructure; |
| import org.apache.uima.cas.SofaFS; |
| import org.apache.uima.cas.impl.CASImpl; |
| import org.apache.uima.cas.impl.FeatureImpl; |
| import org.apache.uima.cas.impl.TypeImpl; |
| import org.apache.uima.cas.impl.TypeSystemConstants; |
| import org.apache.uima.cas.impl.TypeSystemImpl; |
| import org.apache.uima.internal.util.Int2ObjListMap; |
| import org.apache.uima.internal.util.Misc; |
| import org.apache.uima.internal.util.PositiveIntSet; |
| import org.apache.uima.internal.util.PositiveIntSet_impl; |
| import org.apache.uima.jcas.cas.AnnotationBase; |
| import org.apache.uima.jcas.cas.CommonPrimitiveArray; |
| import org.apache.uima.jcas.cas.FSArray; |
| import org.apache.uima.jcas.cas.Sofa; |
| import org.apache.uima.jcas.cas.TOP; |
| import org.apache.uima.jcas.tcas.Annotation; |
| |
| /** |
| * Utility class for doing deep copies of FeatureStructures from one CAS to another. To handle cases |
| * where the source CAS has multiple references to the same FS, you can create one instance of |
| * CasCopier and use it to copy multiple FeatureStructures. The CasCopier will remember previously |
| * copied FeatureStructures, so if you later copy another FS that has a reference to a previously |
| * copied FS, it will not duplicate the multiply-referenced FS. |
| * |
| * This class makes use of CASImpl methods, but is only passed CAS objects, which may be |
| * CAS Wrappers. To make this more feasible, the implementors of CAS Wrappers need to implement |
| * the method getLowLevelCas() which should return a reference to the underlying CAS which can be |
| * successfully cast to a CASImpl. |
| * |
| * The source and target view names for FSs are the same except if: |
| * - The constructor of the CAS Copier instance specifies two different view names, or |
| * - The copyCasView method(s) specify two different view names. |
| * In both these cases, the FSs copied are between different views. |
| * Feature Structures belonging to one view will be belonging to the other view in the copy. |
| * Exception: Feature Structures which are subtypes of AnnotationBase belong to the view |
| * associated with the AnnotationBase's sofa reference. |
| * |
| * The source and target CASs must be separate CASs (that is, not two views of the same CAS), |
| * unless the target View name is different from the source view name. |
| * |
| * The copied FSs have their associated CAS references set to a corresponding view in the target. |
| * |
| * The corresponding view is the view in the target with the same view name as the source view |
| * (perhaps after sofa mapping specified by the target CAS's current component sofa mapping info), |
| * except for the copyCasView calls, where the target view is specified explicitly. |
| * |
| * But if the FS being copied is a subtype of AnnotationBase, then the corresponding view for the copy of the |
| * sofa reference (if not null) is used. |
| * |
| */ |
| public class CasCopier { |
| |
| private static final TypeImpl MISSING_TYPE = TypeImpl.singleton; |
| private static final FeatureImpl MISSING_FEAT = FeatureImpl.singleton; |
| |
| // private static final int FRC_SKIP = 0; // is the default, must be 0 |
| // private static final int FRC_STRING = 1; |
| // private static final int FRC_LONG = 2; |
| // private static final int FRC_DOUBLE = 3; |
| // private static final int FRC_INT_LIKE = 4; |
| // private static final int FRC_REF = 5; |
| // |
| // private static final int K_SRC_FEAT_OFFSET = 0; |
| // private static final int K_TGT_FEAT_CODE = 1; |
| |
| // /** |
| // * TypeInfo stores the mapping from the source to the target type system |
| // * It is set up once at the start, to avoid looking up this correspondence (by name) repeatedly |
| // */ |
| // private class TypeInfo { |
| // final int[] codesAndOffsets; // indexed with count * 2 |
| // /** |
| // * Feature Range class: String, Long, double, int-like, ref, or skip for all others |
| // */ |
| // final byte[] frc; |
| // final int tgtTypeCode; |
| // |
| // TypeInfo(int srcTypeCode) { |
| // |
| // if (tgtTsi == srcTsi) { |
| // tgtTypeCode = srcTypeCode; |
| // } else { |
| // Type srcType = srcTsi.ll_getTypeForCode(srcTypeCode); |
| // Type tgtType = tgtTsi.getType(srcType.getName()); |
| // if (tgtType == null) { |
| // // If in lenient mode, do not act on this FS. Instead just |
| // // return (null) to the caller and let the caller deal with this case. |
| // if (lenient) { |
| // tgtTypeCode = 0; |
| // } else { |
| // throw new UIMARuntimeException(UIMARuntimeException.TYPE_NOT_FOUND_DURING_CAS_COPY, |
| // new Object[] { srcType.getName() }); |
| // } |
| // } else { |
| // tgtTypeCode = tgtTsi.ll_getCodeForType(tgtType); |
| // } |
| // } |
| // |
| // int[] srcFeatCodes = srcTsi.ll_getAppropriateFeatures(srcTypeCode); |
| // int arrayLength = srcFeatCodes.length << 1; |
| // |
| // codesAndOffsets = new int[arrayLength]; |
| // frc = new byte[srcFeatCodes.length]; |
| // |
| // if (srcTsi == tgtTsi) { |
| // for (int i = 0; i < srcFeatCodes.length; i++) { |
| // final int srcFeatCode = srcFeatCodes[i]; |
| // Feature srcFeat = srcTsi.ll_getFeatureForCode(srcFeatCode); |
| // setRangeClass((TypeImpl) srcFeat.getRange(), i); |
| // final int i2 = i << 1; |
| // codesAndOffsets[i2 + K_SRC_FEAT_OFFSET] = originalSrcCasImpl.getFeatureOffset(srcFeatCode); |
| // codesAndOffsets[i2 + K_TGT_FEAT_CODE] = srcFeatCodes[i]; |
| // } |
| // } else { |
| // for (int i = 0; i < srcFeatCodes.length; i++) { |
| // final int srcFeatCode = srcFeatCodes[i]; |
| // Feature srcFeat = srcTsi.ll_getFeatureForCode(srcFeatCode); |
| // String srcFeatName = srcFeat.getName(); |
| // Feature tgtFeat = tgtTsi.getFeatureByFullName(srcFeatName); |
| // if (tgtFeat == null) { |
| // // If in lenient mode, ignore this feature and move on to the next |
| // // feature in this FS (if one exists) |
| // if (lenient) { |
| // continue; // Ignore this feature in the source CAS since it doesn't exist in |
| // // in the target CAS. |
| // } else { |
| // throw new UIMARuntimeException(UIMARuntimeException.FEATURE_NOT_FOUND_DURING_CAS_COPY, |
| // new Object[] { srcFeatName }); |
| // } |
| // } else { |
| // final int i2 = i << 1; |
| // int tgtFeatCode = ((FeatureImpl)tgtFeat).getCode(); |
| // codesAndOffsets[i2 + K_SRC_FEAT_OFFSET] = originalSrcCasImpl.getFeatureOffset(srcFeatCode); |
| // codesAndOffsets[i2 + K_TGT_FEAT_CODE] = tgtFeatCode; |
| // } |
| // |
| // TypeImpl srcRangeType = (TypeImpl) srcFeat.getRange(); |
| // |
| // // verify range types of features have the same name |
| // if (!srcRangeType.getName().equals( |
| // tgtFeat.getRange().getName())) { |
| // throw new UIMARuntimeException(UIMARuntimeException.COPY_CAS_RANGE_TYPE_NAMES_NOT_EQUAL, |
| // new Object[] {srcFeatName, srcFeat.getRange().getName(), tgtFeat.getRange().getName()}); |
| // } |
| // |
| // setRangeClass(srcRangeType, i); |
| // } |
| // } |
| // } |
| // |
| // void setRangeClass(TypeImpl srcRangeType, int i) { |
| // if (srcTsi.ll_subsumes(srcStringTypeCode, srcRangeType.getCode())) { |
| // frc[i] = FRC_STRING; |
| // } else if (srcRangeType == srcTsi.intType || |
| // srcRangeType == srcTsi.floatType || |
| // srcRangeType == srcTsi.booleanType || |
| // srcRangeType == srcTsi.byteType || |
| // srcRangeType == srcTsi.shortType) { |
| // frc[i] = FRC_INT_LIKE; |
| // } else if (srcRangeType == srcTsi.longType) { |
| // frc[i] = FRC_LONG; |
| // } else if (srcRangeType == srcTsi.doubleType) { |
| // frc[i] = FRC_DOUBLE; |
| // } else { |
| // frc[i] = FRC_REF; |
| // } |
| // } |
| // } |
| // |
| // private final TypeInfo[] tInfoArray; |
| |
| // these next are called original, as they are the views used to create the CasCopier instance |
| private final CASImpl originalSrcCas; |
| private final CASImpl originalTgtCas; |
| // // these next are the CASImpls of these |
| // private final CASImpl originalSrcCasImpl; |
| // private final CASImpl originalTgtCasImpl; |
| |
| // these next 2 are like the above, but for explicit view copying |
| |
| private CASImpl srcCasViewImpl; |
| private CASImpl tgtCasViewImpl; |
| |
| private String srcViewName; // these are used when the view name is changed |
| private String tgtViewName; // this is the corresponding target view name for the source view name |
| |
| private final TypeSystemImpl srcTsi; |
| private final TypeSystemImpl tgtTsi; |
| |
| private final Int2ObjListMap<TypeImpl> src2TgtType; |
| private final Int2ObjListMap<FeatureImpl> src2TgtFeat; |
| |
| private final boolean isEqualTypeSystems; |
| |
| private String cachedSrcViewName = ""; |
| private CASImpl cachedTgtView = null; |
| |
| // |
| // private final TypeImpl srcStringType; |
| |
| /** |
| * true if the copyCasView api was used, and the target view name corresponding to the source view name is changed |
| */ |
| private boolean isChangeViewName = false; |
| |
| private Annotation srcCasDocumentAnnotation = null; |
| // /** |
| // * The source view name - may be null if the view is of the base CAS |
| // */ |
| // private String mSrcCasViewName; |
| // /** |
| // * The target view name - not used unless doing a view copy |
| // * Allows copying a view to another CAS under a different name |
| // */ |
| // private String mTgtCasViewName; |
| |
| // final private Feature mDestSofaFeature; |
| |
| final private boolean lenient; //true: ignore feature structures and features that are not defined in the destination CAS |
| |
| /** |
| * key is source FS, value is target FS |
| * Target not set for DocumentAnnotation or SofaFSs |
| * Target not set if lenient specified and src type isn't in target |
| */ |
| final private Map<TOP, TOP> mFsMap; |
| |
| /** |
| * Deferred calls to copy Features of a FS |
| */ |
| final private Deque<Runnable> fsToDo = new ArrayDeque<>(); |
| |
| |
| /** |
| * Creates a new CasCopier that can be used to copy FeatureStructures from one CAS to another. |
| * Note that if you are merging data from multiple CASes, you must create a new CasCopier |
| * for each source CAS. |
| * |
| * Note: If the feature structure and/or feature is not defined in the type system of |
| * the destination CAS, the copy will fail (in other words, the lenient setting is false, |
| * by default). |
| * |
| * @param aSrcCas |
| * the CAS to copy from. |
| * @param aDestCas |
| * the CAS to copy into. |
| */ |
| public CasCopier(CAS aSrcCas, CAS aDestCas) { |
| this(aSrcCas, aDestCas, false); |
| } |
| |
| /** |
| * Creates a new CasCopier that can be used to copy FeatureStructures from one CAS to another. |
| * Note that if you are merging data from multiple CASes, you must create a new CasCopier |
| * for each source CAS. This version of the constructor supports a "lenient copy" option. When set, |
| * the CAS copy function will ignore (not attempt to copy) FSs and features not defined in the type system |
| * of the destination CAS, rather than throwing an exception. |
| * |
| * @param aSrcCas |
| * the CAS to copy from. |
| * @param aDestCas |
| * the CAS to copy into. |
| * @param lenient |
| * ignore FSs and features not defined in the type system of the destination CAS |
| */ |
| public CasCopier(CAS aSrcCas, CAS aDestCas, boolean lenient) { |
| |
| mFsMap = new IdentityHashMap<>(((CASImpl)(aSrcCas.getLowLevelCAS())).getLastUsedFsId()); |
| originalSrcCas = (CASImpl)aSrcCas.getLowLevelCAS(); |
| originalTgtCas = (CASImpl)aDestCas.getLowLevelCAS(); |
| |
| srcTsi = originalSrcCas.getTypeSystemImpl(); |
| tgtTsi = originalTgtCas.getTypeSystemImpl(); |
| |
| src2TgtType = (srcTsi == tgtTsi) ? null : new Int2ObjListMap<TypeImpl>(srcTsi.getTypeArraySize()); |
| src2TgtFeat = (srcTsi == tgtTsi) ? null : new Int2ObjListMap<FeatureImpl>(srcTsi.getNumberOfFeatures() + 1); |
| |
| |
| // tInfoArray = new TypeInfo[srcTsi.getLargestTypeCode() + 1]; |
| |
| // srcStringType = srcTsi.stringType; |
| // srcStringTypeCode = srcStringType.getCode(); |
| |
| // mDestSofaFeature = aDestCas.getTypeSystem().getFeatureByFullName(CAS.FEATURE_FULL_NAME_SOFA); |
| this.lenient = lenient; |
| |
| // the next is to support the style of use where |
| // an instance of this copier is made, corresponding to two views in the same CAS |
| // or corresponding to two views in different CASs |
| // and then individual FeatureStructures are copied using copyFS(...) |
| |
| srcCasViewImpl = (CASImpl) originalSrcCas.getLowLevelCAS(); |
| tgtCasViewImpl = (CASImpl) originalTgtCas.getLowLevelCAS(); |
| |
| srcViewName = srcCasViewImpl.getViewName(); |
| tgtViewName = tgtCasViewImpl.getViewName(); |
| |
| if (srcViewName == null) { // base cas |
| isChangeViewName = (tgtViewName == null) ? false : true; |
| } else { |
| isChangeViewName = !srcViewName.equals(tgtViewName); |
| } |
| |
| isEqualTypeSystems = srcTsi.equals(tgtTsi); |
| } |
| |
| |
| |
| /** |
| * Does a complete deep copy of one CAS into another CAS. The contents of each view |
| * in the source CAS will be copied to the same-named view in the destination CAS. If |
| * the view does not already exist it will be created. All FeatureStructures that are |
| * indexed in a view in the source CAS will become indexed in the same-named view in the |
| * destination CAS. |
| * |
| * Note: If the feature structure and/or feature is not defined in the type system of |
| * the destination CAS, the copy will fail (in other words, the lenient setting is false, |
| * by default). |
| * |
| * @param aSrcCas |
| * the CAS to copy from |
| * @param aDestCas |
| * the CAS to copy to |
| * @param aCopySofa |
| * if true, the sofa data and mimeType of each view will be copied. If false they will not. |
| */ |
| public static void copyCas(CAS aSrcCas, CAS aDestCas, boolean aCopySofa) { |
| copyCas(aSrcCas, aDestCas, aCopySofa, false); |
| } |
| |
| /** |
| * Does a complete deep copy of one CAS into another CAS. The contents of each view |
| * in the source CAS will be copied to the same-named view in the destination CAS. If |
| * the view does not already exist it will be created. All FeatureStructures that are |
| * indexed in a view in the source CAS will become indexed in the same-named view in the |
| * destination CAS. This version of the method supports a "lenient copy" option. When set, |
| * the CAS copy function will ignore (not attempt to copy) FSs and features not defined in the type system |
| * of the destination CAS, rather than throwing an exception. |
| * |
| * @param aSrcCas |
| * the CAS to copy from |
| * @param aDestCas |
| * the CAS to copy to; must be a completely different CAS than the source (that is, not an alternative "view" of the source) |
| * @param aCopySofa |
| * if true, the sofa data and mimeType of each view will be copied. If false they will not. |
| * @param lenient |
| * ignore FSs and features not defined in the type system of the destination CAS |
| */ |
| public static void copyCas(CAS aSrcCas, CAS aDestCas, boolean aCopySofa, boolean lenient) { |
| CasCopier copier = new CasCopier(aSrcCas, aDestCas, lenient); |
| |
| // oops, this misses the initial view if a sofa FS has not yet been created |
| // Iterator<SofaFS> sofaIter = aSrcCas.getSofaIterator(); |
| // while (sofaIter.hasNext()) { |
| // SofaFS sofa = sofaIter.next(); |
| // CAS view = aSrcCas.getView(sofa); |
| // copier.copyCasView(view, aCopySofa); |
| // } |
| |
| if (copier.originalSrcCas.getBaseCAS() == copier.originalTgtCas.getBaseCAS()) { |
| throw new UIMARuntimeException(UIMARuntimeException.ILLEGAL_CAS_COPY_TO_SAME_CAS); |
| } |
| |
| Iterator<CAS> viewIterator = aSrcCas.getViewIterator(); |
| while (viewIterator.hasNext()) { |
| CAS view = viewIterator.next(); |
| copier.copyCasView(view, aCopySofa); |
| |
| } |
| } |
| |
| /** |
| * Does a deep copy of the contents of one CAS View into another CAS's same-named-view |
| * If the destination view already exists in the destination CAS, |
| * then it will be the target of the copy. Otherwise, a new view will be created with |
| * that name and will become the target of the copy. All FeatureStructures |
| * (except for those dropped because the target type system doesn't have the needed type) that are indexed |
| * in the source CAS view will become indexed in the target view. |
| * Cross-view references may result in creating additional views in the destination CAS; |
| * for these views, any Sofa data in the source is *not* copied. |
| * |
| * @param aSrcCasView the CAS to copy from. This must be a view in the src Cas set by the constructor |
| * @param aCopySofa if true, the sofa data and mimeType will be copied. If false they will not. |
| */ |
| public void copyCasView(CAS aSrcCasView, boolean aCopySofa) { |
| copyCasViewDifferentCASs(aSrcCasView, getOrCreateView(originalTgtCas, aSrcCasView.getViewName()), aCopySofa); |
| } |
| |
| /** |
| * Does a deep copy of the contents of one CAS View into another CAS's same-named-view |
| * If the destination view already exists in the destination CAS, |
| * then it will be the target of the copy. Otherwise, a new view will be created with |
| * that name and will become the target of the copy. All FeatureStructures |
| * (except for those dropped because the target type system doesn't have the needed type) that are indexed |
| * in the source CAS view will become indexed in the target view. |
| * Cross-view references may result in creating additional views in the destination CAS; |
| * for these views, any Sofa data in the source is *not* copied. Any views created because |
| * of cross-view references will have the same view name as in the source. |
| * |
| * @param aSrcCasViewName the name of the view in the source CAS to copy from |
| * @param aCopySofa if true, the sofa data and mimeType will be copied. If false they will not. |
| */ |
| public void copyCasView(String aSrcCasViewName, boolean aCopySofa) { |
| copyCasView(getOrCreateView(originalSrcCas, aSrcCasViewName), aCopySofa); |
| } |
| |
| /** |
| * Does a deep copy of the contents of one CAS View into another CAS view, |
| * with a possibly different name. |
| * If the destination view already exists in the destination CAS, |
| * then it will be the target of the copy. Otherwise, a new view will be created with |
| * that name and will become the target of the copy. |
| * All FeatureStructures |
| * (except for those dropped because the target type system doesn't have the needed type) that are indexed |
| * in the source CAS view will become indexed in the target view. |
| * Cross-view references may result in creating additional views in the destination CAS; |
| * for these views, any Sofa data in the source is *not* copied. Any views created because |
| * of cross-view references will have the same view name as in the source. |
| * |
| * @param aSrcCasView The view in the source to copy from |
| * @param aTgtCasViewName The name of the view in the destination CAS to copy into |
| * @param aCopySofa if true, the sofa data and mimeType will be copied. If false they will not. |
| */ |
| public void copyCasView(CAS aSrcCasView, String aTgtCasViewName, boolean aCopySofa) { |
| copyCasView(aSrcCasView, getOrCreateView(originalTgtCas, aTgtCasViewName), aCopySofa); |
| } |
| |
| /** |
| * Does a deep copy of the contents of one CAS View into another CAS view, |
| * with a possibly different name. |
| * All FeatureStructures |
| * (except for those dropped because the target type system doesn't have the needed type) that are indexed |
| * in the source CAS view will become indexed in the target view. |
| * Cross-view references may result in creating additional views in the destination CAS; |
| * for these views, any Sofa data in the source is *not* copied. Any views created because |
| * of cross-view references will have the same view name as in the source. |
| * |
| * @param aSrcCasViewName The name of the view in the Source CAS to copy from |
| * @param aTgtCasView The view in the destination CAS to copy into |
| * @param aCopySofa if true, the sofa data and mimeType will be copied. If false they will not. |
| */ |
| public void copyCasView(String aSrcCasViewName, CAS aTgtCasView, boolean aCopySofa) { |
| copyCasView(getOrCreateView(originalSrcCas, aSrcCasViewName), aTgtCasView, aCopySofa); |
| } |
| |
| private void copyCasViewDifferentCASs(CAS aSrcCasView, CAS aTgtCasView, boolean aCopySofa) { |
| if (originalSrcCas.getBaseCAS() == originalTgtCas.getBaseCAS()) { |
| throw new UIMARuntimeException(UIMARuntimeException.ILLEGAL_CAS_COPY_TO_SAME_CAS); |
| } |
| |
| copyCasView(aSrcCasView, aTgtCasView, aCopySofa); |
| } |
| |
| /** |
| * Does a deep copy of the contents of one CAS View into another CAS view, |
| * with a possibly different name. |
| * All FeatureStructures |
| * (except for those dropped because the target type system doesn't have the needed type) that are indexed |
| * in the source CAS view will become indexed in the target view. |
| * Cross-view references may result in creating additional views in the destination CAS; |
| * for these views, any Sofa data in the source is *not* copied. Any views created because |
| * of cross-view references will have the same view name as in the source. |
| * |
| * If the source and target views are both views of the same CAS, then Feature Structures |
| * in the view are effectively "cloned", with the following change: |
| * Subtypes of AnnotationBase in the source whose sofaRef is for the source View are |
| * cloned with their sofaRefs changed to the new targetView. |
| * |
| * @param aSrcCasView |
| * the CAS view to copy from. This must be a view of the srcCas set in the constructor |
| * @param aTgtCasView |
| * the CAS view to copy to. This must be a view of the tgtCas set in the constructor |
| * @param aCopySofa |
| * if true, the sofa data and mimeType will be copied. If false they will not. |
| * If true and the sofa data is already set in the target, will throw CASRuntimeException |
| */ |
| public void copyCasView(CAS aSrcCasView, CAS aTgtCasView, boolean aCopySofa) { |
| |
| if (!casViewsInSameCas(aSrcCasView, originalSrcCas)) { |
| throw new UIMARuntimeException(UIMARuntimeException.VIEW_NOT_PART_OF_CAS, new Object[] {"Source"}); |
| } |
| if (!casViewsInSameCas(aTgtCasView, originalTgtCas)) { |
| throw new UIMARuntimeException(UIMARuntimeException.VIEW_NOT_PART_OF_CAS, new Object[] {"Destination"}); |
| } |
| |
| // mSrcCasViewName = aSrcCasView.getViewName(); |
| // mTgtCasViewName = aTgtCasView.getViewName(); |
| |
| srcCasViewImpl = (CASImpl) aSrcCasView.getLowLevelCAS(); |
| tgtCasViewImpl = (CASImpl) aTgtCasView.getLowLevelCAS(); |
| |
| try { // to support finally to reset the src/tgt view names |
| srcViewName = srcCasViewImpl.getViewName(); |
| tgtViewName = tgtCasViewImpl.getViewName(); |
| isChangeViewName = !srcViewName.equals(tgtViewName); |
| |
| if ((aSrcCasView == srcCasViewImpl.getBaseCAS()) || (aTgtCasView == tgtCasViewImpl.getBaseCAS())) { |
| throw new UIMARuntimeException(UIMARuntimeException.UNSUPPORTED_CAS_COPY_TO_OR_FROM_BASE_CAS); |
| } |
| |
| srcCasDocumentAnnotation = null; // each view needs to get this once |
| |
| // mLowLevelDestCas = aTgtCasView.getLowLevelCAS(); |
| // mLowLevelSrcCas = aSrcCasView.getLowLevelCAS(); |
| |
| // The top level sofa associated with this view is copied (or not) |
| |
| if (aCopySofa) { |
| // can't copy the SofaFS - just copy the sofa data and mime type |
| SofaFS sofa = srcCasViewImpl.getSofa(); |
| if (null != sofa) { |
| // if the sofa doesn't exist in the target, these calls will create it |
| // (view can exist without Sofa, at least for the initial view) |
| String sofaMime = sofa.getSofaMime(); |
| String docTxt = srcCasViewImpl.getDocumentText(); |
| if (docTxt != null) { |
| aTgtCasView.setSofaDataString(docTxt, sofaMime); |
| } else { |
| String sofaDataURI = srcCasViewImpl.getSofaDataURI(); |
| if (sofaDataURI != null) { |
| aTgtCasView.setSofaDataURI(sofaDataURI, sofaMime); |
| } else { |
| TOP sofaDataArray = (TOP) srcCasViewImpl.getSofaDataArray(); |
| if (sofaDataArray != null) { |
| aTgtCasView.setSofaDataArray(copyFs2Fs(sofaDataArray), sofaMime); |
| } |
| } |
| } |
| |
| } |
| } |
| |
| // now copy indexed FS, but keep track so we don't index anything more than once |
| // values are fs._id's |
| // Note: mFsMap might be used for this, but it doesn't index several kinds of FSs |
| // see the javadoc for this field for details |
| // NOTE: FeatureStructure hashcode / equals use the fs._id's |
| |
| final PositiveIntSet indexedFsAlreadyCopied = new PositiveIntSet_impl(); |
| |
| // The indexedFsAlreadyCopied set starts out "cleared", but |
| // we don't clear the cas copier instance map "mFsMap" here, in order to skip actually copying the |
| // FSs when doing a full CAS copy with multiple views - the 2nd and subsequent |
| // views don't copy, but they do index. |
| |
| FSIterator<TOP> it = srcCasViewImpl.getIndexRepository().getAllIndexedFS(srcTsi.getTopType()); |
| // LowLevelIterator it = ((FSIndexRepositoryImpl)(srcCasViewImpl.getIndexRepository())).ll_getAllIndexedFS(srcTsi.getTopType()); |
| |
| while (it.hasNext()) { |
| final TOP fs = it.nextNvc(); |
| // System.out.format("debug id: %,d type: %s%n", fs.id(), fs._getTypeImpl().getShortName()); |
| // Iterator<LowLevelIndex> indexes = srcCasViewImpl.getIndexRepository().ll_getIndexes(); |
| // while (indexes.hasNext()) { |
| // LowLevelIndex index = indexes.next(); |
| // LowLevelIterator iter = index.ll_iterator(); |
| // while (iter.isValid()) { |
| // final int fs = iter.ll_get(); |
| // iter.moveToNext(); |
| if (!indexedFsAlreadyCopied.contains(fs._id())) { |
| final TOP copyOfFs = copyFs2(fs); |
| // If the lenient option is used, it's possible that no FS was |
| // created (e.g., FS is not defined in the target CAS. So ignore |
| // this FS in the source CAS and move on to the next FS. |
| if (lenient && copyOfFs == null) { |
| continue; // Move to the next FS in the source CAS |
| } |
| // otherwise, won't be null (error thrown instead) |
| |
| // check for annotations with null Sofa reference - this can happen |
| // if the annotations were created with the Low Level CAS API. If the |
| // Sofa reference isn't set, attempting to add the FS to the indexes |
| // will fail. |
| // if (fs instanceof AnnotationBase) { |
| // AnnotationBase fsAb = (AnnotationBase) fs; |
| // int sofaRef = tgtCasViewImpl.ll_getRefValue(copyOfFs, mDestSofaFeatureCode); |
| // if (0 == sofaRef) { |
| // tgtCasViewImpl.ll_setRefValue(copyOfFs, mDestSofaFeatureCode, tgtCasViewImpl.getSofaRef()); |
| // } |
| // } |
| |
| // also don't index the DocumentAnnotation (it's indexed by default) |
| if (!isDocumentAnnotation(fs)) { |
| tgtCasViewImpl.getIndexRepository().addFS(copyOfFs); |
| } |
| indexedFsAlreadyCopied.add(fs._id()); |
| } |
| } |
| } finally { |
| srcCasViewImpl = null; // needed to make copyFS subsequently work. |
| tgtCasViewImpl = null; |
| isChangeViewName = false; |
| } |
| } |
| |
| /** |
| * For long lists, and other structures, the straight-forward impl with recursion can |
| * nest too deep, causing a Java failure - out of stack space. |
| * |
| * This is a non-recursive impl, making use of an aux object: featureStructuresWithSlotsToSet to |
| * hold copied FSs whose slots need to be scanned and set with values. |
| * |
| * The main loop dequeues one element, and copies the features. |
| * |
| * The copying of a FS copies the FS without setting the slots; instead it queues the |
| * copied FS together with its source instance on featureStructuresWithSlotsToSet |
| * for later processing. |
| * |
| */ |
| |
| /** |
| * Copy 1 feature structure from the originalSrcCas to a new Cas. No indexing of the new FS is done. |
| * If the FS has been copied previously (using this CasCopier instance) the |
| * same identical copy will be returned rather than making another copy. |
| * |
| * View handling: ignores the view of the targetCas |
| * |
| * @param aFS the Feature Structure to copy |
| * @param <T> the generic type of the returned Feature Structure |
| * @return a deep copy of the Feature Structure - any referred to FSs will also be copied, or |
| * null if the target CAS doesn't define a corresponding type |
| */ |
| |
| public <T extends FeatureStructure> T copyFs(T aFS) { |
| if (null == srcCasViewImpl) { |
| srcCasViewImpl = originalSrcCas; |
| } |
| if (null == tgtCasViewImpl) { |
| tgtCasViewImpl = originalTgtCas; |
| } |
| |
| // safety - insure DocumentAnnotation is tested. |
| srcCasDocumentAnnotation = null; |
| return (T) copyFs2Fs((TOP)aFS); |
| } |
| |
| /** |
| * Copy one FS from the src CAS to the tgt CAS |
| * View context: |
| * The caller must set the srcCasViewImpl and the tgtCasViewImpl |
| * |
| * @param aFS a Feature Structure reference in the originalSrcCas |
| * @return a Feature Structure reference to a copy in the target CAS, |
| * or null if the target CAS doesn't have a corresponding type definition |
| */ |
| private TOP copyFs2(TOP aFS) { |
| |
| TOP copy = copyFsInner(aFS); // doesn't copy the slot values, but enqueues them |
| // the iteration is done this way because the body can add more to the queue |
| while (fsToDo.size() > 0) { |
| Runnable r = fsToDo.removeFirst(); |
| r.run(); |
| } |
| return copy; |
| } |
| |
| private TOP copyFs2Fs(TOP fs) { |
| return copyFs2(fs); |
| } |
| |
| /** |
| * Copies a FS from the source CAS to the destination CAS. Also copies any referenced FSs, except |
| * that previously copied FS will not be copied again. |
| * |
| * The _casView is set to the target cas view, unless the FS being copied is a subtype of Annotation Base. |
| * - In that case, it is set to the view associated with the sofa ref. |
| * |
| * @param srcFs |
| * the FS to copy. Must be contained within the source CAS. |
| * @return the copy of <code>aFS</code> in the target CAS. |
| */ |
| private TOP copyFsInner(TOP srcFs) { |
| // FS must be in the source CAS |
| // this test must be done by the caller if wanted. |
| // assert (casViewsInSameCas(aFS.getCAS(), originalSrcCas)); |
| |
| // check if we already copied this FS |
| TOP copy = mFsMap.get(srcFs); |
| if (copy != null) { |
| return copy; |
| } |
| |
| // Certain types need to be handled specially |
| |
| // Sofa - cannot be created by normal methods. Instead, we return the Sofa with the |
| // same Sofa ID in the target CAS. If it does not exist it will be created. |
| if (srcFs instanceof Sofa) { |
| Sofa srcSofa = (Sofa) srcFs; |
| return getCorrespondingTgtView(srcSofa.getSofaID()).getSofa(); |
| } |
| |
| final CASImpl tgtView; |
| String viewName = srcFs._casView.getViewName(); |
| if (srcFs instanceof AnnotationBase) { |
| // Sofa srcSofa = (Sofa) ((AnnotationBase)srcFs).getSofa(); |
| tgtView = (viewName == null) ? tgtCasViewImpl // if no info, use existing target view |
| : getCorrespondingTgtView(viewName); |
| } else { |
| // emulate v2 behavior - for FSs not instances of AnnotationBase, use the copier's target view |
| // tgtView = getCorrespondingTgtView(viewName); |
| tgtView = tgtCasViewImpl; |
| } |
| |
| // DocumentAnnotation - instead of creating a new instance, reuse the automatically created |
| // instance in the destination view. |
| if (isDocumentAnnotation(srcFs)) { |
| if (srcFs instanceof UimaSerializable) { |
| ((UimaSerializable)srcFs)._save_to_cas_data(); |
| } |
| // Annotation da = (Annotation) srcFs; |
| // String destViewNamex = getDestSofaId(da.getView().getViewName()); |
| |
| // the DocumentAnnotation could be indexed in a different view than the one being copied |
| // if it was ref'd for the 1st time from a cross-indexed fs |
| // Note: The view might not exist in the target |
| // but this is unlikely. To have this case this would require |
| // indexing some other feature structure in this view, which, in turn, |
| // has a reference to the DocumentAnnotation FS belonging to another view |
| // CASImpl destView = (CASImpl) getOrCreateView(originalTgtCas, destViewName); |
| // do the no-create style so we can create it without adding it to the index yet |
| Annotation destDocAnnot = tgtView.getDocumentAnnotationNoCreate(); |
| if (destDocAnnot == null) { |
| destDocAnnot = tgtView.createDocumentAnnotationNoRemoveNoIndex(0); |
| copyFeatures(srcFs, destDocAnnot); |
| tgtView.getIndexRepository().addFS(destDocAnnot); |
| } else { |
| try (AutoCloseable ac = tgtView.protectIndexes()) { |
| copyFeatures(srcFs, destDocAnnot); |
| } catch (Exception e) { |
| Misc.internalError(e); |
| } |
| } |
| if (destDocAnnot instanceof UimaSerializable) { |
| ((UimaSerializable)destDocAnnot)._init_from_cas_data(); |
| } |
| |
| // note not put into mFsMap, because each view needs a separate copy |
| // and multiple creations (due to multiple refs) won't happen because |
| // the create is bypassed if it already exists |
| return destDocAnnot; |
| } |
| |
| // Arrays - need to be created a populated differently than "normal" FS |
| if (srcFs instanceof CommonArrayFS) { |
| copy = copyArray(srcFs); |
| if (copy != null) { // can be null if trying to copy MyFs[] and type doesn't exist in target type system |
| mFsMap.put(srcFs, copy); |
| } |
| return copy; |
| } |
| |
| TypeImpl tgtTi = getTargetType(((TOP)srcFs)._getTypeImpl()); |
| if (null == tgtTi) { |
| return null; // not in target, no FS to create |
| } |
| // final TypeInfo tInfo = getTypeInfo(srcTypeCode); |
| // final int tgtTypeCode = tInfo.tgtTypeCode; |
| // if (tgtTypeCode == 0) { |
| // return 0; // not in target, no FS to create |
| // } |
| // We need to use the LowLevel CAS interface to create the FS, because the usual |
| // CAS.createFS() call doesn't allow us to create subtypes of AnnotationBase from |
| // a base CAS. In any case we don't need the Sofa reference to be automatically |
| // set because we'll set it manually when in the copyFeatures method. |
| |
| TOP tgtFs = tgtView.createFS(tgtTi); |
| |
| // add to map so we don't try to copy this more than once |
| mFsMap.put((TOP)srcFs, tgtFs); |
| |
| fsToDo.addLast(() -> { |
| if (srcFs instanceof UimaSerializable) { |
| ((UimaSerializable)srcFs)._save_to_cas_data(); |
| } |
| copyFeatures(srcFs, tgtFs); |
| if (tgtFs instanceof UimaSerializable) { |
| ((UimaSerializable)tgtFs)._init_from_cas_data(); |
| } |
| }); |
| return tgtFs; |
| } |
| |
| /** |
| * |
| * @return the view in the target corresponding by name (after sofa name mapping if any) to the source view |
| */ |
| private CASImpl getCorrespondingTgtView(String viewNameFromSrcSofaId) { |
| if (null == viewNameFromSrcSofaId) { // base view |
| return tgtCasViewImpl.getBaseCAS(); |
| } |
| if (viewNameFromSrcSofaId == cachedSrcViewName) { |
| return cachedTgtView; |
| } |
| cachedSrcViewName = viewNameFromSrcSofaId; |
| cachedTgtView = getOrCreateView(tgtCasViewImpl, getDestSofaId(viewNameFromSrcSofaId)); |
| return cachedTgtView; |
| } |
| |
| /** |
| * There are two cases for getting target sofa name from the source one, depending on whether or not |
| * the API which allows specifying a different target view name for the source view name, is in use. |
| * |
| * If so, then whenever the source sofa name is that src view name, replace it in the target with the |
| * specified different target view name. |
| * |
| * @param viewNameFromSrcSofaId |
| * @return id unless the id matches the source view name, and that name is being changed |
| */ |
| private String getDestSofaId(String viewNameFromSrcSofaId) { |
| return (isChangeViewName && viewNameFromSrcSofaId.equals(srcViewName)) ? tgtViewName : viewNameFromSrcSofaId; |
| } |
| |
| // private TypeInfo getTypeInfo(int srcTypeCode) { |
| // TypeInfo tInfo = tInfoArray[srcTypeCode]; |
| // if (tInfo == null) { |
| // return tInfoArray[srcTypeCode] = new TypeInfo(srcTypeCode); |
| // } |
| // return tInfo; |
| // } |
| |
| /** |
| * Copy feature values from one FS to another. For reference-valued features, this does a deep |
| * copy. |
| * |
| * @param srcFS |
| * FeatureStructure to copy from |
| * @param tgtFS |
| * FeatureStructure to copy to, which must not be in the index (index corruption checks skipped) |
| */ |
| private <T extends FeatureStructure> void copyFeatures(T srcFSi, T tgtFSi) { |
| TOP srcFS = (TOP) srcFSi; |
| TypeImpl ti = srcFS._getTypeImpl(); |
| |
| TOP tgtFS = (TOP) tgtFSi; |
| |
| // guaranteed not an array at this point |
| |
| if (isEqualTypeSystems) { |
| for (final FeatureImpl fi : ti.getFeatureImpls()) { |
| final int adjOffset = fi.getAdjustedOffset(); |
| if (fi.isInInt) { |
| tgtFS._setIntValueNcNj(adjOffset, srcFS._getIntValueNc(adjOffset)); |
| if (fi.isLongOrDouble) { |
| tgtFS._setIntValueNcNj(adjOffset + 1, srcFS._getIntValueNc(adjOffset + 1)); |
| } |
| } else if (!fi.getRangeImpl().isRefType) { |
| tgtFS._setRefValueCommon(adjOffset, srcFS._getRefValueCommon(adjOffset)); |
| } else { // is FS reference |
| // feature is a reference to another FS, so enqueue that to copy |
| // unless it's the sofa feature for AnnotationBase - that's feature final, set when created |
| if (fi.isAnnotBaseSofaRef) { |
| continue; |
| } |
| TOP refFs = srcFS._getFeatureValueNc(adjOffset); |
| if (null != refFs) { |
| tgtFS._setFeatureValueNcNj(adjOffset, copyFsInner(refFs)); // recursive call |
| } |
| } |
| } |
| return; |
| } // end of equal type systems special paths |
| |
| for (FeatureImpl fi : ti.getFeatureImpls()) { |
| FeatureImpl tgtFi = getTargetFeature(fi); |
| if (null == tgtFi) { |
| continue; // skip copying features not in the target type system |
| } |
| if (!CASImpl.copyFeatureExceptFsRef(srcFS, fi, tgtFS, tgtFi)) { |
| // feature is a reference to another FS, so enqueue that to copy |
| // unless it's the sofa feature for AnnotationBase - that's feature final, set when created |
| if (fi.isAnnotBaseSofaRef) { |
| continue; |
| } |
| TOP refFs = srcFS._getFeatureValueNc(fi); |
| if (null != refFs) { |
| tgtFS._setFeatureValueNcNj(tgtFi, copyFsInner(refFs)); // recursive call |
| } |
| } |
| } |
| } |
| // |
| // // set feature values |
| // |
| //// final int srcTypeCode = srcCasViewImpl.getTypeCode(srcFS); |
| // |
| //// final TypeInfo tInfo = getTypeInfo(srcTypeCode); |
| // |
| // tgtCasViewImpl.setCacheNotInIndex(tgtFS); |
| // |
| // for (int i = 0; i < tInfo.codesAndOffsets.length; i = i + 2) { |
| // final int tgtFeatCode = tInfo.codesAndOffsets[i + K_TGT_FEAT_CODE]; |
| // if (0 == tgtFeatCode) { |
| // continue; |
| // } |
| // final int srcFeatOffset = tInfo.codesAndOffsets[i + K_SRC_FEAT_OFFSET]; |
| // switch (tInfo.frc[i >> 1]) { |
| // case FRC_SKIP: |
| // break; |
| // case FRC_STRING: |
| // // need feature code to check subtype constraints |
| // tgtCasViewImpl.ll_setStringValue(tgtFS, tgtFeatCode, srcCasViewImpl.ll_getStringValueFeatOffset(srcFS, srcFeatOffset)); |
| // break; |
| // case FRC_INT_LIKE: |
| // tgtCasViewImpl.ll_setIntValue(tgtFS, tgtFeatCode, srcCasViewImpl.ll_getIntValueFeatOffset(srcFS, srcFeatOffset)); |
| // break; |
| // case FRC_LONG: |
| // tgtCasViewImpl.ll_setLongValue(tgtFS, tgtFeatCode, srcCasViewImpl.ll_getLongValueFeatOffset(srcFS, srcFeatOffset)); |
| // break; |
| // case FRC_DOUBLE: |
| // tgtCasViewImpl.ll_setDoubleValue(tgtFS, tgtFeatCode, srcCasViewImpl.ll_getDoubleValueFeatOffset(srcFS, srcFeatOffset)); |
| // break; |
| // case FRC_REF: |
| // int refFS = srcCasViewImpl.ll_getRefValueFeatOffset(srcFS, srcFeatOffset); |
| // if (refFS != 0) { |
| // int copyRefFs = copyFsInner(refFS); |
| // tgtCasViewImpl.ll_setRefValue(tgtFS, tgtFeatCode, copyRefFs); |
| // } |
| // break; |
| // default: |
| // throw new UIMARuntimeException(); // internal error |
| // } |
| // } |
| // } |
| |
| /** |
| * Note: if lenient is in effect, this method will return false for |
| * FSs which are not copied because the target doesn't have that type. |
| * It also returns false for sofa FSs and the documentAnnotation FS. |
| * @param aFS a feature structure |
| * @return true if the given FS has already been copied using this CasCopier. |
| */ |
| public boolean alreadyCopied(FeatureStructure aFS) { |
| return alreadyCopied(((TOP)aFS)); |
| } |
| |
| /** |
| * Note: if lenient is in effect, this method will return false for |
| * FSs which are not copied because the target doesn't have that type. |
| * It also returns false for sofa FSs and the documentAnnotation FS. |
| * @param aFS a feature structure |
| * @return true if the given FS has already been copied using this CasCopier. |
| */ |
| public boolean alreadyCopied(TOP aFS) { |
| return mFsMap.get(aFS) != null; |
| } |
| |
| /** |
| * Note: only for backwards compatibility |
| * Note: if lenient is in effect, this method will return false for |
| * FSs which are not copied because the target doesn't have that type. |
| * It also returns false for sofa FSs and the documentAnnotation FS. |
| * @param aFS a feature structure |
| * @return true if the given FS has already been copied using this CasCopier. |
| */ |
| public boolean alreadyCopied(int aFS) { |
| TOP fs = originalSrcCas.getFsFromId(aFS); |
| return mFsMap.get(fs) != null; |
| } |
| |
| |
| /** |
| * @param arrayFS |
| * @return a copy of the array |
| */ |
| private TOP copyArray(TOP srcFS) { |
| final CommonArrayFS srcCA = (CommonArrayFS) srcFS; |
| final int size = srcCA.size(); |
| final TypeImpl tgtTi = getTargetType(((TOP)srcFS)._getTypeImpl()); // could be null for src = e.g. Annotation[] |
| |
| if (tgtTi == null) { |
| return null; // can't copy |
| } |
| |
| if (srcFS instanceof CommonPrimitiveArray) { |
| CommonArrayFS copy = (CommonArrayFS) tgtCasViewImpl.createArray(tgtTi, size); |
| copy.copyValuesFrom(srcCA); |
| return (TOP) copy; |
| } |
| |
| // is reference array, either |
| // FSArray or |
| // subtype of that (e.g. Annotation[]) |
| |
| FSArray fsArray = (FSArray) tgtCasViewImpl.createArray(tgtTi, size); |
| |
| // we do a direct recursive call here because the depth of this is limited to the depth of array nesting |
| // Contrast with normal FS copying, where the depth could be very large (e.g. FS Lists) |
| int i = 0; |
| TOP[] tgtArray = fsArray._getTheArray(); |
| |
| for (TOP srcItem : ((FSArray)srcFS)._getTheArray()) { |
| if (null != srcItem) { |
| tgtArray[i] = copyFsInner(srcItem); |
| } |
| i++; |
| } |
| |
| return fsArray; |
| } |
| |
| /** |
| * Gets the named view; if the view doesn't exist it will be created. |
| */ |
| private static CASImpl getOrCreateView(CASImpl aCas, String aViewName) { |
| //TODO: there should be some way to do this without the try...catch |
| try { // throws if view doesn't exist |
| return (CASImpl) aCas.getView(aViewName).getLowLevelCAS(); |
| } |
| catch(CASRuntimeException e) { |
| //create the view |
| return (CASImpl) aCas.createView(aViewName).getLowLevelCAS(); |
| } |
| } |
| |
| /** |
| * Determines whether the given FS is the DocumentAnnotation in the srcCasView. |
| * This is more than just a type check; we actually check if it is the one "special" |
| * DocumentAnnotation that CAS.getDocumentAnnotation() would return. |
| */ |
| private <T extends FeatureStructure> boolean isDocumentAnnotation(T aFS) { |
| if (((TOP)aFS)._getTypeCode() != TypeSystemConstants.docTypeCode) { |
| return false; |
| } |
| if (srcCasDocumentAnnotation == null) { |
| srcCasDocumentAnnotation = srcCasViewImpl.getDocumentAnnotationNoCreate(); |
| } |
| return aFS == srcCasDocumentAnnotation; |
| } |
| |
| /** |
| * Change from https://issues.apache.org/jira/browse/UIMA-3112 : |
| * requires that the CAS returned from the getLowLevelCAS() be castable to CASImpl |
| * @param c1 - |
| * @param c2 - |
| * @return true if both views are in the same CAS (e.g., they have the same base CAS) |
| */ |
| private boolean casViewsInSameCas(CAS c1, CAS c2) { |
| if (null == c1 || null == c2) { |
| return false; |
| } |
| |
| CASImpl ci1 = (CASImpl) c1.getLowLevelCAS(); |
| CASImpl ci2 = (CASImpl) c2.getLowLevelCAS(); |
| |
| return ci1.getBaseCAS() == ci2.getBaseCAS(); |
| } |
| |
| private TypeImpl getTargetType(TypeImpl srcTi) { |
| if (srcTsi == tgtTsi) { |
| return srcTi; |
| } |
| int srcTypeCode = srcTi.getCode(); |
| TypeImpl r = src2TgtType.get(srcTypeCode); |
| if (r == null) { |
| r = tgtTsi.getType(srcTi.getName()); |
| src2TgtType.put(srcTypeCode, (null == r) ? MISSING_TYPE : r); |
| } |
| return (r == MISSING_TYPE) ? null : r; |
| } |
| |
| // tiny method to inline |
| private FeatureImpl getTargetFeature(FeatureImpl srcFi) { |
| if (srcTsi == tgtTsi) { |
| return srcFi; |
| } |
| return getTargetFeature2(srcFi); |
| } |
| |
| private FeatureImpl getTargetFeature2(FeatureImpl srcFi) { |
| int srcFeatCode = srcFi.getCode(); |
| FeatureImpl r = src2TgtFeat.get(srcFeatCode); |
| if (r == null) { |
| TypeImpl d = (TypeImpl) srcFi.getDomain(); |
| TypeImpl td = getTargetType(d); |
| if (td == null) { |
| return null; |
| } |
| r = td.getFeatureByBaseName(srcFi.getShortName()); |
| src2TgtFeat.put(srcFeatCode, (null == r) ? MISSING_FEAT : r); |
| } |
| return (r == MISSING_FEAT) ? null : r; |
| } |
| |
| } |