/*
* Licensed 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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC ยง105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.model.sememe;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.commons.lang3.StringUtils;
import sh.isaac.api.Get;
import sh.isaac.api.bootstrap.TermAux;
import sh.isaac.api.chronicle.LatestVersion;
import sh.isaac.api.chronicle.ObjectChronologyType;
import sh.isaac.api.collections.LruCache;
import sh.isaac.api.component.concept.ConceptChronology;
import sh.isaac.api.component.sememe.SememeChronology;
import sh.isaac.api.component.sememe.SememeType;
import sh.isaac.api.component.sememe.version.DescriptionSememe;
import sh.isaac.api.component.sememe.version.DynamicSememe;
import sh.isaac.api.component.sememe.version.SememeVersion;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeColumnInfo;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeData;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeDataType;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeValidatorType;
import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeArray;
import sh.isaac.api.component.sememe.version.dynamicSememe.dataTypes.DynamicSememeString;
import sh.isaac.api.constants.DynamicSememeConstants;
import sh.isaac.model.configuration.StampCoordinates;
//~--- classes ----------------------------------------------------------------
/**
* See {@link DynamicSememeUsageDescription}.
*
* @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a>
*/
public class DynamicSememeUsageDescriptionImpl
implements DynamicSememeUsageDescription {
/** The Constant logger. */
protected static final Logger logger = Logger.getLogger(DynamicSememeUsageDescription.class.getName());
/** The cache. */
private static LruCache<Integer, DynamicSememeUsageDescriptionImpl> cache =
new LruCache<Integer, DynamicSememeUsageDescriptionImpl>(25);
//~--- fields --------------------------------------------------------------
/** The refex usage descriptor sequence. */
int refexUsageDescriptorSequence;
/** The sememe usage description. */
String sememeUsageDescription;
/** The name. */
String name;
/** The refex column info. */
DynamicSememeColumnInfo[] refexColumnInfo;
/** The referenced component type restriction. */
ObjectChronologyType referencedComponentTypeRestriction;
/** The referenced component type sub restriction. */
SememeType referencedComponentTypeSubRestriction;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new dynamic sememe usage description impl.
*/
private DynamicSememeUsageDescriptionImpl() {
// For use by the mock static method
}
/**
* Read the RefexUsageDescription data from the database for a given sequence or nid.
*
* Note that most users should call {@link #read(int)} instead, as that
* utilizes a cache. This always reads directly from the DB.
*
* @param refexUsageDescriptorId sequence or NID of refexUsageDescriptor
*/
@SuppressWarnings("unchecked")
public DynamicSememeUsageDescriptionImpl(int refexUsageDescriptorId) {
final ConceptChronology<?> assemblageConcept = Get.conceptService()
.getConcept(refexUsageDescriptorId);
this.refexUsageDescriptorSequence = assemblageConcept.getConceptSequence();
final TreeMap<Integer, DynamicSememeColumnInfo> allowedColumnInfo = new TreeMap<>();
for (final SememeChronology<? extends DescriptionSememe<?>> descriptionSememe:
assemblageConcept.getConceptDescriptionList()) {
@SuppressWarnings("rawtypes")
final Optional<LatestVersion<DescriptionSememe<?>>> descriptionVersion =
((SememeChronology) descriptionSememe).getLatestVersion(DescriptionSememe.class,
StampCoordinates.getDevelopmentLatestActiveOnly());
if (descriptionVersion.isPresent()) {
@SuppressWarnings("rawtypes")
final DescriptionSememe ds = descriptionVersion.get()
.value();
if (ds.getDescriptionTypeConceptSequence() == TermAux.DEFINITION_DESCRIPTION_TYPE.getConceptSequence()) {
final Optional<SememeChronology<? extends SememeVersion<?>>> nestesdSememe = Get.sememeService()
.getSememesForComponentFromAssemblage(
ds.getNid(),
DynamicSememeConstants.get().DYNAMIC_SEMEME_DEFINITION_DESCRIPTION
.getSequence())
.findAny();
if (nestesdSememe.isPresent()) {
this.sememeUsageDescription = ds.getText();
}
;
}
if (ds.getDescriptionTypeConceptSequence() ==
TermAux.FULLY_SPECIFIED_DESCRIPTION_TYPE.getConceptSequence()) {
this.name = ds.getText();
}
if ((this.sememeUsageDescription != null) && (this.name != null)) {
break;
}
}
}
if (StringUtils.isEmpty(this.sememeUsageDescription)) {
throw new RuntimeException(
"The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. It must contain a description of type Definition with an annotation of type " +
"DynamicSememe.DYNAMIC_SEMEME_DEFINITION_DESCRIPTION");
}
Get.sememeService()
.getSememesForComponent(assemblageConcept.getNid())
.forEach(sememe -> {
if (sememe.getSememeType() == SememeType.DYNAMIC) {
@SuppressWarnings("rawtypes")
final Optional<LatestVersion<? extends DynamicSememe>> sememeVersion =
((SememeChronology) sememe).getLatestVersion(DynamicSememe.class,
StampCoordinates.getDevelopmentLatestActiveOnly());
if (sememeVersion.isPresent()) {
@SuppressWarnings("rawtypes")
final DynamicSememe ds = sememeVersion.get()
.value();
final DynamicSememeData[] refexDefinitionData = ds.getData();
if (sememe.getAssemblageSequence() ==
DynamicSememeConstants.get().DYNAMIC_SEMEME_EXTENSION_DEFINITION.getSequence()) {
if ((refexDefinitionData == null) ||
(refexDefinitionData.length < 3) ||
(refexDefinitionData.length > 7)) {
throw new RuntimeException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. It must contain at least 3 columns in the DynamicSememeDataBI attachment, and no more than 7.");
}
// col 0 is the column number,
// col 1 is the concept with col name
// col 2 is the column data type, stored as a string.
// col 3 (if present) is the default column data, stored as a subtype of DynamicSememeData
// col 4 (if present) is a boolean field noting whether the column is required (true) or optional (false or null)
// col 5 (if present) is the validator {@link DynamicSememeValidatorType}, stored as a string array.
// col 6 (if present) is the validatorData for the validator in column 5, stored as a subtype of DynamicSememeData
try {
final int column = (Integer) refexDefinitionData[0].getDataObject();
final UUID descriptionUUID = (UUID) refexDefinitionData[1].getDataObject();
final DynamicSememeDataType type =
DynamicSememeDataType.valueOf((String) refexDefinitionData[2].getDataObject());
DynamicSememeData defaultData = null;
if (refexDefinitionData.length > 3) {
defaultData = ((refexDefinitionData[3] == null) ? null
: refexDefinitionData[3]);
}
if ((defaultData != null) &&
(type.getDynamicSememeMemberClass() !=
refexDefinitionData[3].getDynamicSememeDataType().getDynamicSememeMemberClass())) {
throw new IOException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. The type of the column (column 3) must match the type of the defaultData (column 4)");
}
Boolean columnRequired = null;
if (refexDefinitionData.length > 4) {
columnRequired = ((refexDefinitionData[4] == null) ? null
: (Boolean) refexDefinitionData[4].getDataObject());
}
DynamicSememeValidatorType[] validators = null;
DynamicSememeData[] validatorsData = null;
if (refexDefinitionData.length > 5) {
if ((refexDefinitionData[5] != null) &&
((DynamicSememeArray<DynamicSememeString>) refexDefinitionData[5]).getDataArray().length >
0) {
final DynamicSememeArray<DynamicSememeString> readValidators =
(DynamicSememeArray<DynamicSememeString>) refexDefinitionData[5];
validators =
new DynamicSememeValidatorType[readValidators.getDataArray().length];
for (int i = 0; i < validators.length; i++) {
validators[i] = DynamicSememeValidatorType.valueOf(
(String) readValidators.getDataArray()[i]
.getDataObject());
}
}
if (refexDefinitionData.length > 6) {
if ((refexDefinitionData[6] != null) &&
((DynamicSememeArray<? extends DynamicSememeData>) refexDefinitionData[6]).getDataArray().length >
0) {
final DynamicSememeArray<? extends DynamicSememeData> readValidatorsData =
(DynamicSememeArray<? extends DynamicSememeData>) refexDefinitionData[6];
validatorsData =
new DynamicSememeData[readValidatorsData.getDataArray().length];
if (validators != null) {
for (int i = 0; i < validators.length; i++) {
if (readValidatorsData.getDataArray()[i] != null) {
validatorsData[i] = readValidatorsData.getDataArray()[i];
} else {
validatorsData[i] = null;
}
}
}
}
}
}
allowedColumnInfo.put(column,
new DynamicSememeColumnInfo(
assemblageConcept.getPrimordialUuid(),
column,
descriptionUUID,
type,
defaultData,
columnRequired,
validators,
validatorsData,
null));
} catch (final Exception e) {
throw new RuntimeException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. The first column must have a data type of integer, and the third column must be a string " +
"that is parseable as a DynamicSememeDataType");
}
} else if (sememe.getAssemblageSequence() ==
DynamicSememeConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION.getSequence()) {
if ((refexDefinitionData == null) || (refexDefinitionData.length < 1)) {
throw new RuntimeException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. If it contains a " +
DynamicSememeConstants.get().DYNAMIC_SEMEME_REFERENCED_COMPONENT_RESTRICTION.getPrimaryName() +
" then it must contain a single column of data, of type string, parseable as a " +
ObjectChronologyType.class.getName());
}
// col 0 is Referenced component restriction information - as a string.
try {
final ObjectChronologyType type =
ObjectChronologyType.parse(refexDefinitionData[0].getDataObject()
.toString(),
false);
if (type == ObjectChronologyType.UNKNOWN_NID) {
// just ignore - it shouldn't have been saved that way anyway.
} else {
this.referencedComponentTypeRestriction = type;
}
} catch (final Exception e) {
throw new RuntimeException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. The component type restriction annotation has an invalid value");
}
// col 1 is an optional Referenced component sub-restriction information - as a string.
if ((refexDefinitionData.length > 1) && (refexDefinitionData[1] != null)) {
try {
final SememeType type = SememeType.parse(refexDefinitionData[1].getDataObject()
.toString(),
false);
if (type == SememeType.UNKNOWN) {
// just ignore - it shouldn't have been saved that way anyway.
} else {
this.referencedComponentTypeSubRestriction = type;
}
} catch (final Exception e) {
throw new RuntimeException("The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. The component type restriction annotation has an invalid value");
}
} else {
this.referencedComponentTypeSubRestriction = null;
}
}
}
}
});
this.refexColumnInfo = new DynamicSememeColumnInfo[allowedColumnInfo.size()];
int i = 0;
for (final int key: allowedColumnInfo.keySet()) {
if (key != i) {
throw new RuntimeException(
"The Assemblage concept: " + assemblageConcept +
" is not correctly assembled for use as an Assemblage for " +
"a DynamicSememeData Refex Type. It must contain sequential column numbers, with no gaps, which start at 0.");
}
this.refexColumnInfo[i++] = allowedColumnInfo.get(key);
}
}
//~--- methods -------------------------------------------------------------
/**
* Equals.
*
* @param obj the obj
* @return true, if successful
* @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;
}
final DynamicSememeUsageDescriptionImpl other = (DynamicSememeUsageDescriptionImpl) obj;
if (this.refexUsageDescriptorSequence != other.refexUsageDescriptorSequence) {
return false;
}
return true;
}
/**
* Hash code.
*
* @return the int
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.refexUsageDescriptorSequence;
return result;
}
/**
* Invent DynamicSememeUsageDescription info for other sememe types (that
* aren't dynamic), otherwise, calls {@link #read(int)} if it is a dynamic
* sememe.
*
* @param sememe the sememe in question
* @return the dynamic sememe usage description
*/
public static DynamicSememeUsageDescription mockOrRead(SememeChronology<?> sememe) {
final DynamicSememeUsageDescriptionImpl dsud = new DynamicSememeUsageDescriptionImpl();
dsud.name = Get.conceptDescriptionText(sememe.getAssemblageSequence());
dsud.referencedComponentTypeRestriction = null;
dsud.referencedComponentTypeSubRestriction = null;
dsud.refexUsageDescriptorSequence = sememe.getAssemblageSequence();
dsud.sememeUsageDescription = "-";
switch (sememe.getSememeType()) {
case COMPONENT_NID:
dsud.refexColumnInfo = new DynamicSememeColumnInfo[] {
new DynamicSememeColumnInfo(
Get.identifierService().getUuidPrimordialFromConceptId(sememe.getAssemblageSequence()).get(),
0,
DynamicSememeConstants.get().DYNAMIC_SEMEME_DT_NID.getPrimordialUuid(),
DynamicSememeDataType.NID,
null,
true,
null,
null,
false) };
break;
case LONG:
dsud.refexColumnInfo = new DynamicSememeColumnInfo[] {
new DynamicSememeColumnInfo(
Get.identifierService().getUuidPrimordialFromConceptId(sememe.getAssemblageSequence()).get(),
0,
DynamicSememeConstants.get().DYNAMIC_SEMEME_DT_LONG.getPrimordialUuid(),
DynamicSememeDataType.LONG,
null,
true,
null,
null,
false) };
break;
case DESCRIPTION:
case STRING:
case LOGIC_GRAPH:
dsud.refexColumnInfo = new DynamicSememeColumnInfo[] {
new DynamicSememeColumnInfo(
Get.identifierService().getUuidPrimordialFromConceptId(sememe.getAssemblageSequence()).get(),
0,
DynamicSememeConstants.get().DYNAMIC_SEMEME_DT_STRING.getPrimordialUuid(),
DynamicSememeDataType.STRING,
null,
true,
null,
null,
false) };
break;
case MEMBER:
dsud.refexColumnInfo = new DynamicSememeColumnInfo[] {};
break;
case DYNAMIC:
return read(sememe.getAssemblageSequence());
case UNKNOWN:
default:
throw new RuntimeException("Use case not yet supported");
}
return dsud;
}
/**
* Read.
*
* @param assemblageNidOrSequence the assemblage nid or sequence
* @return the dynamic sememe usage description
*/
public static DynamicSememeUsageDescription read(int assemblageNidOrSequence) {
// TODO (artf231860) [REFEX] maybe? implement a mechanism to allow the cache to be updated... for now
// cache is uneditable, and may be wrong, if the user changes the definition of a dynamic sememe. Perhaps
// implement a callback to clear the cache when we know a change of a certain type happened instead?
final int sequence = Get.identifierService()
.getConceptSequence(assemblageNidOrSequence);
DynamicSememeUsageDescriptionImpl temp = cache.get(sequence);
if (temp == null) {
logger.log(Level.FINEST, "Cache miss on DynamicSememeUsageDescription Cache");
temp = new DynamicSememeUsageDescriptionImpl(sequence);
cache.put(sequence, temp);
}
return temp;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the column info.
*
* @return the column info
*/
/*
* @see sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription#getColumnInfo()
*/
@Override
public DynamicSememeColumnInfo[] getColumnInfo() {
if (this.refexColumnInfo == null) {
this.refexColumnInfo = new DynamicSememeColumnInfo[] {};
}
return this.refexColumnInfo;
}
/**
* Test if dyn sememe.
*
* @param assemblageNidOrSequence the assemblage nid or sequence
* @return true, if dynamic sememe
*/
public static boolean isDynamicSememe(int assemblageNidOrSequence) {
if ((assemblageNidOrSequence >= 0) ||
(Get.identifierService().getChronologyTypeForNid(assemblageNidOrSequence) ==
ObjectChronologyType.CONCEPT)) {
try {
read(assemblageNidOrSequence);
return true;
} catch (final Exception e) {
return false;
}
} else {
return false;
}
}
/**
* Gets the dynamic sememe name.
*
* @return the dynamic sememe name
*/
/*
* @see sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription#getDyanmicSememeName()
*/
@Override
public String getDynamicSememeName() {
return this.name;
}
/**
* Gets the dynamic sememe usage description.
*
* @return the dynamic sememe usage description
*/
/*
* @see DynamicSememeUsageDescription#getDynamicSememeUsageDescription()
*/
@Override
public String getDynamicSememeUsageDescription() {
return this.sememeUsageDescription;
}
/**
* Gets the dynamic sememe usage descriptor sequence.
*
* @return the dynamic sememe usage descriptor sequence
*/
/*
* @see DynamicSememeUsageDescription#getDynamicSememeUsageDescriptorSequence()
*/
@Override
public int getDynamicSememeUsageDescriptorSequence() {
return this.refexUsageDescriptorSequence;
}
/**
* Gets the referenced component type restriction.
*
* @return the referenced component type restriction
*/
/*
* @see sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription#getReferencedComponentTypeRestriction()
*/
@Override
public ObjectChronologyType getReferencedComponentTypeRestriction() {
return this.referencedComponentTypeRestriction;
}
/**
* Gets the referenced component type sub restriction.
*
* @return the referenced component type sub restriction
*/
/*
* @see sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeUsageDescription#getReferencedComponentTypeSubRestriction()
*/
@Override
public SememeType getReferencedComponentTypeSubRestriction() {
return this.referencedComponentTypeSubRestriction;
}
}