blob: 2b1d41e61122ef8ed78db023adca9a534b7e2ce4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.cas.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CommonArrayFS;
import org.apache.uima.cas.Feature;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.TypeSystem;
import org.apache.uima.cas.impl.SlotKinds.SlotKind;
import org.apache.uima.internal.util.Int2ObjHashMap;
import org.apache.uima.internal.util.Misc;
import org.apache.uima.internal.util.Obj2IntIdentityHashMap;
import org.apache.uima.internal.util.Pair;
import org.apache.uima.internal.util.PositiveIntSet;
import org.apache.uima.internal.util.PositiveIntSet_impl;
import org.apache.uima.internal.util.function.Consumer2;
import org.apache.uima.jcas.cas.BooleanArray;
import org.apache.uima.jcas.cas.ByteArray;
import org.apache.uima.jcas.cas.CommonList;
import org.apache.uima.jcas.cas.DoubleArray;
import org.apache.uima.jcas.cas.EmptyList;
import org.apache.uima.jcas.cas.FSArray;
import org.apache.uima.jcas.cas.FSList;
import org.apache.uima.jcas.cas.FloatArray;
import org.apache.uima.jcas.cas.FloatList;
import org.apache.uima.jcas.cas.IntegerArray;
import org.apache.uima.jcas.cas.IntegerList;
import org.apache.uima.jcas.cas.LongArray;
import org.apache.uima.jcas.cas.NonEmptyFSList;
import org.apache.uima.jcas.cas.NonEmptyFloatList;
import org.apache.uima.jcas.cas.NonEmptyIntegerList;
import org.apache.uima.jcas.cas.NonEmptyStringList;
import org.apache.uima.jcas.cas.ShortArray;
import org.apache.uima.jcas.cas.StringArray;
import org.apache.uima.jcas.cas.StringList;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.util.IntEntry;
// @formatter:off
/**
* Used by tests for Binary Compressed de/serialization code.
* Used by test app: XmiCompare.
*
* Compare 2 CASes, with perhaps different type systems.
* If the type systems are different, construct a type mapper and use that
* to selectively ignore types or features not in other type system
*
* The Mapper is from CAS1 -> CAS2
*
* When computing the things to compare from CAS1, filter to remove
* feature structures not reachable via indexes or refs
*
* The index definitions are not compared.
* The indexes are used to locate the FSs to be compared.
*
* Reports are produced to System.out and System.err as a side effect
* System.out: status messages, type system comparison
* System.err: mismatch comparison information
*
* Usage:
* Use the static compareCASes method for default comparisons
* Use the multi-step approach for more complex comparisons:
* - Make an instance of this class, passing in the two CASes.
* - Set any additional configuration
* cc.compareAll(true) - continue comparing if mismatch found
* cc.compardIds(true) - compare ids (require ids to be ==)
* - Do any transformations needed on the CASes to account for known but allowed differences:
* -- These are transformations done on the CAS Feature Structures outside of this routine
* -- example: for certain type:feature string values, normalize to the same canonical value
* -- example: for certain type:feature string arrays, where the order is not important, sort them
* -- example: for certain type:feature FSArrays, where the order is not important, sort them
* --- using the sortFSArray method
* - Do any configuration to specify congruence sets for String values
* -- example: addStringCongruenceSet( type, feature, set-of-strings, -1 or int index if array)
* -- these are specific to type / feature specs
* -- range can be string or string array - if string array, the spec includes the index or -1
* to indicate all indexes
*
* How it works
* Prepare arrays of all the FSs in the CAS
* - for each of 2 CASes to be compared
* - 2 arrays:
* -- all FSs in any index in any view
* -- the above, plus all FSs reachable via references
* -- but omit some types: only of interest when reached via ref,
* e.g. String/Int/Float/Boolean arrays
*
* The comparison of FSs is done, one FS at a time.
* - in order to determine the right FSs to compare with each other, the FSs for each CAS
* are sorted.
*
* The sort and the CAS compare both use a Compare method.
* - sorting skips items not in the other type system, including features
* - (only possible if comparing two CASes with different type systems, of course)
*
* Compare
* - used for two purposes:
* a) sorting FSs belonging to one CAS
* - can be used by caller to pre-sort any array values where the
* compare should be for set equality (in other words, ignore the order)
* b) comparing a FS in one CAS with a FS in the other CAS
*
* sort keys, in order:
* 1) type
* 2) if primitive array: sort based on
* - size
* - iterating thru all array items
* 3) All the features, considered in an order where non-refs are sorted before refs.
*
* comparing values:
* primitives - value comparison
* refs - compare the ref'd FS, while recording reference paths
* - stop when reach a compare point where the pair being compared has been seen
* - stop at any point if the two FSs compare unequal
* - at the stop point, if compare is equal, check the reference paths, and
* report unequal reference paths (different cycle lengths, or different total lengths,
* see the Prev data structure)
*
* Context information, reused across compares:
* prevCompare - if a particular pair of FSs compared equal
* -- used to speed up comparison
* -- used to stop recursive loops of references
*
* prev1, prev2 - reset for each top level FS compare
* - not reset for inner FS compares of fs-reference values)
* holds information about the reference path for chains of FS references
*/
// @formatter:on
public class CasCompare {
private final static boolean IS_DEBUG_STOP_ON_MISCOMPARE = false;
private final static boolean IS_MEAS_LIST_2_ARRAY = false;
private static final String BLANKS_89 = Misc.blanks.substring(0, 89);
private static boolean IS_SHOW_PROGRESS = false;
/**
* Compare 2 CASes, with perhaps different type systems. - using default configuration.
*
* @param c1
* CAS to compare
* @param c2
* CAS to compare
* @return true if equal (for types / features in both)
*/
public static boolean compareCASes(CASImpl c1, CASImpl c2) {
return new CasCompare(c1, c2).compareCASes();
}
// @formatter:off
/**
* hold info about previous compares, to break cycles in references
* The comparison records cycles and can distinguish different cyclic graphs.
* When a cycle exists, it looks like:
* a b c d e f g h i a cycle starting with a, with refs ending up at i
* ^ v and then looping back to f
* *-----*
*
* This data structure measures both the cycle Length (in this example, 4)
* and the size of the starting part before hitting the loop (in this case 5)
*
* Note: when using, if two FSs end up comparing equal, the instances must be
* rolled back 1 item to allow further items to be compared in the chain.
* Example: a -> b -> c -> d
* d's compared equal, c may have ref next to "e".
*/
// @formatter:on
private static class Prev {
/** ordered list of traversed FSs, including duplicates */
private final ArrayList<TOP> fsList = new ArrayList<>();
/**
* length of the cycle, excluding any leading ref chain -1 until some cycle is detected
*/
private int cycleLen = -1;
/**
* length of the leading ref chain, excludes any cycle part -1 until some cycle is detected
*/
private int cycleStart = -1;
/** ref to the top of the chain; used as a boolean flag */
TOP prevCompareTop;
void clear() {
fsList.clear();
cycleLen = -1;
cycleStart = -1;
prevCompareTop = null;
}
int compareCycleLen(Prev other) {
return Integer.compare(cycleLen, other.cycleLen);
}
int compareUsize(Prev other) {
return Integer.compare(usize(), other.usize());
}
/**
* called when returning from compare with equal result If a loop exists, and the item being
* removed is the one that started the loopback, reset the loop info.
*
* @param fs
*/
void rmvLast(TOP fs) {
int toBeRemoved = fsList.size() - 1;
if (toBeRemoved == -1) {
System.out.println("debug stop wrong call to rmvLast");
}
if (toBeRemoved == usize()) { // means removing the 1st item that looped back
// 0 1 2 3 4 5 6
// a b c d e f g (c d e f g c d) fsList
// ^ loop, cycleStart = 2 , cycleLength = 5
// debug
if (cycleLen < 0) { // never true, because usize() >= 0 ==> there's a cycle
System.out.println("debug cycleLen");
throw Misc.internalError();
}
assert cycleLen >= 0;
assert cycleStart >= 0;
cycleLen = -1; // since the loop is no more, reset the loop info
cycleStart = -1;
}
// System.out.println("debug rmvlast sz b4: " + fsList.size());
if (toBeRemoved >= 0) {
fsList.remove(toBeRemoved);
}
}
void addTop() {
fsList.add(prevCompareTop);
prevCompareTop = null;
}
void add(TOP fs) {
if (cycleLen < 0) {
int i = fsList.lastIndexOf(fs);
if (i >= 0) { // first instance of a cycle detected
cycleLen = fsList.size() - i;
// 0 1 2 3 4 5 6
// a b c d e f g (c)
// ^ i == 2 == length of start segment
cycleStart = i; // cycleStart is length up to but not including the start of the cycle
}
}
// System.out.println("debug add fs sz= "+ (fsList.size() + 1));
fsList.add(fs);
}
/** return size of ref chain including duplicates due to ref loops */
int size() {
return fsList.size();
}
/** return -2 or the length of the cycle including 1 loop */
int usize() {
return cycleStart + cycleLen;
}
}
/** key for StringCongruenceSet */
private static class ScsKey {
final TypeImpl type;
final FeatureImpl feature;
final int index;
ScsKey(TypeImpl type, FeatureImpl feature, int index) {
this.type = type;
this.feature = feature;
this.index = index;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((feature == null) ? 0 : feature.hashCode());
result = prime * result + index;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ScsKey other = (ScsKey) obj;
if (feature == null) {
if (other.feature != null) {
return false;
}
} else if (!feature.equals(other.feature)) {
return false;
}
if (index != other.index) {
return false;
}
if (type == null) {
if (other.type != null) {
return false;
}
} else if (!type.equals(other.type)) {
return false;
}
return true;
}
}
private static class FeatLists {
/**
* first index is easy, easyArrays, refs, refArrays
*/
final FeatureImpl[][] featsByEase = new FeatureImpl[4][];
FeatLists(List<FeatureImpl> easy, List<FeatureImpl> easyArrays, List<FeatureImpl> refs,
List<FeatureImpl> refArrays) {
featsByEase[0] = (FeatureImpl[]) easy.toArray(new FeatureImpl[easy.size()]);
featsByEase[1] = (FeatureImpl[]) easyArrays.toArray(new FeatureImpl[easyArrays.size()]);
featsByEase[2] = (FeatureImpl[]) refs.toArray(new FeatureImpl[refs.size()]);
featsByEase[3] = (FeatureImpl[]) refArrays.toArray(new FeatureImpl[refArrays.size()]);
}
}
// must always be true, need to rework convert lists to arrays if not
private static final boolean IS_CANONICAL_EMPTY_LISTS = true;
// ****************************************************
// Data Structures for converting lists to arrays
// ****************************************************
private static final CommonList removed_list_marker = new NonEmptyFSList<>();
/**
* key = _id, value = arraylist holding well-formed list with this node in it
*/
final private Int2ObjHashMap<ArrayList<CommonList>, ArrayList<CommonList>> map_e_to_a_list = new Int2ObjHashMap(
ArrayList.class);
/**
* set of list elements determined to be members of a non-linear structure
*/
final private PositiveIntSet non_linear_list_elements = new PositiveIntSet_impl();
/**
* a map from list nodes which might be removed, to their place in the fss array list The index is
* 1 more, to avoid colliding with the 0 value, used for missing value
*/
final private Obj2IntIdentityHashMap<CommonList> node_indexes = new Obj2IntIdentityHashMap<>(
CommonList.class, removed_list_marker);
final private PositiveIntSet list_successor_seen = new PositiveIntSet_impl();
final private Map<TypeImpl, FeatLists> type2featLists = new HashMap<>();
final private CASImpl c1;
final private CASImpl c2;
final private TypeSystemImpl ts1;
final private TypeSystemImpl ts2;
// private boolean compareStringArraysAsSets = false;
// private boolean compareArraysByElement = false;
/** if true, continues comparison and reporting after finding the first miscompare */
private boolean isCompareAll = false;
private boolean isCompareIds = false;
private Pair<TOP, TOP> leafErrorReported = null;
final private Set<String> excludedRootNames = new HashSet<String>(0);
final private Set<String> includedTypeNames = new HashSet<String>(0);
// /** key is feature long name (with type) */
// final private Set<String> fsArraysToSort = new HashSet<>();
// /** key is feature long name (with type) */
// final private Set<String> stringArraysToSort = new HashSet<>();
// private boolean compareFSArraysAsSets = false;
// /** true when that FS._id (an Array of some kind) has been sorted */
// final private BitSet alreadySorted1 = new BitSet();
// final private BitSet alreadySorted2 = new BitSet();
/**
* This is used - to speed up the compare - avoid comparing the same things multiple times,
* instead just use previous result - when doing the comparison to break recursion if asked to
* compare the same two things while comparing them.
*
* value is the result of previous comparison.
*/
private final Map<Pair<TOP, TOP>, Integer> prevCompare = new HashMap<>();
private final Set<Pair<TOP, TOP>> prevReport = new HashSet<>();
private final Prev prev1 = new Prev();
private final Prev prev2 = new Prev();
// private final Set<Pair<TOP, TOP>> miscompares = Collections.newSetFromMap(new HashMap<>());
// private TOP fs1, fs2;
private boolean isSrcCas; // used for sorting with a CAS, to differentiate between src and target
// CASes
final private StringBuilder mismatchSb = new StringBuilder();
private boolean inSortContext = false;
private boolean isSkipMismatch = false;
private boolean isTypeMapping;
private final CasTypeSystemMapper typeMapper;
private ArrayList<TOP> c1FoundFSs;
private ArrayList<TOP> c2FoundFSs;
private final Map<ScsKey, String[]> stringCongruenceSets = new HashMap<>();
private boolean isUsingStringCongruenceSets = false;
private int maxId1;
private int maxId2;
private int miscompare_index; // used to pass back additional value from compareAllArrayElements
private int s1maxLen = 0;
private static int working_on;
/**
* Make an instance of this class to set up a compare operation, and optionally use to configure
* the compare.
*
* @param c1
* one CAS to compare
* @param c2
* the other CAS to compare
*/
public CasCompare(CASImpl c1, CASImpl c2) {
this.c1 = c1;
this.c2 = c2;
ts1 = c1.getTypeSystemImpl();
ts2 = c2.getTypeSystemImpl();
typeMapper = ts1.getTypeSystemMapper(ts2);
isTypeMapping = (null != typeMapper);
}
// public void compareStringArraysAsSets(boolean v) {
// compareStringArraysAsSets = v;
// }
//
// public void compareFSArraysAsSets(boolean v) {
// compareFSArraysAsSets = v;
// }
//
// public void compareArraysByElement(boolean v) {
// compareArraysByElement = v;
// }
/**
* Continues the comparison after a miscompare (or not). This is useful when you want to see all
* of the miscompares.
*
* @param v
* defaults to false, set to true to continue the comparison after a miscompare
*/
public void compareAll(boolean v) {
isCompareAll = v;
}
/**
* Normally, compares ignore the Feature Structure ID when comparing.
*
* @param v
* defaults to false, set to true to include the Feature Structure ID in the compare.
*/
public void compareIds(boolean v) {
isCompareIds = v;
}
/**
* Many times some customation needs to be applied to both CASs being compared. This routine does
* that
*
* @param c
* the customization to be applied to both CASs
*/
public void applyToBoth(Consumer<CASImpl> c) {
c.accept(c1);
c.accept(c2);
}
/**
* Before comparing, you can adjust specific features of specific types, arbitrarily. This routine
* applies the adjustments to both CASs.
*
* @param typeName
* the fully qualified name of the type
* @param featureBaseName
* the short feature name to adjust
* @param c
* a function to do the adjustment
*/
public void applyToTypeFeature(String typeName, String featureBaseName,
Consumer2<TOP, Feature> c) {
applyToTypeFeature_inner(c1, typeName, featureBaseName, c);
applyToTypeFeature_inner(c2, typeName, featureBaseName, c);
}
private void applyToTypeFeature_inner(CASImpl cas, String typeName, String featureBaseName,
Consumer2<TOP, Feature> c) {
TypeSystem ts = cas.getTypeSystem();
Type type = ts.getType(typeName);
Feature feat = type.getFeatureByBaseName(featureBaseName);
cas.select(type).allViews().forEach(fs -> c.accept2(fs, feat));
}
/**
* Before comparing, you can create pending values for specific types / features, and return a
* list of runnables, which when run, plug in those pending values.
*
* @param typeName
* the type
* @param featureBaseName
* the feature of the type
* @param c
* the code to run for this type and feature
* @return a list of runnables, for both CASs
*/
public List<Runnable> type_feature_to_runnable(String typeName, String featureBaseName,
BiFunction<TOP, Feature, Runnable> c) {
List<Runnable> r = new ArrayList<>();
working_on = 1;
r.addAll(type_feature_to_runnable(c1, typeName, featureBaseName, c));
working_on = 2;
r.addAll(type_feature_to_runnable(c2, typeName, featureBaseName, c));
return r;
}
private List<Runnable> type_feature_to_runnable(CASImpl cas, String typeName,
String featureBaseName, BiFunction<TOP, Feature, Runnable> c) {
TypeSystem ts = cas.getTypeSystem();
Type type = ts.getType(typeName);
Feature feat = type.getFeatureByBaseName(featureBaseName);
return cas.select(type).allViews().map((TOP fs) -> c.apply(fs, feat))
.collect(Collectors.toList());
}
/**
* Before comparing, you can, for a selected type and feature which has a string value belonging
* to one of a set of strings, change the value to another (fixed) string which will of course
* compare equal.
*
* Use this to ignore selected string-valued features having particular values.
*
* @param typeName
* the fully qualified type name
* @param featureBaseName
* the feature
* @param items_to_change
* an array of strings to change if matched to one of these
* @param canonical_value
* the new value
*/
public void canonicalizeString(String typeName, String featureBaseName, String[] items_to_change,
String canonical_value) {
applyToTypeFeature(typeName, featureBaseName, (fs, feat) -> {
if (Misc.contains(items_to_change, fs.getStringValue(feat))) {
fs.setStringValue(feat, canonical_value);
}
});
}
public List<Runnable> sortFSArray(String typeName, String featureBaseName) {
// fsArraysToSort.add(typeName + ":" + featureBaseName);
return type_feature_to_runnable(typeName, featureBaseName,
(fs, feat) -> sortFSArray((FSArray<?>) fs.getFeatureValue(feat)));
}
public List<Runnable> sort_dedup_FSArray(String typeName, String featureBaseName) {
return type_feature_to_runnable(typeName, featureBaseName,
(fs, feat) -> sort_dedup_FSArray(fs, feat));
}
public List<Runnable> sortStringArray(String typeName, String featureBaseName) {
// stringArraysToSort.add(typeName + ":" + featureBaseName);
return type_feature_to_runnable(typeName, featureBaseName,
(fs, feat) -> sortStringArray((StringArray) fs.getFeatureValue(feat)));
}
// @formatter:off
/**
* The compare can find FeatureStructures to compare either from
* - being in some index in some view, or
* - being referenced through some chain which starts with the above.
*
* It sometimes helps to exclude miscompares of FeatureStructure like StringArrays which (for some
* reason) are indexed, in favor of finding these only via refs.
*
* You can exclude these from being found via indexes by setting types here. They could still be
* found via refs from other Feature Structures.
*
* Calling this disables any includeOnlyTheseTypesFromIndexes call;
*
* @param excluded_typeNames
* type names to exclude
*/
// @formatter:on
public void excludeRootTypesFromIndexes(Set<String> excluded_typeNames) {
includedTypeNames.clear();
excludedRootNames.addAll(excluded_typeNames);
}
// @formatter:off
/**
* The compare can find FeatureStructures to compare either from
* - being in some index in some view, or
* - being referenced through some chain which starts with the above.
*
* It sometimes helps to exclude miscompares of FeatureStructure like StringArrays which (for some
* reason) are indexed, in favor of finding these only via refs.
*
* Call this to exclude the array types: boolean, byte, short, integer, long, float, double,
* string and fs arrays from being found via indexes. They could still be found via refs from
* other Feature Structures.
*
* Calling this disables any includeOnlyTheseTypesFromIndexes call;
*/
// @formatter:on
public void excludeCollectionsTypesFromIndexes() {
includedTypeNames.clear();
excludedRootNames.addAll(Arrays.asList(CAS.TYPE_NAME_BOOLEAN_ARRAY, CAS.TYPE_NAME_BYTE_ARRAY,
CAS.TYPE_NAME_SHORT_ARRAY, CAS.TYPE_NAME_INTEGER_ARRAY, CAS.TYPE_NAME_LONG_ARRAY,
CAS.TYPE_NAME_FLOAT_ARRAY, CAS.TYPE_NAME_DOUBLE_ARRAY, CAS.TYPE_NAME_STRING_ARRAY,
CAS.TYPE_NAME_FS_ARRAY, "org.apache.uima.jcas.cas.FSArrayList",
"org.apache.uima.jcas.cas.FSHashSet", "org.apache.uima.jcas.cas.IntegerArrayList",
"org.apache.uima.jcas.cas.FSLinkedHashSet", "org.apache.uima.jcas.cas.Int2FS"));
}
// @formatter:off
/**
* The compare can find FeatureStructures to compare either from
* - being in some index in some view, or
* - being referenced through some chain which starts with the above.
*
* It sometimes helps to exclude miscompares of List FeatureStructures like StringLists which (for
* some reason) are indexed, in favor of finding these only via refs.
*
* Call this to exclude the list types non-empty Float/Integer/String list elements from being
* found in the index. They could still be found via refs from other Feature Structures.
*
* Calling this disables any includeOnlyTheseTypesFromIndexes call;
*/
// @formatter:on
public void excludeListTypesFromIndexes() {
includedTypeNames.clear();
excludedRootNames.addAll(Arrays.asList(CAS.TYPE_NAME_NON_EMPTY_FLOAT_LIST,
CAS.TYPE_NAME_NON_EMPTY_INTEGER_LIST, CAS.TYPE_NAME_NON_EMPTY_STRING_LIST));
}
// @formatter:off
/**
* The compare can find FeatureStructures to compare either from
* - being in some index in some view, or
* - being referenced through some chain which starts with the above.
*
* It sometimes helps to exclude all types except for a few selected ones which are indexed, in
* favor of finding these only via refs.
*
* Calling this disables any excludeXXXTypesFromIndexes calls;
*
* @param includedTypeNames
* fully qualified type names to include when finding Feature Structures to compare via
* the indexes.
*/
// @formatter:on
public void includeOnlyTheseTypesFromIndexes(List<String> includedTypeNames) {
excludedRootNames.clear();
this.includedTypeNames.addAll(includedTypeNames);
}
// public void compareCanonicalEmptyLists(boolean v) {
// isCanonicalEmptyLists = v;
// }
/**
* Add a set of strings that should be considered equal when doing string comparisons. This is
* conditioned on the typename and feature name
*
* @param typeName
* the fully qualified type name
* @param featureBaseName
* the feature short name
* @param set_of_strings_that_are_equivalent
* a set of strings that should compare equal, if testing the type / feature
* @param index
* if the item being compared is a reference to a string array, which index should be
* compared. Use -1 if not applicable.
*/
public void addStringCongruenceSet(String typeName, String featureBaseName,
String[] set_of_strings_that_are_equivalent, int index) {
TypeImpl t1 = ts1.getType(typeName);
stringCongruenceSets.put(new ScsKey(t1, t1.getFeatureByBaseName(featureBaseName), index),
set_of_strings_that_are_equivalent);
isUsingStringCongruenceSets = true;
}
/**
* call this to show progress of the compare - useful for long compares
*/
public static void showProgress() {
IS_SHOW_PROGRESS = true;
}
/**
* This does the actual comparison operation of the previously specified CASes
*
* @return true if compare is OK
*/
public boolean compareCASes() {
boolean allOk = true;
// final List<TOP> c1FoundFSs;
// final List<TOP> c2FoundFSs;
final boolean savedIsTypeMapping = isTypeMapping;
mismatchSb.setLength(0);
try {
// processIndexedFeatureStructures(c1, false);
Predicate<TOP> includeFilter = isTypeMapping ? (fs -> isTypeInTgt(fs)) : null;
if (IS_SHOW_PROGRESS) {
System.out.println("Finding all FSs in cas 1");
}
// this next call doesn't get just the indexed ones, it includes the "reachable" ones too
c1FoundFSs = new AllFSs(c1, null, includeFilter, isTypeMapping ? typeMapper : null)
.getAllFSsAllViews_sofas_reachable().getAllFSs();
// c1FoundFSs = fssToSerialize; // all reachable FSs, filtered by CAS1 -> CAS2 type systems.
// processIndexedFeatureStructures(c2, false);
if (IS_SHOW_PROGRESS) {
System.out.println("Finding all FSs in cas 2");
}
c2FoundFSs = new AllFSs(c2, null, null, null).getAllFSsAllViews_sofas_reachable().getAllFSs(); // get
// just
// the
// indexed
// ones.
// filter out items only of interest when reached via ref
if (excludedRootNames.size() > 0) {
System.out.println("Excluding Root Names with: "
+ Misc.ppList(Arrays.asList(excludedRootNames.toArray())));
c1FoundFSs = c1FoundFSs.stream()
.filter(fs -> !excludedRootNames.contains(fs.getType().getName()))
.collect(Collectors.toCollection(ArrayList::new));
c2FoundFSs = c2FoundFSs.stream()
.filter(fs -> !excludedRootNames.contains(fs.getType().getName()))
.collect(Collectors.toCollection(ArrayList::new));
} else if (includedTypeNames.size() > 0) {
System.out.println("Including only Root Names: "
+ Misc.ppList(Arrays.asList(includedTypeNames.toArray())));
c1FoundFSs = c1FoundFSs.stream()
.filter(fs -> includedTypeNames.contains(fs.getType().getName()))
.collect(Collectors.toCollection(ArrayList::new));
c2FoundFSs = c2FoundFSs.stream()
.filter(fs -> includedTypeNames.contains(fs.getType().getName()))
.collect(Collectors.toCollection(ArrayList::new));
}
// if type systems are "isEqual()" still need to map because of feature validation testing
int i1 = 0;
int i2 = 0;
maxId1 = c1.peekNextFsId();
maxId2 = c2.peekNextFsId();
// convert_linear_lists_to_arrays may add more items
convert_linear_lists_to_arrays(c1FoundFSs);
convert_linear_lists_to_arrays(c2FoundFSs);
final int sz1 = c1FoundFSs.size(); // max size for comparing, includes added arrays converted
// from lists
final int sz2 = c2FoundFSs.size();
isSrcCas = true; // avoids sorting on types/features not present in ts2
if (IS_SHOW_PROGRESS) {
System.out.println("Sorting FSs in cas 1");
}
sort(c1FoundFSs);
isSrcCas = false; // avoids sorting on types/features not present in ts1
if (IS_SHOW_PROGRESS) {
System.out.println("Sorting FSs in cas 2");
}
sort(c2FoundFSs);
// miscompares.clear();
prevReport.clear();
int fsz = Math.max(sz1, sz2);
int fsz100 = Math.max(1, fsz / 100);
int prev_done = 0;
if (IS_SHOW_PROGRESS) {
System.out.format("Starting compare loop, for %,d FSs%n", Math.max(sz1, sz2));
}
while (i1 < sz1 && i2 < sz2) {
if (IS_SHOW_PROGRESS) {
int done = Math.max(i1, i2);
if (done - prev_done >= fsz100) {
System.out.format("percent done: %d%n", (int) Math.round((done * 100F) / fsz));
prev_done = done;
}
}
TOP fs1 = c1FoundFSs.get(i1); // assumes the elements are in same order??
TOP fs2 = c2FoundFSs.get(i2);
leafErrorReported = null;
if (null == fs1) { // nulls at end indicate list elements converted to arrays
if (null != fs2) {
System.err.format("%,d Feature Structures in CAS2 with no matches in CAS2, e.g. %s%n",
sz2 - i2, fs2.toString(2));
return !allOk;
} else {
return allOk;
}
}
if (null == fs2) { // nulls at end indicate list elements converted to arrays
System.err.format("%,d Feature Structures in CAS1 with no matches in CAS2, e.g. %s%n",
sz1 - i1, fs1.toString(2));
return !allOk;
}
if (IS_CANONICAL_EMPTY_LISTS) {
if (fs1 instanceof EmptyList && !(fs2 instanceof EmptyList)) {
int start = i1;
while (true) {
i1++;
if (i1 >= sz1) {
break;
}
fs1 = c1FoundFSs.get(i1);
if (!(fs1 instanceof EmptyList)) {
break;
}
}
System.out.println("CasCompare skipping " + (i1 - start)
+ " emptylist FSs in 1st CAS to realign.");
} else if (fs2 instanceof EmptyList && !(fs1 instanceof EmptyList)) {
int start = i2;
while (true) {
i2++;
if (i2 >= sz2) {
break;
}
fs2 = c2FoundFSs.get(i2);
if (!(fs2 instanceof EmptyList)) {
break;
}
}
System.out.println("CasCompare skipping " + (i2 - start)
+ " emptylist FSs in 2nd CAS to realign.");
}
}
clearPrevFss();
prev1.prevCompareTop = fs1;
prev2.prevCompareTop = fs2;
if (isTypeMapping) {
// skip compares for types that are missing in the other type system
final boolean typeMissingIn1 = typeMapper.mapTypeTgt2Src(fs2._getTypeImpl()) == null;
final boolean typeMissingIn2 = typeMapper.mapTypeSrc2Tgt(fs1._getTypeImpl()) == null;
if (!typeMissingIn1 && !typeMissingIn2) {
if (0 != compareFss(fs1, fs2, null, null)) {
mismatchFsDisplay();
if (!isCompareAll) {
return false;
}
allOk = false;
int tc = fs1._getTypeImpl().compareTo(fs2._getTypeImpl());
if (tc < 0) {
System.out.print("skiping first to align types ");
while (tc < 0 && i1 < sz1) {
i1++;
tc = c1FoundFSs.get(i1)._getTypeImpl().compareTo(fs2._getTypeImpl());
System.out.print(".");
}
System.out.println("");
} else if (tc > 0) {
System.out.print("skiping second to align types ");
while (tc > 0 && i2 < sz2) {
i2++;
tc = fs1._getTypeImpl().compareTo(c2FoundFSs.get(i2)._getTypeImpl());
System.out.print(".");
}
System.out.println("");
}
}
i1++;
i2++;
continue;
}
if (typeMissingIn1 && typeMissingIn2) {
Misc.internalError();
i1++;
i2++;
continue;
}
if (typeMissingIn1) {
System.out.println("debug - type missing in 1, but test fails for refs");
i2++;
continue;
}
if (typeMissingIn2) {
Misc.internalError();
i1++;
continue;
}
} else { // not type mapping
int rr = -1;
try {
rr = compareFss(fs1, fs2, null, null);
} catch (Throwable e) {
System.out.println("debug caught throwable");
e.printStackTrace();
}
if (0 != rr) {
mismatchFsDisplay();
if (!isCompareAll) {
return false;
}
allOk = false;
int tc = fs1._getTypeImpl().compareTo(fs2._getTypeImpl());
if (tc < 0) {
System.out.print("skiping first to align types ");
while (tc < 0 && i1 < sz1) {
i1++;
tc = c1FoundFSs.get(i1)._getTypeImpl().compareTo(fs2._getTypeImpl());
System.out.print(".");
}
System.out.println("");
} else if (tc > 0) {
System.out.print("skiping second to align types ");
while (tc > 0 && i2 < sz2) {
i2++;
tc = fs1._getTypeImpl().compareTo(c2FoundFSs.get(i2)._getTypeImpl());
System.out.print(".");
}
System.out.println("");
}
// debug
else {
// debug - realign for case where c1 has 1 extra fs
if (i1 + 1 < sz1) {
fs1 = c1FoundFSs.get(i1 + 1);
clearPrevFss();
prev1.prevCompareTop = fs1;
prev2.prevCompareTop = fs2;
if (0 == compareFss(fs1, fs2, null, null)) {
// realign
System.out.println(
"Skipping 1 to realign within same type " + fs1._getTypeImpl().getName());
i1++;
}
}
}
}
i1++;
i2++;
continue;
}
}
if (i1 == sz1 && i2 == sz2) {
return allOk; // end
}
if (isTypeMapping) {
if (i1 < sz1) {
System.err.format("%,d Feature Structures in CAS1 with no matches in CAS2, e.g. %s%n",
sz1 - i1, c1FoundFSs.get(i1));
return false;
}
while (i2 < sz2) {
TOP fs = c2FoundFSs.get(i2);
if (isTypeMapping && typeMapper.mapTypeTgt2Src(fs._getTypeImpl()) != null) { // not a
// complete
// test,
// misses
// refs
return false; // have more FSs in c2 than in c1
}
i2++;
}
return true;
}
// not type mapping, and number of FS didn't match
if (i1 < sz1) {
System.err.format("CAS1 had %,d additional Feature Structures, e.g.: %s%n", sz1 - i1,
c1FoundFSs.get(i1));
} else {
System.err.format("CAS2 had %,d additional Feature Structures, e.g.: %s%n", sz2 - i2,
c2FoundFSs.get(i2));
}
return false;
} finally {
isTypeMapping = savedIsTypeMapping;
clearPrevFss();
}
}
/**
* This is an optional pre-compare operation.
*
* Somtimes, when comparing FSArrays, the order of the elements is not significant, and the
* compare should be done ignoring order differences.
*
* This is accomplished by sorting the elements, before the compare is done, using this method.
* The sort order is not significant; it just needs to be the same order for otherwise equal
* FSArrays.
*
* Use this routine to accomplish the sort, on particular FSArrays you designate. Call it for each
* one you want to sort.
*
* During the sort, links are followed.
*
* The sorting is done in a clone of the array, and the original array is not updated. Instead, a
* Runnable is returned, which may be invoked later to update the original array with the sorted
* copy. This allows sorting to be done on the original item values (in case the links refer back
* to the originals)
*
* @param fsArray
* the array to be sorted
* @return a runnable, which (when invoked) updates the original array with the sorted result.
*/
public Runnable sortFSArray(FSArray<?> fsArray) {
if (fsArray == null || fsArray.size() < 2) {
return null;
}
TOP[] a = fsArray._getTheArray().clone();
clearPrevFss();
inSortContext = true;
Arrays.sort(a, (TOP afs1, TOP afs2) -> {
return compareRefs(afs1, afs2, null, null);
});
return () -> System.arraycopy(a, 0, fsArray._getTheArray(), 0, fsArray.size());
}
/**
* This is an optional pre-compare operation.
*
* It is identical to the method above, except that after sorting, it removes duplicates.
*
* @param fs
* the feature structure having the fsarray feature
* @param feat
* the feature having the fsarray
* @return a runnable, which (when invoked) updates the original array with the sorted result.
*/
public Runnable sort_dedup_FSArray(TOP fs, Feature feat) {
FSArray<?> fsArray = (FSArray<?>) (fs.getFeatureValue(feat));
if (fsArray == null || fsArray.size() < 2) {
return null;
}
TOP[] a = fsArray._getTheArray().clone();
clearPrevFss();
inSortContext = true;
Arrays.sort(a, (TOP afs1, TOP afs2) -> {
return compareRefs(afs1, afs2, null, null);
});
ArrayList<TOP> dedup = new ArrayList<>(a.length);
TOP prev = null;
for (TOP top : a) {
if (top == prev) {
continue;
}
prev = top;
dedup.add(top);
}
TOP[] r = dedup.toArray(new TOP[dedup.size()]);
if (r.length == a.length) {
return () -> System.arraycopy(a, 0, fsArray._getTheArray(), 0, fsArray.size());
} else {
CASImpl cas = fs.getCASImpl();
FSArray<?> fsa = (FSArray<?>) cas.createArray(fsArray._getTypeImpl(), r.length);
// FSArray<?> fsa = new FSArray<>(fs.getJCas(), r.length);
if (IS_SHOW_PROGRESS) {
System.out.format("Dedup found dup in cas %d for type/feature %s, removed %d%n", working_on,
feat.getName(), a.length - r.length);
}
fsa.copyFromArray(r, 0, 0, r.length);
return () -> fs.setFeatureValue(feat, fsa);
}
}
/**
* This is an optional pre-compare operation.
*
* Somtimes, when comparing StringArrays, the order of the elements is not significant, and the
* compare should be done ignoring order differences.
*
* This is accomplished by sorting the elements, before the compare is done, using this method.
*
* Use this routine to accomplish the sort, on particular StringArrays you designate. Call it for
* each one you want to sort.
*
* The sorting is done in a clone of the array, and the original array is not updated. Instead, a
* Runnable is returned, which may be invoked later to update the original array with the sorted
* copy. This allows sorting to be done while keeping the original values until a later time
*
* @param stringArray
* the array to be sorted
* @return null or a runnable, which (when invoked) updates the original array with the sorted
* result. callers should insure the runnable is garbage collected after use
*/
public Runnable sortStringArray(StringArray stringArray) {
if (stringArray == null || stringArray.size() < 2) {
return null;
}
String[] a = stringArray._getTheArray().clone();
inSortContext = true;
Arrays.sort(a);
return () -> System.arraycopy(a, 0, stringArray._getTheArray(), 0, stringArray.size());
}
// *******************************************************************************
// Convert UIMA Lists to arrays, to make the compare go faster
// *******************************************************************************
private void convert_linear_lists_to_arrays(ArrayList<TOP> fss) {
map_e_to_a_list.clear();
non_linear_list_elements.clear();
node_indexes.clear();
int sz = fss.size();
if (sz == 0) {
return;
}
for (int i = 0; i < sz; i++) {
TOP fs = fss.get(i);
if (!(fs instanceof CommonList)) {
continue; // skip: not list node
}
CommonList node = (CommonList) fs;
if (node.isEmpty()) {
fss.set(i, null); // clear it, empty list elements don't need to be compared
continue;
}
if (non_linear_list_elements.contains(node._id())) {
continue; // skip: in non-linear list
}
if (null != map_e_to_a_list.get(node._id())) {
node_indexes.put(node, i + 1); // case: added as a successor
continue; // skip: already seen/processed
}
node_indexes.put(node, i + 1); // in case we have to remove this later
if (!node.isEmpty()) {
ArrayList<CommonList> al = new ArrayList<>(); // start a new arraylist
al.add(node); // add this node
map_e_to_a_list.put(node._id(), al);
if (addSuccessors(node, al)) {
continue;
}
// some successor was in a non-linear situation
move_to_non_linear(al);
}
}
if (IS_MEAS_LIST_2_ARRAY) {
System.out.format("CasCompare converting lists to Arrays, "
+ "nbr of list elements considered: %,d, number of looped lists skipped: %,d%n",
node_indexes.size(), non_linear_list_elements.size());
}
CASImpl view = fss.get(0)._casView;
TypeSystemImpl tsi = view.getTypeSystemImpl();
Set<ArrayList<CommonList>> processed = Collections.newSetFromMap(new IdentityHashMap<>());
for (IntEntry<ArrayList<CommonList>> ie : map_e_to_a_list) {
ArrayList<CommonList> e = ie.getValue();
if (processed.add(e)) {
convert_to_array(e, fss, view, tsi); // changes list element to highest pseudo fs, adds
// array elements
}
}
if (IS_MEAS_LIST_2_ARRAY) {
System.out.format("CasCompare converted %,d lists to Arrays%n", processed.size());
}
// allow gc
map_e_to_a_list.clear();
non_linear_list_elements.clear();
node_indexes.clear();
}
// @formatter:off
/**
* Convert an array list to a uima array (int, float, fs, string)
* - add to fss
* - go thru fss and null out list elements
*
* @param al
* -
* @param fss
* -
*/
// @formatter:on
private void convert_to_array(ArrayList<CommonList> al, ArrayList<TOP> fss, CASImpl view,
TypeSystemImpl tsi) {
CommonList e = al.get(0);
if (e instanceof FSList) {
assert al.size() > 0;
FSArray<TOP> fsa = new FSArray<>(tsi.fsArrayType, view, al.size());
int i = 0;
for (CommonList n : al) {
assert !n.isEmpty();
fsa.set(i++, ((NonEmptyFSList<?>) n).getHead());
fss.set(node_indexes.get(n) - 1, null);
}
fss.add(fsa);
} else if (e instanceof IntegerList) {
IntegerArray a = new IntegerArray(tsi.intArrayType, view, al.size());
int i = 0;
for (CommonList n : al) {
a.set(i++,
(n instanceof EmptyList) ? Integer.MIN_VALUE : ((NonEmptyIntegerList) n).getHead());
fss.set(node_indexes.get(n) - 1, null);
}
fss.add(a);
} else if (e instanceof FloatList) {
FloatArray a = new FloatArray(tsi.floatArrayType, view, al.size());
int i = 0;
for (CommonList n : al) {
a.set(i++, (n instanceof EmptyList) ? Float.MIN_VALUE : ((NonEmptyFloatList) n).getHead());
fss.set(node_indexes.get(n) - 1, null);
}
fss.add(a);
} else if (e instanceof StringList) {
StringArray a = new StringArray(tsi.stringArrayType, view, al.size());
int i = 0;
for (CommonList n : al) {
a.set(i++, (n instanceof EmptyList) ? null : ((NonEmptyStringList) n).getHead());
fss.set(node_indexes.get(n) - 1, null);
}
fss.add(a);
} else {
Misc.internalError();
}
}
// @formatter:off
/**
* walk down list, adding successors, looking for loops
* - each element is added to the array list, and also to the map from id -> array list
* - if loop found, stop and return false
*
* - before adding element, see if already in map from id -> array list
* -- if so, couple the array lists
* @param node -
* @param al -
* @return false if loop found
*/
// @formatter:on
private boolean addSuccessors(CommonList node, ArrayList<CommonList> al) {
try {
list_successor_seen.add(node._id()); // starts reset, reset at end
while (!node.isEmpty()) {
node = node.getCommonTail();
if (node == null || node.isEmpty()) {
break;
}
if (!list_successor_seen.add(node._id())) {
return false; // stop if loop is found
}
ArrayList<CommonList> other = map_e_to_a_list.get(node._id());
if (null != other) {
couple_array_lists(al, other, node);
return true; // rest of list already walked
} else {
al.add(node);
map_e_to_a_list.put(node._id(), al); // every element maps to containing al
}
}
return true;
} finally {
list_successor_seen.clear();
}
}
/**
* merge a2 to follow a1, starting from position where commonNode is in a2
*
* @param a1
* -
* @param a2
* -
* @param commonNode
* -
*/
private void couple_array_lists(ArrayList<CommonList> a1, ArrayList<CommonList> a2,
CommonList commonNode) {
int i = 0;
int sz2 = a2.size();
for (; i < sz2; i++) {
if (commonNode == a2.get(i)) {
break;
}
}
if (i == sz2) {
Misc.internalError();
}
for (; i < sz2; i++) {
CommonList node = a2.get(i);
map_e_to_a_list.put(node._id(), a1); // remove a2 value, put in a1 value
a1.add(node);
}
}
private void move_to_non_linear(ArrayList<CommonList> al) {
for (CommonList e : al) {
map_e_to_a_list.remove(e._id());
non_linear_list_elements.add(e._id());
}
}
private void clearPrevFss() {
prevCompare.clear();
prev1.clear();
prev2.clear();
}
// @formatter:off
/**
* To work with Java sort, must implement the comparator contract:
* - compare(x, y) must be opposite compare(y, x)
* - compare(x, y) < 0 && compare(y, z) < 0 implies compare(x, z) < 0
* - compare(x, y) == 0 implies compare(x, z) same as compare(y, z) for any z
*
* Inner part of compareRefs; that other method adds:
* null-check
* type-mapping skips
* loop determination
*
* If not in a sort context, a miscompare generates messaging information.
*
* @param callerTi
* - the type of another FS referencing this one, or null, used in congruence set testing
* @param callerFi
* - the feature of the another FS referencing this one, or null, used in congruence set
* testing
*
* @return the compare result
*/
// @formatter:on
private int compareFss(TOP fs1, TOP fs2, TypeImpl callerTi, FeatureImpl callerFi) {
if (fs1 == fs2) {
return 0;
}
TypeImpl ti1 = fs1._getTypeImpl();
TypeImpl ti2 = fs2._getTypeImpl(); // even if not type mapping, may be "equal" but not ==
int r = 0;
if (!inSortContext && isTypeMapping) {
ti2 = typeMapper.mapTypeTgt2Src(ti2);
}
r = ti1.compareTo(ti2);
if (r != 0) {
if (!inSortContext) {
mismatchFs(fs1, fs2, "Different Types", callerTi, callerFi); // types mismatch
}
return r;
}
if (isCompareIds && !inSortContext) {
if (fs1._id < maxId1 && fs2._id < maxId2 && fs1._id != fs2._id) {
mismatchFs(fs1, fs2, "IDs miscompare", callerTi, callerFi);
return Integer.compare(fs1._id, fs2._id);
}
}
if (ti1.isArray()) {
return compareFssArray(fs1, fs2, callerTi, callerFi);
}
FeatLists featLists = type2featLists.get(ti1);
if (featLists == null) {
type2featLists.put(ti1, featLists = computeFeatLists(ti1));
}
// compare features, non-refs first (for performance)
for (FeatureImpl[] featSet : featLists.featsByEase) {
for (FeatureImpl fi1 : featSet) {
if (0 != (r = compareFeature(fs1, fs2, ti1, fi1))) {
if (!inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE) {
System.out.println("debug stop feature miscompare " + fi1.getName());
}
return r;
}
}
}
return 0;
}
private int compareFeature(TOP fs1, TOP fs2, TypeImpl ti1, FeatureImpl fi1) {
int r = 0;
if (inSortContext && isTypeMapping) {
if (isSrcCas && typeMapper.getTgtFeature(ti1, fi1) == null) {
return 0; // skip tests for features not in target type system
// so when comparing CASs, the src value won't cause a miscompare
}
if (!isSrcCas && typeMapper.getSrcFeature(ti1, fi1) == null) {
return 0; // types/features belong to target in this case
}
}
FeatureImpl fi2 = (!inSortContext && isTypeMapping) ? typeMapper.getTgtFeature(ti1, fi1) : fi1;
if (fi2 != null) {
r = compareSlot(fs1, fs2, fi1, fi2, ti1);
if (0 != r) {
if (!inSortContext) {
// // debug
// compareSlot(fs1, fs2, fi1, fi2, ti1);
mismatchFs(fs1, fs2, fi1, fi2);
}
return r;
}
} // else we skip the compare - no slot in tgt for src
return r;
}
/**
* called during sort phase
*
* @param ti
* - type being sorted
* @return the feature lists for that type
*/
private FeatLists computeFeatLists(TypeImpl ti) {
List<FeatureImpl> easy = new ArrayList<>();
List<FeatureImpl> easyArrays = new ArrayList<>();
List<FeatureImpl> ref = new ArrayList<>();
List<FeatureImpl> refArrays = new ArrayList<>();
for (FeatureImpl fi : ti.getFeatureImpls()) {
if (isTypeMapping) {
if (isSrcCas && typeMapper.getTgtFeature(ti, fi) == null) {
continue; // skip for features not in target type system
// so when comparing CASs, the src value won't cause a miscompare
}
// probably not executed, since types discovered on first sort
// except for a type that exists only the the target
if (!isSrcCas && typeMapper.getSrcFeature(ti, fi) == null) {
continue; // types/features belong to target in this case
}
}
TypeImpl range = fi.getRangeImpl();
if (range.isArray()) {
TypeImpl_array ra = (TypeImpl_array) range;
if (ra.getComponentType().isRefType) {
refArrays.add(fi);
} else {
easyArrays.add(fi);
}
} else {
if (range.isRefType) {
ref.add(fi);
} else {
easy.add(fi);
}
}
}
return new FeatLists(easy, easyArrays, ref, refArrays);
}
// private int compareFssArray() {
// int r = compareFssArray((CommonArrayFS) fs1, (CommonArrayFS) fs2);
// if (r != 0) {
// if (!inSortContext) mismatchFs();
// }
// return r;
// }
private int compareFssArray(TOP fs1, TOP fs2, TypeImpl callerTi, FeatureImpl callerFi) {
CommonArrayFS<?> a1 = (CommonArrayFS<?>) fs1;
CommonArrayFS<?> a2 = (CommonArrayFS<?>) fs2;
int r;
int len1 = a1.size();
int len2 = a2.size();
r = Integer.compare(len1, len2);
if (r != 0) {
if (!inSortContext) {
mismatchFs(fs1, fs2, callerTi, callerFi);
}
return r;
}
// if (inSortContext && !compareArraysByElement) {
// // quick approximate comparison of arrays, for sort purposes
// return Integer.compare(((FeatureStructureImplC)fs1)._id,
// ((FeatureStructureImplC)fs2)._id);
// }
TypeImpl ti = ((FeatureStructureImplC) a1)._getTypeImpl();
SlotKind kind = ti.getComponentSlotKind();
switch (kind) {
case Slot_BooleanRef:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Boolean.compare(((BooleanArray) a1).get(i), ((BooleanArray) a2).get(i)),
callerTi, callerFi);
break;
case Slot_ByteRef:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Byte.compare(((ByteArray) a1).get(i), ((ByteArray) a2).get(i)), callerTi,
callerFi);
break;
case Slot_ShortRef:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Short.compare(((ShortArray) a1).get(i), ((ShortArray) a2).get(i)), callerTi,
callerFi);
break;
case Slot_Int:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Integer.compare(((IntegerArray) a1).get(i), ((IntegerArray) a2).get(i)),
callerTi, callerFi);
break;
case Slot_LongRef:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Long.compare(((LongArray) a1).get(i), ((LongArray) a2).get(i)), callerTi,
callerFi);
break;
// don't compare floats / doubles directly - because two "equal" NaN are defined to miscompare
case Slot_Float:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Integer.compare(CASImpl.float2int(((FloatArray) a1).get(i)),
CASImpl.float2int(((FloatArray) a2).get(i))),
callerTi, callerFi);
break;
// don't compare floats / doubles directly - because two "equal" NaN are defined to miscompare
case Slot_DoubleRef:
r = compareAllArrayElements(fs1, fs2, len1,
i -> Long.compare(CASImpl.double2long(((DoubleArray) a1).get(i)),
CASImpl.double2long(((DoubleArray) a2).get(i))),
callerTi, callerFi);
break;
case Slot_HeapRef: {
// if (! inSortContext) {
// boolean prevIsSkipMismatch = isSkipMismatch; // support recursion
// isSkipMismatch = true;
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareRefs((TOP) ((FSArray<?>)
// a1).get(i),
// (TOP) ((FSArray<?>) a2).get(i),
// callerTi,
// callerFi), callerTi, callerFi);
// isSkipMismatch = prevIsSkipMismatch;
// if (r != 0) { // a miscompare - see if we were supposed to sort these
// if (callerFi != null && fsArraysToSort.contains(callerFi.getName())) {
// sortFSArray((FSArray<?>) a1)
// .run();
// sortFSArray((FSArray<?>) a2)
// .run();
// inSortContext = false;
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareRefs((TOP) ((FSArray<?>)
// a1).get(i),
// (TOP) ((FSArray<?>) a2).get(i),
// callerTi,
// callerFi), callerTi, callerFi);
// } else { // no special sorting, but was unequal, redo to capture msg
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareRefs((TOP) ((FSArray<?>)
// a1).get(i),
// (TOP) ((FSArray<?>) a2).get(i),
// callerTi,
// callerFi), callerTi, callerFi);
// }
// } // else compared ==
// } else { // was in sort context
r = compareAllArrayElements(fs1, fs2, len1, i -> compareRefs((TOP) ((FSArray<?>) a1).get(i),
(TOP) ((FSArray<?>) a2).get(i), callerTi, callerFi), callerTi, callerFi);
// }
break;
}
case Slot_StrRef: {
// if (! inSortContext) {
// isSkipMismatch = true; // no recursion possible
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareStringsWithNull(
// ((StringArray)a1).get(i),
// ((StringArray)a2).get(i),
// callerTi,
// callerFi,
// i), callerTi, callerFi);
// isSkipMismatch = false;
// if (r != 0) {
// if (callerFi != null && stringArraysToSort.contains(callerFi.getName())) {
// String[] a = ((StringArray)a1)._getTheArray();
// Arrays.sort(a);
// a = ((StringArray)a2)._getTheArray();
// Arrays.sort(a);
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareStringsWithNull(
// ((StringArray)a1).get(i),
// ((StringArray)a2).get(i),
// callerTi,
// callerFi,
// i), callerTi, callerFi);
// } else { // no special sorting, but was unequal, redo to capture msg
// r = compareAllArrayElements(fs1, fs2, len1, i -> compareStringsWithNull(
// ((StringArray)a1).get(i),
// ((StringArray)a2).get(i),
// callerTi,
// callerFi,
// i), callerTi, callerFi);
// }
// } // r was 0, nothing more to do
// } else { // was in sort context
r = compareAllArrayElements(fs1, fs2, len1,
i -> compareStringsWithNull(((StringArray) a1).get(i), ((StringArray) a2).get(i),
callerTi, callerFi, i),
callerTi, callerFi);
// }
break;
}
default:
throw Misc.internalError();
}
if (r != 0 && !inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE) {
System.out.println("debug stop FssArray");
}
return r;
}
private int compareSlot(TOP fs1, TOP fs2, FeatureImpl fi1, FeatureImpl fi2, TypeImpl ti1) {
SlotKind kind = fi1.getSlotKind();
switch (kind) {
case Slot_Int:
return Integer.compare(fs1._getIntValueNc(fi1), fs2._getIntValueNc(fi2));
case Slot_Short:
return Short.compare(fs1._getShortValueNc(fi1), fs2._getShortValueNc(fi2));
case Slot_Boolean:
return Boolean.compare(fs1._getBooleanValueNc(fi1), fs2._getBooleanValueNc(fi2));
case Slot_Byte:
return Byte.compare(fs1._getByteValueNc(fi1), fs2._getByteValueNc(fi2));
// don't compare floats / doubles directly - the NaN is defined to miscompare
case Slot_Float:
return Integer.compare(CASImpl.float2int(fs1._getFloatValueNc(fi1)),
CASImpl.float2int(fs2._getFloatValueNc(fi2)));
case Slot_HeapRef:
return compareRefs(fs1._getFeatureValueNc(fi1), fs2._getFeatureValueNc(fi2), ti1, fi1);
case Slot_StrRef:
return compareStringsWithNull(fs1._getStringValueNc(fi1), fs2._getStringValueNc(fi2), ti1,
fi1, -1);
case Slot_LongRef:
return Long.compare(fs1._getLongValueNc(fi1), fs2._getLongValueNc(fi2));
// don't compare floats / doubles directly - the NaN is defined to miscompare
case Slot_DoubleRef:
return Long.compare(Double.doubleToRawLongBits(fs1._getDoubleValueNc(fi1)),
Double.doubleToRawLongBits(fs2._getDoubleValueNc(fi2)));
default:
Misc.internalError();
return 0;
}
}
// @formatter:off
/**
* Two uses cases supported:
* - comparing for sorting (within on type system)
* -- goal is to be able to compare two CASes
* --- ordering must guarantee that the equal FSs appear in the
* --- same order
* - comparing two FSs (maybe from different CASes)
* -- supporting missing types and features
* -- happens when the two type systems are different
* -- the missing types and features are ignored in the comparison
*
* Different reference chains
* This compare routine may be called recursively
* - use case: FS(a) has slot which is ref to
* FS(b) which has slot which is ref to
* FS(c)
* -- the type of a, b, c may all be different.
*
* Two reference chains for the two arguments may have different structures
* - Difference in two ways:
* -- length of unique (same fs_id) FSs
* -- length of loop (if one exists at the end reached so far)
*
* IMPORTANT: the following 2 chains have different lengths, but this
* won't be discovered if the recursive descent stops too soon:
* - a -> b -> c ( -> b )
* - c -> b ( -> c)
* At the 2nd recursion, we have b vs b, but haven't explored the chain
* deeply enough to know the first one has length 3, and the 2nd length 2.
*
* Meaning of comparision of two refs:
* - recursively defined
* - captures notion of reference chain differences
* -- means if two refs compare 0, the result may still be
* non-0 if the reference chains to get to these are different
* -- first compare on length of unique FSs
* -- if ==, compare on length of loop
* - if comparing (use case 2, two different type systems) with
* type not existing in other type system, skip (treat as 0).
*
* If comparing two FSs in 1 CAS, where there is type mapping, if the mapping to
* the other CAS is null, change the value of the FS to null to match the sort order
* the other CAS will haveand that mapping is
* to null (because the type is missing), use null for the argument(s).
*
* Complexities: the type rfs1 may not be in the target type system.
* For this case - treat rfs2 == null as "equal", rfs2 != null as not equal (always gt)
* Is assymetrical (?) - same logic isn't applied for reverse case.
* @param rfs1 -
* @param rfs2 -
* @param fi -
* @return -
*/
// @formatter:on
private int compareRefs(TOP rfs1, TOP rfs2, TypeImpl callerTi, FeatureImpl callerFi) {
if (inSortContext && isTypeMapping) {
if (isSrcCas) {
if (rfs1 != null && typeMapper.mapTypeSrc2Tgt(rfs1._getTypeImpl()) == null) {
rfs1 = null;
}
if (rfs2 != null && typeMapper.mapTypeSrc2Tgt(rfs2._getTypeImpl()) == null) {
rfs2 = null;
}
} else {
if (rfs1 != null && typeMapper.mapTypeTgt2Src(rfs1._getTypeImpl()) == null) {
rfs1 = null;
}
if (rfs2 != null && typeMapper.mapTypeTgt2Src(rfs2._getTypeImpl()) == null) {
rfs2 = null;
}
}
}
if (rfs1 == null) {
if (rfs2 != null) {
if (!inSortContext && isTypeMapping
&& typeMapper.mapTypeTgt2Src(rfs2._getTypeImpl()) == null) {
return 0;
} else {
if (!inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE && !inSortContext) {
System.out.println("debug stop rfs1 is null, rfs2 is not");
}
return -1;
}
// return (!inSortContext && isTypeMapping &&
// typeMapper.mapTypeTgt2Src(rfs2._getTypeImpl()) == null)
// ? 0 // no source type for this target type, treat as equal
// : -1;
}
return 0; // both are null. no loops in ref chain possible
}
// rfs1 != null at this point
if (rfs2 == null) {
if (!inSortContext && isTypeMapping
&& typeMapper.mapTypeSrc2Tgt(rfs1._getTypeImpl()) == null) {
return 0;
} else {
if (!inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE && !inSortContext) {
System.out.println("debug stop rfs1 is not null rfs2 is null");
}
return 1;
}
// return (!inSortContext && isTypeMapping &&
// typeMapper.mapTypeSrc2Tgt(rfs1._getTypeImpl()) == null)
// ? 0 // no target type for this target type, treat as equal
// : 1;
}
if (rfs1 == rfs2) {
// only for inSortContext
return 0;
}
// next commented out to enable finding length of chain
// if (inSortContext && rfs1._id == rfs2._id) {
// return compareRefResult(rfs1, rfs2);
// }
// both are not null
// do a recursive check
// debug
// if (rfs1._id == 1103 && ! inSortContext) {
// System.out.println("debug stop 1103");
// }
Pair<TOP, TOP> refs = new Pair<>(rfs1, rfs2);
Integer prevComp = prevCompare.get(refs);
if (prevComp != null) {
int v = prevComp;
if (v == 0) {
v = compareRefResult(rfs1, rfs2); // stop recursion, return based on loops
if (v != 0 && !inSortContext) {
mismatchFs(rfs1, rfs2, callerTi, callerFi);
}
return v;
} else {
if (!inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE) {
System.out.println("debug stop");
}
return v;
}
// return (v == 0)
// ? compareRefResult(rfs1, rfs2) // stop recursion, return based on loops
// : v;
}
prevCompare.put(refs, 0); // preset in case recursion compares this again
// need special handling to detect cycles lengths that are back to the original
if (prev1.prevCompareTop != null) {
prev1.addTop();
prev2.addTop();
}
prev1.add(rfs1);
prev2.add(rfs2);
assert prev1.fsList.size() > 0;
// TOP savedFs1 = fs1;
// TOP savedFs2 = fs2;
// fs1 = rfs1;
// fs2 = rfs2;
try {
int v = compareFss(rfs1, rfs2, callerTi, callerFi);
if (v != 0) {
prevCompare.put(refs, v);
}
return v;
} finally {
prev1.rmvLast(rfs1);
prev2.rmvLast(rfs2);
// fs1 = savedFs1;
// fs2 = savedFs2;
}
}
// @formatter:off
/**
* Returning because recursion detected a loop.
*
* @param rfs1 -
* @param rfs2 -
* @return - -1 if ref chain 1 length < ref chain 2 length or is the same length but loop length 1 < 2
* 1 if ref chain 1 length > ref chain 2 length or is the same length but loop length 1 > 2
* 0 if ref chain lengths are the same and loop length is the same
* Exception: if one of the items is a canonical "empty" list element, and the other
* is a non-canonical one - treat as equal.
*/
// @formatter:on
private int compareRefResult(TOP rfs1, TOP rfs2) {
// exception: treat canonical empty lists
if (!inSortContext && IS_CANONICAL_EMPTY_LISTS && rfs1 instanceof EmptyList) {
// if (prev1.size() <= 0 || prev2.size() <= 0) {
return 0;
// }
}
if (prev1.size() <= 0) {
return 0; // no recursion case
}
// had some recursion
prev1.add(rfs1);
prev2.add(rfs2);
try { // only for finally block
// compare cycleLen first, because if !=, all ref pairs in above chain are !=
// but if ==, then all cycle pairs are compare ==.
int r = prev1.compareCycleLen(prev2);
if (r != 0) {
if (!inSortContext && IS_DEBUG_STOP_ON_MISCOMPARE) {
System.out.println("debug stop");
}
return r;
}
if (prev1.cycleLen > 0) { // && is equal to prev2 cycle length
return 0; // at this level, the FSs are equal
}
return prev1.compareUsize(prev2);
} finally {
prev1.rmvLast(rfs1);
prev2.rmvLast(rfs2);
}
}
private int compareAllArrayElements(TOP fs1, TOP fs2, int len, IntUnaryOperator c,
TypeImpl callerTi, FeatureImpl callerFi) {
int r = 0;
for (int i = 0; i < len; i++) {
r = c.applyAsInt(i);
if (r != 0) {
if (!inSortContext) {
// debug
// System.out.println("debug compareAllArrayElements i = " + i);
// System.out.println("debug compareAllArrayElements, fs1: " + fs1.toString(3));
miscompare_index = i;
mismatchFs(fs1, fs2, "Comparing array of length " + len + ", miscompare on index " + i,
callerTi, callerFi);
}
return r;
}
}
// need to return 0 if == in sort context, otherwise violates the
// comparator contract
return 0;
}
private int compareStringsWithNull(String s1, String s2, TypeImpl t, FeatureImpl f, int index) {
if (isUsingStringCongruenceSets) {
String[] scs = stringCongruenceSets.get(new ScsKey(t, f, index));
if (scs != null) {
if (Misc.contains(scs, s1) && Misc.contains(scs, s2)) {
return 0;
}
}
}
if (null == s1) {
return (null == s2) ? 0 : -1;
}
if (null == s2) {
return 1;
}
return s1.compareTo(s2);
}
// private int skipOverTgtFSsNotInSrc(
// int[] heap, int heapEnd, int nextFsIndex, CasTypeSystemMapper typeMapper) {
// final TypeSystemImpl ts = typeMapper.tsTgt;
// for (; nextFsIndex < heapEnd;) {
// final int tCode = heap[nextFsIndex];
// if (typeMapper.mapTypeCodeTgt2Src(tCode) != 0) {
// break;
// }
// nextFsIndex += incrToNextFs(heap, nextFsIndex, ts.getTypeInfo(tCode));
// }
// return nextFsIndex;
// }
//
// public void initSrcTgtIdMapsAndStringsCompare () {
//
// int iTgtHeap = isTypeMapping ? skipOverTgtFSsNotInSrc(c2heap, c2end, 1, typeMapper) : 1;
//
//
// for (int iSrcHeap = 1; iSrcHeap < c1end;) {
// final int tCode = c1heap[iSrcHeap];
// final int tgtTypeCode = isTypeMapping ? typeMapper.mapTypeCodeSrc2Tgt(tCode) : tCode;
// final boolean isIncludedType = (tgtTypeCode != 0);
//
// // record info for type
// fsStartIndexes.addItemId(iSrcHeap, iTgtHeap, isIncludedType); // maps src heap to tgt seq
//
// // for features in type -
// // strings: accumulate those strings that are in the target, if optimizeStrings != null
// // strings either in array, or in individual values
// // byte (array), short (array), long/double (instance or array): record if entries in aux array
// are skipped
// // (not in the target). Note the recording will be in a non-ordered manner (due to possible
// updates by
// // previous delta deserialization)
// final TypeInfo srcTypeInfo = ts1.getTypeInfo(tCode);
// final TypeInfo tgtTypeInfo = (isTypeMapping && isIncludedType) ? ts2.getTypeInfo(tgtTypeCode) :
// srcTypeInfo;
//
// // Advance to next Feature Structure, in both source and target heap frame of reference
// if (isIncludedType) {
// final int deltaTgtHeap = incrToNextFs(c1heap, iSrcHeap, tgtTypeInfo);
// iTgtHeap += deltaTgtHeap;
// if (isTypeMapping) {
// iTgtHeap = skipOverTgtFSsNotInSrc(c2heap, c2end, iTgtHeap, typeMapper);
// }
// }
// iSrcHeap += incrToNextFs(c1heap, iSrcHeap, srcTypeInfo);
// }
// }
private void mismatchFsDisplay() {
String s = mismatchSb.toString();
System.err.println(s);
mismatchSb.setLength(0);
}
private void mismatchFs(TOP fs1, TOP fs2, TypeImpl callerTi, FeatureImpl callerFi) {
if (isSkipMismatch) {
return;
}
Pair<TOP, TOP> pair = new Pair<>(fs1, fs2);
if (prevReport.contains(pair)) {
if (leafErrorReported == null) {
leafErrorReported = pair;
}
return; // already reported
}
prevReport.add(pair);
if (leafErrorReported == null) {
leafErrorReported = pair;
mismatchSb.append(String.format("Mismatched Feature Structures refd from %s %s:%n %s%n %s%n",
(callerTi == null) ? "null" : callerTi.getName(),
(callerFi == null) ? "null" : callerFi.getName(), ps(fs1), ps(fs2)));
} else {
TOP ofs1 = leafErrorReported.t;
TOP ofs2 = leafErrorReported.u;
String s1 = String.format(" from: %s:%d, %s:%d", fs1.getType().getShortName(), fs1._id,
fs2.getType().getShortName(), fs2._id);
s1maxLen = Math.max(s1.length() + 4, s1maxLen);
mismatchSb.append(String.format("%-" + s1maxLen + "s original mismatch: %s:%d, %s, %d%n",
s1, ofs1.getType().getShortName(), ofs1._id, ofs2.getType().getShortName(),
ofs2._id));
}
// // debug
// System.out.println("adding to miscompares: " + fs1._id + " " + fs2._id);
// miscompares.add(new Pair<>(fs1, fs2));
}
// private boolean mismatchFs(int i1, int i2) {
// System.err.format("Mismatched Feature Structures in srcSlot %d, tgtSlot %d%n %s%n %s%n",
// i1, i2, dumpHeapFs(c1, c1heapIndex, ts1), dumpHeapFs(c2, c2heapIndex, ts2));
// return false;
// }
// private void mismatchFs(Feature fi) {
// mismatchSb.append(String.format("Mismatched Feature Structures in feature %s%n %s%n %s%n",
// fi.getShortName(), fs1, fs2));
// }
private void mismatchFs(TOP fs1, TOP fs2, Feature fi, Feature fi2) {
if (isSkipMismatch) {
return;
}
Pair<TOP, TOP> pair = new Pair<>(fs1, fs2);
if (prevReport.contains(pair)) {
if (leafErrorReported == null) {
leafErrorReported = pair;
}
return; // already reported
}
prevReport.add(pair);
if (leafErrorReported == null) {
leafErrorReported = pair;
String mapmsg = fi.equals(fi2) ? ""
: "which mapped to target feature " + fi2.getShortName() + " ";
mismatchSb.append(String.format("Mismatched Feature Structures in feature %s %s%n %s%n %s%n",
fi.getShortName(), mapmsg, ps(fs1), ps(fs2)));
} else {
TOP ofs1 = leafErrorReported.t;
TOP ofs2 = leafErrorReported.u;
String s1 = String.format(" from: %s:%d, %s:%d", fs1.getType().getShortName(), fs1._id,
fs2.getType().getShortName(), fs2._id);
s1maxLen = Math.max(s1.length() + 4, s1maxLen);
mismatchSb.append(String.format("%-" + s1maxLen + "s original mismatch: %s:%d, %s, %d%n",
s1, ofs1.getType().getShortName(), ofs1._id, ofs2.getType().getShortName(),
ofs2._id));
}
}
private void mismatchFs(TOP fs1, TOP fs2, String msg, TypeImpl callerTi, FeatureImpl callerFi) {
if (isSkipMismatch) {
return;
}
Pair<TOP, TOP> pair = new Pair<>(fs1, fs2);
if (prevReport.contains(pair)) {
if (leafErrorReported == null) {
leafErrorReported = pair;
}
return; // already reported
}
prevReport.add(pair);
if (leafErrorReported == null) {
leafErrorReported = pair;
mismatchSb
.append(String.format("Mismatched Feature Structures refd from %s %s, %s%n %s%n %s%n",
(callerTi == null) ? "null" : callerTi.getName(),
(callerFi == null) ? "null" : callerFi.getName(), msg, ps(fs1), ps(fs2)));
} else {
TOP ofs1 = leafErrorReported.t;
TOP ofs2 = leafErrorReported.u;
String s1 = String.format(" from: %s:%d, %s:%d", fs1.getType().getShortName(), fs1._id,
fs2.getType().getShortName(), fs2._id);
s1maxLen = Math.max(s1.length() + 4, s1maxLen);
mismatchSb.append(String.format("%-" + s1maxLen + "s original mismatch: %s:%d, %s, %d%n",
s1, ofs1.getType().getShortName(), ofs1._id, ofs2.getType().getShortName(),
ofs2._id));
}
}
/** called to sort all the FSs before doing the equality compares */
private void sort(List<TOP> fss) {
inSortContext = true;
// do before sorting
clearPrevFss();
try {
fss.sort((afs1, afs2) -> sortCompare(afs1, afs2));
// (afs1, afs2) -> Integer.compare(afs1._id, afs2._id));
} finally {
inSortContext = false;
}
}
/**
* Used for sorting within one type system, for two instances of the same type
*
* Uses field isSrcCas (boolean) to differentiate when being used to sort for srcCas vs tgtCas
*
* When sorting where type mapping is happening between source and target CASs, skip compares for
* features which are not in the opposite CAS.
*
* @param scFs1
* -
* @param scFs2
* -
* @return -
*/
private int sortCompare(TOP scFs1, TOP scFs2) {
if (scFs1 == null) {
return (scFs2 == null) ? 0 : 1;
}
if (scFs2 == null) {
return -1;
}
// miscompares.clear();
prev1.clear();
prev2.clear();
prev1.prevCompareTop = scFs1;
prev2.prevCompareTop = scFs2;
int r = compareFss(scFs1, scFs2, null, null);
prev1.prevCompareTop = null;
prev2.prevCompareTop = null;
if (r == 0) {
r = Integer.compare(scFs1._id, scFs2._id);
}
return r;
}
private boolean isTypeInTgt(TOP fs) {
return !isTypeMapping || (null != typeMapper.mapTypeSrc2Tgt(fs._getTypeImpl()));
}
private String ps(TOP fs) {
StringBuilder sb = new StringBuilder();
fs.prettyPrintShort(sb);
return sb.toString();
}
/**
* Counts and compares the number of Feature Structures, by type, and generates a report
*
* @param cas1
* first CAS to compare
* @param cas2
* second CAS to compare
* @return a StringBuilder with a report
*/
public static StringBuilder compareNumberOfFSsByType(CAS cas1, CAS cas2) {
if (IS_SHOW_PROGRESS) {
System.out.println("comparing the number of FSs by type");
}
CASImpl ci1 = (CASImpl) cas1;
CASImpl ci2 = (CASImpl) cas2;
Iterator<FsIndex_singletype<TOP>> il1 = ci1.indexRepository.streamNonEmptyIndexes(TOP.class)
.collect(Collectors.toList()).iterator();
Iterator<FsIndex_singletype<TOP>> il2 = ci2.indexRepository.streamNonEmptyIndexes(TOP.class)
.collect(Collectors.toList()).iterator();
StringBuilder sb = new StringBuilder();
StringBuilder sba = new StringBuilder();
boolean isSame = il1.hasNext() || il2.hasNext();
while (il1.hasNext() || il2.hasNext()) {
sb.setLength(0);
String ts1 = null, ts2 = null;
int sz1 = 0, sz2 = 0;
FsIndex_singletype<TOP> idx;
if (il1.hasNext()) {
idx = il1.next();
ts1 = idx.getType().getName();
sz1 = idx.size();
sb.append(String.format("%-83s %,5d", ts1, sz1));
} else {
isSame = false;
sb.append(BLANKS_89);
}
if (il2.hasNext()) {
idx = il2.next();
ts2 = idx.getType().getName();
sz2 = idx.size();
String m = (ts2.equals(ts1) && sz2 == sz1) ? "same" : ts2;
sb.append(String.format(" %,5d %s", sz2, m));
} else {
isSame = false;
}
sba.append(sb).append('\n');
if (isSame) {
isSame = ts1.equals(ts2) && sz1 == sz2;
}
}
if (isSame) {
sba.setLength(0);
sba.append("Same number of types");
} else {
sba.append("\nDifferent numbers of types");
}
return sba.append('\n');
}
}