Class Characteristic

All Implemented Interfaces:
Comparable<Characteristic>, Describable, Identifiable
Direct Known Subclasses:
Statement

@Entity @Indexed public class Characteristic extends AbstractDescribable implements Comparable<Characteristic>
Instances of this are used to describe other entities. This base class is just a characteristic that is simply a 'tag' of free text.

Characteristics can have an associated URI from an ontology from getCategoryUri() and getValueUri(), but not necessarily since there might not be an adequate term to represent the conveyed concept. These properties are marked with Nullable and should always be handled with care.

Hibernate Search 7 mapping: indexed root and a critical @IndexedEmbedded contributor — the value/valueUri pair is what makes "free-text-over-ontology-terms-as-they-appear-on-datasets" work (see SEARCH_RECCE.md Section 5.1).

Author:
Paul
  • Constructor Details

    • Characteristic

      public Characteristic()
      No-arg constructor added to satisfy javabean contract
  • Method Details

    • getComparator

      public static Comparator<Characteristic> getComparator()
      Obtain a full comparator for characteristics that fallbacks on the ID if everything else is equal.

      The following fields are compared: category, value, ID.

    • getByCategoryComparator

      public static Comparator<Characteristic> getByCategoryComparator()
      Obtain a comparator to compare terms by category URI (or category if null) in a case-insensitive manner.

      Two terms that are equal in terms of category will be collapsed if using a TreeSet.

      Use this if you want to get a summary of the categories used by a collection of terms irrespective of their IDs.

    • getByCategoryAndValueComparator

      public static Comparator<Characteristic> getByCategoryAndValueComparator()
      Obtain a comparator to order terms by value URI (or value if null) in a case-insensitive manner.

      Two terms that are equal in terms of category and value (i.e. sharing the same ID) will be collapsed if using a TreeSet.

      Use this if you want to get a summary of the annotations used by a collection of terms irrespective of their IDs.

    • getId

      @DocumentId public Long getId()
      Description copied from interface: Identifiable
      Obtain the identifier of the object.
      Specified by:
      getId in interface Identifiable
      Overrides:
      getId in class AbstractIdentifiable
    • getCategory

      @Nullable @FullTextField public String getCategory()
      Returns:
      either the human readable form of the classUri or a free text version if no classUri exists
    • setCategory

      public void setCategory(@Nullable String category)
    • normalizeTermText

      @Nullable protected static String normalizeTermText(@Nullable String s)
      Collapse whitespace on a free-text term field: strip the ends, and reduce internal runs to a single space. Null survives as null.

      These fields carry third-party text — GEO submitters write "cancer cell line " and "high fat diet", and 12,861 of the 13,179 double-spaced values in production had the run in the submitter's own originalValue, so the input reproduces them on every import. The cost is not cosmetic: MySQL's PAD SPACE collation hides a TRAILING space from = but gives internal runs no such cover, so two spellings of one value split under GROUP BY, joins, and every exact-label comparison. Normalizing at the setter is the one point every writer passes through — the GEO converter's seventeen call sites, the curation API, agent writes and the CLI — and getOriginalValue() still holds the submitter's string verbatim, so nothing is lost.

      Safe against Hibernate: the mapping annotates the FIELDS, so hydration assigns them directly and never calls a setter. A loaded entity is therefore not silently rewritten (and not marked dirty) by this.

    • getCategoryUri

      @Nullable @KeywordField public String getCategoryUri()
      Returns:
      The URI of the class that this is an instance of. Will only be different from the termUri when the class is effectively abstract, and this is a concrete instance. By putting the abstract class URI in the object we can more readily group together Characteristics that are instances of the same class. For example: If the classUri is "Sex", then the termUri might be "male" or "female" for various instances. Otherwise, the classUri and the termUri can be the same; for example, for "Age", if the "Age" is defined through its properties declared as associations with this.
    • setCategoryUri

      public void setCategoryUri(@Nullable String categoryUri)
    • getEvidenceCode

      public GOEvidenceCode getEvidenceCode()
    • setEvidenceCode

      public void setEvidenceCode(GOEvidenceCode evidenceCode)
    • getOriginalValue

      @Nullable public String getOriginalValue()
      Returns:
      the originalValue
    • setOriginalValue

      public void setOriginalValue(@Nullable String originalValue)
    • getValue

      @Nullable @FullTextField public String getValue()
      Returns:
      The human-readable term (e.g., "OrganismPart"; "kinase")
    • setValue

      public void setValue(@Nullable String value)
    • getValueUri

      @Nullable @KeywordField public String getValueUri()
      Returns:
      This can be a URI to any resources that describes the characteristic. Often it might be a URI to an OWL ontology term. If the URI is an instance of an abstract class, the classUri should be filled in with the URI for the abstract class.
    • setValueUri

      public void setValueUri(@Nullable String uri)
    • isMigratedToStatement

      @Deprecated public boolean isMigratedToStatement()
      Deprecated.
    • setMigratedToStatement

      @Deprecated public void setMigratedToStatement(boolean migratedToStatement)
      Deprecated.
    • getSupportingEvidence

      @Nullable public String getSupportingEvidence()
    • setSupportingEvidence

      public void setSupportingEvidence(@Nullable String supportingEvidence)
    • hashCode

      public int hashCode()
      Constant, deliberately.

      🛑 This used to hash category/categoryUri and value/valueUri — exactly the fields curation MUTATES. A Characteristic lives in Investigation's HashSet, so re-terming one in place moved it to a bucket computed from its old value while equals(Object) still matched it by id: contains() and remove() then answered false for an element that was demonstrably in the set. The same hash also broke the equals/hashCode contract outright, because two instances sharing an id but differing in content are equal by the id branch below and hashed differently.

      The cost is that a hash collection of Characteristics degrades to a linear scan of one bucket. That is invisible for the per-entity sets this class actually lives in (tens of elements), and it is NOT invisible for bulk keying: building a 5000-entry HashMap keyed by transient Characteristics measured 1141 ms this way versus 42 ms before. ⇒ Do not key a large map by Characteristic. Use a TreeMap, or key by id. The annotation usage-frequency aggregations in ExpressionExperimentDaoImpl are the precedent.

      🛑 Pick the comparator by what the keys are. getComparator() breaks the tie on id, so a map built from TRANSIENT keys (a GROUP BY projection, say) and probed with a persisted characteristic misses: id-vs-null is never zero, even though equals(Object) calls the two equal. For those maps use getByCategoryAndValueComparator() (or getByCategoryComparator() when the value is not part of the key), which collapses exactly the pairs equals(Object) considers equal. Reserve getComparator() for maps whose keys are all persisted and where two distinct rows sharing a term must stay distinct.

      Overrides:
      hashCode in class AbstractDescribable
    • equals

      public boolean equals(Object object)
      Description copied from class: AbstractDescribable
      Important note: The name should be compared in a case-insensitive manner. You can use DescribableUtils.equalsByName(Describable, Describable) to get the correct behavior.

      Important note: Two objects with the same class and non-null ID must be considered equal. If one or both IDs are nulls, the rest of the state can be used to determine equality.

      Specified by:
      equals in class AbstractDescribable
    • compareTo

      public int compareTo(@NonNull Characteristic characteristic)
      Specified by:
      compareTo in interface Comparable<Characteristic>
    • toString

      public String toString()
      Overrides:
      toString in class AbstractDescribable