/*
* Kodkod -- Copyright (c) 2005-present, Emina Torlak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package kodkod.engine.fol2sat;
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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import kodkod.ast.ConstantFormula;
import kodkod.ast.Formula;
import kodkod.ast.Node;
import kodkod.ast.Variable;
import kodkod.engine.Solver;
import kodkod.engine.bool.BooleanMatrix;
import kodkod.engine.bool.BooleanValue;
import kodkod.instance.Bounds;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.util.collections.Containers;
import kodkod.util.collections.FixedMap;
import kodkod.util.ints.Ints;
import kodkod.util.nodes.AnnotatedNode;
import kodkod.util.nodes.Nodes;
/**
* A file-based translation logger that logs translation events
* to a temporary file.
* @specfield originalFormula: Formula // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} formula, provided by the user
* @specfield originalBounds: Bounds // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} bounds, provided by the user
* @specfield formula: Formula // desugaring of this.formula that was translated
* @specfield bounds: Bounds // translation bounds
* @specfield records: (formula.*children & Formula) -> BooleanValue -> Environment<BooleanMatrix>
* @invariant Solver.solve(formula, bounds).instance() == null iff Solver.solve(originalFormula, originalBounds).instance() == null
* @author Emina Torlak
*/
final class FileLogger extends TranslationLogger {
private final FixedMap<Formula, Variable[]> logMap;
private final AnnotatedNode<Formula> annotated;
private final File file;
private DataOutputStream out;
private final Bounds bounds;
/**
* Constructs a new file logger from the given annotated formula.
* @ensures this.formula' = annotated.node
* @ensures this.originalFormula' = annotated.source[annotated.node]
* @ensures this.bounds' = bounds
* @ensures this.log().roots() = Nodes.conjuncts(annotated)
* @ensures no this.records'
*/
FileLogger(final AnnotatedNode<Formula> annotated, Bounds bounds) {
this.annotated = annotated;
try {
this.file = File.createTempFile("kodkod", ".log");
this.out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
} catch (IOException e1) {
throw new RuntimeException(e1);
}
final Map<Formula,Set<Variable>> freeVarMap = freeVars(annotated);
final Variable[] empty = new Variable[0];
this.logMap = new FixedMap<Formula, Variable[]>(freeVarMap.keySet());
for(Map.Entry<Formula, Variable[]> e : logMap.entrySet()) {
Set<Variable> vars = freeVarMap.get(e.getKey());
int size = vars.size();
if (size==0) {
e.setValue(empty);
} else {
e.setValue(Containers.identitySort(vars.toArray(new Variable[size])));
}
}
this.bounds = bounds.unmodifiableView();
}
/**
* Returns a map from all formulas in the given annotated node to their free variables.
* @return a map from all formulas in the given annotated node to their free variables.
*/
@SuppressWarnings("unchecked")
private static Map<Formula,Set<Variable>> freeVars(final AnnotatedNode<Formula> annotated) {
final Map<Formula,Set<Variable>> freeVarMap = new IdentityHashMap<Formula,Set<Variable>>();
final FreeVariableCollector collector = new FreeVariableCollector(annotated.sharedNodes()) {
protected Set<Variable> cache(Node node, Set<Variable> freeVars) {
if (node instanceof Formula) {
freeVarMap.put((Formula)node, freeVars);
}
return super.cache(node, freeVars);
}
public Set<Variable> visit(ConstantFormula constant) {
return cache(constant, Collections.EMPTY_SET);
}
};
annotated.node().accept(collector);
return freeVarMap;
}
/**
* @see kodkod.engine.fol2sat.TranslationLogger#close()
*/
@Override
void close() {
try {
if (out!=null) { out.close(); }
} catch (IOException e1) {
/* unused */
} finally { out = null; }
}
/**
* Records the translation of the source of the
* given transformed formula to the given boolean value
* in the specified environment.
* @requires some this.transforms.f
* @ensures this.records' = this.records + this.transforms.f -> translation -> freeVariables(f)<:env
* @throws IllegalArgumentException no this.transforms.f
* @throws IllegalStateException this log has been closed
*/
@Override
void log(Formula f, BooleanValue v, Environment<BooleanMatrix> env) {
if (out==null) throw new IllegalStateException();
final int index = logMap.indexOf(f);
if (index < 0) throw new IllegalArgumentException();
final Variable[] vars = logMap.get(index);
try {
out.writeInt(index);
out.writeInt(v.label());
for(Variable var : vars) {
out.writeInt(env.lookup(var).denseIndices().min());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @see kodkod.engine.fol2sat.TranslationLogger#log()
*/
@Override
TranslationLog log() {
return new FileLog(annotated, logMap, file, bounds);
}
/**
* @see java.lang.Object#finalize()
*/
protected final void finalize() {
close();
}
/**
* A file-based translation log, written by a FileLogger.
* @author Emina Torlak
*/
private static final class FileLog extends TranslationLog {
private final Set<Formula> roots;
private final Node[] original;
private final Formula[] translated;
private final Variable[][] freeVars;
private final File file;
private final Bounds bounds;
/**
* Constructs a new file log for the sources of the given annotated formula,
* using the provided fixed map, file, and tuplefactory.
* @requires all f: annotated.node.*children & Formula | logMap.get(f) = freeVariables(f)
* @requires the file was written by a FileLogger using the given map
*/
FileLog(AnnotatedNode<Formula> annotated, FixedMap<Formula, Variable[]> logMap, File file, Bounds bounds) {
this.file = file;
this.bounds = bounds;
this.roots = Nodes.conjuncts(annotated.node());
final int size = logMap.entrySet().size();
this.original = new Node[size];
this.translated = new Formula[size];
this.freeVars = new Variable[size][];
int index = 0;
for(Map.Entry<Formula, Variable[]> e : logMap.entrySet()) {
translated[index] = e.getKey();
original[index] = annotated.sourceOf(e.getKey());
freeVars[index] = e.getValue();
index++;
}
}
/**
* Deletes the log file.
* @see java.lang.Object#finalize()
*/
protected final void finalize() {
file.delete();
}
/**
* {@inheritDoc}
* @see kodkod.engine.fol2sat.TranslationLog#roots()
*/
public Set<Formula> roots() { return roots; }
/**
* {@inheritDoc}
* @see kodkod.engine.fol2sat.TranslationLog#bounds()
*/
public Bounds bounds() { return bounds; }
/**
* {@inheritDoc}
* @see kodkod.engine.fol2sat.TranslationLog#replay(kodkod.engine.fol2sat.RecordFilter)
*/
public Iterator<TranslationRecord> replay(final RecordFilter filter) {
try {
return new Iterator<TranslationRecord>() {
final TupleFactory factory = bounds.universe().factory();
final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
final MutableRecord current = new MutableRecord(), next = new MutableRecord();
long remaining = file.length();
public boolean hasNext() {
while(remaining > 0 && next.node == null) {
try {
final long indexLiteral = in.readLong();
final int literal = (int) (indexLiteral);
final int index = (int) (indexLiteral>>>32);
final Variable[] freeVars = FileLog.this.freeVars[index];
final Map<Variable,TupleSet> env;
if (freeVars.length==0) {
env = Collections.emptyMap();
} else {
env = new FixedMap<Variable,TupleSet>(freeVars);
for(int i = 0; i < freeVars.length; i++) {
env.put(freeVars[i], factory.setOf(1, Ints.singleton(in.readInt())));
}
}
if (filter.accept(original[index], translated[index], literal, env)) {
next.setAll(original[index], translated[index], literal, env);
}
remaining -= (8 + (freeVars.length << 2));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (next.node==null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
} else {
return true;
}
}
public TranslationRecord next() {
if (!hasNext()) throw new NoSuchElementException();
return current.setAll(next);
}
public void remove() { throw new UnsupportedOperationException(); }
protected final void finalize() {
try { in.close(); } catch (IOException e) { /* unused */ }
}
};
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
/**
* A mutable translation record.
* @author Emina Torlak
*/
private static final class MutableRecord extends TranslationRecord {
Node node = null;
Formula translated = null;
int literal = 0;
Map<Variable,TupleSet> env = null;
public Map<Variable, TupleSet> env() { return env; }
public int literal() { return literal; }
public Node node() { return node; }
void setAll(Node node, Formula translated, int literal, Map<Variable,TupleSet> env) {
this.node = node;
this.translated = translated;
this.literal = literal;
this.env = env;
}
TranslationRecord setAll(MutableRecord other) {
setAll(other.node, other.translated, other.literal, other.env);
other.setAll(null,null,0,null);
return this;
}
public Formula translated() { return translated;}
}
}