From ae9a6bad58019ef18657568e58f49459fbadc49c Mon Sep 17 00:00:00 2001 From: RncLsn Date: Fri, 14 Aug 2015 19:21:26 +0100 Subject: Incremental Skolemised store (not working). --- .../IncrementalMultiStageQueryEngine.java | 12 +++ .../pagoda/multistage/MultiStageQueryEngine.java | 98 ++++++++++++++++++++-- .../treatement/Pick4NegativeConcept.java | 36 +++++--- .../treatement/Pick4NegativeConceptNaive.java | 12 ++- .../Pick4NegativeConceptQuerySpecific.java | 2 +- .../cs/pagoda/multistage/treatement/Treatment.java | 25 +++++- .../ac/ox/cs/pagoda/reasoner/MyQueryReasoner.java | 39 +++++---- .../pagoda/reasoner/light/RDFoxTripleManager.java | 9 +- .../cs/pagoda/util/ExponentialInterpolation.java | 33 ++++++++ .../ac/ox/cs/pagoda/util/tuples/TupleBuilder.java | 21 ++++- 10 files changed, 241 insertions(+), 46 deletions(-) create mode 100644 src/uk/ac/ox/cs/pagoda/multistage/IncrementalMultiStageQueryEngine.java create mode 100644 src/uk/ac/ox/cs/pagoda/util/ExponentialInterpolation.java (limited to 'src/uk/ac/ox/cs/pagoda') diff --git a/src/uk/ac/ox/cs/pagoda/multistage/IncrementalMultiStageQueryEngine.java b/src/uk/ac/ox/cs/pagoda/multistage/IncrementalMultiStageQueryEngine.java new file mode 100644 index 0000000..30ba9da --- /dev/null +++ b/src/uk/ac/ox/cs/pagoda/multistage/IncrementalMultiStageQueryEngine.java @@ -0,0 +1,12 @@ +package uk.ac.ox.cs.pagoda.multistage; + +/*** + * It is like a MultiStageQueryEngine, but you can call materialiseSkolemly + * multiple times with increasing values of maxTermDepth. + */ +public class IncrementalMultiStageQueryEngine extends MultiStageQueryEngine { + + public IncrementalMultiStageQueryEngine(String name, boolean checkValidity) { + super(name, checkValidity); + } +} diff --git a/src/uk/ac/ox/cs/pagoda/multistage/MultiStageQueryEngine.java b/src/uk/ac/ox/cs/pagoda/multistage/MultiStageQueryEngine.java index 33f9f03..f3a78f6 100644 --- a/src/uk/ac/ox/cs/pagoda/multistage/MultiStageQueryEngine.java +++ b/src/uk/ac/ox/cs/pagoda/multistage/MultiStageQueryEngine.java @@ -1,13 +1,17 @@ package uk.ac.ox.cs.pagoda.multistage; +import org.semanticweb.HermiT.model.Atom; import org.semanticweb.HermiT.model.DLClause; +import org.semanticweb.HermiT.model.Individual; import uk.ac.ox.cs.JRDFox.JRDFStoreException; +import uk.ac.ox.cs.JRDFox.store.DataStore; import uk.ac.ox.cs.pagoda.constraints.BottomStrategy; import uk.ac.ox.cs.pagoda.multistage.treatement.Pick4NegativeConceptNaive; import uk.ac.ox.cs.pagoda.multistage.treatement.Pick4NegativeConceptQuerySpecific; import uk.ac.ox.cs.pagoda.multistage.treatement.Treatment; import uk.ac.ox.cs.pagoda.query.GapByStore4ID; import uk.ac.ox.cs.pagoda.query.QueryRecord; +import uk.ac.ox.cs.pagoda.reasoner.light.RDFoxTripleManager; import uk.ac.ox.cs.pagoda.rules.DatalogProgram; import uk.ac.ox.cs.pagoda.rules.Program; import uk.ac.ox.cs.pagoda.rules.approximators.SkolemTermsManager; @@ -15,21 +19,23 @@ import uk.ac.ox.cs.pagoda.util.PagodaProperties; import uk.ac.ox.cs.pagoda.util.Timer; import uk.ac.ox.cs.pagoda.util.Utility; import uk.ac.ox.cs.pagoda.util.disposable.DisposedException; +import uk.ac.ox.cs.pagoda.util.tuples.Tuple; +import uk.ac.ox.cs.pagoda.util.tuples.TupleBuilder; import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; public class MultiStageQueryEngine extends StageQueryEngine { private HashMap statistics = new HashMap<>(); + private Set> oversizedSkolemisedFacts; + private RDFoxTripleManager rdFoxTripleManager; + private int lastMaxTermDepth = -1; public MultiStageQueryEngine(String name, boolean checkValidity) { super(name, checkValidity); @@ -73,14 +79,19 @@ public class MultiStageQueryEngine extends StageQueryEngine { public int materialiseSkolemly(DatalogProgram dProgram, GapByStore4ID gap, int maxTermDepth) { if(isDisposed()) throw new DisposedException(); + if(maxTermDepth <= lastMaxTermDepth) + throw new IllegalArgumentException("maxTermDepth must be greater than " + lastMaxTermDepth); + lastMaxTermDepth = maxTermDepth; + materialise("lower program", dProgram.getLower().toString()); Program generalProgram = dProgram.getGeneral(); LimitedSkolemisationApplication program = new LimitedSkolemisationApplication(generalProgram, dProgram.getUpperBottomStrategy(), maxTermDepth); - Treatment treatment = new Pick4NegativeConceptNaive(this, program); - return materialise(program, treatment, gap); + rdFoxTripleManager = new RDFoxTripleManager(store, true); + Treatment treatment = new Pick4NegativeConceptNaive(this, program, rdFoxTripleManager); + return materialise(program, treatment, gap, maxTermDepth); } public int materialise4SpecificQuery(Program generalProgram, QueryRecord record, BottomStrategy upperBottom) { @@ -102,6 +113,12 @@ public class MultiStageQueryEngine extends StageQueryEngine { } private int materialise(MultiStageUpperProgram program, Treatment treatment, GapByStore4ID gap) { + return materialise(program, treatment, gap, -1); + } + + private int materialise(MultiStageUpperProgram program, Treatment treatment, GapByStore4ID gap, int maxTermDepth) { + boolean actuallyCleaned = cleanStoreFromOversizedSkolemisedFacts(); + if(gap != null) treatment.addAdditionalGapTuples(); String programName = "multi-stage upper program"; @@ -144,7 +161,7 @@ public class MultiStageQueryEngine extends StageQueryEngine { // store.addRules(new String[] {datalogProgram}); store.importRules(datalogProgram); } - store.applyReasoning(incrementally); + store.applyReasoning(incrementally || actuallyCleaned); } // Utility.logInfo("The number of sameAs assertions in the current store: " + getSameAsNumber()); @@ -172,7 +189,7 @@ public class MultiStageQueryEngine extends StageQueryEngine { } Utility.logDebug("Time to detect violations: " + subTimer.duration()); - store.makeFactsExplicit(); +// store.makeFactsExplicit(); subTimer.reset(); oldTripleCount = store.getTriplesCount(); @@ -190,11 +207,15 @@ public class MultiStageQueryEngine extends StageQueryEngine { Timer localTimer = new Timer(); int number = v.size(); long vOldCounter = store.getTriplesCount(); - if(!treatment.makeSatisfied(v)) { + Set satisfiabilityFacts; + if((satisfiabilityFacts = treatment.makeSatisfied(v)) == null) { validMaterialisation = false; Utility.logInfo(name + " store FAILED for multi-stage materialisation in " + t.duration() + " seconds."); return 0; } + + addOversizedSkolemisedFacts(getOversizedSkolemisedFacts(satisfiabilityFacts, maxTermDepth)); + Utility.logDebug("Time to make the constraint being satisfied: " + localTimer.duration()); Utility.logDebug("Triples in the store: before=" + vOldCounter + ", after=" + store.getTriplesCount() + ", new=" + (store .getTriplesCount() - vOldCounter)); @@ -212,6 +233,65 @@ public class MultiStageQueryEngine extends StageQueryEngine { return 0; } + private boolean cleanStoreFromOversizedSkolemisedFacts() { + if(oversizedSkolemisedFacts == null || oversizedSkolemisedFacts.isEmpty()) + return false; + + try { + for (Tuple tuple : oversizedSkolemisedFacts) { + int[] triple = new int[]{tuple.get(0), tuple.get(1), tuple.get(2)}; + store.addTriplesByResourceIDs(triple, DataStore.UpdateType.ScheduleForDeletion); + } + } catch (JRDFStoreException e) { + e.printStackTrace(); + System.exit(1); + } + oversizedSkolemisedFacts = new HashSet<>(); + + return true; + } + + private void addOversizedSkolemisedFacts(Set> facts) { + if(oversizedSkolemisedFacts == null) + oversizedSkolemisedFacts = new HashSet<>(); + oversizedSkolemisedFacts.addAll(facts); + } + + /** + * Get triples containing Skolem individuals of depth greater or equal than the maximum. + * + * @param satisfiabilityFacts + * @return + */ + private Set> getOversizedSkolemisedFacts(Set satisfiabilityFacts, int maxDepth) { + HashSet> result = new HashSet<>(); + SkolemTermsManager termsManager = SkolemTermsManager.getInstance(); + for (Treatment.AtomWithIDTriple atomWithIDTriple : satisfiabilityFacts) { + Atom atom = atomWithIDTriple.getAtom(); + if(atom.getArity() == 1) { + if(atom.getArgument(0) instanceof Individual && termsManager.getDepthOf((Individual) atom.getArgument(0)) >= maxDepth) { + int[] idTriple = atomWithIDTriple.getIDTriple(); + result.add(new TupleBuilder().append(idTriple[0]).append(idTriple[1]) + .append(idTriple[2]).build()); + } + else if(!(atom.getArgument(0) instanceof Individual)) + throw new IllegalArgumentException("No individuals: " + atom); + } + else { + if((atom.getArgument(0) instanceof Individual && termsManager.getDepthOf((Individual) atom.getArgument(0)) >= maxDepth) + || (atom.getArgument(1) instanceof Individual && termsManager.getDepthOf((Individual) atom.getArgument(1)) >= maxDepth)){ + int[] idTriple = atomWithIDTriple.getIDTriple(); + result.add(new TupleBuilder().append(idTriple[0]).append(idTriple[1]) + .append(idTriple[2]).build()); + } + else if(!(atom.getArgument(0) instanceof Individual) && !(atom.getArgument(1) instanceof Individual)) + throw new IllegalArgumentException("No individuals: " + atom); + } + + } + return result; + } + private void updateStatistics(String key, List value) { if(!statistics.containsKey(key)) statistics.put(key, new ArrayList()); diff --git a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConcept.java b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConcept.java index 3528788..f88a4d7 100644 --- a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConcept.java +++ b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConcept.java @@ -29,10 +29,14 @@ public abstract class Pick4NegativeConcept extends Treatment { PredicateDependency dependencyGraph; boolean addGap = false; - public Pick4NegativeConcept(MultiStageQueryEngine store, MultiStageUpperProgram multiProgram) { + public Pick4NegativeConcept(MultiStageQueryEngine store, MultiStageUpperProgram multiProgram, RDFoxTripleManager tripleManager) { this.engine = store; this.program = multiProgram; - this.tripleManager = new RDFoxTripleManager(store.getDataStore(), true); + this.tripleManager = tripleManager; + } + + public Pick4NegativeConcept(MultiStageQueryEngine store, MultiStageUpperProgram multiProgram) { + this(store, multiProgram, new RDFoxTripleManager(store.getDataStore(), true)); } @Override @@ -41,16 +45,23 @@ public abstract class Pick4NegativeConcept extends Treatment { addGap = true; } - void addTripleByID(Atom atom, Atom gapAtom, Map assignment) { + Set addTripleByID(Atom atom, Atom gapAtom, Map assignment) { if(isDisposed()) throw new DisposedException(); + HashSet result = new HashSet<>(); int[] newTuple = tripleManager.getInstance(atom, assignment); + result.add(new AtomWithIDTriple(atom, newTuple)); tripleManager.addTripleByID(newTuple); - if(addGap) - tripleManager.addTripleByID(tripleManager.getInstance(gapAtom, assignment)); + if(addGap) { + int[] instance = tripleManager.getInstance(gapAtom, assignment); + tripleManager.addTripleByID(instance); + result.add(new AtomWithIDTriple(gapAtom, instance)); + } + return result; } // TODO -RULE- - protected boolean makeSatisfied(Violation violation, Comparator comp) { + protected Set makeSatisfied(Violation violation, Comparator comp) { + HashSet result = new HashSet<>(); LinkedList tuples = violation.getTuples(); DLClause constraint = violation.getConstraint(); Map assignment = new HashMap(); @@ -97,7 +108,7 @@ public abstract class Pick4NegativeConcept extends Treatment { if(lastAdded == null || tComp.compare(lastAdded, tuple) != 0) { lastAdded = tuple; tuple.getAssignment(violation.getVariables(), assignment); - addTripleByID(headAtom, gapHeadAtom, assignment); + result.addAll(addTripleByID(headAtom, gapHeadAtom, assignment)); } iter.remove(); } @@ -105,9 +116,9 @@ public abstract class Pick4NegativeConcept extends Treatment { // tuples.reset(); if(tuples.isEmpty()) - return true; + return result; } - if(!tuples.isEmpty()) return false; + if(!tuples.isEmpty()) return null; } else { Set headAtoms = new HashSet(); @@ -136,7 +147,7 @@ public abstract class Pick4NegativeConcept extends Treatment { if(DLClauseHelper.isGround(tHeadAtom)) { if(!addedGroundAtoms.contains(tHeadAtom)) { program.addUpdatedPredicate(tHeadAtom.getDLPredicate()); - addTripleByID(tHeadAtom, tGapHeadAtom, null); + result.addAll(addTripleByID(tHeadAtom, tGapHeadAtom, null)); addedGroundAtoms.add(tHeadAtom); } } @@ -149,13 +160,14 @@ public abstract class Pick4NegativeConcept extends Treatment { for(AnswerTupleID tuple : tuples) { tuple.getAssignment(violation.getVariables(), assignment); for(Atom atom : headAtoms) { - addTripleByID(atom, getGapAtom(atom), assignment); + Atom gapAtom = getGapAtom(atom); + result.addAll(addTripleByID(atom, gapAtom, assignment)); } } } assignment.clear(); - return true; + return result; } private Atom getGapAtom(Atom atom) { diff --git a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptNaive.java b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptNaive.java index af190fc..2fc2683 100644 --- a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptNaive.java +++ b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptNaive.java @@ -5,19 +5,27 @@ import uk.ac.ox.cs.pagoda.constraints.PredicateDependency; import uk.ac.ox.cs.pagoda.multistage.MultiStageQueryEngine; import uk.ac.ox.cs.pagoda.multistage.MultiStageUpperProgram; import uk.ac.ox.cs.pagoda.multistage.Violation; +import uk.ac.ox.cs.pagoda.reasoner.light.RDFoxTripleManager; import uk.ac.ox.cs.pagoda.util.disposable.DisposedException; +import java.util.Set; + public class Pick4NegativeConceptNaive extends Pick4NegativeConcept { SimpleComparator comp = new SimpleComparator(); - + public Pick4NegativeConceptNaive(MultiStageQueryEngine store, MultiStageUpperProgram multiProgram) { super(store, multiProgram); dependencyGraph = new PredicateDependency(multiProgram.getClauses()); } + + public Pick4NegativeConceptNaive(MultiStageQueryEngine store, MultiStageUpperProgram multiProgram, RDFoxTripleManager rdFoxTripleManager) { + super(store, multiProgram, rdFoxTripleManager); + dependencyGraph = new PredicateDependency(multiProgram.getClauses()); + } @Override - public boolean makeSatisfied(Violation violation) throws JRDFStoreException { + public Set makeSatisfied(Violation violation) throws JRDFStoreException { if(isDisposed()) throw new DisposedException(); return makeSatisfied(violation, comp); } diff --git a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptQuerySpecific.java b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptQuerySpecific.java index 20b4376..675bfc3 100644 --- a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptQuerySpecific.java +++ b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Pick4NegativeConceptQuerySpecific.java @@ -31,7 +31,7 @@ public class Pick4NegativeConceptQuerySpecific extends Pick4NegativeConcept { } @Override - public boolean makeSatisfied(Violation violation) throws JRDFStoreException { + public Set makeSatisfied(Violation violation) throws JRDFStoreException { if(isDisposed()) throw new DisposedException(); return makeSatisfied(violation, comp); } diff --git a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Treatment.java b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Treatment.java index cb81af0..de76d27 100644 --- a/src/uk/ac/ox/cs/pagoda/multistage/treatement/Treatment.java +++ b/src/uk/ac/ox/cs/pagoda/multistage/treatement/Treatment.java @@ -1,12 +1,35 @@ package uk.ac.ox.cs.pagoda.multistage.treatement; +import org.semanticweb.HermiT.model.Atom; import uk.ac.ox.cs.JRDFox.JRDFStoreException; import uk.ac.ox.cs.pagoda.multistage.Violation; import uk.ac.ox.cs.pagoda.util.disposable.Disposable; +import java.util.Set; + public abstract class Treatment extends Disposable { - public abstract boolean makeSatisfied(Violation violation) throws JRDFStoreException; + public abstract Set makeSatisfied(Violation violation) throws JRDFStoreException; public abstract void addAdditionalGapTuples(); + + public class AtomWithIDTriple { + + private Atom atom; + private int[] IDTriple; + + public AtomWithIDTriple(Atom atom, int[] IDTriple) { + this.atom = atom; + this.IDTriple = IDTriple; + } + + public Atom getAtom() { + return atom; + } + + public int[] getIDTriple() { + return IDTriple; + } + + } } diff --git a/src/uk/ac/ox/cs/pagoda/reasoner/MyQueryReasoner.java b/src/uk/ac/ox/cs/pagoda/reasoner/MyQueryReasoner.java index 71d5752..a393474 100644 --- a/src/uk/ac/ox/cs/pagoda/reasoner/MyQueryReasoner.java +++ b/src/uk/ac/ox/cs/pagoda/reasoner/MyQueryReasoner.java @@ -396,24 +396,31 @@ class MyQueryReasoner extends QueryReasoner { relevantStore.importDataFromABoxOf(relevantSubset); String relevantOriginalMarkProgram = OWLHelper.getOriginalMarkProgram(relevantSubset); - int queryDependentMaxTermDepth = properties.getSkolemDepth(); relevantStore.materialise("Mark original individuals", relevantOriginalMarkProgram); - int materialisationTag = relevantStore.materialiseSkolemly(relevantProgram, null, - queryDependentMaxTermDepth); - queryRecord.addProcessingTime(Step.SKOLEM_UPPER_BOUND, t.duration()); - if(materialisationTag == -1) { - throw new Error("A consistent ontology has turned out to be " + - "inconsistent in the Skolemises-relevant-upper-store"); - } - else if(materialisationTag != 1) { - Utility.logInfo("Semi-Skolemised relevant upper store cannot be employed"); - return false; - } - Utility.logInfo("Querying semi-Skolemised upper store..."); - boolean isFullyProcessed = queryUpperStore(relevantStore, queryRecord, - queryRecord.getExtendedQueryText(), - Step.SKOLEM_UPPER_BOUND); + boolean isFullyProcessed = false; + for (int currentMaxTermDepth = 1; + currentMaxTermDepth <= properties.getSkolemDepth() && !isFullyProcessed; currentMaxTermDepth++) { + + Utility.logInfo("Trying with maximum depth " + currentMaxTermDepth); + + int materialisationTag = relevantStore.materialiseSkolemly(relevantProgram, null, + currentMaxTermDepth); + queryRecord.addProcessingTime(Step.SKOLEM_UPPER_BOUND, t.duration()); + if(materialisationTag == -1) { + throw new Error("A consistent ontology has turned out to be " + + "inconsistent in the Skolemises-relevant-upper-store"); + } + else if(materialisationTag != 1) { + Utility.logInfo("Semi-Skolemised relevant upper store cannot be employed"); + return false; + } + + Utility.logInfo("Querying semi-Skolemised upper store..."); + isFullyProcessed = queryUpperStore(relevantStore, queryRecord, + queryRecord.getExtendedQueryText(), + Step.SKOLEM_UPPER_BOUND); + } relevantStore.dispose(); Utility.logInfo("Semi-Skolemised relevant upper store has been evaluated"); diff --git a/src/uk/ac/ox/cs/pagoda/reasoner/light/RDFoxTripleManager.java b/src/uk/ac/ox/cs/pagoda/reasoner/light/RDFoxTripleManager.java index 85f8ef9..62885be 100644 --- a/src/uk/ac/ox/cs/pagoda/reasoner/light/RDFoxTripleManager.java +++ b/src/uk/ac/ox/cs/pagoda/reasoner/light/RDFoxTripleManager.java @@ -93,6 +93,14 @@ public class RDFoxTripleManager { e.printStackTrace(); } } + + public void removeTripleByTermIncrementally(Atom atom) { + try { + m_store.addTriples(getRDFoxTriple(atom), UpdateType.ScheduleForDeletion); + } catch (JRDFStoreException e) { + e.printStackTrace(); + } + } public static GroundTerm[] getRDFoxTriple(Atom instance) { if (instance.getArity() == 1) @@ -258,5 +266,4 @@ public class RDFoxTripleManager { else return "\"" + r.m_lexicalForm + "\"^^<" + r.m_datatype.getIRI() + ">"; } - } diff --git a/src/uk/ac/ox/cs/pagoda/util/ExponentialInterpolation.java b/src/uk/ac/ox/cs/pagoda/util/ExponentialInterpolation.java new file mode 100644 index 0000000..1d12169 --- /dev/null +++ b/src/uk/ac/ox/cs/pagoda/util/ExponentialInterpolation.java @@ -0,0 +1,33 @@ +package uk.ac.ox.cs.pagoda.util; + +/*** + * Get an exponential function given two points. + */ +public class ExponentialInterpolation { + + private final double base; + private final double multiplicativeFactor; + + /*** + * Compute the exponential function passing for the 2 given points. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ + public ExponentialInterpolation(double x1, double y1, double x2, double y2) { + base = Math.pow(y2/y1, 1 / (x2 - x1)); + multiplicativeFactor = y1 / Math.pow(base, x1); + } + + /*** + * Compute value of the function in x. + * + * @param x + * @return + */ + public double computeValue(double x) { + return multiplicativeFactor * Math.pow(base, x); + } +} diff --git a/src/uk/ac/ox/cs/pagoda/util/tuples/TupleBuilder.java b/src/uk/ac/ox/cs/pagoda/util/tuples/TupleBuilder.java index ee2b74d..172e249 100644 --- a/src/uk/ac/ox/cs/pagoda/util/tuples/TupleBuilder.java +++ b/src/uk/ac/ox/cs/pagoda/util/tuples/TupleBuilder.java @@ -1,18 +1,31 @@ package uk.ac.ox.cs.pagoda.util.tuples; +import java.util.Collections; + /** * Allows to create an immutable Tuple in a non-atomic way. * It can create only one Tuple. * */ public class TupleBuilder { - private Tuple tuple = new Tuple(); + private Tuple tuple = new Tuple(); private boolean building = true; - public boolean append(T t) { - if(building) tuple.elements.add(t); - return building; + public TupleBuilder append(T t) { + if(building) { + tuple.elements.add(t); + return this; + } + return null; + } + + public TupleBuilder append(T[] t) { + if(building) { + Collections.addAll(tuple.elements, t); + return this; + } + return null; } public Tuple build() { -- cgit v1.2.3