/*
* 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.builder;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayList;
import java.util.List;
//~--- non-JDK imports --------------------------------------------------------
import javafx.concurrent.Task;
import org.apache.commons.lang3.StringUtils;
import sh.isaac.api.Get;
import sh.isaac.api.LookupService;
import sh.isaac.api.bootstrap.TermAux;
import sh.isaac.api.chronicle.ObjectChronology;
import sh.isaac.api.commit.ChangeCheckerMode;
import sh.isaac.api.component.concept.ConceptBuilder;
import sh.isaac.api.component.concept.ConceptChronology;
import sh.isaac.api.component.concept.ConceptSpecification;
import sh.isaac.api.component.concept.description.DescriptionBuilder;
import sh.isaac.api.component.concept.description.DescriptionBuilderService;
import sh.isaac.api.component.sememe.SememeBuilderService;
import sh.isaac.api.coordinate.EditCoordinate;
import sh.isaac.api.coordinate.LogicCoordinate;
import sh.isaac.api.identity.StampedVersion;
import sh.isaac.api.logic.LogicalExpression;
import sh.isaac.api.logic.LogicalExpressionBuilder;
import sh.isaac.api.task.OptionalWaitTask;
import sh.isaac.model.concept.ConceptChronologyImpl;
//~--- classes ----------------------------------------------------------------
/**
* The Class ConceptBuilderOchreImpl.
*
* @author kec
*/
public class ConceptBuilderOchreImpl
extends ComponentBuilder<ConceptChronology<?>>
implements ConceptBuilder {
/** The description builders. */
private final List<DescriptionBuilder<?, ?>> descriptionBuilders = new ArrayList<>();
/** The logical expression builders. */
private final List<LogicalExpressionBuilder> logicalExpressionBuilders = new ArrayList<>();
/** The logical expressions. */
private final List<LogicalExpression> logicalExpressions = new ArrayList<>();
/** The fsn description builder. */
private transient DescriptionBuilder<?, ?> fsnDescriptionBuilder = null;
/** The preferred description builder. */
private transient DescriptionBuilder<?, ?> preferredDescriptionBuilder = null;
/** The concept name. */
private final String conceptName;
/** The semantic tag. */
private final String semanticTag;
/** The default language for descriptions. */
private final ConceptSpecification defaultLanguageForDescriptions;
/** The default dialect assemblage for descriptions. */
private final ConceptSpecification defaultDialectAssemblageForDescriptions;
/** The default logic coordinate. */
private final LogicCoordinate defaultLogicCoordinate;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new concept builder ochre impl.
*
* @param conceptName - Optional - if specified, a FSN will be created using this value (but see additional information on semanticTag)
* @param semanticTag - Optional - if specified, conceptName must be specified, and two descriptions will be created using the following forms:
* FSN: - "conceptName (semanticTag)"
* Preferred: "conceptName"
* If not specified:
* If the specified FSN contains a semantic tag, the FSN will be created using that value. A preferred term will be created by stripping the
* supplied semantic tag.
* If the specified FSN does not contain a semantic tag, no preferred term will be created.
* @param logicalExpression - Optional
* @param defaultLanguageForDescriptions - Optional - used as the language for the created FSN and preferred term
* @param defaultDialectAssemblageForDescriptions - Optional - used as the language for the created FSN and preferred term
* @param defaultLogicCoordinate - Optional - used during the creation of the logical expression, if either a logicalExpression
* is passed, or if @link {@link #addLogicalExpression(LogicalExpression)} or {@link #addLogicalExpressionBuilder(LogicalExpressionBuilder)} are
* used later.
*/
public ConceptBuilderOchreImpl(String conceptName,
String semanticTag,
LogicalExpression logicalExpression,
ConceptSpecification defaultLanguageForDescriptions,
ConceptSpecification defaultDialectAssemblageForDescriptions,
LogicCoordinate defaultLogicCoordinate) {
this.conceptName = conceptName;
this.semanticTag = semanticTag;
this.defaultLanguageForDescriptions = defaultLanguageForDescriptions;
this.defaultDialectAssemblageForDescriptions = defaultDialectAssemblageForDescriptions;
this.defaultLogicCoordinate = defaultLogicCoordinate;
if (logicalExpression != null) {
this.logicalExpressions.add(logicalExpression);
}
}
//~--- methods -------------------------------------------------------------
/**
* Adds the description.
*
* @param descriptionBuilder the description builder
* @return the concept builder
*/
@Override
public ConceptBuilder addDescription(DescriptionBuilder<?, ?> descriptionBuilder) {
this.descriptionBuilders.add(descriptionBuilder);
return this;
}
/**
* Adds the description.
*
* @param value the value
* @param descriptionType the description type
* @return the concept builder
*/
@Override
public ConceptBuilder addDescription(String value, ConceptSpecification descriptionType) {
if ((this.defaultLanguageForDescriptions == null) || (this.defaultDialectAssemblageForDescriptions == null)) {
throw new IllegalStateException("language and dialect are required if a concept name is provided");
}
if (!this.conceptName.equals(value)) {
this.descriptionBuilders.add(LookupService.getService(DescriptionBuilderService.class)
.getDescriptionBuilder(value, this, descriptionType, this.defaultLanguageForDescriptions)
.addAcceptableInDialectAssemblage(this.defaultDialectAssemblageForDescriptions));
}
return this;
}
/**
* Adds the logical expression.
*
* @param logicalExpression the logical expression
* @return the concept builder
*/
@Override
public ConceptBuilder addLogicalExpression(LogicalExpression logicalExpression) {
this.logicalExpressions.add(logicalExpression);
return this;
}
/**
* Adds the logical expression builder.
*
* @param logicalExpressionBuilder the logical expression builder
* @return the concept builder
*/
@Override
public ConceptBuilder addLogicalExpressionBuilder(LogicalExpressionBuilder logicalExpressionBuilder) {
this.logicalExpressionBuilders.add(logicalExpressionBuilder);
return this;
}
/**
* Builds the.
*
* @param stampCoordinate the stamp coordinate
* @param builtObjects the built objects
* @return the concept chronology
* @throws IllegalStateException the illegal state exception
*/
@Override
public ConceptChronology build(int stampCoordinate,
List<ObjectChronology<? extends StampedVersion>> builtObjects)
throws IllegalStateException {
final ConceptChronologyImpl conceptChronology = (ConceptChronologyImpl) Get.conceptService()
.getConcept(getUuids());
conceptChronology.createMutableVersion(stampCoordinate);
builtObjects.add(conceptChronology);
if (getFullySpecifiedDescriptionBuilder() != null) {
this.descriptionBuilders.add(getFullySpecifiedDescriptionBuilder());
}
if (getSynonymPreferredDescriptionBuilder() != null) {
this.descriptionBuilders.add(getSynonymPreferredDescriptionBuilder());
}
this.descriptionBuilders.forEach((builder) -> {
builder.build(stampCoordinate, builtObjects);
});
if ((this.defaultLogicCoordinate == null) &&
((this.logicalExpressions.size() > 0) || (this.logicalExpressionBuilders.size() > 0))) {
throw new IllegalStateException("A logic coordinate is required when a logical expression is passed");
}
final SememeBuilderService builderService = LookupService.getService(SememeBuilderService.class);
for (final LogicalExpression logicalExpression: this.logicalExpressions) {
this.sememeBuilders.add(builderService.getLogicalExpressionSememeBuilder(logicalExpression,
this,
this.defaultLogicCoordinate.getStatedAssemblageSequence()));
}
for (final LogicalExpressionBuilder builder: this.logicalExpressionBuilders) {
this.sememeBuilders.add(builderService.getLogicalExpressionSememeBuilder(builder.build(),
this,
this.defaultLogicCoordinate.getStatedAssemblageSequence()));
}
this.sememeBuilders.forEach((builder) -> builder.build(stampCoordinate, builtObjects));
return conceptChronology;
}
/**
* Builds the.
*
* @param editCoordinate the edit coordinate
* @param changeCheckerMode the change checker mode
* @param builtObjects the built objects
* @return the optional wait task
* @throws IllegalStateException the illegal state exception
*/
@Override
public OptionalWaitTask<ConceptChronology<?>> build(EditCoordinate editCoordinate,
ChangeCheckerMode changeCheckerMode,
List<ObjectChronology<? extends StampedVersion>> builtObjects)
throws IllegalStateException {
final ArrayList<OptionalWaitTask<?>> nestedBuilders = new ArrayList<>();
final ConceptChronologyImpl conceptChronology = (ConceptChronologyImpl) Get.conceptService()
.getConcept(getUuids());
conceptChronology.createMutableVersion(this.state, editCoordinate);
builtObjects.add(conceptChronology);
if (getFullySpecifiedDescriptionBuilder() != null) {
this.descriptionBuilders.add(getFullySpecifiedDescriptionBuilder());
}
if (getSynonymPreferredDescriptionBuilder() != null) {
this.descriptionBuilders.add(getSynonymPreferredDescriptionBuilder());
}
this.descriptionBuilders.forEach((builder) -> {
nestedBuilders.add(builder.build(editCoordinate,
changeCheckerMode,
builtObjects));
});
if ((this.defaultLogicCoordinate == null) &&
((this.logicalExpressions.size() > 0) || (this.logicalExpressionBuilders.size() > 0))) {
throw new IllegalStateException("A logic coordinate is required when a logical expression is passed");
}
final SememeBuilderService builderService = LookupService.getService(SememeBuilderService.class);
for (final LogicalExpression logicalExpression: this.logicalExpressions) {
this.sememeBuilders.add(builderService.getLogicalExpressionSememeBuilder(logicalExpression,
this,
this.defaultLogicCoordinate.getStatedAssemblageSequence()));
}
for (final LogicalExpressionBuilder builder: this.logicalExpressionBuilders) {
this.sememeBuilders.add(builderService.getLogicalExpressionSememeBuilder(builder.build(),
this,
this.defaultLogicCoordinate.getStatedAssemblageSequence()));
}
this.sememeBuilders.forEach((builder) -> nestedBuilders.add(builder.build(editCoordinate,
changeCheckerMode,
builtObjects)));
Task<Void> primaryNested;
if (changeCheckerMode == ChangeCheckerMode.ACTIVE) {
primaryNested = Get.commitService()
.addUncommitted(conceptChronology);
} else {
primaryNested = Get.commitService()
.addUncommittedNoChecks(conceptChronology);
}
return new OptionalWaitTask<>(primaryNested, conceptChronology, nestedBuilders);
}
/**
* Merge from spec.
*
* @param conceptSpec the concept spec
* @return the concept builder
*/
@Override
public ConceptBuilder mergeFromSpec(ConceptSpecification conceptSpec) {
setPrimordialUuid(conceptSpec.getPrimordialUuid());
addUuids(conceptSpec.getUuids());
if (!this.conceptName.equals(conceptSpec.getConceptDescriptionText())) {
addDescription(conceptSpec.getConceptDescriptionText(), TermAux.SYNONYM_DESCRIPTION_TYPE);
}
return this;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the concept description text.
*
* @return the concept description text
*/
@Override
public String getConceptDescriptionText() {
return this.conceptName;
}
/**
* Gets the fully specified description builder.
*
* @return the fully specified description builder
*/
@Override
public DescriptionBuilder<?, ?> getFullySpecifiedDescriptionBuilder() {
synchronized (this) {
if ((this.fsnDescriptionBuilder == null) && StringUtils.isNotBlank(this.conceptName)) {
final StringBuilder descriptionTextBuilder = new StringBuilder();
descriptionTextBuilder.append(this.conceptName);
if (StringUtils.isNotBlank(this.semanticTag)) {
if ((this.conceptName.lastIndexOf('(') > 0) &&
(this.conceptName.lastIndexOf(')') == this.conceptName.length() - 1)) {
throw new IllegalArgumentException(
"A semantic tag was passed, but this fsn description already appears to contain a semantic tag");
}
descriptionTextBuilder.append(" (");
descriptionTextBuilder.append(this.semanticTag);
descriptionTextBuilder.append(")");
}
if ((this.defaultLanguageForDescriptions == null) ||
(this.defaultDialectAssemblageForDescriptions == null)) {
throw new IllegalStateException("language and dialect are required if a concept name is provided");
}
this.fsnDescriptionBuilder = LookupService.getService(DescriptionBuilderService.class)
.getDescriptionBuilder(descriptionTextBuilder.toString(),
this,
TermAux.FULLY_SPECIFIED_DESCRIPTION_TYPE,
this.defaultLanguageForDescriptions)
.addPreferredInDialectAssemblage(this.defaultDialectAssemblageForDescriptions);
}
}
return this.fsnDescriptionBuilder;
}
/**
* Gets the synonym preferred description builder.
*
* @return the synonym preferred description builder
*/
@Override
public DescriptionBuilder<?, ?> getSynonymPreferredDescriptionBuilder() {
synchronized (this) {
if (this.preferredDescriptionBuilder == null) {
if ((this.defaultLanguageForDescriptions == null) ||
(this.defaultDialectAssemblageForDescriptions == null)) {
throw new IllegalStateException("language and dialect are required if a concept name is provided");
}
String prefName = null;
if (StringUtils.isNotBlank(this.semanticTag)) {
prefName = this.conceptName;
} else if ((this.conceptName.lastIndexOf('(') > 0) &&
(this.conceptName.lastIndexOf(')') == this.conceptName.length())) {
// they didn't provide a stand-alone semantic tag. If they included a semantic tag in what they provided, strip it.
// If not, don't create a preferred term, as it would just be identical to the FSN.
prefName = this.conceptName.substring(0, this.conceptName.lastIndexOf('('))
.trim();
}
if (prefName != null) {
this.preferredDescriptionBuilder = LookupService.getService(DescriptionBuilderService.class)
.getDescriptionBuilder(prefName,
this,
TermAux.SYNONYM_DESCRIPTION_TYPE,
this.defaultLanguageForDescriptions)
.addPreferredInDialectAssemblage(this.defaultDialectAssemblageForDescriptions);
}
}
}
return this.preferredDescriptionBuilder;
}
}