/*
* 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.provider.ibdf.diff;
//~--- JDK imports ------------------------------------------------------------
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
//~--- non-JDK imports --------------------------------------------------------
import sh.isaac.api.Get;
import sh.isaac.api.chronicle.LatestVersion;
import sh.isaac.api.chronicle.ObjectChronology;
import sh.isaac.api.component.concept.ConceptChronology;
import sh.isaac.api.component.concept.ConceptVersion;
import sh.isaac.api.component.sememe.SememeBuilder;
import sh.isaac.api.component.sememe.SememeBuilderService;
import sh.isaac.api.component.sememe.SememeChronology;
import sh.isaac.api.component.sememe.version.ComponentNidSememe;
import sh.isaac.api.component.sememe.version.DescriptionSememe;
import sh.isaac.api.component.sememe.version.DynamicSememe;
import sh.isaac.api.component.sememe.version.LogicGraphSememe;
import sh.isaac.api.component.sememe.version.LongSememe;
import sh.isaac.api.component.sememe.version.MutableComponentNidSememe;
import sh.isaac.api.component.sememe.version.MutableDescriptionSememe;
import sh.isaac.api.component.sememe.version.MutableDynamicSememe;
import sh.isaac.api.component.sememe.version.MutableLogicGraphSememe;
import sh.isaac.api.component.sememe.version.MutableLongSememe;
import sh.isaac.api.component.sememe.version.MutableStringSememe;
import sh.isaac.api.component.sememe.version.SememeVersion;
import sh.isaac.api.component.sememe.version.StringSememe;
import sh.isaac.api.component.sememe.version.dynamicSememe.DynamicSememeData;
import sh.isaac.api.externalizable.OchreExternalizable;
import sh.isaac.api.externalizable.OchreExternalizableObjectType;
import sh.isaac.api.identity.StampedVersion;
import sh.isaac.api.relationship.RelationshipVersionAdaptor;
import sh.isaac.model.configuration.StampCoordinates;
import sh.isaac.model.relationship.RelationshipAdaptorChronicleKeyImpl;
import sh.isaac.model.relationship.RelationshipVersionAdaptorImpl;
import sh.isaac.model.sememe.dataTypes.DynamicSememeLongImpl;
import sh.isaac.model.sememe.dataTypes.DynamicSememeNidImpl;
import sh.isaac.model.sememe.dataTypes.DynamicSememeStringImpl;
import sh.isaac.model.sememe.version.ComponentNidSememeImpl;
import sh.isaac.model.sememe.version.DescriptionSememeImpl;
import sh.isaac.model.sememe.version.DynamicSememeImpl;
import sh.isaac.model.sememe.version.LogicGraphSememeImpl;
import sh.isaac.model.sememe.version.LongSememeImpl;
import sh.isaac.model.sememe.version.SememeVersionImpl;
import sh.isaac.model.sememe.version.StringSememeImpl;
//~--- classes ----------------------------------------------------------------
/**
* Utility methods in support of BinaryDataDifferProvider used to see if two
* components are the same and to create new versions when necessary.
*
* {@link BinaryDataDifferProvider}
*
* @author <a href="mailto:jefron@westcoastinformatics.com">Jesse Efron</a>
*/
public class BinaryDataDifferProviderUtility {
/** The component change found. */
static boolean componentChangeFound = false;
/** The new import date. */
static long newImportDate;
//~--- fields --------------------------------------------------------------
/** The diff on status. */
boolean diffOnStatus;
/** The diff on timestamp. */
boolean diffOnTimestamp;
/** The diff on author. */
boolean diffOnAuthor;
/** The diff on module. */
boolean diffOnModule;
/** The diff on path. */
boolean diffOnPath;
/** The sememe builder service. */
private final SememeBuilderService<?> sememeBuilderService;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new binary data differ provider utility.
*
* @param diffOnStatus the diff on status
* @param diffOnTimestamp the diff on timestamp
* @param diffOnAuthor the diff on author
* @param diffOnModule the diff on module
* @param diffOnPath the diff on path
*/
public BinaryDataDifferProviderUtility(Boolean diffOnStatus,
Boolean diffOnTimestamp,
Boolean diffOnAuthor,
Boolean diffOnModule,
Boolean diffOnPath) {
this.diffOnStatus = diffOnStatus;
this.diffOnTimestamp = diffOnTimestamp;
this.diffOnAuthor = diffOnAuthor;
this.diffOnModule = diffOnModule;
this.diffOnPath = diffOnPath;
this.sememeBuilderService = Get.sememeBuilderService();
}
//~--- methods -------------------------------------------------------------
/**
* Adds the new inactive version.
*
* @param oldChron the old chron
* @param type the type
* @param inactiveStampSeq the inactive stamp seq
* @return the ochre externalizable
*/
public OchreExternalizable addNewInactiveVersion(OchreExternalizable oldChron,
OchreExternalizableObjectType type,
int inactiveStampSeq) {
final LatestVersion<StampedVersion> latestVersion =
((ObjectChronology<StampedVersion>) oldChron).getLatestVersion(StampedVersion.class,
StampCoordinates.getDevelopmentLatestActiveOnly())
.get();
if (type == OchreExternalizableObjectType.CONCEPT) {
((ConceptVersion<?>) latestVersion.value()).getChronology()
.createMutableVersion(inactiveStampSeq);
} else if (type == OchreExternalizableObjectType.SEMEME) {
final SememeVersion originalVersion = (SememeVersion) latestVersion.value();
SememeVersion createdVersion = originalVersion.getChronology()
.createMutableVersion(
getSememeClass((SememeVersion) latestVersion.value()),
inactiveStampSeq);
createdVersion = populateData(createdVersion, originalVersion, inactiveStampSeq);
}
return oldChron;
}
/**
* Diff.
*
* @param oldChron the old chron
* @param newChron the new chron
* @param stampSeq the stamp seq
* @param type the type
* @return the ochre externalizable
*/
public OchreExternalizable diff(ObjectChronology<?> oldChron,
ObjectChronology<?> newChron,
int stampSeq,
OchreExternalizableObjectType type) {
List<StampedVersion> oldVersions = null;
final List<StampedVersion> newVersions = (List<StampedVersion>) newChron.getVersionList();
if (oldChron == null) {
return createNewChronology(newChron, type, stampSeq);
}
boolean newVersionAdded = false;
oldVersions = (List<StampedVersion>) oldChron.getVersionList();
for (final StampedVersion nv: newVersions) {
boolean equivalenceFound = false;
for (final StampedVersion ov: oldVersions) {
if (isEquivalent(ov, nv, type)) {
equivalenceFound = true;
break;
}
}
if (!equivalenceFound) {
// versionsToAdd.add(ov);
addNewActiveVersion(oldChron, nv, type, stampSeq);
newVersionAdded = true;
}
}
if (!newVersionAdded) {
return null;
}
return oldChron;
}
/**
* Adds the new active version.
*
* @param oldChron the old chron
* @param newVersion the new version
* @param type the type
* @param activeStampSeq the active stamp seq
*/
private void addNewActiveVersion(ObjectChronology<?> oldChron,
StampedVersion newVersion,
OchreExternalizableObjectType type,
int activeStampSeq) {
try {
if (type == OchreExternalizableObjectType.CONCEPT) {
((ConceptChronology<?>) oldChron).createMutableVersion(((ConceptVersion<?>) newVersion).getStampSequence());
} else if (type == OchreExternalizableObjectType.SEMEME) {
SememeVersion createdVersion =
((SememeChronology) oldChron).createMutableVersion(((SememeChronology<?>) oldChron).getClass(),
((SememeVersion<?>) newVersion).getStampSequence());
createdVersion = populateData(createdVersion, (SememeVersion<?>) newVersion, activeStampSeq);
}
} catch (final Exception e) {
e.printStackTrace();
}
}
/**
* Creates the new chronology.
*
* @param newChron the new chron
* @param type the type
* @param stampSeq the stamp seq
* @return the ochre externalizable
*/
private OchreExternalizable createNewChronology(ObjectChronology<?> newChron,
OchreExternalizableObjectType type,
int stampSeq) {
try {
if (type == OchreExternalizableObjectType.CONCEPT) {
return newChron;
} else if (type == OchreExternalizableObjectType.SEMEME) {
final List<ObjectChronology<? extends StampedVersion>> builtObjects = new ArrayList<>();
SememeChronology<?> sememe = null;
for (final StampedVersion version: newChron.getVersionList()) {
final SememeBuilder<?> builder = getBuilder((SememeVersion<?>) version);
sememe = builder.build(stampSeq, builtObjects);
}
return sememe;
} else {
throw new Exception("Unsupported OchreExternalizableObjectType: " + type);
}
} catch (final Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Populate data.
*
* @param newVer the new ver
* @param originalVersion the original version
* @param inactiveStampSeq the inactive stamp seq
* @return the sememe version
*/
private SememeVersion<?> populateData(SememeVersion<?> newVer,
SememeVersion<?> originalVersion,
int inactiveStampSeq) {
switch (newVer.getChronology()
.getSememeType()) {
case MEMBER:
return newVer;
case COMPONENT_NID:
((MutableComponentNidSememe<?>) newVer).setComponentNid(
((ComponentNidSememe<?>) originalVersion).getComponentNid());
return newVer;
case DESCRIPTION:
((MutableDescriptionSememe<?>) newVer).setText(((DescriptionSememe<?>) originalVersion).getText());
((MutableDescriptionSememe<?>) newVer).setDescriptionTypeConceptSequence(
((DescriptionSememe<?>) originalVersion).getDescriptionTypeConceptSequence());
((MutableDescriptionSememe<?>) newVer).setCaseSignificanceConceptSequence(
((DescriptionSememe<?>) originalVersion).getCaseSignificanceConceptSequence());
((MutableDescriptionSememe<?>) newVer).setLanguageConceptSequence(
((DescriptionSememe<?>) originalVersion).getLanguageConceptSequence());
return newVer;
case DYNAMIC:
((MutableDynamicSememe<?>) newVer).setData(((DynamicSememe<?>) originalVersion).getData());
return newVer;
case LONG:
((MutableLongSememe<?>) newVer).setLongValue(((LongSememe<?>) originalVersion).getLongValue());
return newVer;
case STRING:
((MutableStringSememe<?>) newVer).setString(((StringSememe<?>) originalVersion).getString());
return newVer;
case RELATIONSHIP_ADAPTOR:
final RelationshipVersionAdaptorImpl origRelVer = (RelationshipVersionAdaptorImpl) originalVersion;
final RelationshipAdaptorChronicleKeyImpl key =
new RelationshipAdaptorChronicleKeyImpl(origRelVer.getOriginSequence(),
origRelVer.getDestinationSequence(),
origRelVer.getTypeSequence(),
origRelVer.getGroup(),
origRelVer.getPremiseType(),
origRelVer.getNodeSequence());
return new RelationshipVersionAdaptorImpl(key, inactiveStampSeq);
case LOGIC_GRAPH:
((MutableLogicGraphSememe<?>) newVer).setGraphData(((LogicGraphSememe<?>) originalVersion).getGraphData());
return newVer;
case UNKNOWN:
throw new UnsupportedOperationException();
}
return null;
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the builder.
*
* @param version the version
* @return the builder
*/
private SememeBuilder<?> getBuilder(SememeVersion<?> version) {
SememeBuilder<?> builder = null;
switch (version.getChronology()
.getSememeType()) {
case COMPONENT_NID:
final ComponentNidSememe<?> compNidSememe = (ComponentNidSememe<?>) version;
builder = this.sememeBuilderService.getComponentSememeBuilder(compNidSememe.getComponentNid(),
compNidSememe.getReferencedComponentNid(),
compNidSememe.getAssemblageSequence());
break;
case DESCRIPTION:
final DescriptionSememe<?> descSememe = (DescriptionSememe<?>) version;
builder =
this.sememeBuilderService.getDescriptionSememeBuilder(descSememe.getCaseSignificanceConceptSequence(),
descSememe.getLanguageConceptSequence(),
descSememe.getDescriptionTypeConceptSequence(),
descSememe.getText(),
descSememe.getReferencedComponentNid());
break;
case DYNAMIC:
final DynamicSememe<?> dynSememe = (DynamicSememe<?>) version;
builder = this.sememeBuilderService.getDynamicSememeBuilder(dynSememe.getReferencedComponentNid(),
dynSememe.getAssemblageSequence(),
dynSememe.getData());
break;
case LONG:
final LongSememe<?> longSememe = (LongSememe<?>) version;
builder = this.sememeBuilderService.getLongSememeBuilder(longSememe.getLongValue(),
longSememe.getReferencedComponentNid(),
longSememe.getAssemblageSequence());
break;
case MEMBER:
builder = this.sememeBuilderService.getMembershipSememeBuilder(version.getReferencedComponentNid(),
version.getAssemblageSequence());
break;
case STRING:
final StringSememe<?> stringSememe = (StringSememe<?>) version;
builder = this.sememeBuilderService.getStringSememeBuilder(stringSememe.getString(),
stringSememe.getReferencedComponentNid(),
stringSememe.getAssemblageSequence());
break;
case LOGIC_GRAPH:
final LogicGraphSememe<?> logicGraphSememe = (LogicGraphSememe<?>) version;
builder = this.sememeBuilderService.getLogicalExpressionSememeBuilder(logicGraphSememe.getLogicalExpression(),
logicGraphSememe.getReferencedComponentNid(),
logicGraphSememe.getAssemblageSequence());
break;
case UNKNOWN:
case RELATIONSHIP_ADAPTOR: // Dan doesn't believe rel adapaters are ever created / written to ibdf
default:
throw new UnsupportedOperationException();
}
builder.setPrimordialUuid(version.getPrimordialUuid());
return builder;
}
/**
* Checks if equivalent.
*
* @param ov the ov
* @param nv the nv
* @param type the type
* @return true, if equivalent
*/
private boolean isEquivalent(StampedVersion ov, StampedVersion nv, OchreExternalizableObjectType type) {
if ((this.diffOnStatus && (ov.getState() != nv.getState())) ||
(this.diffOnTimestamp && (ov.getTime() != nv.getTime())) ||
(this.diffOnAuthor && (ov.getAuthorSequence() != nv.getAuthorSequence())) ||
(this.diffOnModule && (ov.getModuleSequence() != nv.getModuleSequence())) ||
(this.diffOnPath && (ov.getPathSequence() != nv.getPathSequence()))) {
return false;
} else if (type == OchreExternalizableObjectType.CONCEPT) {
// No other value to analyze equivalence for a concept, so return
// true
return true;
} else {
// Analyze Sememe
final DynamicSememeData[] oldData = getSememeData((SememeVersion<?>) ov);
final DynamicSememeData[] newData = getSememeData((SememeVersion<?>) nv);
return isSememeDataEquivalent(oldData, newData);
}
}
/**
* Gets the new import date.
*
* @return the new import date
*/
public long getNewImportDate() {
return newImportDate;
}
//~--- set methods ---------------------------------------------------------
/**
* Sets the new import date.
*
* @param importDate the new new import date
*/
public void setNewImportDate(String importDate) {
// Must be in format of 2005-10-06
try {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
newImportDate = sdf.parse(importDate)
.getTime();
} catch (final ParseException e) {
final Date d = new Date();
newImportDate = d.getTime();
}
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the sememe class.
*
* @param sememe the sememe
* @return the sememe class
*/
private Class<?> getSememeClass(SememeVersion<?> sememe) {
{
switch (sememe.getChronology()
.getSememeType()) {
case COMPONENT_NID:
return ComponentNidSememeImpl.class;
case DESCRIPTION:
return DescriptionSememeImpl.class;
case DYNAMIC:
return DynamicSememeImpl.class;
case LONG:
return LongSememeImpl.class;
case MEMBER:
return SememeVersionImpl.class;
case STRING:
return StringSememeImpl.class;
case RELATIONSHIP_ADAPTOR:
return RelationshipVersionAdaptorImpl.class;
case LOGIC_GRAPH:
return LogicGraphSememeImpl.class;
case UNKNOWN:
default:
throw new UnsupportedOperationException();
}
}
}
/**
* Gets the sememe data.
*
* @param sememe the sememe
* @return the sememe data
*/
private DynamicSememeData[] getSememeData(SememeVersion<?> sememe) {
{
switch (sememe.getChronology()
.getSememeType()) {
case COMPONENT_NID:
return new DynamicSememeData[] {
new DynamicSememeNidImpl(((ComponentNidSememe<?>) sememe).getComponentNid()) };
case DESCRIPTION:
return new DynamicSememeData[] { new DynamicSememeStringImpl(((DescriptionSememe<?>) sememe).getText()) };
case DYNAMIC:
return ((DynamicSememe<?>) sememe).getData();
case LONG:
return new DynamicSememeData[] { new DynamicSememeLongImpl(((LongSememe<?>) sememe).getLongValue()) };
case MEMBER:
return new DynamicSememeData[] {};
case STRING:
return new DynamicSememeData[] { new DynamicSememeStringImpl(((StringSememe<?>) sememe).getString()) };
case RELATIONSHIP_ADAPTOR:
return new DynamicSememeData[] {
new DynamicSememeStringImpl(((RelationshipVersionAdaptor<?>) sememe).toString()) };
case LOGIC_GRAPH:
return new DynamicSememeData[] { new DynamicSememeStringImpl(((LogicGraphSememe<?>) sememe).toString()) };
case UNKNOWN:
default:
throw new UnsupportedOperationException();
}
}
}
/**
* Checks if sememe data equivalent.
*
* @param oldData the old data
* @param newData the new data
* @return true, if sememe data equivalent
*/
private boolean isSememeDataEquivalent(DynamicSememeData[] oldData, DynamicSememeData[] newData) {
// Verify same values
if (oldData.length != newData.length) {
return false;
} else {
for (int i = 0; i < oldData.length; i++) {
boolean matchFound = false;
if (((oldData[i] == null) && (newData[i] == null)) ||
Arrays.equals(oldData[i].getData(), newData[i].getData())) {
matchFound = true;
continue;
}
if (!matchFound) {
return false;
}
}
return true;
}
}
}