Class CharacteristicUtils
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic Statementstatic StringcanonicalLabel(String uri, String label) The label that goes withcanonicalUri(String).static StringcanonicalUri(String uri) The URI Gemma should report for a term, which is not always the one stored.static intcompareTerm(String a, String aUri, String b, String bUri) Compare a pair of ontology terms.static booleanCompare a pair of ontology terms.static CategoryCreate a new characteristic that represents the category of a given characteristic.static Characteristicstatic StringgetNormalizedValue(Characteristic characteristic) The whole canonicalization table, from-URI → [to-URI, to-label, rule, lane].static booleanhasAnyValue(Characteristic c, Value... values) Check if the given characteristic has any of the specified values.static booleanhasCategory(Characteristic c, Category category) Check if a given characteristics has a specific category.static intHash an ontology term.static booleanhasRecordedEvidence(com.fasterxml.jackson.databind.JsonNode evidence) Whether a supporting-evidence payload actually records something — i.e.static booleanhasValue(Characteristic c, Value value) Check if the given characteristic has a particular value.static booleanCheck if the given characteristic is a free-text value.static booleanCheck if the given characteristic has or is a free-text category.static booleanisRemappedUri(String uri) static booleanCheck if the given characteristic is uncategorized.static com.fasterxml.jackson.databind.JsonNodeParse a characteristic's opaquesupportingEvidenceJSON into a tree for serialization.static intstatic booleanStatement-aware equality for the "is this the same tag?" question driving the idempotent set-replace annotation writes (experiment- and biomaterial-level).static StringserializeSupportingEvidence(com.fasterxml.jackson.databind.JsonNode evidence) Inverse ofparseSupportingEvidence(String): flatten a supporting-evidence tree back to the string theSUPPORTING_EVIDENCEcolumn stores.
-
Constructor Details
-
CharacteristicUtils
public CharacteristicUtils()
-
-
Method Details
-
canonicalUri
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
uriunchanged when nothing maps it (the common case)
- malformed URIs — a bare CURIE (
-
canonicalLabel
The label that goes withcanonicalUri(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
uriis remapped, otherwiselabelunchanged
-
isRemappedUri
- Returns:
- true if this URI is one the shim rewrites.
-
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
-
hasCategory
Check if a given characteristics has a specific category.Comparisons are performed as per
equals(String, String, String, String). -
getCategory
Create a new characteristic that represents the category of a given characteristic. -
getCategoryAsCharacteristic
-
hasValue
Check if the given characteristic has a particular value. -
hasAnyValue
Check if the given characteristic has any of the specified values. -
isUncategorized
Check if the given characteristic is uncategorized. -
isFreeTextCategory
Check if the given characteristic has or is a free-text category. -
isFreeText
Check if the given characteristic is a free-text value. -
hash
-
asStatement
Returncas aStatement, converting a plainCharacteristicif 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 fieldCharacteristicdeclares is carried here.- Returns:
citself when it is already a Statement, so an entity that is already persistent keeps its identity and is never replaced by a copy.
-
sameTag
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
Statementand a plainCharacteristicwith 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
addAnnotationstop rejecting duplicates and makesupdateAnnotationsdrop and re-add the entire set. Content equality makes the upgrade safe in both directions and in either order. Comparisons delegate toequals(String, String, String, String)(case-insensitive, URI-aware). Used by bothExpressionExperimentService.updateAnnotationsandBioMaterialService.updateAnnotationsso the two diff implementations cannot drift. -
parseSupportingEvidence
@Nullable public static com.fasterxml.jackson.databind.JsonNode parseSupportingEvidence(@Nullable String json) Parse a characteristic's opaquesupportingEvidenceJSON into a tree for serialization.The column is a verbatim provenance payload the curation agents emitted (the agents-side
FindingEvidenceshape: 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 yieldsnullrather than propagating a parse failure into a read response.Lives here rather than on any one value object because every read surface over a
Characteristicneeds the same treatment —AnnotationValueObject,CharacteristicValueObject, and the design path'sStatementValueObject— and three private copies would be three chances to drift. -
serializeSupportingEvidence
@Nullable public static String serializeSupportingEvidence(@Nullable com.fasterxml.jackson.databind.JsonNode evidence) Inverse ofparseSupportingEvidence(String): flatten a supporting-evidence tree back to the string theSUPPORTING_EVIDENCEcolumn stores.An absent, null, or empty tree yields
nullrather 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 surviveserializeSupportingEvidence(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!= nulllets that payload through the guard, and because the serializer maps an empty tree tonullthe 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
-
compareTerm
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.
-