2021-01-02 16:15:27 +01:00
|
|
|
/*******************************************************************************
|
|
|
|
* Copyright (c) 2012 IT Just working.
|
|
|
|
* All rights reserved. This program and the accompanying materials
|
|
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
|
|
* which accompanies this distribution, and is available at
|
|
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
|
|
*
|
|
|
|
* Contributors:
|
|
|
|
* IT Just working - initial API and implementation
|
|
|
|
*******************************************************************************/
|
|
|
|
package com.minres.scviewer.database.text;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
2021-01-08 15:04:30 +01:00
|
|
|
import java.util.List;
|
2021-01-02 16:15:27 +01:00
|
|
|
import java.util.Map;
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
import java.util.zip.GZIPInputStream;
|
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
import org.mapdb.DB;
|
|
|
|
import org.mapdb.DB.TreeMapSink;
|
|
|
|
import org.mapdb.DBMaker;
|
|
|
|
import org.mapdb.Serializer;
|
|
|
|
|
2021-01-07 17:38:29 +01:00
|
|
|
import com.google.common.collect.HashMultimap;
|
2021-01-02 16:15:27 +01:00
|
|
|
import com.minres.scviewer.database.AssociationType;
|
|
|
|
import com.minres.scviewer.database.DataType;
|
2021-01-03 14:16:56 +01:00
|
|
|
import com.minres.scviewer.database.EventKind;
|
2021-01-02 16:15:27 +01:00
|
|
|
import com.minres.scviewer.database.IWaveform;
|
|
|
|
import com.minres.scviewer.database.IWaveformDb;
|
|
|
|
import com.minres.scviewer.database.IWaveformDbLoader;
|
|
|
|
import com.minres.scviewer.database.InputFormatException;
|
|
|
|
import com.minres.scviewer.database.RelationType;
|
2021-01-04 17:39:11 +01:00
|
|
|
import com.minres.scviewer.database.RelationTypeFactory;
|
2021-01-03 14:16:56 +01:00
|
|
|
import com.minres.scviewer.database.tx.ITx;
|
2021-01-02 16:15:27 +01:00
|
|
|
|
|
|
|
public class TextDbLoader implements IWaveformDbLoader{
|
|
|
|
|
|
|
|
private Long maxTime=0L;
|
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
DB mapDb=null;
|
|
|
|
|
|
|
|
final Map<String, RelationType> relationTypes=new HashMap<>();
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
final Map<Long, TxStream> txStreams = new HashMap<>();
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
final Map<Long, TxGenerator> txGenerators = new HashMap<>();
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-07 17:38:29 +01:00
|
|
|
Map<Long, ScvTx> transactions = null;
|
2021-01-04 17:39:11 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
final Map<String, TxAttributeType> attributeTypes = new HashMap<>();
|
2021-01-02 16:15:27 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
final HashMultimap<Long, ScvRelation> relationsIn = HashMultimap.create();
|
2021-01-02 16:15:27 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
final HashMultimap<Long, ScvRelation> relationsOut = HashMultimap.create();
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
HashMap<Long, Tx> txCache = new HashMap<>();
|
|
|
|
|
|
|
|
List<Thread> threads = new ArrayList<>();
|
|
|
|
|
2021-01-03 14:16:56 +01:00
|
|
|
@Override
|
2021-01-02 16:15:27 +01:00
|
|
|
public Long getMaxTime() {
|
|
|
|
return maxTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Collection<IWaveform> getAllWaves() {
|
2021-01-03 14:16:56 +01:00
|
|
|
return new ArrayList<>(txStreams.values());
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static final byte[] x = "scv_tr_stream".getBytes();
|
|
|
|
|
2021-01-03 14:16:56 +01:00
|
|
|
@SuppressWarnings("unchecked")
|
2021-01-02 16:15:27 +01:00
|
|
|
@Override
|
|
|
|
public boolean load(IWaveformDb db, File file) throws InputFormatException {
|
2021-01-08 15:04:30 +01:00
|
|
|
dispose();
|
2021-01-02 16:15:27 +01:00
|
|
|
if(file.isDirectory() || !file.exists()) return false;
|
2021-01-02 18:04:48 +01:00
|
|
|
TextDbParser parser = new TextDbParser(this);
|
|
|
|
boolean gzipped = isGzipped(file);
|
2021-01-02 16:15:27 +01:00
|
|
|
try {
|
2021-01-02 18:04:48 +01:00
|
|
|
if(!isTxfile(gzipped?new GZIPInputStream(new FileInputStream(file)):new FileInputStream(file)))
|
|
|
|
return false;
|
2021-01-03 14:16:56 +01:00
|
|
|
} catch(Exception e) {
|
2021-01-02 18:04:48 +01:00
|
|
|
throw new InputFormatException();
|
|
|
|
}
|
2021-01-08 15:04:30 +01:00
|
|
|
|
|
|
|
if(file.length() < 75000000*(gzipped?1:10) || "memory".equals(System.getProperty("ScvBackingDB", "file")))
|
|
|
|
mapDb = DBMaker
|
|
|
|
.memoryDirectDB()
|
|
|
|
.allocateStartSize(512*1024*1024)
|
|
|
|
.allocateIncrement(128*1024*1024)
|
|
|
|
.cleanerHackEnable()
|
|
|
|
.make();
|
|
|
|
else {
|
|
|
|
File mapDbFile;
|
|
|
|
try {
|
|
|
|
mapDbFile = File.createTempFile("."+file.getName(), ".mapdb", null /*file.parentFile*/);
|
|
|
|
} catch (IOException e1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
mapDbFile.delete(); // we just need a file name
|
|
|
|
mapDb = DBMaker
|
|
|
|
.fileDB(mapDbFile)
|
|
|
|
.fileMmapEnable() // Always enable mmap
|
|
|
|
.fileMmapEnableIfSupported()
|
|
|
|
.fileMmapPreclearDisable()
|
|
|
|
.allocateStartSize(512*1024*1024)
|
|
|
|
.allocateIncrement(128*1024*1024)
|
|
|
|
.cleanerHackEnable()
|
|
|
|
.make();
|
|
|
|
mapDbFile.deleteOnExit();
|
|
|
|
}
|
2021-01-02 18:04:48 +01:00
|
|
|
try {
|
2021-01-08 15:04:30 +01:00
|
|
|
parser.txSink = mapDb.treeMap("transactions", Serializer.LONG,Serializer.JAVA).createFromSink();
|
|
|
|
parser.parseInput(gzipped?new GZIPInputStream(new FileInputStream(file)):new FileInputStream(file));
|
|
|
|
transactions = parser.txSink.create();
|
2021-01-02 17:02:05 +01:00
|
|
|
} catch(IllegalArgumentException|ArrayIndexOutOfBoundsException e) {
|
2021-01-03 14:16:56 +01:00
|
|
|
} catch(Exception e) {
|
2021-01-02 16:15:27 +01:00
|
|
|
System.out.println("---->>> Exception "+e.toString()+" caught while loading database");
|
|
|
|
e.printStackTrace();
|
2021-01-02 18:04:48 +01:00
|
|
|
return false;
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
2021-01-08 15:04:30 +01:00
|
|
|
for(TxStream stream:txStreams.values()) {
|
|
|
|
Thread t = new Thread() {
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
stream.calculateConcurrency();
|
|
|
|
} catch (Exception e) {/* don't let exceptions bubble up */ }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
threads.add(t);
|
|
|
|
t.start();
|
|
|
|
}
|
2021-01-02 17:02:05 +01:00
|
|
|
return true;
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
public void dispose() {
|
|
|
|
relationTypes.clear();
|
|
|
|
txStreams.clear();
|
|
|
|
txGenerators.clear();
|
|
|
|
transactions = null;
|
|
|
|
attributeTypes.clear();
|
|
|
|
relationsIn.clear();
|
|
|
|
relationsOut.clear();
|
|
|
|
if(mapDb!=null) {
|
|
|
|
mapDb.close();
|
|
|
|
mapDb=null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 16:15:27 +01:00
|
|
|
private static boolean isTxfile(InputStream istream) {
|
|
|
|
byte[] buffer = new byte[x.length];
|
|
|
|
try {
|
|
|
|
int readCnt = istream.read(buffer, 0, x.length);
|
|
|
|
istream.close();
|
|
|
|
if(readCnt==x.length){
|
|
|
|
for(int i=0; i<x.length; i++)
|
|
|
|
if(buffer[i]!=x[i]) return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (IOException e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isGzipped(File f) {
|
|
|
|
try(InputStream is = new FileInputStream(f)) {
|
|
|
|
byte [] signature = new byte[2];
|
|
|
|
int nread = is.read( signature ); //read the gzip signature
|
|
|
|
return nread == 2 && signature[ 0 ] == (byte) 0x1f && signature[ 1 ] == (byte) 0x8b;
|
|
|
|
} catch (IOException e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
public Collection<RelationType> getAllRelationTypes(){
|
|
|
|
return relationTypes.values();
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
static class TextDbParser {
|
|
|
|
static final Pattern scv_tr_stream = Pattern.compile("^scv_tr_stream\\s+\\(ID (\\d+),\\s+name\\s+\"([^\"]+)\",\\s+kind\\s+\"([^\"]+)\"\\)$");
|
|
|
|
static final Pattern scv_tr_generator = Pattern.compile("^scv_tr_generator\\s+\\(ID\\s+(\\d+),\\s+name\\s+\"([^\"]+)\",\\s+scv_tr_stream\\s+(\\d+),$");
|
|
|
|
static final Pattern begin_attribute = Pattern.compile("^begin_attribute \\(ID (\\d+), name \"([^\"]+)\", type \"([^\"]+)\"\\)$");
|
|
|
|
static final Pattern end_attribute = Pattern.compile("^end_attribute \\(ID (\\d+), name \"([^\"]+)\", type \"([^\"]+)\"\\)$");
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
final TextDbLoader loader;
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
HashMap<Long, ScvTx> transactionById = new HashMap<>();
|
|
|
|
|
|
|
|
TreeMapSink<Long, ScvTx> txSink;
|
|
|
|
|
2021-01-03 14:16:56 +01:00
|
|
|
BufferedReader reader = null;
|
|
|
|
|
2021-01-07 17:38:29 +01:00
|
|
|
TxGenerator generator=null;
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
public TextDbParser(TextDbLoader loader) {
|
|
|
|
super();
|
|
|
|
this.loader = loader;
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
void parseInput(InputStream inputStream) throws IOException{
|
|
|
|
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
|
|
|
|
String curLine = reader.readLine();
|
|
|
|
String nextLine = null;
|
|
|
|
while((nextLine=reader.readLine())!=null && curLine!=null) {
|
|
|
|
curLine=parseLine(curLine, nextLine);
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
2021-01-02 18:04:48 +01:00
|
|
|
if(curLine!=null)
|
|
|
|
parseLine(curLine, nextLine);
|
|
|
|
}
|
|
|
|
|
2021-01-03 14:16:56 +01:00
|
|
|
private TxAttributeType getAttrType(String name, DataType dataType, AssociationType type){
|
2021-01-07 17:38:29 +01:00
|
|
|
String key =name+"-"+dataType.toString();
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttributeType res;
|
|
|
|
if(loader.attributeTypes.containsKey(key)){
|
|
|
|
res=loader.attributeTypes.get(key);
|
|
|
|
} else {
|
|
|
|
res=new TxAttributeType(name, dataType, type);
|
|
|
|
loader.attributeTypes.put(key, res);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-01-02 18:04:48 +01:00
|
|
|
private String parseLine(String curLine, String nextLine) throws IOException{
|
|
|
|
String[] tokens = curLine.split("\\s+");
|
2021-01-04 17:39:11 +01:00
|
|
|
if("tx_record_attribute".equals(tokens[0])){
|
2021-01-02 18:04:48 +01:00
|
|
|
Long id = Long.parseLong(tokens[1]);
|
|
|
|
String name = tokens[2].substring(1, tokens[2].length());
|
|
|
|
DataType type = DataType.valueOf(tokens[3]);
|
2021-01-04 17:39:11 +01:00
|
|
|
String remaining = tokens.length>5?String.join(" ", Arrays.copyOfRange(tokens, 5, tokens.length)):"";
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttributeType attrType = getAttrType(name, type, AssociationType.RECORD);
|
2021-01-08 15:04:30 +01:00
|
|
|
transactionById.get(id).attributes.add(new TxAttribute(attrType, remaining));
|
2021-01-02 18:04:48 +01:00
|
|
|
} else if("tx_begin".equals(tokens[0])){
|
|
|
|
Long id = Long.parseLong(tokens[1]);
|
2021-01-03 14:16:56 +01:00
|
|
|
Long genId = Long.parseLong(tokens[2]);
|
2021-01-07 17:38:29 +01:00
|
|
|
TxGenerator gen=loader.txGenerators.get(genId);
|
|
|
|
ScvTx scvTx = new ScvTx(id, gen.stream.getId(), genId, Long.parseLong(tokens[3])*stringToScale(tokens[4]));
|
|
|
|
Tx tx = new Tx(loader, scvTx);
|
|
|
|
loader.maxTime = loader.maxTime>scvTx.beginTime?loader.maxTime:scvTx.beginTime;
|
|
|
|
TxStream stream = loader.txStreams.get(gen.stream.getId());
|
2021-01-02 18:04:48 +01:00
|
|
|
stream.setConcurrency(stream.getConcurrency()+1);
|
|
|
|
if(nextLine!=null && nextLine.charAt(0)=='a') {
|
|
|
|
int idx=0;
|
|
|
|
while(nextLine!=null && nextLine.charAt(0)=='a') {
|
|
|
|
String[] attrTokens=nextLine.split("\\s+");
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttribute attr = new TxAttribute(gen.beginAttrs.get(idx), attrTokens[1]);
|
2021-01-08 15:04:30 +01:00
|
|
|
scvTx.attributes.add(attr);
|
2021-01-02 18:04:48 +01:00
|
|
|
idx++;
|
|
|
|
nextLine=reader.readLine();
|
|
|
|
}
|
|
|
|
}
|
2021-01-08 15:04:30 +01:00
|
|
|
txSink.put(id, scvTx);
|
|
|
|
transactionById.put(id, scvTx);
|
2021-01-07 17:38:29 +01:00
|
|
|
gen.getTransactions().add(tx);
|
2021-01-04 17:39:11 +01:00
|
|
|
} else if("tx_end".equals(tokens[0])){
|
2021-01-02 18:04:48 +01:00
|
|
|
Long id = Long.parseLong(tokens[1]);
|
2021-01-08 15:04:30 +01:00
|
|
|
//ScvTx tx = loader.transactions.get(id);
|
|
|
|
ScvTx scvTx = transactionById.get(id);
|
|
|
|
assert Long.parseLong(tokens[2])==scvTx.generatorId;
|
|
|
|
scvTx.endTime=Long.parseLong(tokens[3])*stringToScale(tokens[4]);
|
|
|
|
loader.maxTime = loader.maxTime>scvTx.endTime?loader.maxTime:scvTx.endTime;
|
|
|
|
TxGenerator gen = loader.txGenerators.get(scvTx.generatorId);
|
2021-01-07 17:38:29 +01:00
|
|
|
TxStream stream = loader.txStreams.get(gen.stream.getId());
|
2021-01-08 15:04:30 +01:00
|
|
|
if(scvTx.beginTime==scvTx.endTime)
|
|
|
|
stream.addEvent(new TxEvent(loader, EventKind.SINGLE, id, scvTx.beginTime));
|
2021-01-03 14:16:56 +01:00
|
|
|
else {
|
2021-01-08 15:04:30 +01:00
|
|
|
stream.addEvent(new TxEvent(loader, EventKind.BEGIN, id, scvTx.beginTime));
|
|
|
|
stream.addEvent(new TxEvent(loader, EventKind.END, id, scvTx.endTime));
|
2021-01-03 14:16:56 +01:00
|
|
|
}
|
2021-01-02 18:04:48 +01:00
|
|
|
stream.setConcurrency(stream.getConcurrency()-1);
|
|
|
|
if(nextLine!=null && nextLine.charAt(0)=='a') {
|
|
|
|
int idx=0;
|
|
|
|
while(nextLine!=null && nextLine.charAt(0)=='a') {
|
|
|
|
String[] attrTokens=nextLine.split("\\s+");
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttribute attr = new TxAttribute(gen.endAttrs.get(idx), attrTokens[1]);
|
2021-01-08 15:04:30 +01:00
|
|
|
scvTx.attributes.add(attr);
|
2021-01-02 18:04:48 +01:00
|
|
|
idx++;
|
|
|
|
nextLine=reader.readLine();
|
|
|
|
}
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
2021-01-04 17:39:11 +01:00
|
|
|
} else if("tx_relation".equals(tokens[0])){
|
2021-01-03 14:16:56 +01:00
|
|
|
Long tr2= Long.parseLong(tokens[2]);
|
|
|
|
Long tr1= Long.parseLong(tokens[3]);
|
2021-01-04 17:39:11 +01:00
|
|
|
String relType=tokens[1].substring(1, tokens[1].length()-1);
|
2021-01-02 18:04:48 +01:00
|
|
|
if(!loader.relationTypes.containsKey(relType))
|
2021-01-04 17:39:11 +01:00
|
|
|
loader.relationTypes.put(relType, RelationTypeFactory.create(relType));
|
2021-01-03 14:16:56 +01:00
|
|
|
ScvRelation rel = new ScvRelation(loader.relationTypes.get(relType), tr1, tr2);
|
2021-01-07 17:38:29 +01:00
|
|
|
loader.relationsOut.put(tr1, rel);
|
|
|
|
loader.relationsIn.put(tr2, rel);
|
2021-01-02 18:04:48 +01:00
|
|
|
} else if("scv_tr_stream".equals(tokens[0])){
|
|
|
|
Matcher matcher = scv_tr_stream.matcher(curLine);
|
|
|
|
if (matcher.matches()) {
|
|
|
|
Long id = Long.parseLong(matcher.group(1));
|
|
|
|
TxStream stream = new TxStream(loader, id, matcher.group(2), matcher.group(3));
|
2021-01-07 17:38:29 +01:00
|
|
|
loader.txStreams.put(id, stream);
|
2021-01-02 18:04:48 +01:00
|
|
|
}
|
|
|
|
} else if("scv_tr_generator".equals(tokens[0])){
|
|
|
|
Matcher matcher = scv_tr_generator.matcher(curLine);
|
|
|
|
if ((matcher.matches())) {
|
|
|
|
Long id = Long.parseLong(matcher.group(1));
|
2021-01-07 17:38:29 +01:00
|
|
|
TxStream stream=loader.txStreams.get(Long.parseLong(matcher.group(3)));
|
2021-01-02 18:04:48 +01:00
|
|
|
generator=new TxGenerator(id, stream, matcher.group(2));
|
2021-01-07 17:38:29 +01:00
|
|
|
loader.txGenerators.put(id, generator);
|
2021-01-02 18:04:48 +01:00
|
|
|
}
|
|
|
|
} else if("begin_attribute".equals(tokens[0])){
|
|
|
|
Matcher matcher = begin_attribute.matcher(curLine);
|
|
|
|
if ((matcher.matches())) {
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttributeType attrType = getAttrType(matcher.group(2), DataType.valueOf(matcher.group(3)), AssociationType.BEGIN);
|
|
|
|
generator.beginAttrs.add(attrType);
|
2021-01-02 18:04:48 +01:00
|
|
|
}
|
|
|
|
} else if("end_attribute".equals(tokens[0])){
|
|
|
|
Matcher matcher = end_attribute.matcher(curLine);
|
|
|
|
if ((matcher.matches())) {
|
2021-01-03 14:16:56 +01:00
|
|
|
TxAttributeType attrType = getAttrType(matcher.group(2), DataType.valueOf(matcher.group(3)), AssociationType.END);
|
|
|
|
generator.endAttrs.add(attrType);
|
2021-01-02 18:04:48 +01:00
|
|
|
}
|
|
|
|
} else if(")".equals(tokens[0])){
|
|
|
|
generator=null;
|
|
|
|
} else if("a".equals(tokens[0])){//matcher = line =~ /^a\s+(.+)$/
|
|
|
|
System.out.println("Don't know what to do with: '"+curLine+"'");
|
|
|
|
} else
|
|
|
|
System.out.println("Don't know what to do with: '"+curLine+"'");
|
|
|
|
return nextLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
private long stringToScale(String scale){
|
|
|
|
String cmp = scale.trim();
|
|
|
|
if("fs".equals(cmp)) return 1L;
|
|
|
|
if("ps".equals(cmp)) return 1000L;
|
|
|
|
if("ns".equals(cmp)) return 1000000L;
|
|
|
|
if("us".equals(cmp)) return 1000000000L;
|
|
|
|
if("ms".equals(cmp)) return 1000000000000L;
|
|
|
|
if("s".equals(cmp) ) return 1000000000000000L;
|
|
|
|
return 1L;
|
|
|
|
}
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
2021-01-03 14:16:56 +01:00
|
|
|
|
2021-01-08 15:04:30 +01:00
|
|
|
public ITx getTransaction(long txId) {
|
|
|
|
if(txCache.containsKey(txId))
|
|
|
|
return txCache.get(txId);
|
|
|
|
Tx tx = new Tx(this, txId);
|
|
|
|
txCache.put(txId, tx);
|
|
|
|
return tx;
|
2021-01-03 14:16:56 +01:00
|
|
|
}
|
2021-01-07 17:38:29 +01:00
|
|
|
|
2021-01-02 16:15:27 +01:00
|
|
|
}
|
|
|
|
|