/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* 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 edu.mit.csail.sdg.alloy4compiler.sim;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/** Immutable; represents a tuple.
*
* <p><b>Thread Safety:</b> Safe.
*/
public final class SimTuple implements Iterable<SimAtom> {
/** Stores the tuple. */
private SimAtom[] array;
/** If nonzero, it caches the hash code. */
private int hashCode = 0;
/** Construct a tuple backed by the given array as-is; thus the caller must not modify it any more. */
private SimTuple(SimAtom[] array) {
if (array.length==0) throw new IllegalArgumentException();
this.array=array;
}
/** Construct the n-ary tuple; throws an exception if the given list is empty. */
public static SimTuple make(List<SimAtom> list) {
if (list.size()==0) throw new IllegalArgumentException();
SimAtom[] array = new SimAtom[list.size()];
for(int i=0, n=list.size(); i<n; i++) { array[i] = list.get(i); }
return new SimTuple(array);
}
/** Construct the binary tuple (a,b) */
public static SimTuple make(SimAtom a, SimAtom b) {
return new SimTuple(new SimAtom[]{a, b});
}
/** Construct the unary tuple containing the given atom. */
public static SimTuple make(SimAtom atom) {
return new SimTuple(new SimAtom[]{atom});
}
/** Construct the unary tuple containing the given atom. */
public static SimTuple make(String atom) {
return new SimTuple(new SimAtom[]{SimAtom.make(atom)});
}
/** Construct the tuple containing the given list of atoms; the list must not be empty. */
public static SimTuple make(String[] atoms) {
SimAtom[] ans = new SimAtom[atoms.length];
for(int i=0; i<atoms.length; i++) ans[i] = SimAtom.make(atoms[i]);
return new SimTuple(ans);
}
/** Write this SimTuple as (".." ".." "..") */
void write(BufferedOutputStream out) throws IOException {
out.write('(');
for(int n=array.length, i=0; i<n; i++) {
if (i>0) out.write(' ');
array[i].write(out);
}
out.write(')');
}
/** Read a (".." ".." "..") tuple assuming the leading "(" has already been consumed. */
static SimTuple read(BufferedInputStream in) throws IOException {
List<SimAtom> list = new ArrayList<SimAtom>();
while(true) {
int c = in.read();
if (c<0) throw new IOException("Unexpected EOF");
if (c>0 && c<=' ') continue; // skip whitespace
if (c==')') break;
if (c!='\"') throw new IOException("Expecting start of atom");
list.add(SimAtom.read(in));
c = in.read();
if (c<0) throw new IOException("Unexpected EOF");
if (c==')') break;
if (!(c<=' ')) throw new IOException("Expecting \')\' or white space after an atom.");
}
if (list.size()==0) throw new IOException("Tuple arity cannot be 0.");
return make(list);
}
/** Returns the arity of this tuple. */
public int arity() { return array.length; }
/** Return the i-th atom from this tuple. */
public SimAtom get(int i) { return array[i]; }
/** Returns true if this tuple contains at least one occurrence of the given atom. */
public boolean has(SimAtom atom) {
for(int i=array.length-1; i>=0; i--) if (array[i]==atom) return true;
return false;
}
/** Replace the i-th atom, and return the resulting SimTuple. */
public SimTuple replace(int i, SimAtom newAtom) {
if (array[i] == newAtom) return this;
SimAtom ar[] = new SimAtom[array.length];
for(int j=0; j<ar.length; j++) ar[j] = (j==i) ? newAtom : array[j];
return new SimTuple(ar);
}
/** Replace each atom using the given SimAtom->SimAtom map; any atom not in the map will stay unchanged. */
public SimTuple replace(Map<SimAtom,SimAtom> map) {
SimAtom[] newarray = new SimAtom[array.length];
for(int i=array.length-1; i>=0; i--) {
SimAtom oldX = array[i];
SimAtom newX = map.get(oldX);
newarray[i] = (newX==null ? oldX : newX);
}
return new SimTuple(newarray);
}
/** Prepend the given atom to the front of this tuple, then return the resulting new Tuple. */
public SimTuple prepend(SimAtom atom) {
SimAtom[] newarray = new SimAtom[array.length+1];
newarray[0] = atom;
for(int i=0; i<array.length; i++) newarray[i+1] = array[i];
return new SimTuple(newarray);
}
/** Append the given atom to the back of this tuple, then return the resulting new Tuple. */
public SimTuple append(SimAtom atom) {
SimAtom[] newarray = new SimAtom[array.length+1];
for(int i=0; i<array.length; i++) newarray[i] = array[i];
newarray[array.length] = atom;
return new SimTuple(newarray);
}
/** Return the product of this tuple and that tuple. */
public SimTuple product(SimTuple that) {
SimAtom[] c = new SimAtom[array.length + that.array.length]; // If integer overflows, we'll get an exception here, which is good
for(int i=0; i<this.array.length; i++) c[i] = array[i];
for(int i=0; i<that.array.length; i++) c[i+array.length] = that.array[i];
return new SimTuple(c);
}
/** Return the relational join of this tuple and that tuple; throws an exception if the join point doesn't match or if both sides are unary. */
public SimTuple join(SimTuple that) {
if (array.length+that.array.length==2 || array[array.length-1]!=that.array[0]) throw new IllegalArgumentException();
SimAtom[] c = new SimAtom[array.length + that.array.length - 2]; // If integer overflows, we'll get an exception here, which is good
for(int i=0; i<this.array.length-1; i++) c[i] = array[i];
for(int i=0; i<that.array.length-1; i++) c[i+array.length-1] = that.array[i+1];
return new SimTuple(c);
}
/** {@inheritDoc} */
@Override public int hashCode() {
int ans = hashCode;
if (ans == 0) {
// We already know each SimAtom has been canonicalized, so just computing its IdentityHashCode is faster
for(int i=array.length-1; i>=0; i--) ans = ans*31 + System.identityHashCode(array[i]);
if (ans==0) ans++; // so that we don't end up re-computing this SimTuple's hashcode over and over again
hashCode = ans;
}
return ans;
}
/** {@inheritDoc} */
@Override public boolean equals(Object that) {
if (this==that) return true; else if (!(that instanceof SimTuple)) return false;
SimAtom[] other = ((SimTuple)that).array;
if (array==other) return true; else if (array.length != other.length) return false;
if (hashCode() != that.hashCode()) return false;
for(int i=array.length-1; i>=0; i--) if (array[i]!=other[i]) return false;
array=other;
// Change it so we share the same array; this is thread safe since these array contents are never mutated,
// so it doesn't matter if some thread sees the old array and some sees the new array.
// JLS 3rd Edition 17.7 guarantees that writes and reads of references are atomic though not necessarily visible,
// so another thread will either see the old array or the new array.
return true;
}
/** Returns the first atom of this tuple. */
public SimAtom head() { return array[0]; }
/** Returns the last atom of this tuple. */
public SimAtom tail() { return array[array.length-1]; }
/** Returns the subtuple containing the first n atoms (n must be between 1 and arity) */
public SimTuple head(int n) {
if (n<=0 || n>array.length) throw new IllegalArgumentException(); else if (n==array.length) return this;
SimAtom newtuple[] = new SimAtom[n];
for(int c=0; c<newtuple.length; c++) newtuple[c] = array[c];
return new SimTuple(newtuple);
}
/** Returns the subtuple containing the last n atoms (n must be between 1 and arity) */
public SimTuple tail(int n) {
if (n<=0 || n>array.length) throw new IllegalArgumentException(); else if (n==array.length) return this;
SimAtom newtuple[] = new SimAtom[n];
for(int a=array.length-n, c=0; c<newtuple.length; c++) newtuple[c] = array[c+a];
return new SimTuple(newtuple);
}
/** Write a human-readable representation of this tuple into the given StringBuilder object. */
public void toString(StringBuilder sb) {
for(int i=0; i<array.length; i++) {
if (i>0) sb.append("->");
sb.append(array[i]);
}
}
/** {@inheritDoc} */
@Override public String toString() {
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
/** {@inheritDoc} */
public Iterator<SimAtom> iterator() {
return new Iterator<SimAtom>() {
private int i = 0; // the next index to read out
public boolean hasNext() { return i < array.length; }
public SimAtom next() { if (i < array.length) {i++; return array[i-1];} else throw new NoSuchElementException(); }
public void remove() { throw new UnsupportedOperationException(); }
};
}
}