/*
* 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.
*
*/
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package sh.isaac.provider.identifier;
//~--- JDK imports ------------------------------------------------------------
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.OptionalInt;
import java.util.concurrent.locks.StampedLock;
import java.util.stream.IntStream;
//~--- non-JDK imports --------------------------------------------------------
import sh.isaac.api.collections.NativeIntIntHashMap;
//~--- classes ----------------------------------------------------------------
/**
* Sequences start at 1.
* @author kec
*/
public class SequenceMap {
/** The Constant FIRST_SEQUENCE. */
public static final int FIRST_SEQUENCE = 1;
/** The Constant MINIMUM_LOAD_FACTOR. */
private static final double MINIMUM_LOAD_FACTOR = 0.75;
/** The Constant MAXIMUM_LOAD_FACTOR. */
private static final double MAXIMUM_LOAD_FACTOR = 0.9;
//~--- fields --------------------------------------------------------------
/** The sl. */
StampedLock sl = new StampedLock();
/** The next sequence. */
int nextSequence = FIRST_SEQUENCE;
/** The nid sequence map. */
final NativeIntIntHashMap nidSequenceMap;
/** The sequence nid map. */
final NativeIntIntHashMap sequenceNidMap;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new sequence map.
*
* @param defaultCapacity the default capacity
*/
public SequenceMap(int defaultCapacity) {
this.nidSequenceMap = new NativeIntIntHashMap(defaultCapacity, MINIMUM_LOAD_FACTOR, MAXIMUM_LOAD_FACTOR);
this.sequenceNidMap = new NativeIntIntHashMap(defaultCapacity, MINIMUM_LOAD_FACTOR, MAXIMUM_LOAD_FACTOR);
}
//~--- methods -------------------------------------------------------------
/**
* Adds the nid.
*
* @param nid the nid
* @return the int
*/
public int addNid(int nid) {
final long stamp = this.sl.writeLock();
try {
if (!this.nidSequenceMap.containsKey(nid)) {
final int sequence = this.nextSequence++;
this.nidSequenceMap.put(nid, sequence);
this.sequenceNidMap.put(sequence, nid);
return sequence;
}
return this.nidSequenceMap.get(nid);
} finally {
this.sl.unlockWrite(stamp);
}
}
/**
* Adds the nid if missing.
*
* @param nid the nid
* @return the int
*/
public int addNidIfMissing(int nid) {
long stamp = this.sl.tryOptimisticRead();
final boolean containsKey = this.nidSequenceMap.containsKey(nid);
int value = this.nidSequenceMap.get(nid);
if (this.sl.validate(stamp) && containsKey) {
return value;
}
stamp = this.sl.writeLock();
try {
if (this.nidSequenceMap.containsKey(nid)) {
return this.nidSequenceMap.get(nid);
}
value = this.nextSequence++;
this.nidSequenceMap.put(nid, value);
this.sequenceNidMap.put(value, nid);
return value;
} finally {
this.sl.unlockWrite(stamp);
}
}
/**
* Contains nid.
*
* @param nid the nid
* @return true, if successful
*/
public boolean containsNid(int nid) {
long stamp = this.sl.tryOptimisticRead();
boolean value = this.nidSequenceMap.containsKey(nid);
if (!this.sl.validate(stamp)) {
stamp = this.sl.readLock();
try {
value = this.nidSequenceMap.containsKey(nid);
} finally {
this.sl.unlockRead(stamp);
}
}
return value;
}
/**
* Read.
*
* @param mapFile the map file
* @throws IOException Signals that an I/O exception has occurred.
*/
public void read(File mapFile)
throws IOException {
try (DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(mapFile)))) {
final int size = input.readInt();
this.nextSequence = input.readInt();
this.nidSequenceMap.ensureCapacity(size);
this.sequenceNidMap.ensureCapacity(size);
for (int i = 0; i < size; i++) {
final int nid = input.readInt();
final int sequence = input.readInt();
this.nidSequenceMap.put(nid, sequence);
this.sequenceNidMap.put(sequence, nid);
}
}
}
/**
* Removes the nid.
*
* @param nid the nid
*/
public void removeNid(int nid) {
final long stamp = this.sl.writeLock();
try {
if (this.nidSequenceMap.containsKey(nid)) {
final int sequence = this.nidSequenceMap.get(nid);
this.nidSequenceMap.removeKey(nid);
this.sequenceNidMap.removeKey(sequence);
}
} finally {
this.sl.unlockWrite(stamp);
}
}
/**
* Write.
*
* @param mapFile the map file
* @throws IOException Signals that an I/O exception has occurred.
*/
public void write(File mapFile)
throws IOException {
try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mapFile)))) {
output.writeInt(this.nidSequenceMap.size());
output.writeInt(this.nextSequence);
this.nidSequenceMap.forEachPair((int nid,
int sequence) -> {
try {
output.writeInt(nid);
output.writeInt(sequence);
return true;
} catch (final IOException ex) {
throw new RuntimeException(ex);
}
});
}
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the concept nid stream.
*
* @return the concept nid stream
*/
public IntStream getConceptNidStream() {
return IntStream.of(this.nidSequenceMap.keys()
.elements());
}
/**
* Gets the next sequence.
*
* @return the next sequence
*/
public int getNextSequence() {
return this.nextSequence;
}
/**
* Gets the nid.
*
* @param sequence the sequence
* @return the nid
*/
public OptionalInt getNid(int sequence) {
long stamp = this.sl.tryOptimisticRead();
int value = this.sequenceNidMap.get(sequence);
if (!this.sl.validate(stamp)) {
stamp = this.sl.readLock();
try {
value = this.sequenceNidMap.get(sequence);
} finally {
this.sl.unlockRead(stamp);
}
}
if (value == 0) {
return OptionalInt.empty();
}
return OptionalInt.of(value);
}
/**
* Gets the nid fast.
*
* @param sequence the sequence
* @return the nid fast
*/
public int getNidFast(int sequence) {
long stamp = this.sl.tryOptimisticRead();
int value = this.sequenceNidMap.get(sequence);
if (!this.sl.validate(stamp)) {
stamp = this.sl.readLock();
try {
value = this.sequenceNidMap.get(sequence);
} finally {
this.sl.unlockRead(stamp);
}
}
return value;
}
/**
* Gets the sequence.
*
* @param nid the nid
* @return the sequence
*/
public OptionalInt getSequence(int nid) {
if (containsNid(nid)) {
long stamp = this.sl.tryOptimisticRead();
int value = this.nidSequenceMap.get(nid);
if (!this.sl.validate(stamp)) {
stamp = this.sl.readLock();
try {
value = this.nidSequenceMap.get(nid);
} finally {
this.sl.unlockRead(stamp);
}
}
return OptionalInt.of(value);
}
return OptionalInt.empty();
}
/**
* Gets the sequence fast.
*
* @param nid the nid
* @return the sequence fast
*/
public int getSequenceFast(int nid) {
long stamp = this.sl.tryOptimisticRead();
int value = this.nidSequenceMap.get(nid);
if (!this.sl.validate(stamp)) {
stamp = this.sl.readLock();
try {
value = this.nidSequenceMap.get(nid);
} finally {
this.sl.unlockRead(stamp);
}
}
return value;
}
/**
* Gets the sequence stream.
*
* @return the sequence stream
*/
public IntStream getSequenceStream() {
return IntStream.of(this.sequenceNidMap.keys()
.elements());
}
/**
* Gets the size.
*
* @return the size
*/
public int getSize() {
assert this.nidSequenceMap.size() == this.sequenceNidMap.size():
"nidSequenceMap.size() = " + this.nidSequenceMap.size() + " sequenceNidMap.size() = " +
this.sequenceNidMap.size();
return this.sequenceNidMap.size();
}
}