Class CharacteristicUtils

java.lang.Object
ubic.gemma.model.common.description.CharacteristicUtils

public class CharacteristicUtils extends Object
  • Constructor Details

    • CharacteristicUtils

      public CharacteristicUtils()
  • Method Details

    • canonicalUri

      @Nullable public static String canonicalUri(@Nullable String uri)
      The URI Gemma should report for a term, which is not always the one stored.

      Two populations are remapped, and neither is a judgement call made here — both were settled with evidence and written down in TermUriMigration.tsv:

      • malformed URIs — a bare CURIE (CL:0000236), a colon where OBO uses an underscore, an id concatenated with itself. Each repair was verified by resolving the repaired IRI against the live ontology.
      • CLO twins — two live CLO classes for one cell line, decided by an EFO inbound xref, else a definition, else usage.

      🛑 This is a READ-TIME SHIM standing in for a database migration that is written and parked (scripts/sql/term_uri_migration.sql). It exists because the agent pipeline is calibrated against a May snapshot and migrating prod now would desynchronize them. When the migration runs, empty the resource — a shim left over a corrected corpus silently rewrites rows that are already right.

      Returns:
      the canonical URI, or uri unchanged when nothing maps it (the common case)
    • canonicalLabel

      @Nullable public static String canonicalLabel(@Nullable String uri, @Nullable String label)
      The label that goes with canonicalUri(String).

      The label has to move with the URI. Reporting the new URI beside the old label produces a row that says one thing and means another, and the label is what search matches and what every table renders.

      Returns:
      the canonical label when uri is remapped, otherwise label unchanged
    • isRemappedUri

      public static boolean isRemappedUri(@Nullable String uri)
      Returns:
      true if this URI is one the shim rewrites.
    • getUriMigrations

      public static Map<String,String[]> getUriMigrations()
      The whole canonicalization table, from-URI → [to-URI, to-label, rule, lane].

      Exposed so a client that resolves terms before asking Gemma can hold the same answer rather than a hand-copied subset: a local synonym table cannot be corrected by a server-side change, which is how two authorities on the same question come to disagree.

    • remappedUriCount

      public static int remappedUriCount()
      Returns:
      how many mappings the shim carries; 0 once the migration has run.
    • getNormalizedValue

      public static String getNormalizedValue(Characteristic characteristic)
    • hasCategory

      public static boolean hasCategory(Characteristic c, Category category)
      Check if a given characteristics has a specific category.

      Comparisons are performed as per equals(String, String, String, String).

    • getCategory

      public static Category getCategory(Characteristic t)
      Create a new characteristic that represents the category of a given characteristic.
    • getCategoryAsCharacteristic

      public static Characteristic getCategoryAsCharacteristic(Characteristic t)
    • hasValue

      public static boolean hasValue(Characteristic c, Value value)
      Check if the given characteristic has a particular value.
    • hasAnyValue

      public static boolean hasAnyValue(Characteristic c, Value... values)
      Check if the given characteristic has any of the specified values.
    • isUncategorized

      public static boolean isUncategorized(Characteristic c)
      Check if the given characteristic is uncategorized.
    • isFreeTextCategory

      public static boolean isFreeTextCategory(Characteristic c)
      Check if the given characteristic has or is a free-text category.
    • isFreeText

      public static boolean isFreeText(Characteristic c)
      Check if the given characteristic is a free-text value.
    • hash

      public static int hash(String value, String valueUri)
      Hash an ontology term.
    • asStatement

      public static Statement asStatement(Characteristic c)
      Return c as a Statement, converting a plain Characteristic if needed.

      Experiment-level tags are statements — a bare one is simply a statement with no predicate or object, which is byte-identical in storage to a plain characteristic apart from the discriminator. Normalizing on the way in means an existing tag and a newly written one always compare on content alone, and adding a predicate to a tag later is an update rather than a delete plus recreate.

      🛑 Do NOT substitute Statement.Factory.newInstance( Characteristic ): it copies only category and value, so it would silently drop the evidence code, the supporting evidence and the original value. Every field Characteristic declares is carried here.

      Returns:
      c itself when it is already a Statement, so an entity that is already persistent keeps its identity and is never replaced by a copy.
    • sameTag

      public static boolean sameTag(Characteristic a, Characteristic b)
      Statement-aware equality for the "is this the same tag?" question driving the idempotent set-replace annotation writes (experiment- and biomaterial-level).

      Identity is the CONTENT — (category, value) plus the two predicate/object pairs — and never the Java type. A subject-only Statement and a plain Characteristic with the same (category, value) ARE the same tag: they are byte-identical in storage apart from the discriminator, so calling them different would mean an annotation that nobody edited compares as changed.

      🛑 This used to return false whenever one side was a Statement and the other was not, so that a plain ↔ Statement change round-tripped as drop+add. That rule cannot survive experiment tags being upgraded to statements: during the upgrade, one side of every comparison is whichever form the row or the caller happens to carry. Under the old rule an identical tag compares as different, which makes addAnnotation stop rejecting duplicates and makes updateAnnotations drop and re-add the entire set. Content equality makes the upgrade safe in both directions and in either order. Comparisons delegate to equals(String, String, String, String) (case-insensitive, URI-aware). Used by both ExpressionExperimentService.updateAnnotations and BioMaterialService.updateAnnotations so the two diff implementations cannot drift.

    • parseSupportingEvidence

      @Nullable public static com.fasterxml.jackson.databind.JsonNode parseSupportingEvidence(@Nullable String json)
      Parse a characteristic's opaque supportingEvidence JSON into a tree for serialization.

      The column is a verbatim provenance payload the curation agents emitted (the agents-side FindingEvidence shape: a JSON array of {quote, source, location, …} items). Gemma stores and serves it opaquely — the agents repo owns the schema — so this only turns the stored string back into a tree. Writes always store a serialized tree, so it round-trips; a null / blank or (defensively) unparseable value yields null rather than propagating a parse failure into a read response.

      Lives here rather than on any one value object because every read surface over a Characteristic needs the same treatment — AnnotationValueObject, CharacteristicValueObject, and the design path's StatementValueObject — and three private copies would be three chances to drift.

    • serializeSupportingEvidence

      @Nullable public static String serializeSupportingEvidence(@Nullable com.fasterxml.jackson.databind.JsonNode evidence)
      Inverse of parseSupportingEvidence(String): flatten a supporting-evidence tree back to the string the SUPPORTING_EVIDENCE column stores.

      An absent, null, or empty tree yields null rather than "[]" or "null", so "nothing recorded" has exactly one representation in the database and a caller cannot accidentally persist an empty array that later reads as though evidence were recorded and found wanting.

    • hasRecordedEvidence

      public static boolean hasRecordedEvidence(@Nullable com.fasterxml.jackson.databind.JsonNode evidence)
      Whether a supporting-evidence payload actually records something — i.e. whether it would survive serializeSupportingEvidence(JsonNode).

      🛑 The point is that [] is NOT a record of anything, and must not be read as one. Every write path on the curation route treats a null evidence field as "no change", so that a client which does not carry provenance cannot wipe provenance somebody else recorded. An empty array is that same statement — "I have none" — and a client building a payload from a reference file stamps it on every entity that has no evidence, which is most of them. Testing != null lets that payload through the guard, and because the serializer maps an empty tree to null the write then CLEARS the column: a wipe of every stored block it touches, reported as an ordinary success.

      So the guard asks this instead of asking for non-null. The consequence is that evidence cannot be cleared through the commit route at all — which is the safe direction to be wrong in, and leaves an explicit erase to be designed if one is ever wanted.

    • equals

      public static boolean equals(String a, String aUri, String b, String bUri)
      Compare a pair of ontology terms.
    • compareTerm

      public static int compareTerm(String a, @Nullable String aUri, String b, @Nullable String bUri)
      Compare a pair of ontology terms.

      Terms are sorted by label and then URI. If two term have an identical URI, this method will return zero regardless of the label.

      All URI and label comparisons are case-insensitive.