}, where n is
+ * the scope of elem. Without this constraint, it might not be true that Ord.last is
+ * a subset of elem, and that the domain and range of Ord.next lie inside elem.
+ *
+ * author: Ilya Shlyakhter
+ * revisions: Daniel jackson
+ */
+
+private one sig Ord {
+ First: set elem,
+ Next: elem -> elem
+} {
+ pred/totalOrder[elem,First,Next]
+}
+
+/** first */
+fun first: one elem { Ord.First }
+
+/** last */
+fun last: one elem { elem - (next.elem) }
+
+/** return a mapping from each element to its predecessor */
+fun prev : elem->elem { ~(Ord.Next) }
+
+/** return a mapping from each element to its successor */
+fun next : elem->elem { Ord.Next }
+
+/** return elements prior to e in the ordering */
+fun prevs [e: elem]: set elem { e.^(~(Ord.Next)) }
+
+/** return elements following e in the ordering */
+fun nexts [e: elem]: set elem { e.^(Ord.Next) }
+
+/** e1 is less than e2 in the ordering */
+pred lt [e1, e2: elem] { e1 in prevs[e2] }
+
+/** e1 is greater than e2 in the ordering */
+pred gt [e1, e2: elem] { e1 in nexts[e2] }
+
+/** e1 is less than or equal to e2 in the ordering */
+pred lte [e1, e2: elem] { e1=e2 || lt [e1,e2] }
+
+/** e1 is greater than or equal to e2 in the ordering */
+pred gte [e1, e2: elem] { e1=e2 || gt [e1,e2] }
+
+/** returns the larger of the two elements in the ordering */
+fun larger [e1, e2: elem]: elem { lt[e1,e2] => e2 else e1 }
+
+/** returns the smaller of the two elements in the ordering */
+fun smaller [e1, e2: elem]: elem { lt[e1,e2] => e1 else e2 }
+
+/**
+ * returns the largest element in es
+ * or the empty set if es is empty
+ */
+fun max [es: set elem]: lone elem { es - es.^(~(Ord.Next)) }
+
+/**
+ * returns the smallest element in es
+ * or the empty set if es is empty
+ */
+fun min [es: set elem]: lone elem { es - es.^(Ord.Next) }
+
+assert correct {
+ let mynext = Ord.Next |
+ let myprev = ~mynext | {
+ ( all b:elem | (lone b.next) && (lone b.prev) && (b !in b.^mynext) )
+ ( (no first.prev) && (no last.next) )
+ ( all b:elem | (b!=first && b!=last) => (one b.prev && one b.next) )
+ ( !one elem => (one first && one last && first!=last && one first.next && one last.prev) )
+ ( one elem => (first=elem && last=elem && no myprev && no mynext) )
+ ( myprev=~mynext )
+ ( elem = first.*mynext )
+ (all disj a,b:elem | a in b.^mynext or a in b.^myprev)
+ (no disj a,b:elem | a in b.^mynext and a in b.^myprev)
+ (all disj a,b,c:elem | (b in a.^mynext and c in b.^mynext) =>(c in a.^mynext))
+ (all disj a,b,c:elem | (b in a.^myprev and c in b.^myprev) =>(c in a.^myprev))
+ }
+}
+run {} for exactly 0 elem expect 0
+run {} for exactly 1 elem expect 1
+run {} for exactly 2 elem expect 1
+run {} for exactly 3 elem expect 1
+run {} for exactly 4 elem expect 1
+check correct for exactly 0 elem
+check correct for exactly 1 elem
+check correct for exactly 2 elem
+check correct for exactly 3 elem
+check correct for exactly 4 elem
+check correct for exactly 5 elem
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als
new file mode 100644
index 00000000..d648a77c
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als
@@ -0,0 +1,101 @@
+module util/relation
+
+/*
+ * Utilities for some common operations and constraints
+ * on binary relations. The keyword 'univ' represents the
+ * top-level type, which all other types implicitly extend.
+ * Therefore, all the functions and predicates in this model
+ * may be applied to binary relations of any type.
+ *
+ * author: Greg Dennis
+ */
+
+/** returns the domain of a binary relation */
+fun dom [r: univ->univ] : set (r.univ) { r.univ }
+
+/** returns the range of a binary relation */
+fun ran [r: univ->univ] : set (univ.r) { univ.r }
+
+/** r is total over the domain s */
+pred total [r: univ->univ, s: set univ] {
+ all x: s | some x.r
+}
+
+/** r is a partial function over the domain s */
+pred functional [r: univ->univ, s: set univ] {
+ all x: s | lone x.r
+}
+
+/** r is a total function over the domain s */
+pred function [r: univ->univ, s: set univ] {
+ all x: s | one x.r
+}
+
+/** r is surjective over the codomain s */
+pred surjective [r: univ->univ, s: set univ] {
+ all x: s | some r.x
+}
+
+/** r is injective */
+pred injective [r: univ->univ, s: set univ] {
+ all x: s | lone r.x
+}
+
+/** r is bijective over the codomain s */
+pred bijective[r: univ->univ, s: set univ] {
+ all x: s | one r.x
+}
+
+/** r is a bijection over the domain d and the codomain c */
+pred bijection[r: univ->univ, d, c: set univ] {
+ function[r, d] && bijective[r, c]
+}
+
+/** r is reflexive over the set s */
+pred reflexive [r: univ -> univ, s: set univ] {s<:iden in r}
+
+/** r is irreflexive */
+pred irreflexive [r: univ -> univ] {no iden & r}
+
+/** r is symmetric */
+pred symmetric [r: univ -> univ] {~r in r}
+
+/** r is anti-symmetric */
+pred antisymmetric [r: univ -> univ] {~r & r in iden}
+
+/** r is transitive */
+pred transitive [r: univ -> univ] {r.r in r}
+
+/** r is acyclic over the set s */
+pred acyclic[r: univ->univ, s: set univ] {
+ all x: s | x !in x.^r
+}
+
+/** r is complete over the set s */
+pred complete[r: univ->univ, s: univ] {
+ all x,y:s | (x!=y => x->y in (r + ~r))
+}
+
+/** r is a preorder (or a quasi-order) over the set s */
+pred preorder [r: univ -> univ, s: set univ] {
+ reflexive[r, s]
+ transitive[r]
+}
+
+/** r is an equivalence relation over the set s */
+pred equivalence [r: univ->univ, s: set univ] {
+ preorder[r, s]
+ symmetric[r]
+}
+
+/** r is a partial order over the set s */
+pred partialOrder [r: univ -> univ, s: set univ] {
+ preorder[r, s]
+ antisymmetric[r]
+}
+
+/** r is a total order over the set s */
+pred totalOrder [r: univ -> univ, s: set univ] {
+ partialOrder[r, s]
+ complete[r, s]
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als
new file mode 100644
index 00000000..1fe3802d
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als
@@ -0,0 +1,108 @@
+module util/seqrel[elem]
+
+/*
+ * A sequence utility for modeling sequences as just a
+ * relation as opposed to reifying them into sequence
+ * atoms like the util/sequence module does.
+ *
+ * @author Greg Dennis
+ */
+
+open util/integer
+open util/ordering[SeqIdx] as ord
+
+sig SeqIdx {}
+
+/** sequence covers a prefix of SeqIdx */
+pred isSeq[s: SeqIdx -> elem] {
+ s in SeqIdx -> lone elem
+ s.inds - ord/next[s.inds] in ord/first
+}
+
+/** returns all the elements in this sequence */
+fun elems [s: SeqIdx -> elem]: set elem { SeqIdx.s }
+
+/** returns the first element in the sequence */
+fun first [s: SeqIdx -> elem]: lone elem { s[ord/first] }
+
+/** returns the last element in the sequence */
+fun last [s: SeqIdx -> elem]: lone elem { s[lastIdx[s]] }
+
+/** returns the cdr of the sequence */
+fun rest [s: SeqIdx -> elem] : SeqIdx -> elem {
+ (ord/next).s
+}
+
+/** returns all but the last element of the sequence */
+fun butlast [s: SeqIdx -> elem] : SeqIdx -> elem {
+ (SeqIdx - lastIdx[s]) <: s
+}
+
+/** true if the sequence is empty */
+pred isEmpty [s: SeqIdx -> elem] { no s }
+
+/** true if this sequence has duplicates */
+pred hasDups [s: SeqIdx -> elem] { # elems[s] < # inds[s] }
+
+/** returns all the indices occupied by this sequence */
+fun inds [s: SeqIdx -> elem]: set SeqIdx { s.elem }
+
+/** returns last index occupied by this sequence */
+fun lastIdx [s: SeqIdx -> elem]: lone SeqIdx { ord/max[inds[s]] }
+
+/**
+ * returns the index after the last index
+ * if this sequence is empty, returns the first index,
+ * if this sequence is full, returns empty set
+ */
+fun afterLastIdx [s: SeqIdx -> elem] : lone SeqIdx {
+ ord/min[SeqIdx - inds[s]]
+}
+
+/** returns first index at which given element appears or the empty set if it doesn't */
+fun idxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/min[indsOf[s, e]] }
+
+/** returns last index at which given element appears or the empty set if it doesn't */
+fun lastIdxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/max[indsOf[s, e]] }
+
+/** returns set of indices at which given element appears or the empty set if it doesn't */
+fun indsOf [s: SeqIdx -> elem, e: elem] : set SeqIdx { s.e }
+
+/**
+ * return the result of appending e to the end of s
+ * just returns s if s exhausted SeqIdx
+ */
+fun add [s: SeqIdx -> elem, e: elem] : SeqIdx -> elem {
+ setAt[s, afterLastIdx[s], e]
+}
+
+/** returns the result of setting the value at index i in sequence to e */
+fun setAt [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem {
+ s ++ i -> e
+}
+
+/** returns the result of inserting value e at index i */
+fun insert [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem {
+ (ord/prevs[i] <: s) + (i->e) + (~(ord/next)).((ord/nexts[i] + i) <: s)
+}
+
+/** returns the result of deleting the value at index i */
+fun delete[s: SeqIdx -> elem, i: SeqIdx] : SeqIdx -> elem {
+ (ord/prevs[i] <: s) + (ord/next).(ord/nexts[i] <: s)
+}
+
+/** appended is the result of appending s2 to s1 */
+fun append [s1, s2: SeqIdx -> elem] : SeqIdx -> elem {
+ let shift = {i', i: SeqIdx | #ord/prevs[i'] = add[#ord/prevs[i], add[#ord/prevs[lastIdx[s1]], 1]] } |
+ s1 + shift.s2
+}
+
+/** returns the subsequence of s between from and to, inclusive */
+fun subseq [s: SeqIdx -> elem, from, to: SeqIdx] : SeqIdx -> elem {
+ let shift = {i', i: SeqIdx | #ord/prevs[i'] = sub[#ord/prevs[i], #ord/prevs[from]] } |
+ shift.((SeqIdx - ord/nexts[to]) <: s)
+}
+
+fun firstIdx: SeqIdx { ord/first }
+
+fun finalIdx: SeqIdx { ord/last }
\ No newline at end of file
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als
new file mode 100644
index 00000000..524e3cf7
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als
@@ -0,0 +1,166 @@
+module util/sequence[elem]
+
+/*
+ * Creates sequences (or lists) of elements. The ordered signature SeqIdx
+ * represents the indexes at which elements can be located, and a sequence
+ * is modeled as a mapping from indexes to elements. Empty sequences are
+ * allowed, and a sequence may have a single element appear multiple times.
+ * Maximum length of a sequence is determined by the scope of SeqIdx.
+ *
+ * Sequences always cover an initial segment of SeqIdx. That is, every
+ * sequence (except the empty sequence) begins at the first SeqIdx and does
+ * not have gaps in indexes that it covers. In other words, if a sequence has
+ * its last element at index i, then the sequence has elements at all the
+ * indexes that precede i.
+ *
+ * Oftentimes, a model will need to require that all sequences that could
+ * exist, do exist. Calling the allExist predicate will ensure that that there is
+ * a Seq atom for each possible sequences of elements with length less than
+ * or equal to the scope of SeqIdx.
+ *
+ * The functions and predicates at the bottom of this module provide all
+ * functionality of util/ordering on SeqIdx.
+ *
+ * revisions: Greg Dennis
+ */
+
+open util/ordering[SeqIdx] as ord
+
+sig SeqIdx {}
+
+sig Seq {
+ seqElems: SeqIdx -> lone elem
+}
+{
+ // Ensure that elems covers only an initial segment of SeqIdx,
+ // equal to the length of the signature
+ all i: SeqIdx - ord/first | some i.seqElems => some ord/prev[i].seqElems
+}
+
+/** no two sequences are identical */
+fact canonicalizeSeqs {
+ no s1, s2: Seq | s1!=s2 && s1.seqElems=s2.seqElems
+}
+
+/** invoke if you want none of the sequences to have duplicates */
+pred noDuplicates {
+ all s: Seq | !s.hasDups
+}
+
+/** invoke if you want all sequences within scope to exist */
+pred allExist {
+ (some s: Seq | s.isEmpty) &&
+ (all s: Seq | SeqIdx !in s.inds => (all e: elem | some s': Seq | s.add[e, s']))
+}
+
+/** invoke if you want all sequences within scope with no duplicates */
+pred allExistNoDuplicates {
+ some s: Seq | s.isEmpty
+ all s: Seq {
+ !s.hasDups
+ SeqIdx !in s.inds => (all e: elem - s.elems | some s': Seq | s.add[e, s'])
+ }
+}
+
+/** returns element at the given index */
+fun at [s: Seq, i: SeqIdx]: lone elem { i.(s.seqElems) }
+
+/** returns all the elements in this sequence */
+fun elems [s: Seq]: set elem { SeqIdx.(s.seqElems) }
+
+/** returns the first element in the sequence */
+fun first [s:Seq]: lone elem { s.at[ord/first] }
+
+/** returns the last element in the sequence */
+fun last [s:Seq]: lone elem { s.at[s.lastIdx] }
+
+/**
+ * true if the argument is the "cdr" of this sequence
+ * false if this sequence is empty
+ */
+pred rest [s, r: Seq] {
+ !s.isEmpty
+ all i: SeqIdx | r.at[i] = s.at[ord/next[i]]
+}
+
+/** true if the sequence is empty */
+pred isEmpty [s:Seq] { no s.elems }
+
+/** true if this sequence has duplicates */
+pred hasDups [s:Seq] { # elems[s] < # inds[s] }
+
+/** returns all the indices occupied by this sequence */
+fun inds [s:Seq] : set SeqIdx { elem.~(s.seqElems) }
+
+/** returns last index occupied by this sequence */
+fun lastIdx [s:Seq] : lone SeqIdx { ord/max[s.inds] }
+
+/**
+ * returns the index after the last index
+ * if this sequence is empty, returns the first index,
+ * if this sequence is full, returns empty set
+ */
+fun afterLastIdx [s:Seq] : lone SeqIdx {
+ ord/min[SeqIdx - s.inds]
+}
+
+/** returns first index at which given element appears or the empty set if it doesn't */
+fun idxOf [s: Seq, e: elem] : lone SeqIdx { ord/min[s.indsOf[e]] }
+
+/** returns last index at which given element appears or the empty set if it doesn't */
+fun lastIdxOf [s: Seq, e: elem] : lone SeqIdx { ord/max[s.indsOf[e]] }
+
+/** returns set of indices at which given element appears or the empty set if it doesn't */
+fun indsOf [s: Seq, e: elem] : set SeqIdx { (s.seqElems).e }
+
+/** true if this starts with prefix */
+pred startsWith [s, prefix: Seq] {
+ all i: prefix.inds | s.at[i] = prefix.at[i]
+}
+
+/** added is the result of appending e to the end of s */
+pred add [s: Seq, e: elem, added: Seq] {
+ added.startsWith[s]
+ added.seqElems[s.afterLastIdx] = e
+ #added.inds = #s.inds.add[1]
+}
+
+/** setted is the result of setting value at index i to e */
+pred setAt [s: Seq, idx: SeqIdx, e: elem, setted: Seq] {
+ setted.seqElems = s.seqElems ++ idx->e
+}
+
+/** inserts is the result of inserting value e at index i */
+pred insert [s: Seq, idx: SeqIdx, e: elem, inserted: Seq] {
+ inserted.at[idx] = e
+ all i: ord/prevs[idx] | inserted.at[i] = s.at[i]
+ all i: ord/nexts[idx] | inserted.at[i] = s.at[ord/prev[i]]
+ #inserted.inds = #s.inds.add[1]
+}
+
+/** copies source into dest starting at destStart */
+pred copy [source, dest: Seq, destStart: SeqIdx] {
+ all sourceIdx : source.inds | some destIdx: SeqIdx {
+ ord/gte[destIdx, destStart]
+ dest.at[destIdx] = source.at[sourceIdx]
+ #ord/prevs[sourceIdx] = #(ord/prevs[destIdx] - ord/prevs[destStart])
+ }
+}
+
+/** appended is the result of appending s2 to s1 */
+pred append [s1, s2, appended: Seq] {
+ appended.startsWith[s1]
+ copy[s2, appended, s1.afterLastIdx]
+ #appended.inds = #s1.inds.add[#s2.inds]
+}
+
+/** sub is the subsequence of s between from and to, inclusive */
+pred subseq [s, sub: Seq, from, to: SeqIdx] {
+ ord/lte[from, to]
+ copy[sub, s, from]
+ #sub.inds = #(to + ord/prevs[to] - ord/prevs[from])
+}
+
+fun firstIdx: SeqIdx { ord/first }
+
+fun finalIdx: SeqIdx { ord/last }
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als
new file mode 100644
index 00000000..efa8bf42
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als
@@ -0,0 +1,137 @@
+module util/sequniv
+
+open util/integer as ui
+
+/*
+ * NOTE: Do not include this module manually.
+ * Instead, use the "seq" keyword which will automatically
+ * import this module with the correct additional constraints as needed.
+ */
+
+/*
+ * A sequence utility for modeling sequences as just a
+ * relation as opposed to reifying them into sequence
+ * atoms like the util/sequence module does.
+ *
+ * Precondition: each input sequence must range over a prefix
+ * of seq/Int.
+ *
+ * Postcondition: we guarantee the returned sequence
+ * also ranges over a prefix of seq/Int.
+ *
+ * @author Greg Dennis
+ */
+
+/** sequence covers a prefix of seq/Int */
+pred isSeq[s: Int -> univ] {
+ s in seq/Int -> lone univ
+ s.inds - ui/next[s.inds] in 0
+}
+
+/** returns all the elements in this sequence */
+fun elems [s: Int -> univ]: set (Int.s) { seq/Int . s }
+
+/**
+ * returns the first element in the sequence
+ * (Returns the empty set if the sequence is empty)
+ */
+fun first [s: Int -> univ]: lone (Int.s) { s[0] }
+
+/**
+ * returns the last element in the sequence
+ * (Returns the empty set if the sequence is empty)
+ */
+fun last [s: Int -> univ]: lone (Int.s) { s[lastIdx[s]] }
+
+/**
+ * returns the cdr of the sequence
+ * (Returns the empty sequence if the sequence has 1 or fewer element)
+ */
+fun rest [s: Int -> univ] : s { seq/Int <: ((ui/next).s) }
+
+/** returns all but the last element of the sequence */
+fun butlast [s: Int -> univ] : s {
+ (seq/Int - lastIdx[s]) <: s
+}
+
+/** true if the sequence is empty */
+pred isEmpty [s: Int -> univ] { no s }
+
+/** true if this sequence has duplicates */
+pred hasDups [s: Int -> univ] { # elems[s] < # inds[s] }
+
+/** returns all the indices occupied by this sequence */
+fun inds [s: Int -> univ]: set Int { s.univ }
+
+/**
+ * returns last index occupied by this sequence
+ * (Returns the empty set if the sequence is empty)
+ */
+fun lastIdx [s: Int -> univ]: lone Int { ui/max[inds[s]] }
+
+/**
+ * returns the index after the last index
+ * if this sequence is empty, returns 0
+ * if this sequence is full, returns empty set
+ */
+fun afterLastIdx [s: Int -> univ] : lone Int { ui/min[seq/Int - inds[s]] }
+
+/** returns first index at which given element appears or the empty set if it doesn't */
+fun idxOf [s: Int -> univ, e: univ] : lone Int { ui/min[indsOf[s, e]] }
+
+/** returns last index at which given element appears or the empty set if it doesn't */
+fun lastIdxOf [s: Int -> univ, e: univ] : lone Int { ui/max[indsOf[s, e]] }
+
+/** returns set of indices at which given element appears or the empty set if it doesn't */
+fun indsOf [s: Int -> univ, e: univ] : set Int { s.e }
+
+/**
+ * return the result of appending e to the end of s
+ * (returns s if s exhausted seq/Int)
+ */
+fun add [s: Int -> univ, e: univ] : s + (seq/Int->e) {
+ setAt[s, afterLastIdx[s], e]
+}
+
+/**
+ * returns the result of setting the value at index i in sequence to e
+ * Precondition: 0 <= i < #s
+ */
+fun setAt [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) {
+ s ++ i -> e
+}
+
+/**
+ * returns the result of inserting value e at index i
+ * (if sequence was full, the original last element will be removed first)
+ * Precondition: 0 <= i <= #s
+ */
+fun insert [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) {
+ seq/Int <: ((ui/prevs[i] <: s) + (i->e) + ui/prev.((ui/nexts[i] + i) <: s))
+}
+
+/**
+ * returns the result of deleting the value at index i
+ * Precondition: 0 <= i < #s
+ */
+fun delete[s: Int -> univ, i: Int] : s {
+ (ui/prevs[i] <: s) + (ui/next).(ui/nexts[i] <: s)
+}
+
+/**
+ * appended is the result of appending s2 to s1
+ * (If the resulting sequence is too long, it will be truncated)
+ */
+fun append [s1, s2: Int -> univ] : s1+s2 {
+ let shift = {i', i: seq/Int | int[i'] = ui/add[int[i], ui/add[int[lastIdx[s1]], 1]] } |
+ no s1 => s2 else (s1 + shift.s2)
+}
+
+/**
+ * returns the subsequence of s between from and to, inclusive
+ * Precondition: 0 <= from <= to < #s
+ */
+fun subseq [s: Int -> univ, from, to: Int] : s {
+ let shift = {i', i: seq/Int | int[i'] = ui/sub[int[i], int[from]] } |
+ shift.((seq/Int - ui/nexts[to]) <: s)
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als
new file mode 100644
index 00000000..6dbc0881
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als
@@ -0,0 +1,50 @@
+module util/ternary
+
+/*
+ * Utilities for some common operations and constraints
+ * on ternary relations. The keyword 'univ' represents the
+ * top-level type, which all other types implicitly extend.
+ * Therefore, all the functions and predicates in this model
+ * may be applied to ternary relations of any type.
+ *
+ * author: Greg Dennis
+ */
+
+/** returns the domain of a ternary relation */
+fun dom [r: univ->univ->univ] : set ((r.univ).univ) { (r.univ).univ }
+
+/** returns the range of a ternary relation */
+fun ran [r: univ->univ->univ] : set (univ.(univ.r)) { univ.(univ.r) }
+
+/** returns the "middle range" of a ternary relation */
+fun mid [r: univ->univ->univ] : set (univ.(r.univ)) { univ.(r.univ) }
+
+/** returns the first two columns of a ternary relation */
+fun select12 [r: univ->univ->univ] : r.univ {
+ r.univ
+}
+
+/** returns the first and last columns of a ternary relation */
+fun select13 [r: univ->univ->univ] : ((r.univ).univ) -> (univ.(univ.r)) {
+ {x: (r.univ).univ, z: univ.(univ.r) | some (x.r).z}
+}
+
+/** returns the last two columns of a ternary relation */
+fun select23 [r: univ->univ->univ] : univ.r {
+ univ.r
+}
+
+/** flips the first two columns of a ternary relation */
+fun flip12 [r: univ->univ->univ] : (univ.(r.univ))->((r.univ).univ)->(univ.(univ.r)) {
+ {x: univ.(r.univ), y: (r.univ).univ, z: univ.(univ.r) | y->x->z in r}
+}
+
+/** flips the first and last columns of a ternary relation */
+fun flip13 [r: univ->univ->univ] : (univ.(univ.r))->(univ.(r.univ))->((r.univ).univ) {
+ {x: univ.(univ.r), y: univ.(r.univ), z: (r.univ).univ | z->y->x in r}
+}
+
+/** flips the last two columns of a ternary relation */
+fun flip23 [r: univ->univ->univ] : ((r.univ).univ)->(univ.(univ.r))->(univ.(r.univ)) {
+ {x: (r.univ).univ, y: univ.(univ.r), z: univ.(r.univ) | x->z->y in r}
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als
new file mode 100644
index 00000000..b6f9d8ca
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als
@@ -0,0 +1,53 @@
+open util/ordering[Time]
+
+sig Time { }
+
+let dynamic[x] = x one-> Time
+
+let dynamicSet[x] = x -> Time
+
+let then [a, b, t, t'] {
+ some x:Time | a[t,x] && b[x,t']
+}
+
+let while = while3
+
+let while9 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while8[cond,body,x,t']
+}
+
+let while8 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while7[cond,body,x,t']
+}
+
+let while7 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while6[cond,body,x,t']
+}
+
+let while6 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while5[cond,body,x,t']
+}
+
+let while5 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while4[cond,body,x,t']
+}
+
+let while4 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while3[cond,body,x,t']
+}
+
+let while3 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while2[cond,body,x,t']
+}
+
+let while2 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while1[cond,body,x,t']
+}
+
+let while1 [cond, body, t, t'] {
+ some x:Time | (cond[t] => body[t,x] else t=x) && while0[cond,body,x,t']
+}
+
+let while0 [cond, body, t, t'] {
+ !cond[t] && t=t'
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties b/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties
new file mode 100644
index 00000000..2b3f0f00
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties
@@ -0,0 +1,8 @@
+Lanceur.wrong.framework=Wrong framework: try minisat or ubcsat
+MoreThanSAT.0=Satisfiable \!
+MoreThanSAT.1=BackBone:
+MoreThanSAT.2=Counting solutions...
+MoreThanSAT.3=Number of solutions:
+MoreThanSAT.4=Unsatisfiable\!
+MoreThanSAT.5=Unsatisfiable (trivial)\!
+MoreThanSAT.6=Timeout, sorry\!
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/overview.html b/Source/eu.modelwriter.alloyanalyzer/bin/overview.html
new file mode 100644
index 00000000..0fd29581
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/overview.html
@@ -0,0 +1,33 @@
+
+SAT4J: a SATisfiability library for Java.
+
+
+/*******************************************************************************
+* SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
+*
+* 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
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU Lesser General Public License Version 2.1 or later (the
+* "LGPL"), in which case the provisions of the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of the LGPL, and not to allow others to use your version of
+* this file under the terms of the EPL, indicate your decision by deleting
+* the provisions above and replace them with the notice and other provisions
+* required by the LGPL. If you do not delete the provisions above, a recipient
+* may use your version of this file under the terms of the EPL or the LGPL.
+*
+* Based on the original MiniSat specification from:
+*
+* An extensible SAT solver. Niklas Een and Niklas Sorensson. Proceedings of the
+* Sixth International Conference on Theory and Applications of Satisfiability
+* Testing, LNCS 2919, pp 502-518, 2003.
+*
+* See www.minisat.se for the original solver in C++.
+*
+*******************************************************************************/
+
+
\ No newline at end of file
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version b/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version
new file mode 100644
index 00000000..a27f8f26
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version
@@ -0,0 +1 @@
+2.3.2.v20120709
diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..f14a9926
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF
@@ -0,0 +1,31 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %bundleName
+Bundle-SymbolicName: org.sat4j.core
+Bundle-Version: 2.3.2.v20120709
+Export-Package: org.sat4j;version="2.3.2.v20120709",
+ org.sat4j.core;version="2.3.2.v20120709",
+ org.sat4j.minisat;version="2.3.2.v20120709",
+ org.sat4j.minisat.constraints;version="2.3.2.v20120709",
+ org.sat4j.minisat.constraints.card;version="2.3.2.v20120709",
+ org.sat4j.minisat.constraints.cnf;version="2.3.2.v20120709",
+ org.sat4j.minisat.core;version="2.3.2.v20120709",
+ org.sat4j.minisat.learning;version="2.3.2.v20120709",
+ org.sat4j.minisat.orders;version="2.3.2.v20120709",
+ org.sat4j.minisat.restarts;version="2.3.2.v20120709",
+ org.sat4j.opt;version="2.3.2.v20120709",
+ org.sat4j.reader;version="2.3.2.v20120709",
+ org.sat4j.specs;version="2.3.2.v20120709",
+ org.sat4j.tools;version="2.3.2.v20120709",
+ org.sat4j.tools.xplain;version="2.3.2.v20120709"
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Built-By: Daniel Le Berre
+Main-Class: org.sat4j.BasicLauncher
+Specification-Title: SAT4J
+Specification-Version: NA
+Specification-Vendor: Daniel Le Berre
+Implementation-Title: SAT4J
+Implementation-Version: 2.3.2.v20120709
+Implementation-Vendor: CRIL CNRS UMR 8188 - Universite d'Artois
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
diff --git a/Source/eu.modelwriter.alloyanalyzer/build.properties b/Source/eu.modelwriter.alloyanalyzer/build.properties
new file mode 100644
index 00000000..20001ebb
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/build.properties
@@ -0,0 +1,9 @@
+source.alloyanalyzer.jar = src/
+bin.includes = META-INF/,\
+ lib/alloy4.2.jar,\
+ alloyanalyzer.jar,\
+ bin/,\
+ build.properties,\
+ .settings/,\
+ .project,\
+ .classpath
diff --git a/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar b/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar
new file mode 100644
index 00000000..3be21612
Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar differ
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF
new file mode 100644
index 00000000..c10f81ea
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF
@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Created-By: 1.5.0 (Sun Microsystems Inc.)
+Main-Class: edu.mit.csail.sdg.alloy4whole.SimpleGUI
+
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/README.TXT b/Source/eu.modelwriter.alloyanalyzer/src/README.TXT
new file mode 100644
index 00000000..7cd623ea
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/README.TXT
@@ -0,0 +1,63 @@
+The Alloy Analyzer
+
+ The Alloy Analyzer is a tool developed by the Software Design
+ Group (http://sdg.csail.mit.edu/) for analyzing models written in
+ Alloy, a simple structural modeling language based on first-order
+ logic. The tool can generate instances of invariants, simulate
+ the execution of operations (even those defined implicitly), and
+ check user-specified properties of a model. Alloy and its
+ analyzer have been used primarily to explore abstract software
+ designs. Its use in analyzing code for conformance to a
+ specification and as an automatic test case generator are being
+ investigated in ongoing research projects.
+
+ See the web page for a description of what's new in Alloy:
+
+ http://alloy.mit.edu/
+
+
+Detailed Instructions:
+
+ 1. Java 5 or later
+
+ Java runtimes are available at no economic charge from Sun and
+ IBM and others. One may have come pre-installed in your OS.
+ Alloy does not currently work with gcj because of its limited
+ library support.
+
+ 2. Running Alloy on Mac OS X
+
+ Just double-click on the dmg file,
+ then drag the Alloy application into your application directory.
+
+ 3. Running Alloy on other platforms
+
+ Just double-click on the jar file, or type:
+
+ java -jar alloy4.jar
+
+The source code for the Alloy Analyzer is available
+under the MIT license.
+
+The Alloy Analyzer utilizes several third-party packages whose code may
+be distributed under a different license (see the various LICENSE files
+in the distribution for details). We are extremely grateful to the authors
+of these packages for making their source code freely available.
+
+ * Kodkod
+ http://web.mit.edu/~emina/www/kodkod.html
+
+ * CUP Parser Generator for Java
+ http://www2.cs.tum.edu/projects/cup/
+
+ * JFlex scanner generator for Java
+ http://jflex.de/
+
+ * The zChaff solver
+ http://www.princeton.edu/~chaff/zchaff.html
+
+ * The MiniSat solver
+ http://www.cs.chalmers.se/Cs/Research/FormalMethods/MiniSat/
+
+ * The SAT4J solver
+ http://www.sat4j.org/
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/README.md b/Source/eu.modelwriter.alloyanalyzer/src/README.md
new file mode 100644
index 00000000..9942d4bc
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/README.md
@@ -0,0 +1,45 @@
+Alloy Analyzer (source code mirror)
+===================================
+
+Summary
+-------
+This is a copy of the source code for [MIT's Alloy Analyzer model checking tool](http://alloy.mit.edu/alloy/).
+It also includes an Ant build.xml script, which is not part of the original MIT source code.
+This copy was created to facilitate modification to the core Alloy tool (the parts which fall
+under the `edu.mit` package structure).
+
+It was created as follows (not necessarily in this order):
+
+1. Downloaded the JAR file located at: http://alloy.mit.edu/alloy/downloads/alloy4.2.jar
+2. Extracted the JAR file.
+3. Added this `README.md` file and a `build.xml` file.
+3. Deleted core `.class` files (using the _clean_ target in `build.xml`)
+
+Building
+--------
+The Ant build.xml script contains the following targets:
+
+- _build_: Compiles the `.java` files under the `edu` directory.
+
+ Other directories are not touched; it is assumed that these contain libraries
+ which have been pre-compiled.
+
+ The auto-generated parser and lexer `.java` files (located in the `edu/mit/csail/sdg/alloy4compiler/parser` directory)
+ are neither deleted nor generated by the Ant script. The directory already contains shell scripts
+ to re-generate them using JFlex and CUP.
+- _dist_: Creates an executable JAR file in the `dist` directory. This JAR file looks essentially like the official
+ Alloy JAR file released by MIT.
+- _all_: Runs _dist_.
+- _clean_: Deletes the `dist` directory and all class files under the `edu` directory.
+
+Notes
+-----
+
+- As per the manifest, the main class is `edu.mit.csail.sdg.alloy4whole.SimpleGUI`.
+- The version number and build date which the tool displays are not accurate.
+ These are set in the `edu.mit.csail.sdg.alloy4.Version` class, and are supposed to be
+ updated by the build script when building a release.
+ This project was not intended to create official releases, so it was left as-is.
+- There is a class `edu.mit.csail.sdg.alloy4.MailBug` which includes logic to email
+ crash reports to MIT. You should change this class if you are modifying the source code
+ and creating your own release.
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/about.html b/Source/eu.modelwriter.alloyanalyzer/src/about.html
new file mode 100644
index 00000000..99988c9b
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/about.html
@@ -0,0 +1,56 @@
+
+
+
+
+About
+
+
+About This Content
+
+June, 2010
+License
+
+The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html . For purposes of the EPL, "Program" will mean the Content.
+
+If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org .
+
+Third Party Content
+The Content includes items that have been sourced from third parties as set out below. If you did not receive this Content directly from the Eclipse Foundation, the following is provided for informational purposes only, and you should look to the Redistributor’s license for terms and conditions of use.
+SAT4J 2.3.2 SUBSET (Core)
+The SAT4J project makes available all content in this plug-in ("Content"). Your use of the Content is governed by the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content.
+Alternatively, the Content may be obtained from the SAT4J project website at http://www.sat4j.org/ for use under the terms of either the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the LGPL are applicable instead of those above. If you wish to allow use of your version of the Content only under the terms of the LGPL, and not to allow others to use your version of this Content under the terms of the EPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of the EPL or the LGPL.
+The Eclipse Foundation elects to include this software in this distribution under the EPL license. The source code for this plug-in can be obtained from the SAT4J project website at http://www.sat4j.org/
+
+SAT4J includes content that was obtained under licenses that differ from the SAT4J licenses.
+ The content in the following classes
+
+
+ org/sat4j/core/Vec.java
+ org/sat4j/core/VecInt.java
+ org/sat4j/minisat/core/Solver.java
+
+is based on code obtained from the Minisat 1.1.4 implementation, the source code for which can be found at www.minisat.se , under the following permissive license:
+
+ MiniSat -- Copyright (c) 2003-2005, Niklas Een, Niklas Sorensson
+
+ 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.
+
+
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin
new file mode 100644
index 00000000..f0aa2cee
Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin differ
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so
new file mode 100644
index 00000000..d286aa4b
Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so differ
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so
new file mode 100644
index 00000000..392474ac
Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so differ
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so
new file mode 100644
index 00000000..dc9c229d
Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so differ
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java
new file mode 100644
index 00000000..85f61920
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java
@@ -0,0 +1,186 @@
+/*
+ * 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.alloy4;
+
+/**
+ * This class receives diagnostic, progress, and warning messages from Alloy4. (This default
+ * implementation ignores all calls; you should subclass it to do the appropriate screen output)
+ */
+
+public class A4Reporter {
+
+ /** If nonnull, then we will forward requests to this reporter. */
+ private final A4Reporter parent;
+
+ /** This is a pre-constructed instance that simply ignores all calls. */
+ public static final A4Reporter NOP = new A4Reporter();
+
+ /** Constructs a default A4Reporter object that does nothing. */
+ public A4Reporter() {
+ parent = null;
+ }
+
+ /** Constructs an A4Reporter that forwards each method to the given A4Reporter. */
+ public A4Reporter(A4Reporter reporter) {
+ parent = reporter;
+ }
+
+ /**
+ * This method is called at various points to report the current progress; it is intended as a
+ * debugging aid for the developers; the messages are generally not useful for end users.
+ */
+ public void debug(String msg) {
+ if (parent != null)
+ parent.debug(msg);
+ }
+
+ /** This method is called by the parser to report parser events. */
+ public void parse(String msg) {
+ if (parent != null)
+ parent.parse(msg);
+ }
+
+ /**
+ * This method is called by the typechecker to report the type for each
+ * field/function/predicate/assertion, etc.
+ */
+ public void typecheck(String msg) {
+ if (parent != null)
+ parent.typecheck(msg);
+ }
+
+ /** This method is called by the typechecker to report a nonfatal type error. */
+ public void warning(ErrorWarning msg) {
+ if (parent != null)
+ parent.warning(msg);
+ }
+
+ /** This method is called by the ScopeComputer to report the scope chosen for each sig. */
+ public void scope(String msg) {
+ if (parent != null)
+ parent.scope(msg);
+ }
+
+ /**
+ * This method is called by the BoundsComputer to report the bounds chosen for each sig and each
+ * field.
+ */
+ public void bound(String msg) {
+ if (parent != null)
+ parent.bound(msg);
+ }
+
+ /**
+ * This method is called by the translator just before it begins generating CNF.
+ *
+ * @param solver - the solver chosen by the user (eg. SAT4J, MiniSat...)
+ * @param bitwidth - the integer bitwidth chosen by the user
+ * @param maxseq - the scope on seq/Int chosen by the user
+ * @param skolemDepth - the skolem function depth chosen by the user (0, 1, 2...)
+ * @param symmetry - the amount of symmetry breaking chosen by the user (0...)
+ */
+ public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) {
+ if (parent != null)
+ parent.translate(solver, bitwidth, maxseq, skolemDepth, symmetry);
+ }
+
+ /**
+ * This method is called by the translator just after it generated the CNF.
+ *
+ * @param primaryVars - the total number of primary variables
+ * @param totalVars - the total number of variables including the number of primary variables
+ * @param clauses - the total number of clauses
+ */
+ public void solve(int primaryVars, int totalVars, int clauses) {
+ if (parent != null)
+ parent.solve(primaryVars, totalVars, clauses);
+ }
+
+ /**
+ * If solver==KK or solver==CNF, this method is called by the translator after it constructed the
+ * Kodkod or CNF file.
+ *
+ * @param filename - the Kodkod or CNF file generated by the translator
+ */
+ public void resultCNF(String filename) {
+ if (parent != null)
+ parent.resultCNF(filename);
+ }
+
+ /**
+ * If solver!=KK and solver!=CNF, this method is called by the translator if the formula is
+ * satisfiable.
+ *
+ * @param command - this is the original Command used to generate this solution
+ * @param solvingTime - this is the number of milliseconds the solver took to obtain this result
+ * @param solution - the satisfying A4Solution object
+ */
+ public void resultSAT(Object command, long solvingTime, Object solution) {
+ if (parent != null)
+ parent.resultSAT(command, solvingTime, solution);
+ }
+
+ /**
+ * If solver!=KK and solver!=CNF, this method is called by the translator before starting the
+ * unsat core minimization.
+ *
+ * @param command - this is the original Command used to generate this solution
+ * @param before - the size of the unsat core before calling minimization
+ */
+ public void minimizing(Object command, int before) {
+ if (parent != null)
+ parent.minimizing(command, before);
+ }
+
+ /**
+ * If solver!=KK and solver!=CNF, this method is called by the translator after performing the
+ * unsat core minimization.
+ *
+ * @param command - this is the original Command used to generate this solution
+ * @param before - the size of the unsat core before calling minimization
+ * @param after - the size of the unsat core after calling minimization
+ */
+ public void minimized(Object command, int before, int after) {
+ if (parent != null)
+ parent.minimized(command, before, after);
+ }
+
+ /**
+ * If solver!=KK and solver!=CNF, this method is called by the translator if the formula is
+ * unsatisfiable.
+ *
+ * @param command - this is the original Command used to generate this solution
+ * @param solvingTime - this is the number of milliseconds the solver took to obtain this result
+ * @param solution - the unsatisfying A4Solution object
+ */
+ public void resultUNSAT(Object command, long solvingTime, Object solution) {
+ if (parent != null)
+ parent.resultUNSAT(command, solvingTime, solution);
+ }
+
+ /**
+ * This method is called by the A4SolutionWriter when it is writing a particular sig, field, or
+ * skolem.
+ */
+ public void write(Object expr) {
+ if (parent != null)
+ parent.write(expr);
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java
new file mode 100644
index 00000000..265f9e19
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java
@@ -0,0 +1,130 @@
+/* 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.alloy4;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.zip.Deflater;
+
+/** Mutable; implements a growable array of bytes.
+ *
+ * This class is more efficient than Java's ByteArrayOutputStream when writing large amount of data,
+ * because ByteArrayOutputStream will resize and copy entire existing contents every time the array needs to grow,
+ * whereas this class maintains a linked list of arrays (so when capacity is expanded we don't need to copy old data)
+ */
+
+public final class ByteBuffer {
+
+ /** The size per chunk. */
+ private static final int SIZE = 65536;
+
+ /** The list of chunks allocated so far; always has at least one chunk; every chunk is always exactly of size SIZE. */
+ private final LinkedList list = new LinkedList();
+
+ /** The number of bytes stored in the latest chunk; every chunk before that is always fully filled. */
+ private int n = 0;
+
+ /** Construct an empty byte buffer. */
+ public ByteBuffer() { list.add(new byte[SIZE]); }
+
+ /** Write the given byte into this byte buffer. */
+ private ByteBuffer w(int b) {
+ if (n==SIZE) { list.add(new byte[SIZE]); n=0; }
+ byte[] array = list.getLast();
+ array[n] = (byte)b;
+ n++;
+ return this;
+ }
+
+ /** Write the given array of bytes into this byte buffer. */
+ private ByteBuffer write(byte[] b, int offset, int len) {
+ if (b==null || len<=0) return this; else if (n==SIZE) { list.add(new byte[SIZE]); n=0; }
+ while(true) { // loop invariant: len>0 and SIZE>n
+ byte[] array = list.getLast();
+ if (len <= (SIZE-n)) { System.arraycopy(b, offset, array, n, len); n += len; return this; }
+ System.arraycopy(b, offset, array, n, SIZE-n);
+ offset += (SIZE-n);
+ len -= (SIZE-n);
+ n = 0;
+ list.add(new byte[SIZE]);
+ }
+ }
+
+ /** Write the given String into this byte buffer (by converting the String into its UTF-8 representation) */
+ public ByteBuffer write(String string) {
+ if (string.length() == 0) return this;
+ byte[] b;
+ try { b = string.getBytes("UTF-8"); } catch(UnsupportedEncodingException ex) { return this; } // exception not possible
+ return write(b, 0, b.length);
+ }
+
+ /** Write the given number into this byte buffer, followed by a space. */
+ public ByteBuffer writes(long x) {
+ return write(Long.toString(x)).w(' ');
+ }
+
+ /** Write the given number into this byte buffer (truncated to the range -32767..+32767), followed by a space. */
+ public strictfp ByteBuffer writes(double x) {
+ // These extreme values shouldn't happen, but we want to protect against them
+ if (Double.isNaN(x)) return write("0 "); else if (x>32767) return write("32767 "); else if (x<-32767) return write("-32767 ");
+ long num = (long)(x * 1000000);
+ if (num>=32767000000L) return write("32767 "); else if (num<=(-32767000000L)) return write("-32767 ");
+ // Now, regular doubles... let's allow up to 6 digits after the decimal point
+ if (num<0) { w('-'); num = -num; }
+ String str = Long.toString(num);
+ int len = str.length();
+ if (len<=6) {
+ w('.');
+ while(len<6) { w('0'); len++; }
+ return write(str).w(' ');
+ }
+ return write(str.substring(0, str.length()-6)).w('.').write(str.substring(str.length()-6)).w(' ');
+ }
+
+ /** Write the entire content into the given file using Flate compression (see RFC1951) then return the number of bytes written. */
+ public long dumpFlate(RandomAccessFile os) throws IOException {
+ Deflater zip = new Deflater(Deflater.BEST_COMPRESSION);
+ byte[] output = new byte[8192];
+ Iterator it = list.iterator(); // when null, that means we have told the Deflater that no more input would be coming
+ long ans = 0; // the number of bytes written out so far
+ while(true) {
+ if (it!=null && zip.needsInput() && it.hasNext()) {
+ byte[] in = it.next();
+ if (in == list.getLast()) { zip.setInput(in, 0, n); it=null; zip.finish(); } else { zip.setInput(in, 0, SIZE); }
+ }
+ if (it==null && zip.finished()) break;
+ int count = zip.deflate(output);
+ if (count > 0) {
+ ans = ans + count;
+ if (ans < 0) throw new IOException("Data too large to be written to the output file.");
+ os.write(output, 0, count);
+ }
+ }
+ return ans;
+ }
+
+ /** Write the entire content into the given file as-is, then return the number of bytes written. */
+ public long dump(RandomAccessFile os) throws IOException {
+ if (list.size() >= (Long.MAX_VALUE / SIZE)) throw new IOException("Data too large to be written to the output file.");
+ byte[] last = list.getLast();
+ for(byte[] x: list) if (x!=last) os.write(x);
+ if (n>0) os.write(last, 0, n);
+ return ((long)(list.size()-1)) * SIZE + n;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java
new file mode 100644
index 00000000..3e490f45
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java
@@ -0,0 +1,26 @@
+/* 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.alloy4;
+
+/** This defines a compute() method that takes an Object input and produces a String output. */
+
+public interface Computer {
+
+ /** This method takes an Object input and produces a String output.
+ * @throws Exception if an error occurred during the computation.
+ */
+ public String compute (Object input) throws Exception;
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java
new file mode 100644
index 00000000..ffdfcae7
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java
@@ -0,0 +1,154 @@
+/* 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.alloy4;
+
+import java.io.Serializable;
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.RandomAccess;
+
+/** Immutable; implements a list based on equals(); null values are allowed.
+ *
+ * @param - the type of element
+ */
+
+public final class ConstList extends AbstractList implements Serializable, RandomAccess {
+
+ /** Mutable; this implements a modifiable list that can be used to construct a ConstList; null values are allowed.
+ *
+ * @param - the type of element
+ */
+ public static final class TempList {
+
+ /** The underlying list. */
+ private final ArrayList list;
+
+ /** Nonnull iff this list is no longer modifiable. */
+ private ConstList clist;
+
+ /** Construct an empty TempList. */
+ public TempList() { list = new ArrayList(); }
+
+ /** Construct an empty TempList with initial capacity of n (if n<=0, the list will be given a default capacity of 0) */
+ public TempList(int n) { list = new ArrayList(n>=0 ? n : 0); }
+
+ /** Construct a new TempList whose initial content is n references to the given elem (if n<=0, the created list is empty) */
+ public TempList(int n, T elem) { list = new ArrayList(n>0 ? n : 0); while(n>0) { list.add(elem); n--; } }
+
+ /** Construct a new TempList whose initial content is equal to the given collection. */
+ public TempList(Collection extends T> all) { list = new ArrayList(all); }
+
+ /** Construct a new TempList whose initial content is equal to the given array. */
+ public TempList(T... all) { list = new ArrayList(all.length); for(int i=0; i clear() { chk(); list.clear(); return this; }
+
+ /** Appends the given element to the list, then return itself. */
+ public TempList add(T elem) { chk(); list.add(elem); return this; }
+
+ /** Appends the elements in the given collection to the list, then return itself. */
+ public TempList addAll(Iterable extends T> all) {
+ chk();
+ if (all instanceof Collection) list.addAll((Collection extends T>)all); else if (all!=null) { for(T x: all) list.add(x); }
+ return this;
+ }
+
+ /** Changes the i-th element to be the given element, then return itself. */
+ public TempList set(int index, T elem) { chk(); list.set(index, elem); return this; }
+
+ /** Makes this TempList unmodifiable, then construct a ConstList backed by this TempList. */
+ @SuppressWarnings("unchecked")
+ public ConstList makeConst() { if (clist==null) clist=(list.isEmpty() ? emptylist : new ConstList(list)); return clist; }
+ }
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The underlying unmodifiable list. */
+ private final List list;
+
+ /** This caches an unmodifiable empty list. */
+ @SuppressWarnings("unchecked")
+ private static final ConstList emptylist = new ConstList(new ArrayList(0));
+
+ /** Construct a ConstList with the given list as its backing store. */
+ private ConstList(List list) {
+ this.list = list;
+ }
+
+ /** Return an unmodifiable empty list. */
+ @SuppressWarnings("unchecked")
+ public static ConstList make() {
+ return (ConstList) emptylist;
+ }
+
+ /** Return an unmodifiable list consisting of "n" references to "elem".
+ * (If n<=0, we'll return an unmodifiable empty list)
+ */
+ public static ConstList make(int n, T elem) {
+ if (n <= 0) return make();
+ ArrayList ans = new ArrayList(n);
+ while(n > 0) { ans.add(elem); n--; }
+ return new ConstList(ans);
+ }
+
+ /** Return an unmodifiable list with the same elements as the given collection.
+ * (If collection==null, we'll return an unmodifiable empty list)
+ */
+ public static ConstList make(Iterable collection) {
+ if (collection == null) return make();
+ if (collection instanceof ConstList) return (ConstList) collection;
+ if (collection instanceof Collection) {
+ Collection col = (Collection)collection;
+ if (col.isEmpty()) return make(); else return new ConstList(new ArrayList(col));
+ }
+ ArrayList ans = null;
+ for(T x: collection) {
+ if (ans == null) ans = new ArrayList();
+ ans.add(x);
+ }
+ if (ans==null) return make(); else return new ConstList(ans);
+ }
+
+ /** Returns the i-th element
+ * @throws ArrayIndexOutOfBoundsException if the given index doesn't exist
+ */
+ @Override public T get(int index) { return list.get(index); }
+
+ /** Returns the number of elements in this list. */
+ @Override public int size() { return list.size(); }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java
new file mode 100644
index 00000000..601922da
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java
@@ -0,0 +1,83 @@
+/* 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.alloy4;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.io.Serializable;
+
+/** Immutable; implements a map based on hashCode() and equals(); null key and values are allowed.
+ *
+ * @param - the type of key
+ * @param - the type of value
+ */
+
+public final class ConstMap extends AbstractMap implements Serializable {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The underlying Collections.unmodifiableMap map. */
+ private final Map map;
+
+ /** This caches a read-only empty map. */
+ private static final ConstMap emptymap = new ConstMap(new HashMap(0));
+
+ /** Constructs an unmodifiable map with the given map as the backing store. */
+ private ConstMap(Map extends K,? extends V> map) {
+ this.map = Collections.unmodifiableMap(map);
+ }
+
+ /** Returns an unmodifiable empty map. */
+ @SuppressWarnings("unchecked")
+ public static ConstMap make() {
+ return (ConstMap) emptymap;
+ }
+
+ /** Returns an unmodifiable map with the same entries and traversal order as the given map.
+ * (If map==null, we'll return an unmodifiable empty map)
+ */
+ public static ConstMap make(Map map) {
+ if (map instanceof ConstMap) return (ConstMap)map;
+ if (map == null || map.isEmpty()) return make(); else return new ConstMap(new LinkedHashMap(map));
+ }
+
+ /** Returns an unmodifiable view of the mappings in this map. */
+ @Override public Set> entrySet() { return map.entrySet(); }
+
+ /** Returns an unmodifiable view of the keys in this map. */
+ @Override public Set keySet() { return map.keySet(); } // overridden for performance
+
+ /** Returns an unmodifiable view of the values in this map. */
+ @Override public Collection values() { return map.values(); } // overridden for performance
+
+ /** Returns the number of (key, value) mapping in this map. */
+ @Override public int size() { return map.size(); } // overridden for performance
+
+ /** Returns true if exists at least one (k, v) mapping where (k==null ? key==null : k.equals(key)) */
+ @Override public boolean containsKey(Object key) { return map.containsKey(key); } // overridden for performance
+
+ /** Returns true if exists at least one (k, v) mapping where (v==null ? value==null : v.equals(value)) */
+ @Override public boolean containsValue(Object value) { return map.containsValue(value); } // overridden for performance
+
+ /** Returns the value associated with the key (or null if not found); null is also returned if the given key maps to null. */
+ @Override public V get(Object key) { return map.get(key); } // overridden for performance
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java
new file mode 100644
index 00000000..f7d79121
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java
@@ -0,0 +1,74 @@
+/* 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.alloy4;
+
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Iterator;
+import java.io.Serializable;
+
+/** Immutable; implements a set based on hashCode() and equals(); null value is allowed.
+ *
+ * @param - the type of element
+ */
+
+public final class ConstSet extends AbstractSet implements Serializable {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The underlying Collections.unmodifiableSet set. */
+ private final Set set;
+
+ /** This caches a readonly empty Set. */
+ private static final ConstSet emptyset = new ConstSet(new HashSet(0));
+
+ /** Constructs an unmodifiable map with the given set as the backing store. */
+ private ConstSet(Set extends K> set) {
+ this.set = Collections.unmodifiableSet(set);
+ }
+
+ /** Returns an unmodifiable empty set. */
+ @SuppressWarnings("unchecked")
+ public static ConstSet make() {
+ return (ConstSet) emptyset;
+ }
+
+ /** Returns an unmodifiable set with the same elements and traversal order as the given set.
+ * (If set==null, we'll return an unmodifiable empty set)
+ */
+ public static ConstSet make(Iterable collection) {
+ if (collection instanceof ConstSet) return (ConstSet)collection;
+ LinkedHashSet ans = null;
+ if (collection != null) for(K element: collection) {
+ if (ans == null) ans = new LinkedHashSet();
+ ans.add(element);
+ }
+ if (ans==null) return make(); else return new ConstSet(ans);
+ }
+
+ /** Returns the number of objects in this set. */
+ @Override public int size() { return set.size(); }
+
+ /** Returns a read-only iterator over this set. */
+ @Override public Iterator iterator() { return set.iterator(); }
+
+ /** Returns true if the given object is in this set. */
+ @Override public boolean contains(Object element) { return set.contains(element); } // overridden for performance
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java
new file mode 100644
index 00000000..319e3278
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java
@@ -0,0 +1,80 @@
+/* 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.alloy4;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.IdentityHashMap;
+
+/** Mutable; implements a directed graph; null node is allowed.
+ *
+ * Note: it uses n1==n2 for comparing nodes rather than using n1.equals(n2)
+ *
+ * @param - the type of node
+ */
+
+public final class DirectedGraph {
+
+ /** This substitutes for null nodes. This allows hasPath() method to use put() to both insert and test membership in one step. */
+ private static final Object NULL = new Object();
+
+ /** This field maps each node X to a list of "neighbor nodes" that X can reach by following directed edges zero or more times. */
+ private final Map> nodeToTargets = new IdentityHashMap>();
+
+ /** Constructs an empty graph. */
+ public DirectedGraph () { }
+
+ /** Add a directed edge from start node to end node (if there wasn't such an edge already). */
+ public void addEdge (N start, N end) {
+ if (start == end) return;
+ Object a = (start==null ? NULL : start);
+ Object b = (end==null ? NULL : end);
+ List targets = nodeToTargets.get(a);
+ if (targets == null) {
+ targets = new ArrayList();
+ targets.add(b);
+ nodeToTargets.put(a, targets);
+ } else {
+ for (int i = targets.size()-1; i >= 0; i--) if (targets.get(i) == b) return;
+ targets.add(b);
+ }
+ }
+
+ /** Returns whether there is a directed path from start node to end node by following directed edges 0 or more times (breath-first). */
+ public boolean hasPath (N start, N end) {
+ if (start == end) return true;
+ Object a = (start==null ? NULL : start);
+ Object b = (end==null ? NULL : end);
+ List todo = new ArrayList();
+ Map visited = new IdentityHashMap();
+ // The correctness and guaranteed termination relies on following three invariants:
+ // (1) Every time we add X to "visited", we also simultaneously add X to "todo".
+ // (2) Every time we add X to "todo", we also simultaneously add X to "visited".
+ // (3) Nothing is ever removed.
+ visited.put(a, a);
+ todo.add(a);
+ for(int k = 0; k < todo.size(); k++) { // use an integer loop since we will be adding to the "todo" list as we iterate
+ List targets = nodeToTargets.get(todo.get(k));
+ if (targets != null) for (int i = targets.size()-1; i >= 0; i--) {
+ Object next = targets.get(i);
+ if (next == b) { addEdge(start, end); return true; } // Cache so that later hasPath(start,end) returns true immediately
+ if (visited.put(next, next) == null) todo.add(next);
+ }
+ }
+ return false;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java
new file mode 100644
index 00000000..be5e1643
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java
@@ -0,0 +1,106 @@
+/* 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.alloy4;
+
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.LinkedHashMap;
+
+/** Mutable; implements a undoable map based on hashCode() and equals(); null key and values are allowed.
+ *
+ * To be more precise, every key is internally mapped to a list of values.
+ * The put(X,Y) method appends Y onto the end of X's list.
+ * The get(X) method returns the last element in X's list.
+ * The remove(X) method removes the last element in X's list.
+ *
+ *
This is very useful for representing lexical scoping: when a local
+ * variable is introduced with the same name as an existing variable,
+ * the new variable "hides" the old mapping; and when the new variable falls
+ * out of scope, the previous mapping is once again "revealed".
+ *
+ * @param - the type for Value
+ */
+
+public final class Env {
+
+ /** If a key is bound to one or more values, this stores the first value.
+ *
+ * For example: if key K is bound to list of values V1,V2,V3...Vn, then map1.get(K) returns V1
+ *
+ * Invariant: map2.containsKey(x) implies (map1.containsKey(x) && map2.get(x).size()>0)
+ */
+ private final Map map1 = new LinkedHashMap();
+
+ /** If a key is bound to more than one value, this stores every value except the first value.
+ *
+ * For example: if key K is bound to list of values V1,V2,V3...Vn, then map2.get(K) returns the sublist V2..Vn
+ *
+ * Invariant: map2.containsKey(x) implies (map1.containsKey(x) && map2.get(x).size()>0)
+ */
+ private final Map> map2 = new LinkedHashMap>();
+
+ /** Constructs an initially empty environment. */
+ public Env () { }
+
+ /** Returns true if the key is mapped to one or more values. */
+ public boolean has (K key) { return map1.containsKey(key); }
+
+ /** Returns the latest value associated with the key (and returns null if none).
+ *
+ * Since null is also a possible value, if you get null as the answer,
+ * you need to call has(key) to determine whether the key really has a mapping or not.
+ */
+ public V get (K key) {
+ LinkedList list = map2.get(key);
+ return (list != null) ? list.getLast() : map1.get(key);
+ }
+
+ /** Associates the key with the value (which can be null). */
+ public void put (K key, V value) {
+ LinkedList list = map2.get(key);
+ if (list != null) {
+ list.add(value);
+ } else if (!map1.containsKey(key)) {
+ map1.put(key, value);
+ } else {
+ list = new LinkedList();
+ list.add(value);
+ map2.put(key, list);
+ }
+ }
+
+ /** Removes the latest mapping for the key (and if the key had previous mappings, they become visible).
+ * If there are no mappings for the key, then this method does nothing.
+ */
+ public void remove (K key) {
+ LinkedList list = map2.get(key);
+ if (list == null) map1.remove(key); else if (list.size() == 1) map2.remove(key); else list.removeLast();
+ }
+
+ /** Removes all mappings. */
+ public void clear() {
+ map1.clear();
+ map2.clear();
+ }
+
+ /** Make a shallow copy of this environment. */
+ public Env dup() {
+ Env ans = new Env();
+ ans.map1.putAll(map1);
+ for(Map.Entry> e: map2.entrySet()) ans.map2.put(e.getKey(), new LinkedList(e.getValue()));
+ return ans;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java
new file mode 100644
index 00000000..af8f7c53
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java
@@ -0,0 +1,56 @@
+/* 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.alloy4;
+
+/** Immutable; this is the abstract parent class of the various possible errors. */
+
+public abstract class Err extends Exception {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** This stores the filename/line/column information (Pos.UNKNOWN if unknown) (never null) */
+ public final Pos pos;
+
+ /** The actual error message (never null) */
+ public final String msg;
+
+ /** Constructs a new Err object.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it will be recorded as the cause of this exception
+ */
+ Err(Pos pos, String msg, Throwable cause) {
+ super((msg==null ? "" : msg), cause);
+ this.pos = (pos==null ? Pos.UNKNOWN : pos);
+ this.msg = (msg==null ? "" : msg);
+ }
+
+ /** Two Err objects are equal if the type, position, and message are the same. */
+ @Override public final boolean equals(Object other) {
+ if (this==other) return true; else if (other==null || getClass()!=other.getClass()) return false;
+ Err that = (Err) other;
+ return pos.equals(that.pos) && msg.equals(that.msg);
+ }
+
+ /** Returns a hash code consistent with equals() */
+ @Override public final int hashCode() {
+ return msg.hashCode();
+ }
+
+ /** Returns this exception type, its error message, and its complete stack trace as a String. */
+ public final String dump() { return MailBug.dump(this); }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java
new file mode 100644
index 00000000..01e62c05
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java
@@ -0,0 +1,48 @@
+/* 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.alloy4;
+
+/** Immutable; this represents an API usage error. */
+
+public final class ErrorAPI extends Err {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new API usage error.
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorAPI(String msg) { super(null, msg, null); }
+
+ /** Constructs a new API usage error with "cause" as the underlying cause.
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it is the cause of this exception
+ */
+ public ErrorAPI(String msg, Throwable cause) { super(null, msg, cause); }
+
+ /** Constructs a new API usage error.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorAPI(Pos pos, String msg) { super(pos, msg, null); }
+
+ /** Returns a textual description of the error. */
+ @Override public String toString() {
+ if (pos==Pos.UNKNOWN) return "API usage error:\n"+msg;
+ if (pos.filename.length()>0) return "API usage error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg;
+ return "API usage error at line " + pos.y + " column " + pos.x + ":\n" + msg;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java
new file mode 100644
index 00000000..812b4da3
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java
@@ -0,0 +1,48 @@
+/* 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.alloy4;
+
+/** Immutable; this represents a fatal error. */
+
+public final class ErrorFatal extends Err {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new fatal error.
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorFatal(String msg) { super(null, msg, null); }
+
+ /** Constructs a new fatal error with "cause" as the underlying cause.
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it is the cause of this exception
+ */
+ public ErrorFatal(String msg, Throwable cause) { super(null, msg, cause); }
+
+ /** Constructs a new fatal error.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorFatal(Pos pos, String msg) { super(pos, msg, null); }
+
+ /** Returns a textual description of the error. */
+ @Override public String toString() {
+ if (pos==Pos.UNKNOWN) return "Fatal error:\n"+msg;
+ if (pos.filename.length()>0) return "Fatal error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg;
+ return "Fatal error at line " + pos.y + " column " + pos.x + ":\n" + msg;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java
new file mode 100644
index 00000000..11497995
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java
@@ -0,0 +1,48 @@
+/* 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.alloy4;
+
+/** Immutable; this represents a syntax error that should be reported to the user. */
+
+public final class ErrorSyntax extends Err {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new syntax error.
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorSyntax(String msg) { super(null, msg, null); }
+
+ /** Constructs a new syntax error with "cause" as the underlying cause.
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it is the cause of this exception
+ */
+ public ErrorSyntax(String msg, Throwable cause) { super(null, msg, cause); }
+
+ /** Constructs a new syntax error.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorSyntax(Pos pos, String msg) { super(pos, msg, null); }
+
+ /** Returns a textual description of the error. */
+ @Override public String toString() {
+ if (pos==Pos.UNKNOWN) return "Syntax error:\n"+msg;
+ if (pos.filename.length()>0) return "Syntax error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg;
+ return "Syntax error at line " + pos.y + " column " + pos.x + ":\n" + msg;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java
new file mode 100644
index 00000000..518a720d
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java
@@ -0,0 +1,48 @@
+/* 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.alloy4;
+
+/** Immutable; this represents a type error that should be reported to the user. */
+
+public final class ErrorType extends Err {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new type error.
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorType(String msg) { super(null, msg, null); }
+
+ /** Constructs a new type error with "cause" as the underlying cause.
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it is the cause of this exception
+ */
+ public ErrorType(String msg, Throwable cause) { super(null, msg, cause); }
+
+ /** Constructs a new type error.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorType(Pos pos, String msg) { super(pos, msg, null); }
+
+ /** Returns a textual description of the error. */
+ @Override public String toString() {
+ if (pos==Pos.UNKNOWN) return "Type error:\n"+msg;
+ if (pos.filename.length()>0) return "Type error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg;
+ return "Type error at line " + pos.y + " column " + pos.x + ":\n" + msg;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java
new file mode 100644
index 00000000..959b8314
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java
@@ -0,0 +1,48 @@
+/* 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.alloy4;
+
+/** Immutable; this represents a nonfatal warning that should be reported to the user. */
+
+public final class ErrorWarning extends Err {
+
+ /** This ensures this class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new warning.
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorWarning(String msg) { super(null, msg, null); }
+
+ /** Constructs a new warning with "cause" as the underlying cause.
+ * @param msg - the actual error message (can be null)
+ * @param cause - if nonnull, it is the cause of this exception
+ */
+ public ErrorWarning(String msg, Throwable cause) { super(null, msg, cause); }
+
+ /** Constructs a new warning.
+ * @param pos - the filename/line/row information (can be null if unknown)
+ * @param msg - the actual error message (can be null)
+ */
+ public ErrorWarning(Pos pos, String msg) { super(pos, msg, null); }
+
+ /** Returns a textual description of the error. */
+ @Override public String toString() {
+ if (pos==Pos.UNKNOWN) return msg;
+ if (pos.filename.length()>0) return "Line "+pos.y+" column "+pos.x+" in "+pos.filename+":\n"+msg;
+ return "Line " + pos.y + " column " + pos.x + ":\n" + msg;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java
new file mode 100644
index 00000000..e7bf4b42
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java
@@ -0,0 +1,92 @@
+/* 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.alloy4;
+
+import java.io.Serializable;
+import java.util.AbstractList;
+
+/** Immutable; implements a list where it is combine them; null values are NOT allowed. */
+
+public final class JoinableList extends AbstractList implements Serializable {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The number of items stored in this list.
+ * Invariant: count == (pre!=null ? pre.count : 0) + (item!=null ? 1 : 0) + (post!=null ? post.count : 0)
+ */
+ private final int count;
+
+ /** The list of items before "this.item"; may be null. */
+ private final JoinableList pre;
+
+ /** The list of items after "this.item"; may be null. */
+ private final JoinableList post;
+
+ /** If nonnull, it stores an item. */
+ private final E item;
+
+ /** Construct a JoinableList object. */
+ private JoinableList(int count, JoinableList pre, E item, JoinableList post) {
+ this.count = count;
+ this.pre = pre;
+ this.item = item;
+ this.post = post;
+ }
+
+ /** Construct an empty list. */
+ public JoinableList() { this(0, null, null, null); }
+
+ /** Construct a list containing a single item, or return an empty list if item==null. */
+ public JoinableList(E item) { this((item!=null ? 1 : 0), null, item, null); }
+
+ /** Returns a list that represents the concatenation of this list and that list. */
+ public JoinableList make(JoinableList that) {
+ if (that == null || that.count == 0) return this; else if (count == 0) return that;
+ int sum = count + that.count;
+ if (sum < count) throw new OutOfMemoryError(); // integer overflow
+ if (post != null) return new JoinableList(sum, this, null, that); else return new JoinableList(sum, pre, item, that);
+ }
+
+ /** Returns a list that represents the result of appending newItem onto this list; if newItem==null we return this list as-is. */
+ public JoinableList make(E newItem) {
+ if (newItem == null) return this;
+ int sum = count + 1; // integer overflow
+ if (sum < 1) throw new OutOfMemoryError();
+ if (post != null) return new JoinableList(sum, this, newItem, null);
+ if (item != null) return new JoinableList(sum, pre, item, new JoinableList(newItem));
+ return new JoinableList(sum, pre, newItem, null);
+ }
+
+ /** If the list if nonempty, arbitrarily return one of the item, otherwise throw ArrayIndexOutOfBoundsException. */
+ public E pick() { if (item!=null) return item; else return get(0); }
+
+ /** Return the i-th element
+ * @throws ArrayIndexOutOfBoundsException if the given index doesn't exist
+ */
+ @Override public E get(int i) {
+ if (i < 0 || i >= count) throw new ArrayIndexOutOfBoundsException();
+ JoinableList x = this;
+ while(true) {
+ int pre = (x.pre == null) ? 0 : x.pre.count;
+ if (i < pre) { x = x.pre; continue; }
+ if (x.item == null) { i = i - pre; x = x.post; } else if (i != pre) { i = i - pre - 1; x = x.post; } else return x.item;
+ }
+ }
+
+ /** Returns the number of elements in this list. */
+ @Override public int size() { return count; }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java
new file mode 100644
index 00000000..b992f1de
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java
@@ -0,0 +1,30 @@
+/* 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.alloy4;
+
+/** This defines an interface for receiving events. */
+
+public interface Listener {
+
+ /** This defines the list of possible events. */
+ enum Event { CLICK, STATUS_CHANGE, FOCUSED, CTRL_PAGE_UP, CTRL_PAGE_DOWN, CARET_MOVED};
+
+ /** This method is called when the given zero-argument-event occurs. */
+ public Object do_action(Object sender, Event event);
+
+ /** This method is called when the given single-argument-event occurs. */
+ public Object do_action(Object sender, Event event, Object arg);
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java
new file mode 100644
index 00000000..2f559505
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java
@@ -0,0 +1,46 @@
+/* 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.alloy4;
+
+import java.util.ArrayList;
+import edu.mit.csail.sdg.alloy4.Listener.Event;
+
+/** This stores a list of listeners. */
+
+public final class Listeners {
+
+ /** The actual list of listeners. */
+ private final ArrayList listeners = new ArrayList();
+
+ /** Construct a empty list of listeners. */
+ public Listeners() { }
+
+ /** Add a listener to this group of listeners (if not already in the list) */
+ public void add(Listener listener) {
+ for(Listener x: listeners) if (x == listener) return;
+ listeners.add(listener);
+ }
+
+ /** Send the following zero-argument event to every listener. */
+ public void fire(Object sender, Event event) {
+ for(Listener x: listeners) x.do_action(sender, event);
+ }
+
+ /** Send the following one-argument event to every listener. */
+ public void fire(Object sender, Event event, Object arg) {
+ for(Listener x: listeners) x.do_action(sender, event, arg);
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java
new file mode 100644
index 00000000..fa9bc391
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java
@@ -0,0 +1,77 @@
+/* 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.alloy4;
+
+import javax.swing.SwingUtilities;
+import com.apple.eawt.Application;
+import com.apple.eawt.ApplicationAdapter;
+import com.apple.eawt.ApplicationEvent;
+import com.apple.eawt.ApplicationListener;
+
+/** This class provides better integration on Mac OS X.
+ *
+ * You must not call any methods here if you're not on Mac OS X,
+ * since that triggers the loading of com.apple.eawt.* which are not available on other platforms.
+ *
+ *
Thread Safety: Safe.
+ */
+
+public final class MacUtil {
+
+ /** Constructor is private, since this class never needs to be instantiated. */
+ private MacUtil() { }
+
+ /** The cached Application object. */
+ private static Application app = null;
+
+ /** The previous ApplicationListener (or null if there was none). */
+ private static ApplicationListener listener = null;
+
+ /** Register a Mac OS X "ApplicationListener"; if there was a previous listener, it will be removed first.
+ * @param reopen - when the user clicks on the Dock icon, we'll call reopen.run() using SwingUtilities.invokeLater
+ * @param about - when the user clicks on About Alloy4, we'll call about.run() using SwingUtilities.invokeLater
+ * @param open - when a file needs to be opened, we'll call open.run(filename) using SwingUtilities.invokeLater
+ * @param quit - when the user clicks on Quit, we'll call quit.run() using SwingUtilities.invokeAndWait
+ */
+ public synchronized static void registerApplicationListener
+ (final Runnable reopen, final Runnable about, final Runner open, final Runnable quit) {
+ if (app == null) app = new Application(); else if (listener != null) app.removeApplicationListener(listener);
+ listener = new ApplicationAdapter() {
+ @Override public void handleReOpenApplication(ApplicationEvent arg) {
+ SwingUtilities.invokeLater(reopen);
+ }
+ @Override public void handleAbout(ApplicationEvent arg) {
+ arg.setHandled(true);
+ SwingUtilities.invokeLater(about);
+ }
+ @Override public void handleOpenFile(ApplicationEvent arg) {
+ final String filename = arg.getFilename();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() { open.run(filename); }
+ });
+ }
+ @Override public void handleQuit(ApplicationEvent arg) {
+ try {
+ if (SwingUtilities.isEventDispatchThread()) quit.run(); else SwingUtilities.invokeAndWait(quit);
+ } catch (Throwable e) {
+ // Nothing we can do; we're already trying to quit!
+ }
+ arg.setHandled(false);
+ }
+ };
+ app.addApplicationListener(listener);
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java
new file mode 100644
index 00000000..334e56f4
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java
@@ -0,0 +1,276 @@
+/* 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.alloy4;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.Map;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.awt.Color;
+import java.awt.Dimension;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+
+/** This class asks the user for permission to email a bug report when an uncaught exception occurs. */
+
+public final class MailBug implements UncaughtExceptionHandler, Runnable {
+
+ /** The version number of the most recent Alloy4 (as queried from alloy.mit.edu); -1 if alloy.mit.edu has not replied yet. */
+ private static int latestAlloyVersion = -1;
+
+ /** The name of the most recent Alloy4 (as queried from alloy.mit.edu); "unknown" if alloy.mit.edu has not replied yet. */
+ private static String latestAlloyVersionName = "unknown";
+
+ /** The URL where the bug report should be sent. */
+ private static final String ALLOY_URL = "http://alloy.mit.edu/postbug4.php";
+
+ /** The URL where the current version info can be queried. */
+ private static final String ALLOY_NOW = "http://alloy.mit.edu/alloy4/download/alloy4.txt";
+
+ /** If alloy.mit.edu has replied, then return the latest Alloy build number, else return -1. */
+ public static int latestBuildNumber() { synchronized(MailBug.class) { return latestAlloyVersion; } }
+
+ /** If alloy.mit.edu has replied, then return the latest Alloy build name, else return "unknown" */
+ public static String latestBuildName() { synchronized(MailBug.class) { return latestAlloyVersionName; } }
+
+ /** Construct a new MailBug object. */
+ private MailBug() { }
+
+ /** Setup the uncaught-exception-handler and use a separate thread to query alloy.mit.edu for latest version number. */
+ public static void setup() {
+ if (Thread.getDefaultUncaughtExceptionHandler() != null) return;
+ MailBug x = new MailBug();
+ Thread.setDefaultUncaughtExceptionHandler(x);
+ new Thread(x).start();
+ }
+
+ /** This method concatenates a Throwable's message and stack trace and all its causes into a single String. */
+ public static String dump (Throwable ex) {
+ StringBuilder sb = new StringBuilder();
+ while(ex != null) {
+ sb.append(ex.getClass()).append(": ").append(ex.getMessage()).append('\n');
+ StackTraceElement[] trace = ex.getStackTrace();
+ if (trace != null) for(int n = trace.length, i = 0; i < n; i++) sb.append(trace[i]).append('\n');
+ ex = ex.getCause();
+ if (ex != null) sb.append("caused by...\n");
+ }
+ return sb.toString().trim();
+ }
+
+ /** This method returns true if the exception appears to be a Sun Java GUI bug. */
+ private static boolean isGUI(Throwable ex) {
+ // If the root of the stack trace is within Java framework itself,
+ // and no where is Alloy, Kodkod, or SAT4J anywhere along the trace,
+ // then it's almost *always* a Sun Java GUI bug from what we've seen.
+ // And it's better to ignore it rather than kill the file that the user is editing.
+ while(ex != null) {
+ StackTraceElement[] trace = ex.getStackTrace();
+ for(int n=(trace==null ? 0 : trace.length), i=0; i e: System.getProperties().entrySet()) {
+ String k = String.valueOf(e.getKey()), v = String.valueOf(e.getValue());
+ pw.printf("%s = %s\n", k.trim(), v.trim());
+ }
+ pw.printf("\n========================= The End ==========================\n\n");
+ pw.close();
+ sw.flush();
+ return sw.toString();
+ }
+
+ /** This method opens a connection then read the entire content (it converts non-ASCII into '?'); if error occurred it returns "".
+ * @param URL - the remote URL we want to read from
+ * @param send - if nonempty we will send it to the remote URL before attempting to read from the remote URL
+ */
+ private static String readAll(String URL, String send, String failure) {
+ BufferedInputStream bis = null;
+ InputStream in = null;
+ OutputStream out = null;
+ String ans;
+ try {
+ URLConnection connection = new URL(URL).openConnection();
+ if (send!=null && send.length() > 0) {
+ connection.setDoOutput(true);
+ out = connection.getOutputStream();
+ out.write(send.getBytes("UTF-8"));
+ out.close();
+ out = null;
+ }
+ in = connection.getInputStream();
+ bis = new BufferedInputStream(in);
+ StringBuilder sb = new StringBuilder();
+ int i;
+ while((i = bis.read()) >= 0) { sb.append((char)(i<=0x7F ? i : '?')); }
+ ans = Util.convertLineBreak(sb.toString());
+ } catch (Throwable ex) {
+ ans = failure;
+ } finally {
+ Util.close(bis);
+ Util.close(in);
+ Util.close(out);
+ }
+ return ans;
+ }
+
+ /** This method will query alloy.mit.edu for the latest version number. */
+ public void run() {
+ String result = readAll(ALLOY_NOW + "?buildnum=" + Version.buildNumber() + "&builddate=" + Version.buildDate(), "", "");
+ if (!result.startsWith("Alloy Build ")) return;
+ // Now that we know we're online, try to remove the old ill-conceived "Java WebStart" versions of Alloy4 BETA1..BETA7
+ //Subprocess.exec(20000, new String[]{
+ // "javaws", "-silent", "-offline", "-uninstall", "http://alloy.mit.edu/alloy4/download/alloy4.jnlp"});
+ // Now parse the result
+ int num = 0;
+ boolean found = false;
+ for(int i=0, len=result.length(); ; i++) {
+ if (i >= len) return; // malformed
+ char c = result.charAt(i);
+ if (!(c>='0' && c<='9')) { if (!found) continue; else { result = result.substring(i).trim(); break; } }
+ found = true;
+ num = num*10 + (c - '0');
+ }
+ synchronized(MailBug.class) { latestAlloyVersionName = result; latestAlloyVersion = num; }
+ }
+
+ /** This method sends the crash report then displays alloy.mit.edu's reply in a text window. */
+ private static void sendCrashReport (Thread thread, Throwable ex, String email, String problem) {
+ try {
+ final String report = prepareCrashReport(thread, ex, email, problem);
+ final String alt = "Sorry. An error has occurred in posting the bug report.\n" +
+ "Please email this report to alloy@mit.edu directly.\n\n" + dump(ex);
+ final JTextArea status = OurUtil.textarea("Sending the bug report... please wait...",
+ 10, 40, false, true, new LineBorder(Color.GRAY));
+ new Thread(new Runnable() {
+ public void run() {
+ final String output = readAll(ALLOY_URL, report, alt);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ status.setText(output);
+ status.setCaretPosition(0);
+ }
+ });
+ }
+ }).start();
+ OurDialog.showmsg("Sending the bug report... please wait...", status);
+ } finally {
+ System.exit(1);
+ }
+ }
+
+ /** This method is an exception handler for uncaught exceptions. */
+ public void uncaughtException (Thread thread, Throwable ex) {
+ if (isGUI(ex)) return;
+ final int ver;
+ final String name;
+ synchronized(MailBug.class) { ver = latestAlloyVersion; name = latestAlloyVersionName; }
+ if (ex!=null) {
+ System.out.flush();
+ System.err.flush();
+ System.err.println("Exception: " + ex.getClass());
+ System.err.println("Message: " + ex);
+ System.err.println("Stacktrace:");
+ System.err.println(dump(ex));
+ System.err.flush();
+ }
+ final String yes = "Send the Bug Report", no = "Don't Send the Bug Report";
+ final JTextField email = OurUtil.textfield("", 20, new LineBorder(Color.DARK_GRAY));
+ final JTextArea problem = OurUtil.textarea("", 50, 50, true, false, new EmptyBorder(0,0,0,0));
+ final JScrollPane scroll = OurUtil.scrollpane(problem, new LineBorder(Color.DARK_GRAY), new Dimension(300, 200));
+ for(Throwable ex2 = ex; ex2 != null; ex2 = ex2.getCause()) {
+ if (ex2 instanceof StackOverflowError) OurDialog.fatal(new Object[] {
+ "Sorry. The Alloy Analyzer has run out of stack space.",
+ " ",
+ "Try simplifying your model or reducing the scope.",
+ "And try reducing Options->SkolemDepth to 0.",
+ "And try increasing Options->Stack.",
+ " ",
+ "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy."
+ });
+ if (ex2 instanceof OutOfMemoryError) OurDialog.fatal(new Object[] {
+ "Sorry. The Alloy Analyzer has run out of memory.",
+ " ",
+ "Try simplifying your model or reducing the scope.",
+ "And try reducing Options->SkolemDepth to 0.",
+ "And try increasing Options->Memory.",
+ " ",
+ "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy."
+ });
+ }
+ if (ver > Version.buildNumber()) OurDialog.fatal(new Object[] {
+ "Sorry. A fatal error has occurred.",
+ " ",
+ "You are running Alloy Analyzer " + Version.version(),
+ "but the most recent is Alloy Analyzer "+ name,
+ " ",
+ "Please try to upgrade to the newest version",
+ "as the problem may have already been fixed.",
+ " ",
+ "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy."
+ });
+ if (OurDialog.yesno(new Object[] {
+ "Sorry. A fatal internal error has occurred.",
+ " ",
+ "You may submit a bug report (via HTTP).",
+ "The error report will include your system",
+ "configuration, but no other information.",
+ " ",
+ "If you'd like to be notified about a fix,",
+ "please describe the problem and enter your email address.",
+ " ",
+ OurUtil.makeHT("Email:", 5, email, null),
+ OurUtil.makeHT("Problem:", 5, scroll, null)
+ }, yes, no)) sendCrashReport(thread, ex, email.getText(), problem.getText());
+ System.exit(1);
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java
new file mode 100644
index 00000000..9fe1d854
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java
@@ -0,0 +1,86 @@
+/* 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.alloy4;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.util.WeakHashMap;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTextPane;
+import javax.swing.text.DefaultHighlighter;
+
+/** Graphical convenience methods for managing and constructing antialias-capable components.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurAntiAlias {
+
+ /** This constructor is private, since this utility class never needs to be instantiated. */
+ private OurAntiAlias() { }
+
+ /** Use anti-alias or not. */
+ private static boolean antiAlias = Util.onMac() || Util.onWindows();
+
+ /** Stores weak references of all objects that need to be redrawn when anti-alias setting changes. */
+ private static WeakHashMap map = new WeakHashMap();
+
+ /** Changes whether anti-aliasing should be done or not (when changed, we will automatically repaint all affected components). */
+ public static void enableAntiAlias(boolean enableAntiAlias) {
+ if (antiAlias == enableAntiAlias || Util.onMac() || Util.onWindows()) return;
+ antiAlias = enableAntiAlias;
+ for(JComponent x: map.keySet()) if (x!=null) { x.invalidate(); x.repaint(); x.validate(); }
+ }
+
+ /** Constructs an antialias-capable JLabel.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JLabel label(String label, Object... attributes) {
+ JLabel ans = new JLabel(label) {
+ static final long serialVersionUID = 0;
+ @Override public void paint(Graphics gr) {
+ if (antiAlias && gr instanceof Graphics2D) {
+ ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+ super.paint(gr);
+ }
+ };
+ OurUtil.make(ans, attributes);
+ map.put(ans, Boolean.TRUE);
+ return ans;
+ }
+
+ /** Constructs an antialias-capable JTextPane with a DefaultHighlighter associated with it.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JTextPane pane(Object... attributes) {
+ JTextPane ans = new JTextPane() {
+ static final long serialVersionUID = 0;
+ @Override public void paint(Graphics gr) {
+ if (antiAlias && gr instanceof Graphics2D) {
+ ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+ super.paint(gr);
+ }
+ };
+ OurUtil.make(ans, attributes);
+ ans.setHighlighter(new DefaultHighlighter());
+ map.put(ans, Boolean.TRUE);
+ return ans;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java
new file mode 100644
index 00000000..3338ea76
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java
@@ -0,0 +1,89 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+import javax.swing.border.Border;
+
+/** Graphical border on zero, one, two, three, or all four sides of a component.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurBorder implements Border {
+
+ /** non-null if we want to draw a border line of that Color above the component. */
+ private final Color top;
+
+ /** non-null if we want to draw a border line of that Color to the left of the component. */
+ private final Color left;
+
+ /** non-null if we want to draw a border line of that Color below the component. */
+ private final Color bottom;
+
+ /** non-null if we want to draw a border line of that Color to the right of the component. */
+ private final Color right;
+
+ /** Construct a Border object that draws a border on 0, 1, 2, 3, or all 4 sides of the component.
+ * Note: it paints the borders top, bottom, left, then right.
+ * @param top - nonnull if we want to draw a border line (with that color) above the component
+ * @param left - nonnull if we want to draw a border line (with that color) to the left of the component
+ * @param bottom - nonnull if we want to draw a border line (with that color) below the component
+ * @param right - nonnull if we want to draw a border line (with that color) to the right of the component
+ */
+ public OurBorder (Color top, Color left, Color bottom, Color right) {
+ this.top = top;
+ this.left = left;
+ this.bottom = bottom;
+ this.right = right;
+ }
+
+ /** Construct a Border object that draws a light gray line on 0, 1, 2, 3, or all 4 sides of the component.
+ * Note: it paints the borders top, bottom, left, then right.
+ * @param top - true if we want to draw a Color.LIGHT_GRAY border line above the component
+ * @param left - true if we want to draw a Color.LIGHT_GRAY border line to the left of the component
+ * @param bottom - true if we want to draw a Color.LIGHT_GRAY border line below the component
+ * @param right - true if we want to draw a Color.LIGHT_GRAY border line to the right of the component
+ */
+ public OurBorder (boolean top, boolean left, boolean bottom, boolean right) {
+ this.top = top ? Color.LIGHT_GRAY : null;
+ this.left = left ? Color.LIGHT_GRAY : null;
+ this.bottom = bottom ? Color.LIGHT_GRAY : null;
+ this.right = right ? Color.LIGHT_GRAY : null;
+ }
+
+ /** This method is called by Swing to actually draw the borders. */
+ public void paintBorder (Component component, Graphics graphics, int x, int y, int width, int height) {
+ if (width<1 || height<1) return;
+ Color old = graphics.getColor();
+ if (top != null) { graphics.setColor(top); graphics.drawLine(x, y, x+width-1, y ); }
+ if (bottom != null) { graphics.setColor(bottom); graphics.drawLine(x, y+height-1, x+width-1, y+height-1); }
+ if (left != null) { graphics.setColor(left); graphics.drawLine(x, y, x, y+height-1); }
+ if (right != null) { graphics.setColor(right); graphics.drawLine(x+width-1, y, x+width-1, y+height-1); }
+ graphics.setColor(old);
+ }
+
+ /** This method is called by Swing to retrieve the dimension of the border. */
+ public Insets getBorderInsets (Component c) {
+ return new Insets(top!=null?1:0, left!=null?1:0, bottom!=null?1:0, right!=null?1:0);
+ }
+
+ /** This method is called by Swing to find out whether this border object needs to fill in its own background. */
+ public boolean isBorderOpaque() { return true; }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java
new file mode 100644
index 00000000..34109763
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java
@@ -0,0 +1,99 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+/** Graphical checkbox.
+ *
+ *
Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public abstract class OurCheckbox extends JPanel {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The icon to use when the checkbox is off. */
+ public static final Icon OFF = OurUtil.loadIcon("images/cb0.gif");
+
+ /** The icon to use when the checkbox is on. */
+ public static final Icon ON = OurUtil.loadIcon("images/cb1.gif");
+
+ /** The icon to use when the checkbox is off entirely. */
+ public static final Icon ALL_OFF = OurUtil.loadIcon("images/tcb01.gif");
+
+ /** The icon to use when the checkbox is on entirely. */
+ public static final Icon ALL_ON = OurUtil.loadIcon("images/tcb02.gif");
+
+ /** The icon to use when the checkbox is off due to inheritance. */
+ public static final Icon INH_OFF = OurUtil.loadIcon("images/tcb03.gif");
+
+ /** The icon to use when the checkbox is on due to inheritance. */
+ public static final Icon INH_ON = OurUtil.loadIcon("images/tcb04.gif");
+
+ /** The underlying JCheckBox object. */
+ private final JCheckBox jbox;
+
+ /** The JLabel object for displaying a label next to the checkbox. */
+ private final JLabel jlabel;
+
+ /** Constructs a OurCheckbox object.
+ * @param label - the label to display next to the checkbox
+ * @param tooltip - the tool tip to show when the mouse hovers over this checkbox
+ * @param icon - the initial icon to display (should be one of ON/OFF/ALL_ON/ALL_OFF/INH_ON/INH_OFF)
+ */
+ public OurCheckbox(String label, String tooltip, Icon icon) {
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ jbox = new JCheckBox(icon);
+ jbox.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Icon icon = do_action();
+ if (icon != jbox.getIcon()) jbox.setIcon(icon);
+ }
+ });
+ jbox.setMaximumSize(jbox.getPreferredSize());
+ jbox.setToolTipText(tooltip);
+ jlabel = OurUtil.label(label, tooltip);
+ if (icon==ON || icon==OFF) { add(jbox); add(jlabel); } else { add(jlabel); add(jbox); }
+ setAlignmentX(RIGHT_ALIGNMENT);
+ }
+
+ /** This method is called when the user clicks on the checkbox; subclasses should override this to provide the custom behavior. */
+ public abstract Icon do_action();
+
+ /** This method is called by Swing to enable/disable a component. */
+ @Override public final void setEnabled(boolean enabled) {
+ if (jbox != null) jbox.setEnabled(enabled);
+ if (jlabel != null) jlabel.setEnabled(enabled);
+ // jbox and jlabel may be null if during the constructor, some method call causes Swing to call this method early
+ }
+
+ /** This method is called by Swing to change its background color. */
+ @Override public final void setBackground(Color color) {
+ super.setBackground(color);
+ if (jbox != null) jbox.setBackground(color);
+ if (jlabel != null) jlabel.setBackground(color);
+ // jbox and jlabel may be null if during the constructor, some method call causes Swing to call this method early
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java
new file mode 100644
index 00000000..3b25d327
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java
@@ -0,0 +1,97 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.Vector;
+import javax.swing.Icon;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import javax.swing.border.EmptyBorder;
+
+/** Graphical combobox.
+ *
+ *
Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public class OurCombobox extends JComboBox {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** This caches a preconstructed JLabel that is used for the rendering of each Combo value. */
+ private static JLabel jlabel;
+
+ /** Subclass can override this method to provide the custom text for any given value (It should return "" if no text is needed) */
+ public String do_getText(Object value) { return String.valueOf(value); }
+
+ /** Subclass can override this method to provide the custom icon for any given value (It should return null if no icon is needed) */
+ public Icon do_getIcon(Object value) { return null; }
+
+ /** Subclass can override this method to react upon selection change. */
+ public void do_changed(Object newValue) { }
+
+ /** This helper method makes a copy of the list, and then optionally prepend null at the beginning of the list. */
+ private static Vector do_copy (Object[] list, boolean addNull) {
+ Vector answer = new Vector(list.length + (addNull ? 1 : 0));
+ if (addNull) answer.add(null);
+ for(int i=0; i 25) height = 25; // Otherwise, the height is too big on Windows
+ setPreferredSize(new Dimension(width, height));
+ setMaximumSize(new Dimension(width, height));
+ if (!Util.onWindows() && !Util.onMac()) setBorder(new EmptyBorder(4, 3, 4, 0));
+ }
+ if (initialValue != null) { setSelectedItem(initialValue); }
+ addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) { do_changed(getSelectedItem()); }
+ });
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java
new file mode 100644
index 00000000..1bf57a79
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java
@@ -0,0 +1,279 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Event;
+import java.awt.Font;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.AbstractAction;
+import javax.swing.JPanel;
+import javax.swing.JScrollBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTextPane;
+import javax.swing.KeyStroke;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Caret;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyledDocument;
+
+/** Graphical input/output prompt.
+ *
+ * This class's constructor takes a Computer object, then constructs a JScrollPane
+ * in which the user can type commands, and the output from the Computer object will be displayed.
+ * Empty input lines are ignored.
+ * This interactive prompt supports UP and DOWN arrow command histories and basic copy/cut/paste editing.
+ *
+ *
For each user input, if the Computer object returns a String, it is displayed in blue.
+ * But if the Computer object throws an exception, the exception will be displayed in red.
+ *
+ *
Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurConsole extends JScrollPane {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The style for default text. */
+ private final AttributeSet plain = style("Verdana", 14, false, Color.BLACK, 0);
+
+ /** The style for bold text. */
+ private final AttributeSet bold = style("Verdana", 14, true, Color.BLACK, 0);
+
+ /** The style for successful result. */
+ private final AttributeSet good = style("Verdana", 14, false, Color.BLUE, 15);
+
+ /** The style for failed result. */
+ private final AttributeSet bad = style("Verdana", 14, false, Color.RED, 15);
+
+ /** The number of characters that currently exist above the horizontal divider bar.
+ * (The interactive console is composed of a JTextPane which contains 0 or more input/output pairs, followed
+ * by a horizontal divider bar, followed by an embedded sub-JTextPane (where the user can type in the next input))
+ */
+ private int len = 0;
+
+ /** The main JTextPane containing 0 or more input/output pairs, followed by a horizontal bar, followed by this.sub */
+ private final JTextPane main = do_makeTextPane(false, 5, 5, 5);
+
+ /** The sub JTextPane where the user can type in the next command. */
+ private final JTextPane sub = do_makeTextPane(true, 10, 10, 0);
+
+ /** The history of all commands entered so far, plus an extra String representing the user's next command. */
+ private final List history = new ArrayList(); { history.add(""); }
+
+ /** The position in this.history that is currently showing. */
+ private int browse = 0;
+
+ /** Helper method that construct a mutable style with the given font name, font size, boldness, color, and left indentation. */
+ static MutableAttributeSet style(String fontName, int fontSize, boolean boldness, Color color, int leftIndent) {
+ MutableAttributeSet s = new SimpleAttributeSet();
+ StyleConstants.setFontFamily(s, fontName);
+ StyleConstants.setFontSize(s, fontSize);
+ StyleConstants.setBold(s, boldness);
+ StyleConstants.setForeground(s, color);
+ StyleConstants.setLeftIndent(s, leftIndent);
+ return s;
+ }
+
+ /** Construct a JScrollPane that allows the user to interactively type in commands and see replies.
+ *
+ * @param computer - this object is used to evaluate the user input
+ *
+ * @param syntaxHighlighting - if true, the "input area" will be syntax-highlighted
+ *
+ * @param initialMessages - this is a list of String and Boolean; each String is printed to the screen as is,
+ * and Boolean.TRUE will turn subsequent text bold, and Boolean.FALSE will turn subsequent text non-bold.
+ */
+ public OurConsole(final Computer computer, boolean syntaxHighlighting, Object... initialMessages) {
+ super(VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ if (syntaxHighlighting) { sub.setDocument(new OurSyntaxUndoableDocument("Verdana", 14)); }
+ setViewportView(main);
+ // show the initial message
+ AttributeSet st = plain;
+ for(Object x: initialMessages) {
+ if (x instanceof Boolean) st = (Boolean.TRUE.equals(x) ? bold : plain); else do_add(-1, String.valueOf(x), st);
+ }
+ do_add(-1, "\n", plain); // we must add a linebreak to ensure that subsequent text belong to a "different paragraph"
+ // insert the divider and the sub JTextPane
+ StyledDocument doc = main.getStyledDocument();
+ JPanel divider = new JPanel(); divider.setBackground(Color.LIGHT_GRAY); divider.setPreferredSize(new Dimension(1,1));
+ MutableAttributeSet dividerStyle = new SimpleAttributeSet(); StyleConstants.setComponent(dividerStyle, divider);
+ MutableAttributeSet inputStyle = new SimpleAttributeSet(); StyleConstants.setComponent(inputStyle, sub);
+ len = doc.getLength();
+ do_add(-1, " \n", dividerStyle); // The space character won't be displayed; it will instead be drawn as a divider
+ do_add(-1, " \n", inputStyle); // The space character won't be displayed; it will instead display the input buffer
+ final Caret subCaret = sub.getCaret(), mainCaret = main.getCaret();
+ // When caret moves in the sub JTextPane, we cancel any active selection in the main JTextPane
+ subCaret.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ if (mainCaret.getMark() != mainCaret.getDot()) mainCaret.setDot(mainCaret.getDot());
+ }
+ });
+ // When caret moves in the main JTextPane, we cancel any active selection in the sub JTextPane
+ mainCaret.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ if (subCaret.getMark() != subCaret.getDot()) subCaret.setDot(subCaret.getDot());
+ }
+ });
+ // now, create the paste/copy/cut actions
+ AbstractAction alloy_paste = new AbstractAction("alloy_paste") {
+ static final long serialVersionUID = 0;
+ public void actionPerformed(ActionEvent x) { sub.paste(); }
+ };
+ AbstractAction alloy_copy = new AbstractAction("alloy_copy") {
+ static final long serialVersionUID = 0;
+ public void actionPerformed(ActionEvent x) {
+ if (sub.getSelectionStart() != sub.getSelectionEnd()) sub.copy(); else main.copy();
+ }
+ };
+ AbstractAction alloy_cut = new AbstractAction("alloy_cut") {
+ static final long serialVersionUID = 0;
+ public void actionPerformed(ActionEvent x) {
+ if (sub.getSelectionStart() != sub.getSelectionEnd()) sub.cut(); else main.copy();
+ }
+ };
+ // create the keyboard associations: ctrl-{c,v,x,insert} and shift-{insert,delete}
+ for(JTextPane x: Arrays.asList(main, sub)) {
+ x.getActionMap().put("alloy_paste", alloy_paste);
+ x.getActionMap().put("alloy_copy", alloy_copy);
+ x.getActionMap().put("alloy_cut", alloy_cut);
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, Event.CTRL_MASK), "alloy_paste");
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.CTRL_MASK), "alloy_copy");
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, Event.CTRL_MASK), "alloy_cut");
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, Event.SHIFT_MASK), "alloy_paste");
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, Event.CTRL_MASK), "alloy_copy");
+ x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, Event.SHIFT_MASK), "alloy_cut");
+ }
+ // configure so that, upon receiving focus, we automatically focus and scroll to the sub-JTextPane
+ FocusAdapter focus = new FocusAdapter() {
+ public void focusGained(FocusEvent e) {
+ sub.requestFocusInWindow();
+ sub.scrollRectToVisible(new Rectangle(0, sub.getY(), 1, sub.getHeight()));
+ }
+ };
+ addFocusListener(focus);
+ sub.addFocusListener(focus);
+ main.addFocusListener(focus);
+ // configure so that mouse clicks in the main JTextPane will immediately transfer focus to the sub JTextPane
+ main.addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) { sub.requestFocusInWindow(); }
+ public void mouseClicked(MouseEvent e) { sub.requestFocusInWindow(); }
+ });
+ // configure the behavior for PAGE_UP, PAGE_DOWN, UP, DOWN, TAB, and ENTER
+ sub.addKeyListener(new KeyListener() {
+ public void keyTyped(KeyEvent e) {
+ if (e.getKeyChar() == '\t') { e.consume(); }
+ if (e.getKeyChar() == '\n') { e.consume(); String cmd = sub.getText(); sub.setText(""); do_command(computer, cmd); }
+ }
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode()==KeyEvent.VK_TAB) e.consume();
+ if (e.getKeyCode() == KeyEvent.VK_PAGE_UP) { e.consume(); do_pageup(); }
+ if (e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { e.consume(); do_pagedown(); }
+ if (e.getKeyCode() == KeyEvent.VK_UP) {
+ e.consume();
+ if (browse == history.size() - 1) { history.set(browse, sub.getText()); }
+ if (browse > 0 && browse - 1 < history.size()) { browse--; sub.setText(history.get(browse)); }
+ }
+ if (e.getKeyCode() == KeyEvent.VK_DOWN) {
+ e.consume();
+ if (browse < history.size() - 1) { browse++; sub.setText(history.get(browse)); }
+ }
+ }
+ public void keyReleased(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_TAB) e.consume();
+ }
+ });
+ }
+
+ /** This helper method constructs a JTextPane with the given settings. */
+ private static JTextPane do_makeTextPane(boolean editable, int topMargin, int bottomMargin, int otherMargin) {
+ JTextPane x = OurAntiAlias.pane(Color.BLACK, Color.WHITE, new Font("Verdana", Font.PLAIN, 14));
+ x.setEditable(editable);
+ x.setAlignmentX(0);
+ x.setAlignmentY(0);
+ x.setCaretPosition(0);
+ x.setMargin(new Insets(topMargin, otherMargin, bottomMargin, otherMargin));
+ return x;
+ }
+
+ /** This method processes a user command. */
+ private void do_command(Computer computer, String cmd) {
+ cmd = cmd.trim();
+ if (cmd.length()==0) return;
+ StyledDocument doc = main.getStyledDocument();
+ if (history.size()>=2 && cmd.equals(history.get(history.size()-2))) {
+ // If the user merely repeated the most recent command, then don't grow the history
+ history.set(history.size()-1, "");
+ } else {
+ // Otherwise, grow the history
+ history.set(history.size()-1, cmd);
+ history.add("");
+ }
+ browse = history.size()-1;
+ // display the command
+ int old = doc.getLength(); do_add(len, cmd+"\n\n", plain); len += (doc.getLength() - old);
+ // perform the computation
+ boolean isBad = false;
+ try { cmd = computer.compute(cmd); } catch(Throwable ex) { cmd = ex.toString(); isBad = true; }
+ int savePosition = len;
+ // display the outcome
+ old = doc.getLength(); do_add(len, cmd.trim()+"\n\n", (isBad ? bad : good)); len += (doc.getLength() - old);
+ // indent the outcome
+ main.setSelectionStart(savePosition+1); main.setSelectionEnd(len); main.setParagraphAttributes(good, false);
+ // redraw then scroll to the bottom
+ invalidate();
+ repaint();
+ validate();
+ sub.scrollRectToVisible(new Rectangle(0, sub.getY(), 1, sub.getHeight()));
+ do_pagedown(); // need to do this after the validate() so that the scrollbar knows the new limit
+ }
+
+ /** Performs "page up" in the JScrollPane. */
+ private void do_pageup() {
+ JScrollBar bar = getVerticalScrollBar();
+ bar.setValue(bar.getValue() - 200);
+ }
+
+ /** Performs "page down" in the JScrollPane. */
+ private void do_pagedown() {
+ JScrollBar bar = getVerticalScrollBar();
+ bar.setValue(bar.getValue() + 200);
+ }
+
+ /** Insert the given text into the given location and with the given style if where>=0; append the text if where<0. */
+ private void do_add(int where, String text, AttributeSet style) {
+ StyledDocument doc = main.getStyledDocument();
+ try { doc.insertString(where >= 0 ? where : doc.getLength(), text, style); } catch(BadLocationException ex) { }
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java
new file mode 100644
index 00000000..9246113f
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java
@@ -0,0 +1,247 @@
+/* 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.alloy4;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.Locale;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.FileDialog;
+import java.awt.Frame;
+import java.awt.GraphicsEnvironment;
+import java.awt.HeadlessException;
+import java.awt.event.KeyListener;
+import java.awt.event.KeyEvent;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.filechooser.FileFilter;
+import static javax.swing.JOptionPane.YES_NO_OPTION;
+import static javax.swing.JOptionPane.QUESTION_MESSAGE;
+import static javax.swing.JOptionPane.WARNING_MESSAGE;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+
+/** Graphical dialog methods for asking the user some questions.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurDialog {
+
+ /** The constructor is private, since this utility class never needs to be instantiated. */
+ private OurDialog() { }
+
+ /** Helper method for constructing an always-on-top modal dialog. */
+ private static Object show(String title, int type, Object message, Object[] options, Object initialOption) {
+ if (options == null) { options = new Object[]{"Ok"}; initialOption = "Ok"; }
+ JOptionPane p = new JOptionPane(message, type, JOptionPane.DEFAULT_OPTION, null, options, initialOption);
+ p.setInitialValue(initialOption);
+ JDialog d = p.createDialog(null, title);
+ p.selectInitialValue();
+ d.setAlwaysOnTop(true);
+ d.setVisible(true);
+ d.dispose();
+ return p.getValue();
+ }
+
+ /** Popup the given informative message, then ask the user to click Close to close it. */
+ public static void showmsg(String title, Object... msg) {
+ JButton dismiss = new JButton(Util.onMac() ? "Dismiss" : "Close");
+ Object[] objs = new Object[msg.length + 1];
+ System.arraycopy(msg, 0, objs, 0, msg.length);
+ objs[objs.length - 1] = OurUtil.makeH(null, dismiss, null);
+ JOptionPane about = new JOptionPane(objs, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{});
+ JDialog dialog = about.createDialog(null, title);
+ dismiss.addActionListener(Runner.createDispose(dialog));
+ dialog.setAlwaysOnTop(true);
+ dialog.setVisible(true);
+ dialog.dispose();
+ }
+
+ /** Popup the given error message. */
+ public static void alert(Object message) {
+ show("Error", ERROR_MESSAGE, message, null, null);
+ }
+
+ /** Popup the given error message, then terminate the program. */
+ public static void fatal(Object message) {
+ try { show("Fatal Error", ERROR_MESSAGE, message, null, null); } finally { System.exit(1); }
+ }
+
+ /** Ask if the user wishes to save the file, discard the file, or cancel the entire operation (default is cancel).
+ * @return 'c' if cancel, 's' if save, 'd' if discard
+ */
+ public static char askSaveDiscardCancel(String description) {
+ description = description + " has not been saved. Do you want to";
+ Object ans = show(
+ "Warning", WARNING_MESSAGE,
+ new String[] {description, "cancel the operation, close the file without saving, or save it and close?"},
+ new Object[] {"Save", "Don't Save", "Cancel"},
+ "Cancel"
+ );
+ return (ans == "Save") ? 's' : (ans == "Don't Save" ? 'd' : 'c');
+ }
+
+ /** Ask if the user really wishes to overwrite the file (default is no).
+ * @return true if the user wishes to overwrite the file, false if the user does not wish to overwrite the file.
+ */
+ public static boolean askOverwrite(String filename) {
+ return "Overwrite" == show("Warning: file already exists", WARNING_MESSAGE,
+ new String[] {"The file \"" + filename + "\"", "already exists. Do you wish to overwrite it?"},
+ new String[] {"Overwrite", "Cancel"},
+ "Cancel"
+ );
+ }
+
+ /** This caches the result of the call to get all fonts. */
+ private static String[] allFonts = null;
+
+ /** Returns true if a font with that name exists on the system (comparison is case-insensitive). */
+ public synchronized static boolean hasFont(String fontname) {
+ if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+ for(int i = 0; i < allFonts.length; i++) if (fontname.compareToIgnoreCase(allFonts[i]) == 0) return true;
+ return false;
+ }
+
+ /** Asks the user to choose a font; returns "" if the user cancels the request. */
+ public synchronized static String askFont() {
+ if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+ JComboBox jcombo = new OurCombobox(allFonts);
+ Object ans = show("Font", JOptionPane.INFORMATION_MESSAGE,
+ new Object[] {"Please choose the new font:", jcombo}, new Object[] {"Ok", "Cancel"}, "Cancel"
+ );
+ Object value = jcombo.getSelectedItem();
+ if (ans=="Ok" && (value instanceof String)) return (String)value; else return "";
+ }
+
+ /** True if we should use AWT (instead of Swing) to display the OPEN and SAVE dialog. */
+ private static boolean useAWT = Util.onMac();
+
+ /** Use the platform's preferred file chooser to ask the user to select a file.
+ * Note: if it is a save operation, and the user didn't include an extension, then we'll add the extension.
+ * @param isOpen - true means this is an Open operation; false means this is a Save operation
+ * @param dir - the initial directory (or null if we want to use the default)
+ * @param ext - the file extension (including "."; using lowercase letters; for example, ".als") or ""
+ * @param description - the description for the given extension
+ * @return null if the user didn't choose anything, otherwise it returns the selected file
+ */
+ public static File askFile (boolean isOpen, String dir, final String ext, final String description) {
+ if (dir == null) dir = Util.getCurrentDirectory();
+ if (!(new File(dir).isDirectory())) dir = System.getProperty("user.home");
+ dir = Util.canon(dir);
+ String ans;
+ if (useAWT) {
+ Frame parent = new Frame("Alloy File Dialog"); // this window is unused and not shown; needed by FileDialog and nothing more
+ FileDialog open = new FileDialog(parent, isOpen ? "Open..." : "Save...");
+ open.setAlwaysOnTop(true);
+ open.setMode(isOpen ? FileDialog.LOAD : FileDialog.SAVE);
+ open.setDirectory(dir);
+ if (ext.length()>0) open.setFilenameFilter(new FilenameFilter() {
+ public boolean accept(File dir, String name) { return name.toLowerCase(Locale.US).endsWith(ext); }
+ });
+ open.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog.
+ parent.dispose();
+ if (open.getFile() == null) return null; else ans = open.getDirectory() + File.separatorChar + open.getFile();
+ } else {
+ try {
+ JFileChooser open = new JFileChooser(dir) {
+ private static final long serialVersionUID = 0;
+ public JDialog createDialog(Component parent) throws HeadlessException {
+ JDialog dialog = super.createDialog(null);
+ dialog.setAlwaysOnTop(true);
+ return dialog;
+ }
+ };
+ open.setDialogTitle(isOpen ? "Open..." : "Save...");
+ open.setApproveButtonText(isOpen ? "Open" : "Save");
+ open.setDialogType(isOpen ? JFileChooser.OPEN_DIALOG : JFileChooser.SAVE_DIALOG);
+ if (ext.length()>0) open.setFileFilter(new FileFilter() {
+ public boolean accept(File file) { return !file.isFile() || file.getPath().toLowerCase(Locale.US).endsWith(ext); }
+ public String getDescription() { return description; }
+ });
+ if (open.showDialog(null, null) != JFileChooser.APPROVE_OPTION || open.getSelectedFile() == null) return null;
+ ans = open.getSelectedFile().getPath();
+ } catch(Exception ex) {
+ // Some combination of Windows version and JDK version will trigger this failure.
+ // In such a case, we'll fall back to using the "AWT" file open dialog
+ useAWT = true;
+ return askFile(isOpen, dir, ext, description);
+ }
+ }
+ if (!isOpen) {
+ int lastSlash = ans.lastIndexOf(File.separatorChar);
+ int lastDot = (lastSlash>=0) ? ans.indexOf('.', lastSlash) : ans.indexOf('.');
+ if (lastDot < 0) ans = ans + ext;
+ }
+ return new File(Util.canon(ans));
+ }
+
+ /** Display "msg" in a modal dialog window, and ask the user to choose "yes" versus "no" (default is "no"). */
+ public static boolean yesno(Object msg, String yes, String no) {
+ return show("Question", WARNING_MESSAGE, msg, new Object[]{yes, no}, no) == yes;
+ }
+
+ /** Display "msg" in a modal dialog window, and ask the user to choose "Yes" versus "No" (default is "no"). */
+ public static boolean yesno(Object msg) { return yesno(msg, "Yes", "No"); }
+
+ /** Display a modal dialog window containing the "objects"; returns true iff the user clicks Ok. */
+ public static boolean getInput(String title, Object... objects) {
+ // If there is a JTextField or a JTextArea here, then let the first JTextField or JTextArea be the initially focused widget
+ Object main = "Ok";
+ for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JTextArea) { main = obj; break; }
+ // Construct the dialog panel
+ final JOptionPane pane = new JOptionPane(objects, QUESTION_MESSAGE, YES_NO_OPTION, null, new Object[]{"Ok", "Cancel"}, main);
+ final JDialog dialog = pane.createDialog(null, title);
+ // For each JTextField and JCheckBox, add a KeyListener that detects VK_ENTER and treat it as if the user clicked OK
+ for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JCheckBox) {
+ ((JComponent)obj).addKeyListener(new KeyListener() {
+ public void keyPressed(KeyEvent e) { if (e.getKeyCode()==KeyEvent.VK_ENTER) { pane.setValue("Ok"); dialog.dispose(); } }
+ public void keyReleased(KeyEvent e) { }
+ public void keyTyped(KeyEvent e) { }
+ });
+ }
+ dialog.setAlwaysOnTop(true);
+ dialog.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog.
+ dialog.dispose();
+ return pane.getValue() == "Ok";
+ }
+
+ /** Display a simple non-modal window showing some text. */
+ public static JFrame showtext(String title, String text) {
+ JFrame window = new JFrame(title);
+ JButton done = new JButton("Close");
+ done.addActionListener(Runner.createDispose(window));
+ JScrollPane scrollPane = OurUtil.scrollpane(OurUtil.textarea(text, 20, 60, false, false));
+ window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ window.getContentPane().setLayout(new BorderLayout());
+ window.getContentPane().add(scrollPane, BorderLayout.CENTER);
+ window.getContentPane().add(done, BorderLayout.SOUTH);
+ window.pack();
+ window.setSize(500, 500);
+ window.setLocationRelativeTo(null);
+ window.setVisible(true);
+ return window;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java
new file mode 100644
index 00000000..183ff4d9
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java
@@ -0,0 +1,58 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Highlighter;
+import javax.swing.text.JTextComponent;
+
+/** Graphica highlighter.
+ *
+ *
Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurHighlighter implements Highlighter.HighlightPainter {
+
+ /** The color to use when drawing highlights. */
+ public final Color color;
+
+ /** Construct a highlighter with the given color. */
+ public OurHighlighter(Color color) { this.color = color; }
+
+ /** This method is called by Swing to draw highlights. */
+ public void paint(Graphics gr, int start, int end, Shape shape, JTextComponent text) {
+ Color old = gr.getColor();
+ gr.setColor(color);
+ try {
+ Rectangle box = shape.getBounds(), a = text.getUI().modelToView(text, start), b = text.getUI().modelToView(text, end);
+ if (a.y == b.y) {
+ // same line (Note: furthermore, if start==end, then we draw all the way to the right edge)
+ Rectangle r = a.union(b);
+ gr.fillRect(r.x, r.y, (r.width<=1 ? (box.x + box.width - r.x) : r.width), r.height);
+ } else {
+ // Multiple lines; (Note: on first line we'll draw from "start" and extend to rightmost)
+ gr.fillRect(a.x, a.y, box.x + box.width - a.x, a.height);
+ if (a.y + a.height < b.y) gr.fillRect(box.x, a.y + a.height, box.width, b.y - (a.y + a.height));
+ gr.fillRect(box.x, b.y, b.x - box.x, b.height);
+ }
+ } catch (BadLocationException e) { } // Failure to highlight is not fatal
+ gr.setColor(old);
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java
new file mode 100644
index 00000000..7346cb72
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java
@@ -0,0 +1,277 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Polygon;
+import java.awt.Shape;
+import java.awt.geom.PathIterator;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/** Graphical convenience methods for producing PDF files.
+ *
+ *
This implementation explicitly generates a very simple 8.5 inch by 11 inch one-page PDF consisting of graphical operations.
+ * Hopefully this class will no longer be needed in the future once Java comes with better PDF support.
+ */
+
+public final strictfp class OurPDFWriter {
+
+ /** The filename. */
+ private final String filename;
+
+ /** The page width (in terms of dots). */
+ private final long width;
+
+ /** The page height (in terms of dots). */
+ private final long height;
+
+ /** Latest color expressed as RGB (-1 if none has been explicitly set so far) */
+ private int color = -1;
+
+ /** Latest line style (0=normal, 1=bold, 2=dotted, 3=dashed) */
+ private int line = 0;
+
+ /** The buffer that will store the list of graphical operations issued so far (null if close() has been called successfully) */
+ private ByteBuffer buf = new ByteBuffer();
+
+ /** Begin a blank PDF file with the given dots-per-inch and the given scale (the given file, if existed, will be overwritten)
+ * @throws IllegalArgumentException if dpi is less than 50 or is greater than 3000
+ */
+ public OurPDFWriter(String filename, int dpi, double scale) {
+ if (dpi<50 || dpi>3000) throw new IllegalArgumentException("The DPI must be between 50 and 3000");
+ this.filename = filename;
+ width = dpi*8L + (dpi/2L); // "8.5 inches"
+ height = dpi*11L; // "11 inches"
+ // Write the default settings, and flip (0, 0) into the top-left corner of the page, scale the page, then leave 0.5" margin
+ buf.write("q\n" + "1 J\n" + "1 j\n" + "[] 0 d\n" + "1 w\n" + "1 0 0 -1 0 ").writes(height).write("cm\n");
+ buf.writes(scale).write("0 0 ").writes(scale).writes(dpi/2.0).writes(dpi/2.0).write("cm\n");
+ buf.write("1 0 0 1 ").writes(dpi/2.0).writes(dpi/2.0).write("cm\n");
+ }
+
+ /** Changes the color for subsequent graphical drawing. */
+ public OurPDFWriter setColor(Color color) {
+ int rgb = color.getRGB() & 0xFFFFFF, r = (rgb>>16), g = (rgb>>8) & 0xFF, b = (rgb & 0xFF);
+ if (this.color == rgb) return this; else this.color = rgb; // no need to change
+ buf.writes(r/255.0).writes(g/255.0).writes(b/255.0).write("RG\n");
+ buf.writes(r/255.0).writes(g/255.0).writes(b/255.0).write("rg\n");
+ return this;
+ }
+
+ /** Changes the line style to be normal. */
+ public OurPDFWriter setNormalLine() { if (line!=0) buf.write("1 w [] 0 d\n"); line=0; return this; }
+
+ /** Changes the line style to be bold. */
+ public OurPDFWriter setBoldLine() { if (line!=1) buf.write("2 w [] 0 d\n"); line=1; return this; }
+
+ /** Changes the line style to be dotted. */
+ public OurPDFWriter setDottedLine() { if (line!=2) buf.write("1 w [1 3] 0 d\n"); line=2; return this; }
+
+ /** Changes the line style to be dashed. */
+ public OurPDFWriter setDashedLine() { if (line!=3) buf.write("1 w [6 3] 0 d\n"); line=3; return this; }
+
+ /** Shifts the coordinate space by the given amount. */
+ public OurPDFWriter shiftCoordinateSpace(int x, int y) { buf.write("1 0 0 1 ").writes(x).writes(y).write("cm\n"); return this; }
+
+ /** Draws a line from (x1, y1) to (x2, y2). */
+ public OurPDFWriter drawLine(int x1, int y1, int x2, int y2) {
+ buf.writes(x1).writes(y1).write("m ").writes(x2).writes(y2).write("l S\n"); return this;
+ }
+
+ /** Draws a circle of the given radius, centered at (0, 0). */
+ public OurPDFWriter drawCircle(int radius, boolean fillOrNot) {
+ double k = (0.55238 * radius); // Approximate a circle using 4 cubic bezier curves
+ buf.writes( radius).write("0 m ");
+ buf.writes( radius).writes( k).writes( k).writes( radius).write("0 ") .writes( radius).write("c ");
+ buf.writes( -k).writes( radius).writes(-radius).writes( k).writes(-radius).write("0 c ");
+ buf.writes(-radius).writes( -k).writes( -k).writes(-radius).write("0 ") .writes(-radius).write("c ");
+ buf.writes( k).writes(-radius).writes( radius).writes( -k).writes(radius) .write(fillOrNot ? "0 c f\n" : "0 c S\n");
+ return this;
+ }
+
+ /** Draws a shape. */
+ public OurPDFWriter drawShape(Shape shape, boolean fillOrNot) {
+ if (shape instanceof Polygon) {
+ Polygon obj = (Polygon)shape;
+ for(int i = 0; i < obj.npoints; i++) buf.writes(obj.xpoints[i]).writes(obj.ypoints[i]).write(i==0 ? "m\n" : "l\n");
+ buf.write("h\n");
+ } else {
+ double moveX = 0, moveY = 0, nowX = 0, nowY = 0, pt[] = new double[6];
+ for(PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) switch(it.currentSegment(pt)) {
+ case PathIterator.SEG_MOVETO:
+ nowX = moveX = pt[0]; nowY = moveY = pt[1]; buf.writes(nowX).writes(nowY).write("m\n"); break;
+ case PathIterator.SEG_CLOSE:
+ nowX = moveX; nowY = moveY; buf.write("h\n"); break;
+ case PathIterator.SEG_LINETO:
+ nowX = pt[0]; nowY = pt[1]; buf.writes(nowX).writes(nowY).write("l\n"); break;
+ case PathIterator.SEG_CUBICTO:
+ nowX = pt[4]; nowY = pt[5];
+ buf.writes(pt[0]).writes(pt[1]).writes(pt[2]).writes(pt[3]).writes(nowX).writes(nowY).write("c\n"); break;
+ case PathIterator.SEG_QUADTO: // Convert quadratic bezier into cubic bezier using de Casteljau algorithm
+ double px = nowX + (pt[0] - nowX)*(2.0/3.0), qx = px + (pt[2] - nowX)/3.0;
+ double py = nowY + (pt[1] - nowY)*(2.0/3.0), qy = py + (pt[3] - nowY)/3.0;
+ nowX = pt[2]; nowY = pt[3];
+ buf.writes(px).writes(py).writes(qx).writes(qy).writes(nowX).writes(nowY).write("c\n"); break;
+ }
+ }
+ buf.write(fillOrNot ? "f\n" : "S\n");
+ return this;
+ }
+
+ /* PDF File Structure Summary:
+ * ===========================
+ *
+ * File should ideally start with the following 13 bytes: "%PDF-1.3" 10 "%" -127 10 10
+ * Now comes one or more objects.
+ * One simple single-page arrangement is to have exactly 5 objects in this order: FONT, CONTENT, PAGE, PAGES, and CATALOG.
+ *
+ * Font Object (1 because FONT is #1)
+ * ==================================
+ *
+ * 1 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >> endobj\n\n
+ *
+ * Content Object (2 because CONTENT is #2) (${LEN} is the number of bytes in ${CONTENT} when compressed)
+ * ======================================================================================================
+ *
+ * 2 0 obj << /Length ${LEN} /Filter /FlateDecode >> stream\r\n${CONTENT}endstream endobj\n\n
+ *
+ * Here is a quick summary of various PDF Graphics operations
+ * ==========================================================
+ *
+ * $x $y m --> begins a new path at the given coordinate
+ * $x $y l --> add the segment (LASTx,LASTy)..($x,$y) to the current path
+ * $cx $cy $x $y v --> add the bezier curve (LASTx,LASTy)..(LASTx,LASTy)..($cx,$cy)..($x,$y) to the current path
+ * $cx $cy $x $y y --> add the bezier curve (LASTx,LASTy)....($cx,$cy).....($x,$y)...($x,$y) to the current path
+ * $ax $ay $bx $by $x $y c --> add the bezier curve (LASTx,LASTy)....($ax,$ay)....($bx,$by)..($x,$y) to the current path
+ * h --> close the current subpath by straightline segment from current point to the start of this subpath
+ * $x $y $w $h re --> append a rectangle to the current path as a complete subpath with lower-left corner at $x $y
+ *
+ * S --> assuming we've just described a path, draw the path
+ * f --> assuming we've just described a path, fill the path
+ * B --> assuming we've just described a path, fill then draw the path
+ *
+ * q --> saves the current graphics state
+ * 1 J --> sets the round cap
+ * 1 j --> sets the round joint
+ * [] 0 d --> sets the dash pattern as SOLID
+ * [4 6] 0 d --> sets the dash pattern as 4 UNITS ON then 6 UNITS OFF
+ * 5 w --> sets the line width as 5 UNITS
+ * $a $b $c $d $e $f cm --> appends the given matrix; for example, [1 0 0 1 dx dy] means "translation to dx dy"
+ * $R $G $B RG --> sets the stroke color (where 0 <= $R <= 1, etc)
+ * $R $G $B rg --> sets the fill color (where 0 <= $R <= 1, etc)
+ * Q --> restores the current graphics state
+ *
+ * Page Object (3 because PAGE is #3) (4 beacuse PAGES is #4) (2 because CONTENTS is #2)
+ * =====================================================================================
+ *
+ * 3 0 obj << /Type /Page /Parent 4 0 R /Contents 2 0 R >> endobj\n\n
+ *
+ * Pages Object (4 because PAGES is #4) (3 because PAGE is #3) (${W} is 8.5*DPI, ${H} is 11*DPI) (1 because FONT is #1)
+ * ====================================================================================================================
+ *
+ * 4 0 obj << /Type /Pages /Count 1 /Kids [3 0 R] /MediaBox [0 0 ${W} ${H}] /Resources << /Font << /F1 1 0 R >> >> >> endobj\n\n
+ *
+ * Catalog Object (5 because CATALOG is #5) (4 because PAGES is #4)
+ * ================================================================
+ *
+ * 5 0 obj << /Type /Catalog /Pages 4 0 R >> endobj\n\n
+ *
+ * END_OF_FILE format (assuming we have obj1 obj2 obj3 obj4 obj5 where obj5 is the "PDF Catalog")
+ * ==============================================================================================
+ *
+ * xref\n
+ * 0 6\n // 6 is because it's the number of objects plus 1
+ * 0000000000 65535 f\r\n
+ * ${offset1} 00000 n\r\n // ${offset1} is byte offset of start of obj1, left-padded-with-zero until you get exactly 10 digits
+ * ${offset2} 00000 n\r\n // ${offset2} is byte offset of start of obj2, left-padded-with-zero until you get exactly 10 digits
+ * ${offset3} 00000 n\r\n // ${offset3} is byte offset of start of obj3, left-padded-with-zero until you get exactly 10 digits
+ * ${offset4} 00000 n\r\n // ${offset4} is byte offset of start of obj4, left-padded-with-zero until you get exactly 10 digits
+ * ${offset5} 00000 n\r\n // ${offset5} is byte offset of start of obj5, left-padded-with-zero until you get exactly 10 digits
+ * trailer\n
+ * <<\n
+ * /Size 6\n // 6 is because it's the number of objects plus 1
+ * /Root 5 0 R\n // 5 is because it's the Catalog Object's object ID
+ * >>\n
+ * startxref\n
+ * ${xref}\n // $xref is the byte offset of the start of this entire "xref" paragraph
+ * %%EOF\n
+ */
+
+ /** Helper method that writes the given String to the output file, then return the number of bytes written. */
+ private static int out(RandomAccessFile file, String string) throws IOException {
+ byte[] array = string.getBytes("UTF-8");
+ file.write(array);
+ return array.length;
+ }
+
+ /** Close and save this PDF object. */
+ public void close() throws IOException {
+ if (buf == null) return; // already closed
+ final boolean compressOrNot = true;
+ RandomAccessFile out = null;
+ try {
+ String space = " "; // reserve 20 bytes for the file size, which is far far more than enough
+ final long fontID = 1, contentID = 2, pageID = 3, pagesID = 4, catalogID = 5, offset[] = new long[6];
+ // Write %PDF-1.3, followed by a non-ASCII comment to force the PDF into binary mode
+ out = new RandomAccessFile(filename, "rw");
+ out.setLength(0);
+ byte[] head = new byte[]{'%', 'P', 'D', 'F', '-', '1', '.', '3', 10, '%', -127, 10, 10};
+ out.write(head);
+ long now = head.length;
+ // Font
+ offset[1] = now;
+ now += out(out, fontID + " 0 obj << /Type /Font /Subtype /Type1 /BaseFont"
+ + " /Helvetica /Encoding /WinAnsiEncoding >> endobj\n\n");
+ // Content
+ offset[2] = now;
+ now += out(out, contentID + " 0 obj << /Length " + space
+ + (compressOrNot ? " /Filter /FlateDecode" : "") + " >> stream\r\n");
+ buf.write("Q\n");
+ final long ct = compressOrNot ? buf.dumpFlate(out) : buf.dump(out);
+ now += ct + out(out, "endstream endobj\n\n");
+ // Page
+ offset[3] = now;
+ now += out(out, pageID + " 0 obj << /Type /Page /Parent " + pagesID + " 0 R /Contents " + contentID + " 0 R >> endobj\n\n");
+ // Pages
+ offset[4] = now;
+ now += out(out, pagesID + " 0 obj << /Type /Pages /Count 1 /Kids [" + pageID + " 0 R] /MediaBox [0 0 "
+ + width + " " + height + "] /Resources << /Font << /F1 " + fontID + " 0 R >> >> >> endobj\n\n");
+ // Catalog
+ offset[5] = now;
+ now += out(out, catalogID + " 0 obj << /Type /Catalog /Pages " + pagesID + " 0 R >> endobj\n\n");
+ // Xref
+ String xr = "xref\n" + "0 " + offset.length + "\n";
+ for(int i = 0; i < offset.length; i++) {
+ String txt = Long.toString(offset[i]);
+ while(txt.length() < 10) txt = "0" + txt; // must be exactly 10 characters long
+ if (i==0) xr = xr + txt + " 65535 f\r\n"; else xr = xr + txt + " 00000 n\r\n";
+ }
+ // Trailer
+ xr = xr + "trailer\n<<\n/Size " + offset.length + "\n/Root " + catalogID + " 0 R\n>>\n" + "startxref\n" + now + "\n%%EOF\n";
+ out(out, xr);
+ out.seek(offset[2]);
+ out(out, contentID + " 0 obj << /Length " + ct); // move the file pointer back so we can write out the real Content Size
+ out.close();
+ buf = null; // only set buf to null if the file was saved successfully and no exception was thrown
+ } catch(Throwable ex) {
+ Util.close(out);
+ if (ex instanceof IOException) throw (IOException)ex;
+ if (ex instanceof OutOfMemoryError) throw new IOException("Out of memory trying to save the PDF file to " + filename);
+ if (ex instanceof StackOverflowError) throw new IOException("Out of memory trying to save the PDF file to " + filename);
+ throw new IOException("Error writing the PDF file to " + filename + " (" + ex + ")");
+ }
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java
new file mode 100644
index 00000000..c22bad08
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java
@@ -0,0 +1,148 @@
+/* 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.alloy4;
+
+import java.io.IOException;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.awt.image.BufferedImage;
+import javax.imageio.ImageIO;
+
+/** Graphical convenience methods for producing PNG files. */
+
+public final strictfp class OurPNGWriter {
+
+ /** The constructor is private, since this utility class never needs to be instantiated. */
+ private OurPNGWriter () { }
+
+ /** Writes the image as a PNG file with the given horizontal and vertical dots-per-inch. */
+ public static void writePNG (BufferedImage image, String filename, double dpiX, double dpiY) throws IOException {
+ try {
+ ImageIO.write(image, "PNG", new File(filename)); // some versions of Java sometimes throws an exception during saving...
+ setDPI(filename, dpiX, dpiY);
+ } catch(Throwable ex) {
+ if (ex instanceof IOException) throw (IOException)ex;
+ if (ex instanceof StackOverflowError) throw new IOException("Out of memory trying to save the PNG file to " + filename);
+ if (ex instanceof OutOfMemoryError) throw new IOException("Out of memory trying to save the PNG file to " + filename);
+ throw new IOException("Error writing the PNG file to " + filename + " (" + ex + ")");
+ }
+ }
+
+ /* PNG consists of a "8 byte header" followed by one or more CHUNK...
+ *
+ * Each CHUNK:
+ * ===========
+ * 4 bytes: an integer N expressed with most-significant-byte first
+ * 4 bytes: Chunk Type
+ * N bytes: Chunk Data
+ * 4 bytes: Checksum (this checksum is computed over the Chunk Type and Chunk Data)
+ *
+ * Each PNG must contain an IDAT chunk (this is the actual pixels of the image)
+ *
+ * Each PNG may contain an optional pHYs chunk that describes the horizontal and vertical dots-per-meter information.
+ * If such a chunk exists, it must come before (though not necessarily immediately before) the IDAT chunk.
+ *
+ * pHYs CHUNK:
+ * ===========
+ * 4 bytes: 0 , 0 , 0 , 9
+ * 4 bytes: 'p' , 'H' , 'Y' , 's'
+ * 4 bytes: horizontal dots per meter (most-significant-byte first)
+ * 4 bytes: vertical dots per meter (most-significant-byte first)
+ * 1 byte: 1
+ * 4 bytes: Checksum
+ */
+
+ /** Modifies the given PNG file to have the given horizontal and vertical dots-per-inch. */
+ private static void setDPI (String filename, double dpiX, double dpiY) throws IOException {
+ RandomAccessFile f = null;
+ try {
+ f = new RandomAccessFile(filename, "rw");
+ for(long total=f.length(), pos=8; pos>>24, x>>>16, x>>>8, x, y>>>24, y>>>16, y>>>8, y, 1});
+ }
+
+ /** Write the given chunk into the given file; Note: data.length must be at least 4. */
+ private static void writeChunk (RandomAccessFile file, int[] data) throws IOException {
+ int crc = (-1), len = data.length - 4;
+ file.write((len>>>24) & 255); file.write((len>>>16) & 255); file.write((len>>>8) & 255); file.write(len & 255);
+ for(int i=0; i>> 8); file.write(x & 255); }
+ crc = crc ^ (-1);
+ file.write((crc>>>24) & 255); file.write((crc>>>16) & 255); file.write((crc>>>8) & 255); file.write(crc & 255);
+ }
+
+ /** This precomputed table makes it faster to calculate CRC; this is based on the suggestion in the PNG specification. */
+ private static final int[] table = new int[] {
+ 0,1996959894,-301047508,-1727442502,124634137,1886057615,-379345611,-1637575261,249268274
+ ,2044508324,-522852066,-1747789432,162941995,2125561021,-407360249,-1866523247,498536548,1789927666
+ ,-205950648,-2067906082,450548861,1843258603,-187386543,-2083289657,325883990,1684777152,-43845254
+ ,-1973040660,335633487,1661365465,-99664541,-1928851979,997073096,1281953886,-715111964,-1570279054
+ ,1006888145,1258607687,-770865667,-1526024853,901097722,1119000684,-608450090,-1396901568,853044451
+ ,1172266101,-589951537,-1412350631,651767980,1373503546,-925412992,-1076862698,565507253,1454621731
+ ,-809855591,-1195530993,671266974,1594198024,-972236366,-1324619484,795835527,1483230225,-1050600021
+ ,-1234817731,1994146192,31158534,-1731059524,-271249366,1907459465,112637215,-1614814043,-390540237
+ ,2013776290,251722036,-1777751922,-519137256,2137656763,141376813,-1855689577,-429695999,1802195444
+ ,476864866,-2056965928,-228458418,1812370925,453092731,-2113342271,-183516073,1706088902,314042704
+ ,-1950435094,-54949764,1658658271,366619977,-1932296973,-69972891,1303535960,984961486,-1547960204
+ ,-725929758,1256170817,1037604311,-1529756563,-740887301,1131014506,879679996,-1385723834,-631195440
+ ,1141124467,855842277,-1442165665,-586318647,1342533948,654459306,-1106571248,-921952122,1466479909
+ ,544179635,-1184443383,-832445281,1591671054,702138776,-1328506846,-942167884,1504918807,783551873
+ ,-1212326853,-1061524307,-306674912,-1698712650,62317068,1957810842,-355121351,-1647151185,81470997
+ ,1943803523,-480048366,-1805370492,225274430,2053790376,-468791541,-1828061283,167816743,2097651377
+ ,-267414716,-2029476910,503444072,1762050814,-144550051,-2140837941,426522225,1852507879,-19653770
+ ,-1982649376,282753626,1742555852,-105259153,-1900089351,397917763,1622183637,-690576408,-1580100738
+ ,953729732,1340076626,-776247311,-1497606297,1068828381,1219638859,-670225446,-1358292148,906185462
+ ,1090812512,-547295293,-1469587627,829329135,1181335161,-882789492,-1134132454,628085408,1382605366
+ ,-871598187,-1156888829,570562233,1426400815,-977650754,-1296233688,733239954,1555261956,-1026031705
+ ,-1244606671,752459403,1541320221,-1687895376,-328994266,1969922972,40735498,-1677130071,-351390145
+ ,1913087877,83908371,-1782625662,-491226604,2075208622,213261112,-1831694693,-438977011,2094854071
+ ,198958881,-2032938284,-237706686,1759359992,534414190,-2118248755,-155638181,1873836001,414664567
+ ,-2012718362,-15766928,1711684554,285281116,-1889165569,-127750551,1634467795,376229701,-1609899400
+ ,-686959890,1308918612,956543938,-1486412191,-799009033,1231636301,1047427035,-1362007478,-640263460
+ ,1088359270,936918000,-1447252397,-558129467,1202900863,817233897,-1111625188,-893730166,1404277552
+ ,615818150,-1160759803,-841546093,1423857449,601450431,-1285129682,-1000256840,1567103746,711928724
+ ,-1274298825,-1022587231,1510334235,755167117
+ };
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java
new file mode 100644
index 00000000..f2b7c051
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java
@@ -0,0 +1,259 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultEditorKit;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.MutableAttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.TabSet;
+import javax.swing.text.TabStop;
+import static edu.mit.csail.sdg.alloy4.OurConsole.style;
+
+/** Graphical syntax-highlighting StyledDocument.
+ *
+ * Thread Safety: Can be called only by the AWT event thread
+ */
+
+class OurSyntaxDocument extends DefaultStyledDocument {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The "comment mode" at the start of each line (0 = no comment) (1 = block comment) (2 = javadoc comment) (-1 = unknown) */
+ private final List comments = new ArrayList();
+
+ /** Whether syntax highlighting is currently enabled or not. */
+ private boolean enabled = true;
+
+ /** The current font name is. */
+ private String font = "Monospaced";
+
+ /** The current font size. */
+ private int fontSize = 14;
+
+ /** The current tab size. */
+ private int tabSize = 4;
+
+ /** The list of font+color styles (eg. regular text, symbols, keywords, comments, etc). */
+ private final List all = new ArrayList();
+
+ /** The character style for regular text. */
+ private final MutableAttributeSet styleNormal = style(font, fontSize, false, Color.BLACK, 0); { all.add(styleNormal); }
+
+ /** The character style for symbols. */
+ private final MutableAttributeSet styleSymbol = style(font, fontSize, true, Color.BLACK, 0); { all.add(styleSymbol); }
+
+ /** The character style for integer constants. */
+ private final MutableAttributeSet styleNumber = style(font, fontSize, true, new Color(0xA80A0A), 0); { all.add(styleNumber); }
+
+ /** The character style for keywords. */
+ private final MutableAttributeSet styleKeyword = style(font, fontSize, true, new Color(0x1E1EA8), 0); { all.add(styleKeyword); }
+
+ /** The character style for string literals. */
+ private final MutableAttributeSet styleString = style(font, fontSize, false, new Color(0xA80AA8), 0); { all.add(styleString); }
+
+ /** The character style for up-to-end-of-line-style comment. */
+ private final MutableAttributeSet styleComment = style(font, fontSize, false, new Color(0x0A940A), 0); { all.add(styleComment); }
+
+ /** The character style for non-javadoc-style block comment. */
+ private final MutableAttributeSet styleBlock = style(font, fontSize, false, new Color(0x0A940A), 0); { all.add(styleBlock); }
+
+ /** The character style for javadoc-style block comment. */
+ private final MutableAttributeSet styleJavadoc = style(font, fontSize, true, new Color(0x0A940A), 0); { all.add(styleJavadoc); }
+
+ /** The paragraph style for indentation. */
+ private final MutableAttributeSet tabset = new SimpleAttributeSet();
+
+ /** This stores the currently recognized set of reserved keywords. */
+ private static final String[] keywords = new String[] {"abstract", "all", "and", "as", "assert", "but", "check", "disj",
+ "disjoint", "else", "enum", "exactly", "exh", "exhaustive", "expect", "extends", "fact", "for", "fun", "iden",
+ "iff", "implies", "in", "Int", "int", "let", "lone", "module", "no", "none", "not", "one", "open", "or", "part",
+ "partition", "pred", "private", "run", "seq", "set", "sig", "some", "String", "sum", "this", "univ"
+ };
+
+ /** Returns true if array[start .. start+len-1] matches one of the reserved keyword. */
+ private static final boolean do_keyword(String array, int start, int len) {
+ if (len >= 2 && len <= 10) for(int i = keywords.length - 1; i >= 0; i--) {
+ String str = keywords[i];
+ if (str.length()==len) for(int j=0; ;j++) if (j==len) return true; else if (str.charAt(j) != array.charAt(start+j)) break;
+ }
+ return false;
+ }
+
+ /** Returns true if "c" can be in the start or middle or end of an identifier. */
+ private static final boolean do_iden(char c) {
+ return (c>='A' && c<='Z') || (c>='a' && c<='z') || c=='$' || (c>='0' && c<='9') || c=='_' || c=='\'' || c=='\"';
+ }
+
+ /** Constructor. */
+ public OurSyntaxDocument(String fontName, int fontSize) {
+ putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n");
+ tabSize++;
+ do_setFont(fontName, fontSize, tabSize - 1); // assigns the given font, and also forces recomputation of the tab size
+ }
+
+ /** Enables or disables syntax highlighting. */
+ public final void do_enableSyntax (boolean flag) {
+ if (enabled == flag) return; else { enabled = flag; comments.clear(); }
+ if (flag) do_reapplyAll(); else setCharacterAttributes(0, getLength(), styleNormal, false);
+ }
+
+ /** Return the number of lines represented by the current text (where partial line counts as a line).
+ * For example: count("")==1, count("x")==1, count("x\n")==2, and count("x\ny")==2
+ */
+ public final int do_getLineCount() {
+ String txt = toString();
+ for(int n=txt.length(), ans=1, i=0; ; i++) if (i>=n) return ans; else if (txt.charAt(i)=='\n') ans++;
+ }
+
+ /** Return the starting offset of the given line (If "line" argument is too large, it will return the last line's starting offset)
+ *
For example: given "ab\ncd\n", start(0)==0, start(1)==3, start(2...)==6. Same thing when given "ab\ncd\ne".
+ */
+ public final int do_getLineStartOffset(int line) {
+ String txt = toString();
+ for(int n=txt.length(), ans=0, i=0, y=0; ; i++) if (i>=n || y>=line) return ans; else if (txt.charAt(i)=='\n') {ans=i+1; y++;}
+ }
+
+ /** Return the line number that the offset is in (If "offset" argument is too large, it will just return do_getLineCount()-1).
+ *
For example: given "ab\ncd\n", offset(0..2)==0, offset(3..5)==1, offset(6..)==2. Same thing when given "ab\ncd\ne".
+ */
+ public final int do_getLineOfOffset(int offset) {
+ String txt = toString();
+ for(int n=txt.length(), ans=0, i=0; ; i++) if (i>=n || i>=offset) return ans; else if (txt.charAt(i)=='\n') ans++;
+ }
+
+ /** This method is called by Swing to insert a String into this document.
+ * We intentionally ignore "attr" and instead use our own coloring.
+ */
+ @Override public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException {
+ if (string.indexOf('\r')>=0) string = Util.convertLineBreak(string); // we don't want '\r'
+ if (!enabled) { super.insertString(offset, string, styleNormal); return; }
+ int startLine = do_getLineOfOffset(offset);
+ for(int i = 0; i < string.length(); i++) { // For each inserted '\n' we need to shift the values in "comments" array down
+ if (string.charAt(i)=='\n') { if (startLine < comments.size()-1) comments.add(startLine+1, -1); }
+ }
+ super.insertString(offset, string, styleNormal);
+ try { do_update(startLine); } catch(Exception ex) { comments.clear(); }
+ }
+
+ /** This method is called by Swing to delete text from this document. */
+ @Override public void remove(int offset, int length) throws BadLocationException {
+ if (!enabled) { super.remove(offset, length); return; }
+ int i = 0, startLine = do_getLineOfOffset(offset);
+ for(String oldText = toString(); i 0) this.remove(offset, length);
+ if (string != null && string.length() > 0) this.insertString(offset, string, styleNormal);
+ }
+
+ /** Reapply styles assuming the given line has just been modified */
+ private final void do_update(int line) throws BadLocationException {
+ String content = toString();
+ int lineCount = do_getLineCount();
+ while(line>0 && (line>=comments.size() || comments.get(line)<0)) line--; // "-1" in comments array are always contiguous
+ int comment = do_reapply(line==0 ? 0 : comments.get(line), content, line);
+ for (line++; line < lineCount; line++) { // update each subsequent line until it already starts with its expected comment mode
+ if (line < comments.size() && comments.get(line) == comment) break; else comment = do_reapply(comment, content, line);
+ }
+ }
+
+ /** Re-color the given line assuming it starts with a given comment mode, then return the comment mode for start of next line. */
+ private final int do_reapply(int comment, final String txt, final int line) {
+ while (line >= comments.size()) comments.add(-1); // enlarge array if needed
+ comments.set(line, comment); // record the fact that this line starts with the given comment mode
+ for(int n = txt.length(), i = do_getLineStartOffset(line); i < n;) {
+ final int oldi = i;
+ final char c = txt.charAt(i);
+ if (c=='\n') break;
+ if (comment==0 && c=='/' && i0) {
+ AttributeSet style = (comment==1 ? styleBlock : styleJavadoc);
+ while(i='0' && c<='9') ? styleNumber : (do_keyword(txt, oldi, i-oldi) ? styleKeyword : styleNormal);
+ setCharacterAttributes(oldi, i-oldi, style, false);
+ } else {
+ for(i++; i 100) tabSize = 100;
+ if (fontName.equals(this.font) && fontSize == this.fontSize && tabSize == this.tabSize) return;
+ this.font = fontName;
+ this.fontSize = fontSize;
+ this.tabSize = tabSize;
+ for(MutableAttributeSet s: all) { StyleConstants.setFontFamily(s, fontName); StyleConstants.setFontSize(s, fontSize); }
+ do_reapplyAll();
+ BufferedImage im = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); // this is used to derive the tab width
+ int gap = tabSize * im.createGraphics().getFontMetrics(new Font(fontName, Font.PLAIN, fontSize)).charWidth('X');
+ TabStop[] pos = new TabStop[100];
+ for(int i=0; i<100; i++) { pos[i] = new TabStop(i*gap + gap); }
+ StyleConstants.setTabSet(tabset, new TabSet(pos));
+ setParagraphAttributes(0, getLength(), tabset, false);
+ }
+
+ /** Overriden to return the full text of the document.
+ * @return the entire text
+ */
+ @Override public String toString() {
+ try { return getText(0, getLength()); } catch(BadLocationException ex) { return ""; }
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java
new file mode 100644
index 00000000..a489ae38
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java
@@ -0,0 +1,139 @@
+/* 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.alloy4;
+
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import static java.lang.System.arraycopy;
+
+/** Graphical syntax-highlighting StyledDocument with undo+redo support.
+ *
+ * Thread Safety: Can be called only by the AWT event thread
+ */
+
+final class OurSyntaxUndoableDocument extends OurSyntaxDocument {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The maximum number of UNDO actions we want to keep. */
+ private static final int MAXUNDO = 100;
+
+ /** For each i = 0..now-1, insert[i] means whether the i-th operation was an insertion or not. */
+ private boolean insert[] = new boolean[MAXUNDO];
+
+ /** For each i = 0..now-1, text[i] means the i-th operation was an insertion/deletion of that text. */
+ private String text[] = new String[MAXUNDO];
+
+ /** For each i = 0..now-1, where[i] means the i-th operation was an insertion/deletion at that offset. */
+ private int where[] = new int[MAXUNDO];
+
+ /** The number of undoable operations currently remembered in insert[], text[], and where[]. */
+ private int now;
+
+ /** The number of undoable opeartions that are currently "undone". */
+ private int undone;
+
+ /** Caches a default AttributeSet (just so that we don't pass null as a AttributeSet. */
+ private final AttributeSet attr = new SimpleAttributeSet();
+
+ /** Constructor. */
+ public OurSyntaxUndoableDocument(String fontName, int fontSize) { super(fontName, fontSize); }
+
+ /** Clear the undo history. */
+ public void do_clearUndo() { now = undone = 0; }
+
+ /** Returns true if we can perform undo right now. */
+ public boolean do_canUndo() { return undone < now; }
+
+ /** Returns true if we can perform redo right now. */
+ public boolean do_canRedo() { return undone > 0 ; }
+
+ /** Perform undo then return where the new desired caret location should be (or return -1 if undo is not possible right now) */
+ public int do_undo() {
+ if (undone >= now) return -1; else undone++;
+ boolean insert = this.insert[now - undone];
+ String text = this.text[now - undone];
+ int where = this.where[now - undone];
+ try {
+ if (insert) { super.remove(where, text.length()); return where; }
+ else { super.insertString(where, text, attr); return where + text.length(); }
+ } catch(BadLocationException ex) { return -1; }
+ }
+
+ /** Perform redo then return where the new desired caret location should be (or return -1 if redo is not possible right now) */
+ public int do_redo() {
+ if (undone <= 0) return -1;
+ boolean insert = this.insert[now - undone];
+ String text = this.text[now - undone];
+ int where = this.where[now - undone];
+ undone--;
+ try {
+ if (insert) { super.insertString(where, text, attr); return where + text.length(); }
+ else { super.remove(where, text.length()); return where; }
+ } catch(BadLocationException ex) { return -1; }
+ }
+
+ /** This method is called by Swing to insert a String into this document. */
+ @Override public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException {
+ if (string.length()==0) return; else if (string.indexOf('\r')>=0) string = Util.convertLineBreak(string); // we don't want '\r'
+ if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries
+ super.insertString(offset, string, attr);
+ if (now > 0 && insert[now-1]) { // merge with last edit if possible
+ if (where[now-1] == offset - text[now-1].length()) { text[now - 1] += string; return; }
+ }
+ if (now >= MAXUNDO) {
+ arraycopy(insert, 1, insert, 0, MAXUNDO-1);
+ arraycopy(text, 1, text, 0, MAXUNDO-1);
+ arraycopy(where, 1, where, 0, MAXUNDO-1);
+ now--;
+ }
+ insert[now]=true; text[now]=string; where[now]=offset; now++;
+ }
+
+ /** This method is called by Swing to delete text from this document. */
+ @Override public void remove(int offset, int length) throws BadLocationException {
+ if (length==0) return;
+ if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries
+ String string = toString().substring(offset, offset+length);
+ super.remove(offset, length);
+ if (now > 0 && !insert[now-1]) { // merge with last edit if possible
+ if (where[now-1] == offset) { text[now-1] += string; return; }
+ if (where[now-1] == offset+length) { where[now-1] = offset; text[now-1] = string + text[now-1]; return; }
+ }
+ if (now >= MAXUNDO) {
+ arraycopy(insert, 1, insert, 0, MAXUNDO-1);
+ arraycopy(text, 1, text, 0, MAXUNDO-1);
+ arraycopy(where, 1, where, 0, MAXUNDO-1);
+ now--;
+ }
+ insert[now]=false; text[now]=string; where[now]=offset; now++;
+ }
+
+ /** This method is called by Swing to replace text in this document. */
+ @Override public void replace(int offset, int length, String string, AttributeSet attrs) throws BadLocationException {
+ if (length > 0) this.remove(offset, length);
+ if (string != null && string.length() > 0) this.insertString(offset, string, this.attr);
+ }
+
+ /** Overriden to return the full text of the document.
+ * @return the entire text
+ */
+ @Override public String toString() {
+ try { return getText(0, getLength()); } catch(BadLocationException ex) { return ""; }
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java
new file mode 100644
index 00000000..8dee1630
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java
@@ -0,0 +1,299 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.io.File;
+import java.util.Collection;
+import javax.swing.AbstractAction;
+import javax.swing.JComponent;
+import javax.swing.JScrollPane;
+import javax.swing.JTextPane;
+import javax.swing.KeyStroke;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.AbstractDocument;
+import javax.swing.text.BoxView;
+import javax.swing.text.Document;
+import javax.swing.text.Element;
+import javax.swing.text.StyledEditorKit;
+import javax.swing.text.View;
+import javax.swing.text.ViewFactory;
+import edu.mit.csail.sdg.alloy4.Listener.Event;
+
+/** Graphical syntax-highlighting editor.
+ *
+ *
Thread Safety: Can be called only by the AWT event thread
+ */
+
+public final class OurSyntaxWidget {
+
+ /** The current list of listeners; possible events are { STATUS_CHANGE, FOCUSED, CTRL_PAGE_UP, CTRL_PAGE_DOWN, CARET_MOVED }. */
+ public final Listeners listeners = new Listeners();
+
+ /** The JScrollPane containing everything. */
+ private final JScrollPane component = OurUtil.make(new JScrollPane(), new EmptyBorder(0, 0, 0, 0));
+
+ /** This is an optional JComponent annotation. */
+ public final JComponent obj1;
+
+ /** This is an optional JComponent annotation. */
+ public final JComponent obj2;
+
+ /** The underlying StyledDocument being displayed (changes will trigger the STATUS_CHANGE event) */
+ private final OurSyntaxUndoableDocument doc = new OurSyntaxUndoableDocument("Monospaced", 14);
+
+ /** The underlying JTextPane being displayed. */
+ private final JTextPane pane = OurAntiAlias.pane(Color.BLACK, Color.WHITE, new EmptyBorder(1, 1, 1, 1));
+
+ /** The filename for this JTextPane (changes will trigger the STATUS_CHANGE event) */
+ private String filename = "";
+
+ /** Whether this JTextPane has been modified since last load/save (changes will trigger the STATUS_CHANGE event) */
+ private boolean modified;
+
+ /** Whether this JTextPane corresponds to an existing disk file (changes will trigger the STATUS_CHANGE event) */
+ private boolean isFile;
+
+ /** Caches the most recent background painter if nonnull. */
+ private OurHighlighter painter;
+
+ /** Constructs a syntax-highlighting widget. */
+ public OurSyntaxWidget() { this(true, "", "Monospaced", 14, 4, null, null); }
+
+ /** Constructs a syntax-highlighting widget. */
+ @SuppressWarnings("serial")
+ public OurSyntaxWidget
+ (boolean enableSyntax, String text, String fontName, int fontSize, int tabSize, JComponent obj1, JComponent obj2) {
+ this.obj1 = obj1;
+ this.obj2 = obj2;
+ final OurSyntaxWidget me = this;
+ final ViewFactory defaultFactory = (new StyledEditorKit()).getViewFactory();
+ doc.do_enableSyntax(enableSyntax);
+ doc.do_setFont(fontName, fontSize, tabSize);
+ pane.setEditorKit(new StyledEditorKit() { // Prevents line-wrapping up to width=30000, and tells it to use our Document obj
+ @Override public Document createDefaultDocument() { return doc; }
+ @Override public ViewFactory getViewFactory() {
+ return new ViewFactory() {
+ public View create(Element x) {
+ if (!AbstractDocument.SectionElementName.equals(x.getName())) return defaultFactory.create(x);
+ return new BoxView(x, View.Y_AXIS) { // 30000 is a good width to use here; value > 32767 appears to cause errors
+ @Override public final float getMinimumSpan(int axis) { return super.getPreferredSpan(axis); }
+ @Override public final void layout(int w, int h) { try {super.layout(30000, h);} catch(Throwable ex) {} }
+ };
+ }
+ };
+ }
+ });
+ if (text.length()>0) { pane.setText(text); pane.setCaretPosition(0); }
+ doc.do_clearUndo();
+ pane.getActionMap().put("alloy_copy", new AbstractAction("alloy_copy") {
+ public void actionPerformed(ActionEvent e) { pane.copy(); }
+ });
+ pane.getActionMap().put("alloy_cut", new AbstractAction("alloy_cut") {
+ public void actionPerformed(ActionEvent e) { pane.cut(); }
+ });
+ pane.getActionMap().put("alloy_paste", new AbstractAction("alloy_paste") {
+ public void actionPerformed(ActionEvent e) { pane.paste(); }
+ });
+ pane.getActionMap().put("alloy_ctrl_pageup", new AbstractAction("alloy_ctrl_pageup") {
+ public void actionPerformed(ActionEvent e) { listeners.fire(me, Event.CTRL_PAGE_UP); }
+ });
+ pane.getActionMap().put("alloy_ctrl_pagedown", new AbstractAction("alloy_ctrl_pagedown") {
+ public void actionPerformed(ActionEvent e) { listeners.fire(me, Event.CTRL_PAGE_DOWN); }
+ });
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "alloy_copy");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK), "alloy_cut");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "alloy_paste");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK), "alloy_copy");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.SHIFT_MASK), "alloy_paste");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_MASK), "alloy_cut");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK), "alloy_ctrl_pageup");
+ pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK), "alloy_ctrl_pagedown");
+ doc.addDocumentListener(new DocumentListener() {
+ public void insertUpdate(DocumentEvent e) { modified=true; listeners.fire(me, Event.STATUS_CHANGE); }
+ public void removeUpdate(DocumentEvent e) { modified=true; listeners.fire(me, Event.STATUS_CHANGE); }
+ public void changedUpdate(DocumentEvent e) { }
+ });
+ pane.addFocusListener(new FocusAdapter() {
+ @Override public void focusGained(FocusEvent e) { listeners.fire(me, Event.FOCUSED); }
+ });
+ pane.addCaretListener(new CaretListener() {
+ public void caretUpdate(CaretEvent e) { listeners.fire(me, Event.CARET_MOVED); }
+ });
+ component.addFocusListener(new FocusAdapter() {
+ @Override public void focusGained(FocusEvent e) { pane.requestFocusInWindow(); }
+ });
+ component.setFocusable(false);
+ component.setMinimumSize(new Dimension(50, 50));
+ component.setViewportView(pane);
+ modified = false;
+ }
+
+ /** Add this object into the given container. */
+ public void addTo(JComponent newParent, Object constraint) { newParent.add(component, constraint); }
+
+ /** Returns true if this textbox is currently shaded. */
+ boolean shaded() { return pane.getHighlighter().getHighlights().length > 0; }
+
+ /** Remove all shading. */
+ void clearShade() { pane.getHighlighter().removeAllHighlights(); }
+
+ /** Shade the range of text from start (inclusive) to end (exclusive). */
+ void shade(Color color, int start, int end) {
+ int c = color.getRGB() & 0xFFFFFF;
+ if (painter==null || (painter.color.getRGB() & 0xFFFFFF)!=c) painter = new OurHighlighter(color);
+ try { pane.getHighlighter().addHighlight(start, end, painter); } catch(Throwable ex) { } // exception is okay
+ }
+
+ /** Returns the filename. */
+ public String getFilename() { return filename; }
+
+ /** Returns the modified-or-not flag. */
+ public boolean modified() { return modified; }
+
+ /** Returns whether this textarea is based on an actual disk file. */
+ public boolean isFile() { return isFile; }
+
+ /** Changes the font name, font size, and tab size for the document. */
+ void setFont(String fontName, int fontSize, int tabSize) { if (doc!=null) doc.do_setFont(fontName, fontSize, tabSize); }
+
+ /** Enables or disables syntax highlighting. */
+ void enableSyntax(boolean flag) { if (doc!=null) doc.do_enableSyntax(flag); }
+
+ /** Return the number of lines represented by the current text (where partial line counts as a line).
+ *
For example: count("")==1, count("x")==1, count("x\n")==2, and count("x\ny")==2
+ */
+ public int getLineCount() { return doc.do_getLineCount(); }
+
+ /** Return the starting offset of the given line (If "line" argument is too large, it will return the last line's starting offset)
+ *
For example: given "ab\ncd\n", start(0)==0, start(1)==3, start(2...)==6. Same thing when given "ab\ncd\ne".
+ */
+ public int getLineStartOffset(int line) { return doc.do_getLineStartOffset(line); }
+
+ /** Return the line number that the offset is in (If "offset" argument is too large, it will just return do_getLineCount()-1).
+ *
For example: given "ab\ncd\n", offset(0..2)==0, offset(3..5)==1, offset(6..)==2. Same thing when given "ab\ncd\ne".
+ */
+ public int getLineOfOffset(int offset) { return doc.do_getLineOfOffset(offset); }
+
+ /** Returns true if we can perform undo right now. */
+ public boolean canUndo() { return doc.do_canUndo(); }
+
+ /** Returns true if we can perform redo right now. */
+ public boolean canRedo() { return doc.do_canRedo(); }
+
+ /** Perform undo if possible. */
+ public void undo() { int i = doc.do_undo(); if (i>=0 && i<=pane.getText().length()) moveCaret(i, i); }
+
+ /** Perform redo if possible. */
+ public void redo() { int i = doc.do_redo(); if (i>=0 && i<=pane.getText().length()) moveCaret(i, i); }
+
+ /** Clear the undo history. */
+ public void clearUndo() { doc.do_clearUndo(); }
+
+ /** Return the caret position. */
+ public int getCaret() { return pane.getCaretPosition(); }
+
+ /** Select the content between offset a and offset b, and move the caret to offset b. */
+ public void moveCaret(int a, int b) {
+ try { pane.setCaretPosition(a); pane.moveCaretPosition(b); } catch(Exception ex) { if (a!=0 || b!=0) moveCaret(0, 0); }
+ }
+
+ /** Return the entire text. */
+ public String getText() { return pane.getText(); }
+
+ /** Change the entire text to the given text (and sets the modified flag) */
+ public void setText(String text) { pane.setText(text); }
+
+ /** Copy the current selection into the clipboard. */
+ public void copy() { pane.copy(); }
+
+ /** Cut the current selection into the clipboard. */
+ public void cut() { pane.cut(); }
+
+ /** Paste the current clipboard content. */
+ public void paste() { pane.paste(); }
+
+ /** Discard all; if askUser is true, we'll ask the user whether to save it or not if the modified==true.
+ * @return true if this text buffer is now a fresh empty text buffer
+ */
+ boolean discard(boolean askUser, Collection bannedNames) {
+ char ans = (!modified || !askUser) ? 'd' : OurDialog.askSaveDiscardCancel("The file \"" + filename + "\"");
+ if (ans=='c' || (ans=='s' && save(false, bannedNames)==false)) return false;
+ for(int i=1; ;i++) if (!bannedNames.contains(filename = Util.canon("Untitled " + i + ".als"))) break;
+ pane.setText(""); clearUndo(); modified=false; isFile=false; listeners.fire(this, Event.STATUS_CHANGE);
+ return true;
+ }
+
+ /** Discard current content then read the given file; return true if the entire operation succeeds. */
+ boolean load(String filename) {
+ String x;
+ try {
+ x = Util.readAll(filename);
+ } catch(Throwable ex) { OurDialog.alert("Error reading the file \"" + filename + "\""); return false; }
+ pane.setText(x); moveCaret(0,0); clearUndo(); modified=false; isFile=true; this.filename=filename;
+ listeners.fire(this, Event.STATUS_CHANGE);
+ return true;
+ }
+
+ /** Discard (after confirming with the user) current content then reread from disk file. */
+ void reload() {
+ if (!isFile) return; // "untitled" text buffer does not have a on-disk file to refresh from
+ if (modified && !OurDialog.yesno("You have unsaved changes to \"" + filename + "\"\nAre you sure you wish to discard "
+ + "your changes and reload it from disk?", "Discard your changes", "Cancel this operation")) return;
+ String t;
+ try { t=Util.readAll(filename); } catch(Throwable ex) { OurDialog.alert("Cannot read \""+filename+"\""); return; }
+ if (modified==false && t.equals(pane.getText())) return; // no text change nor status change
+ int c=pane.getCaretPosition(); pane.setText(t); moveCaret(c,c); modified=false; listeners.fire(this, Event.STATUS_CHANGE);
+ }
+
+ /** Save the current tab content to the file system, and return true if successful. */
+ boolean saveAs(String filename, Collection bannedNames) {
+ filename = Util.canon(filename);
+ if (bannedNames.contains(filename)) {
+ OurDialog.alert("The filename \""+filename+"\"\nis already open in another tab.");
+ return false;
+ }
+ try { Util.writeAll(filename, pane.getText()); }
+ catch (Throwable e) { OurDialog.alert("Error writing to the file \""+filename+"\""); return false; }
+ this.filename = Util.canon(filename); // a new file's canonical name may change
+ modified=false; isFile=true; listeners.fire(this, Event.STATUS_CHANGE); return true;
+ }
+
+ /** Save the current tab content to the file system and return true if successful.
+ * @param alwaysPickNewName - if true, it will always pop up a File Selection dialog box to ask for the filename
+ */
+ boolean save(boolean alwaysPickNewName, Collection bannedNames) {
+ String n = this.filename;
+ if (alwaysPickNewName || isFile==false || n.startsWith(Util.jarPrefix())) {
+ File f = OurDialog.askFile(false, null, ".als", ".als files"); if (f==null) return false;
+ n = Util.canon(f.getPath()); if (f.exists() && !OurDialog.askOverwrite(n)) return false;
+ }
+ if (saveAs(n, bannedNames)) {Util.setCurrentDirectory(new File(filename).getParentFile()); return true;} else return false;
+ }
+
+ /** Transfer focus to this component. */
+ public void requestFocusInWindow() { if (pane!=null) pane.requestFocusInWindow(); }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java
new file mode 100644
index 00000000..202887ca
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java
@@ -0,0 +1,298 @@
+/* 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.alloy4;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Rectangle;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import edu.mit.csail.sdg.alloy4.Listener.Event;
+
+/** Graphical multi-tabbed syntax-highlighting editor.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ *
+ *
Invariant : each tab has distinct file name.
+ */
+
+public final class OurTabbedSyntaxWidget {
+
+ /** The current list of listeners; possible events are { STATUS_CHANGE, FOCUSED, CARET_MOVED }. */
+ public final Listeners listeners = new Listeners();
+
+ /** The JScrollPane containing everything. */
+ private final JPanel component = OurUtil.make(new JPanel());
+
+ /** Background color for the list of tabs. */
+ private static final Color GRAY = new Color(0.9f, 0.9f, 0.9f);
+
+ /** Background color for an inactive tab. */
+ private static final Color INACTIVE = new Color(0.8f, 0.8f, 0.8f);
+
+ /** Background color for a inactive and highlighted tab. */
+ private static final Color INACTIVE_HIGHLIGHTED = new Color(0.7f, 0.5f, 0.5f);
+
+ /** Foreground color for a active and highlighted tab. */
+ private static final Color ACTIVE_HIGHLIGHTED = new Color(0.5f, 0.2f, 0.2f);
+
+ /** Default border color for each tab. */
+ private static final Color BORDER = Color.LIGHT_GRAY;
+
+ /** The font name to use in the JTextArea */
+ private String fontName = "Monospaced";
+
+ /** The font size to use in the JTextArea */
+ private int fontSize = 14;
+
+ /** The tabsize to use in the JTextArea */
+ private int tabSize = 4;
+
+ /** Whether syntax highlighting is current enabled or not. */
+ private boolean syntaxHighlighting;
+
+ /** The list of clickable tabs. */
+ private final JPanel tabBar;
+
+ /** The scrollPane that wraps around this.tabbar */
+ private final JScrollPane tabBarScroller;
+
+ /** The list of tabs. */
+ private final List tabs = new ArrayList();
+
+ /** The currently selected tab from 0 to list.size()-1 (This value must be 0 if there are no tabs) */
+ private int me = 0;
+
+ /** This object receives messages from sub-JTextPane objects. */
+ private final Listener listener = new Listener() {
+ public Object do_action(Object sender, Event e) {
+ final OurTabbedSyntaxWidget me = OurTabbedSyntaxWidget.this;
+ if (sender instanceof OurSyntaxWidget) switch(e) {
+ case FOCUSED: listeners.fire(me, e); break;
+ case CARET_MOVED: listeners.fire(me, Event.STATUS_CHANGE); break;
+ case CTRL_PAGE_UP: prev(); break;
+ case CTRL_PAGE_DOWN: next(); break;
+ case STATUS_CHANGE:
+ clearShade();
+ OurSyntaxWidget t = (OurSyntaxWidget)sender;
+ String n = t.getFilename();
+ t.obj1.setToolTipText(n);
+ int i = n.lastIndexOf('/'); if (i >= 0) n = n.substring(i + 1);
+ i = n.lastIndexOf('\\'); if (i >= 0) n = n.substring(i + 1);
+ i = n.lastIndexOf('.'); if (i >= 0) n = n.substring(0, i);
+ if (n.length() > 14) { n = n.substring(0, 14) + "..."; }
+ if (t.obj1 instanceof JLabel) { ((JLabel)(t.obj1)).setText(" " + n + (t.modified() ? " * " : " ")); }
+ listeners.fire(me, Event.STATUS_CHANGE);
+ break;
+ }
+ return true;
+ }
+ public Object do_action(Object sender, Event e, Object arg) { return true; }
+ };
+
+ /** Constructs a tabbed editor pane. */
+ public OurTabbedSyntaxWidget(String fontName, int fontSize, int tabSize) {
+ component.setBorder(null);
+ component.setLayout(new BorderLayout());
+ JPanel glue = OurUtil.makeHB(new Object[]{null});
+ glue.setBorder(new OurBorder(null, null, BORDER, null));
+ tabBar = OurUtil.makeHB(glue);
+ if (!Util.onMac()) { tabBar.setOpaque(true); tabBar.setBackground(GRAY); }
+ tabBarScroller = new JScrollPane(tabBar, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ tabBarScroller.setFocusable(false);
+ tabBarScroller.setBorder(null);
+ setFont(fontName, fontSize, tabSize);
+ newtab(null);
+ tabBarScroller.addComponentListener(new ComponentListener() {
+ public final void componentResized(ComponentEvent e) { select(me); }
+ public final void componentMoved(ComponentEvent e) { select(me); }
+ public final void componentShown(ComponentEvent e) { select(me); }
+ public final void componentHidden(ComponentEvent e) { }
+ });
+ }
+
+ /** Add this object into the given container. */
+ public void addTo(JComponent newParent, Object constraint) { newParent.add(component, constraint); }
+
+ /** Adjusts the background and foreground of all labels. */
+ private void adjustLabelColor() {
+ for(int i=0; i= tabs.size()) return; else { me=i; component.revalidate(); adjustLabelColor(); component.removeAll(); }
+ if (tabs.size() > 1) component.add(tabBarScroller, BorderLayout.NORTH);
+ tabs.get(me).addTo(component, BorderLayout.CENTER);
+ component.repaint();
+ tabs.get(me).requestFocusInWindow();
+ tabBar.scrollRectToVisible(new Rectangle(0,0,0,0)); // Forces recalculation
+ tabBar.scrollRectToVisible(new Rectangle(tabs.get(me).obj2.getX(), 0, tabs.get(me).obj2.getWidth()+200, 1));
+ listeners.fire(this, Event.STATUS_CHANGE);
+ }
+
+ /** Refresh all tabs. */
+ public void reloadAll() { for(OurSyntaxWidget t: tabs) t.reload(); }
+
+ /** Return the list of all filenames except the filename in the i-th tab. */
+ private List getAllNamesExcept(int i) {
+ ArrayList ans = new ArrayList();
+ for(int x=0; ;x++) if (x == tabs.size()) return ans; else if (x != i) ans.add(tabs.get(x).getFilename());
+ }
+
+ /** Save the current tab content to the file system.
+ * @param alwaysPickNewName - if true, it will always pop up a File Selection dialog box to ask for the filename
+ * @return null if an error occurred (otherwise, return the filename)
+ */
+ public String save(boolean alwaysPickNewName) {
+ if (me < 0 || me >= tabs.size() || !tabs.get(me).save(alwaysPickNewName, getAllNamesExcept(me))) return null;
+ return tabs.get(me).getFilename();
+ }
+
+ /** Close the i-th tab (if there are no more tabs afterwards, we'll create a new empty tab).
+ *
+ * If the text editor content is not modified since the last save, then return true; otherwise, ask the user.
+ *
If the user says to save it, we will attempt to save it, then return true iff the save succeeded.
+ *
If the user says to discard it, this method returns true.
+ *
If the user says to cancel it, this method returns false (and the original tab and its contents will not be harmed).
+ */
+ private boolean close(int i) {
+ clearShade();
+ if (i<0 || i>=tabs.size()) return true; else if (!tabs.get(i).discard(true, getAllNamesExcept(i))) return false;
+ if (tabs.size() > 1) { tabBar.remove(i); tabs.remove(i); if (me >= tabs.size()) me = tabs.size() - 1; }
+ select(me);
+ return true;
+ }
+
+ /** Close the current tab (then create a new empty tab if there were no tabs remaining) */
+ public void close() { close(me); }
+
+ /** Close every tab then create a new empty tab; returns true iff success. */
+ public boolean closeAll() {
+ for(int i=tabs.size()-1; i>=0; i--) if (tabs.get(i).modified()==false) close(i); // first close all the unmodified files
+ for(int i=tabs.size()-1; i>=0; i--) if (close(i)==false) return false; // then attempt to close modified files one-by-one
+ return true;
+ }
+
+ /** Returns the number of tabs. */
+ public int count() { return tabs.size(); }
+
+ /** Return a modifiable map from each filename to its text content (Note: changes to the map does NOT affect this object) */
+ public Map takeSnapshot() {
+ Map map = new LinkedHashMap();
+ for(OurSyntaxWidget t: tabs) { map.put(t.getFilename(), t.getText()); }
+ return map;
+ }
+
+ /** Returns the list of filenames corresponding to each text buffer. */
+ public List getFilenames() { return getAllNamesExcept(-1); }
+
+ /** Changes the font name, font size, and tabsize of every text buffer. */
+ public void setFont(String name, int size, int tabSize) {
+ fontName=name; fontSize=size; this.tabSize=tabSize; for(OurSyntaxWidget t: tabs) t.setFont(name, size, tabSize);
+ }
+
+ /** Enables or disables syntax highlighting. */
+ public void enableSyntax(boolean flag) { syntaxHighlighting=flag; for(OurSyntaxWidget t: tabs) t.enableSyntax(flag); }
+
+ /** Returns the JTextArea of the current text buffer. */
+ public OurSyntaxWidget get() { return (me>=0 && me=0 && i=2) select(me==0 ? tabs.size()-1 : (me-1)); }
+
+ /** Switches to the next tab. */
+ public void next() { if (tabs.size()>=2) select(me==tabs.size()-1 ? 0 : (me+1)); }
+
+ /** Create a new tab with the given filename (if filename==null, we'll create a blank tab instead)
+ * If a text buffer with that filename already exists, we will just switch to it; else we'll read that file into a new tab.
+ * @return false iff an error occurred
+ */
+ public boolean newtab(String filename) {
+ if (filename!=null) {
+ filename = Util.canon(filename);
+ for(int i=0; i=0; i--) if (!tabs.get(i).isFile() && tabs.get(i).getText().length()==0) {
+ tabs.get(i).discard(false, getFilenames()); close(i); break; // Remove the rightmost untitled empty tab
+ }
+ }
+ select(tabs.size() - 1); // Must call this to switch to the new tab; and it will fire STATUS_CHANGE message which is important
+ return true;
+ }
+
+ /** Highlights the text editor, based on the location information in the set of Pos objects. */
+ public void shade(Iterable set, Color color, boolean clearOldHighlightsFirst) {
+ if (clearOldHighlightsFirst) clearShade();
+ OurSyntaxWidget text = null;
+ int c = 0, d;
+ for(Pos p: set) if (p!=null && p.filename.length()>0 && p.y>0 && p.x>0 && newtab(p.filename)) {
+ text = get();
+ c = text.getLineStartOffset(p.y-1) + p.x - 1;
+ d = text.getLineStartOffset(p.y2-1) + p.x2 - 1;
+ text.shade(color, c, d+1);
+ }
+ if (text!=null) { text.moveCaret(0, 0); text.moveCaret(c, c); } // Move to 0 ensures we'll scroll to the highlighted section
+ get().requestFocusInWindow();
+ adjustLabelColor();
+ listeners.fire(this, Event.STATUS_CHANGE);
+ }
+
+ /** Highlights the text editor, based on the location information in the Pos object. */
+ public void shade(Pos pos) { shade(Util.asList(pos), new Color(0.9f, 0.4f, 0.4f), true); }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java
new file mode 100644
index 00000000..26f959bb
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java
@@ -0,0 +1,164 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.util.IdentityHashMap;
+import java.util.List;
+import javax.swing.JLabel;
+import javax.swing.JTree;
+import javax.swing.UIManager;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.TreeModelListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import edu.mit.csail.sdg.alloy4.OurUtil;
+
+/** Graphical tree.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public abstract class OurTree extends JTree {
+
+ /** The current list of listeners; whenever a node X is selected we'll send (Event.CLICK, X) to each listener. */
+ public final Listeners listeners = new Listeners();
+
+ /** Custom TreeCellRenderer to print the tree nodes better. (The idea of using JLabel is inspired by DefaultTreeCellRenderer) */
+ private final class OurTreeRenderer extends JLabel implements TreeCellRenderer {
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+ /** This stores the height of one line of text. */
+ private int height;
+ /** If preferredHeight > 0, then preferredWidth is the desired width for the current object being drawn. */
+ private int preferredWidth = 0;
+ /** If preferredHeight > 0, then preferredHeight is the desired height for the current object being drawn. */
+ private int preferredHeight = 0;
+ /** Whether the current object is selected or not. */
+ private boolean isSelected;
+ /** Whether the current object is focused or not. */
+ private boolean isFocused;
+ /** Constructs this renderer. */
+ public OurTreeRenderer(int fontSize) {
+ super("Anything"); // This ensures that the height is calculated properly
+ setFont(OurUtil.getVizFont().deriveFont((float)fontSize));
+ setVerticalAlignment(JLabel.BOTTOM);
+ setBorder(new EmptyBorder(0, 3, 0, 3));
+ setText("Anything"); // So that we can derive the height
+ height = super.getPreferredSize().height;
+ }
+ /** This method is called by Swing to return an object to be drawn. */
+ public Component getTreeCellRendererComponent
+ (JTree tree, Object value, boolean isSelected, boolean expanded, boolean isLeaf, int row, boolean isFocused) {
+ String string = tree.convertValueToText(value, isSelected, expanded, isLeaf, row, isFocused);
+ this.isFocused = isFocused;
+ this.isSelected = isSelected;
+ setText(string);
+ setForeground(UIManager.getColor(isSelected ? "Tree.selectionForeground" : "Tree.textForeground"));
+ preferredHeight = 0; // By default, don't override width/height
+ if (do_isDouble(value)) { Dimension d = super.getPreferredSize(); preferredWidth=d.width+3; preferredHeight=d.height*2; }
+ return this;
+ }
+ /** We override the getPreferredSize() method to return a custom size if do_isDouble() returns true for that value. */
+ @Override public Dimension getPreferredSize() {
+ if (preferredHeight > 0) return new Dimension(preferredWidth, preferredHeight);
+ Dimension d = super.getPreferredSize();
+ return new Dimension(d.width+3, d.height);
+ }
+ /** We override the paint() method to avoid drawing the box around the "extra space" (if height is double height) */
+ @Override public void paint(Graphics g) {
+ int w=getWidth(), h=getHeight(), y=h-height;
+ Color background = isSelected ? UIManager.getColor("Tree.selectionBackground") : Color.WHITE;
+ Color border = isFocused ? UIManager.getColor("Tree.selectionBorderColor") : null;
+ if (background!=null) { g.setColor(background); g.fillRect(0, y, w, h-y); }
+ if (border!=null && isSelected) { g.setColor(border); g.drawRect(0, y, w-1, h-1-y); }
+ super.paint(g);
+ }
+ }
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Subclass should implement this method to return the root of the tree. */
+ public abstract Object do_root();
+
+ /** Subclass should implement this method to return the list of children nodes given a particular node. */
+ public abstract List> do_ask(Object parent);
+
+ /** Subclass should override this method to return whether a given item should be double-height or not (default = no). */
+ protected boolean do_isDouble(Object object) { return false; }
+
+ /** Subclass should call this when all fields are initialized; we won't call do_root() and do_ask() until subclass calls this. */
+ protected final void do_start() {
+ // Create a custom TreeModel that calls do_root() and do_ask() whenever the tree needs expansion
+ setModel(new TreeModel() {
+ // Cache the parent->child list so that we always get the exact same OBJECT REFERENCE when navigating the tree
+ private final IdentityHashMap> map = new IdentityHashMap>();
+ public Object getChild(Object parent, int index) {
+ List> ans = map.get(parent);
+ if (ans==null) { ans = do_ask(parent); map.put(parent, ans); }
+ return (index >= 0 && index < ans.size()) ? ans.get(index) : null;
+ }
+ public int getIndexOfChild(Object parent, Object child) {
+ getChild(parent, 0);
+ List> ans = map.get(parent);
+ for(int i=0; ;i++) if (i==ans.size()) return -1; else if (ans.get(i)==child) return i;
+ }
+ public Object getRoot() { return do_root(); }
+ public int getChildCount(Object node) { getChild(node, 0); return map.get(node).size(); }
+ public boolean isLeaf(Object node) { getChild(node, 0); return map.get(node).isEmpty(); }
+ public void valueForPathChanged(TreePath path, Object newValue) { }
+ public void addTreeModelListener(TreeModelListener l) { }
+ public void removeTreeModelListener(TreeModelListener l) { }
+ });
+ }
+
+ /** This method is called by Swing to figure out what text should be displayed for each node in the tree. */
+ @Override public abstract String convertValueToText(Object v, boolean select, boolean expand, boolean leaf, int i, boolean focus);
+
+ /** Construct a Tree object with the given font size. */
+ public OurTree(int fontSize) {
+ Font font = OurUtil.getVizFont().deriveFont((float)fontSize);
+ OurTreeRenderer renderer = new OurTreeRenderer(fontSize);
+ renderer.setFont(font);
+ renderer.invalidate();
+ renderer.validate();
+ setRowHeight(0); // To allow variable row height on Mac OS X
+ setCellRenderer(renderer);
+ setFont(font);
+ setBorder(new EmptyBorder(8, 8, 2, 2));
+ getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ putClientProperty("JTree.lineStyle", "Angled");
+ setRootVisible(true);
+ setForeground(Color.BLACK);
+ setBackground(Color.WHITE);
+ setOpaque(true);
+ addTreeSelectionListener(new TreeSelectionListener() {
+ public void valueChanged(TreeSelectionEvent e) {
+ TreePath path = OurTree.this.getSelectionPath();
+ if (path!=null) OurTree.this.listeners.fire(OurTree.this, Listener.Event.CLICK, path.getLastPathComponent());
+ }
+ });
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java
new file mode 100644
index 00000000..b41959ac
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java
@@ -0,0 +1,327 @@
+/* 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.alloy4;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.net.URL;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.Icon;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JSplitPane;
+import javax.swing.KeyStroke;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
+
+/** Graphical convenience methods.
+ *
+ * Thread Safety: Can be called only by the AWT event thread.
+ */
+
+public final class OurUtil {
+
+ /** This constructor is private, since this utility class never needs to be instantiated. */
+ private OurUtil() { }
+
+ /** Assign the given attributes to the given JComponent, then return the JComponent again.
+ *
If Font x is given in the list, we call obj.setFont(x)
+ *
If String x is given in the list, we call obj.setToolTipText(x)
+ *
If Border x is given in the list, we call obj.setBorder(x)
+ *
If Dimension x is given in the list, we call obj.setPreferredSize(x)
+ *
If Color x is given in the list, and it's the first color, we call obj.setForeground(x)
+ *
If Color x is given in the list, and it's not the first color, we call obj.setBackground(x) then obj.setOpaque(true)
+ *
(If no Font is given, then after all these changes have been applied, we will call obj.setFont() will a default font)
+ */
+ public static X make(X obj, Object... attributes) {
+ boolean hasFont = false, hasForeground = false;
+ if (attributes!=null) for(Object x: attributes) {
+ if (x instanceof Font) { obj.setFont((Font)x); hasFont=true; }
+ if (x instanceof String) { obj.setToolTipText((String)x); }
+ if (x instanceof Border) { obj.setBorder((Border)x); }
+ if (x instanceof Dimension) { obj.setPreferredSize((Dimension)x); }
+ if (x instanceof Color && !hasForeground) { obj.setForeground((Color)x); hasForeground=true; continue; }
+ if (x instanceof Color) { obj.setBackground((Color)x); obj.setOpaque(true); }
+ }
+ if (!hasFont) obj.setFont(getVizFont());
+ return obj;
+ }
+
+ /** Make a JLabel, then call Util.make() to apply a set of attributes to it.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JLabel label (String label, Object... attributes) { return make(new JLabel(label), attributes); }
+
+ /** Make a JTextField with the given text and number of columns, then call Util.make() to apply a set of attributes to it.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JTextField textfield (String text, int columns, Object... attributes) {
+ return make(new JTextField(text, columns), attributes);
+ }
+
+ /** Make a JTextArea with the given text and number of rows and columns, then call Util.make() to apply a set of attributes to it.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JTextArea textarea (String text, int rows, int columns, boolean editable, boolean wrap, Object... attributes) {
+ JTextArea ans = make(new JTextArea(text, rows, columns), Color.BLACK, Color.WHITE, new EmptyBorder(0,0,0,0));
+ ans.setEditable(editable);
+ ans.setLineWrap(wrap);
+ ans.setWrapStyleWord(wrap);
+ return make(ans, attributes);
+ }
+
+ /** Make a JScrollPane containing the given component (which can be null), then apply a set of attributes to it.
+ * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)}
+ */
+ public static JScrollPane scrollpane (Component component, Object... attributes) {
+ JScrollPane ans = make(new JScrollPane(), new EmptyBorder(0,0,0,0));
+ if (component!=null) ans.setViewportView(component);
+ ans.setMinimumSize(new Dimension(50, 50));
+ return make(ans, attributes);
+ }
+
+ /** Returns the recommended font to use in the visualizer, based on the OS. */
+ public static Font getVizFont() {
+ return Util.onMac() ? new Font("Lucida Grande", Font.PLAIN, 11) : new Font("Dialog", Font.PLAIN, 12);
+ }
+
+ /** Returns the screen width (in pixels). */
+ public static int getScreenWidth() { return Toolkit.getDefaultToolkit().getScreenSize().width; }
+
+ /** Returns the screen height (in pixels). */
+ public static int getScreenHeight() { return Toolkit.getDefaultToolkit().getScreenSize().height; }
+
+ /** Make a graphical button
+ * @param label - the text to show beneath the button
+ * @param tip - the tooltip to show when the mouse hovers over the button
+ * @param iconFileName - if nonnull, it's the filename of the icon to show (it will be loaded from an accompanying jar file)
+ * @param func - if nonnull, it's the function to call when the button is pressed
+ */
+ public static JButton button (String label, String tip, String iconFileName, ActionListener func) {
+ JButton button = new JButton(label, (iconFileName!=null && iconFileName.length()>0) ? loadIcon(iconFileName) : null);
+ if (func != null) button.addActionListener(func);
+ button.setVerticalTextPosition(JButton.BOTTOM);
+ button.setHorizontalTextPosition(JButton.CENTER);
+ button.setBorderPainted(false);
+ button.setFocusable(false);
+ if (!Util.onMac()) button.setBackground(new Color(0.9f, 0.9f, 0.9f));
+ button.setFont(button.getFont().deriveFont(10.0f));
+ if (tip != null && tip.length() > 0) button.setToolTipText(tip);
+ return button;
+ }
+
+ /** Load the given image file from an accompanying JAR file, and return it as an Icon object. */
+ public static Icon loadIcon(String pathname) {
+ URL url = OurUtil.class.getClassLoader().getResource(pathname);
+ if (url!=null) return new ImageIcon(Toolkit.getDefaultToolkit().createImage(url));
+ return new ImageIcon(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB));
+ }
+
+ /** Make a JPanel with horizontal or vertical BoxLayout, then add the list of components to it (each aligned by xAlign and yAlign)
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ private static JPanel makeBox(boolean horizontal, float xAlign, float yAlign, Object[] components) {
+ JPanel ans = new JPanel();
+ ans.setLayout(new BoxLayout(ans, horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS));
+ ans.setAlignmentX(0.0f);
+ ans.setAlignmentY(0.0f);
+ Color color = null;
+ for(Object x: components) {
+ Component c = null;
+ if (x instanceof Color) { color = (Color)x; ans.setBackground(color); continue; }
+ if (x instanceof Dimension) { ans.setPreferredSize((Dimension)x); ans.setMaximumSize((Dimension)x); continue; }
+ if (x instanceof Component) { c = (Component)x; }
+ if (x instanceof String) { c = label((String)x, Color.BLACK); }
+ if (x instanceof Integer) { int i = (Integer)x; c = Box.createRigidArea(new Dimension(horizontal?i:1, horizontal?1:i)); }
+ if (x==null) { c = horizontal ? Box.createHorizontalGlue() : Box.createVerticalGlue(); }
+ if (c==null) continue;
+ if (color!=null) c.setBackground(color);
+ if (c instanceof JComponent) { ((JComponent)c).setAlignmentX(xAlign); ((JComponent)c).setAlignmentY(yAlign); }
+ ans.add(c);
+ }
+ return ans;
+ }
+
+ /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be center-aligned).
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ public static JPanel makeH(Object... components) { return makeBox(true, 0.5f, 0.5f, components); }
+
+ /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be top-aligned).
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ public static JPanel makeHT(Object... components) { return makeBox(true, 0.5f, 0.0f, components); }
+
+ /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be bottom-aligned).
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ public static JPanel makeHB(Object... components) { return makeBox(true, 0.5f, 1.0f, components); }
+
+ /** Make a JPanel using vertical BoxLayout, and add the components to it (each component will be left-aligned).
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ public static JPanel makeVL(Object... components) { return makeBox(false, 0.0f, 0.5f, components); }
+
+ /** Make a JPanel using vertical BoxLayout, and add the components to it (each component will be right-aligned).
+ * If a component is Color, it's the background of this JPanel and every component after it (until we see another Color)
+ * If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize.
+ * If a component is String, we will insert a JLabel with it as the label.
+ * If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead.
+ * If a component is null, we will insert a horizontal (or vertical) glue instead.
+ */
+ public static JPanel makeVR(Object... components) { return makeBox(false, 1.0f, 0.5f, components); }
+
+ /** Constructs a new SplitPane containing the two components given as arguments
+ * @param orientation - the orientation (HORIZONTAL_SPLIT or VERTICAL_SPLIT)
+ * @param first - the left component (if horizontal) or top component (if vertical)
+ * @param second - the right component (if horizontal) or bottom component (if vertical)
+ * @param initialDividerLocation - the initial divider location (in pixels)
+ */
+ public static JSplitPane splitpane (int orientation, Component first, Component second, int initialDividerLocation) {
+ JSplitPane x = make(new JSplitPane(orientation, first, second), new EmptyBorder(0,0,0,0));
+ x.setContinuousLayout(true);
+ x.setDividerLocation(initialDividerLocation);
+ x.setOneTouchExpandable(false);
+ x.setResizeWeight(0.5);
+ if (Util.onMac() && (x.getUI() instanceof BasicSplitPaneUI)) {
+ boolean h = (orientation != JSplitPane.HORIZONTAL_SPLIT);
+ ((BasicSplitPaneUI)(x.getUI())).getDivider().setBorder(new OurBorder(h,h,h,h)); // Makes the border look nicer on Mac OS X
+ }
+ return x;
+ }
+
+ /** Convenience method that recursively enables every JMenu and JMenuItem inside "menu".
+ * @param menu - the menu to start the recursive search
+ */
+ public static void enableAll (JMenu menu) {
+ for(int i = 0; i < menu.getMenuComponentCount(); i++) {
+ Component x = menu.getMenuComponent(i);
+ if (x instanceof JMenuItem) ((JMenuItem)x).setEnabled(true); else if (x instanceof JMenu) enableAll((JMenu)x);
+ }
+ }
+
+ /** Construct a new JMenu and add it to an existing JMenuBar.
+ * Note: every time the user expands then collapses this JMenu, we automatically enable all JMenu and JMenuItem objects in it.
+ *
+ * @param parent - the JMenuBar to add this Menu into (or null if we don't want to add it to a JMenuBar yet)
+ * @param label - the label to show on screen (if it contains '&' followed by 'a'..'z', we'll remove '&' and use it as mnemonic)
+ * @param func - if nonnull we'll call its "run()" method right before expanding this menu
+ */
+ public static JMenu menu (JMenuBar parent, String label, final Runnable func) {
+ final JMenu x = new JMenu(label.replace("&", ""), false);
+ if (!Util.onMac()) {
+ int i = label.indexOf('&');
+ if (i>=0 && i+1='a' && i<='z') x.setMnemonic((i-'a')+'A'); else if (i>='A' && i<='Z') x.setMnemonic(i);
+ }
+ x.addMenuListener(new MenuListener() {
+ public void menuSelected (MenuEvent e) { if (func != null) func.run(); }
+ public void menuDeselected (MenuEvent e) { OurUtil.enableAll(x); }
+ public void menuCanceled (MenuEvent e) { OurUtil.enableAll(x); }
+ });
+ if (parent!=null) parent.add(x);
+ return x;
+ }
+
+ /** Construct a new JMenuItem then add it to an existing JMenu.
+ * @param parent - the JMenu to add this JMenuItem into (or null if you don't want to add it to any JMenu yet)
+ * @param label - the text to show on the menu
+ * @param attrs - a list of attributes to apply onto the new JMenuItem
+ * If one positive number a is supplied, we call setMnemonic(a)
+ *
If two positive numbers a and b are supplied, and a!=VK_ALT, and a!=VK_SHIFT, we call setMnemoic(a) and setAccelerator(b)
+ *
If two positive numbers a and b are supplied, and a==VK_ALT or a==VK_SHIFT, we call setAccelerator(a | b)
+ *
If an ActionListener is supplied, we call addActionListener(x)
+ *
If an Boolean x is supplied, we call setEnabled(x)
+ *
If an Icon x is supplied, we call setIcon(x)
+ */
+ public static JMenuItem menuItem (JMenu parent, String label, Object... attrs) {
+ JMenuItem m = new JMenuItem(label, null);
+ int accelMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
+ boolean hasMnemonic = false;
+ for(Object x: attrs) {
+ if (x instanceof Character || x instanceof Integer) {
+ int k = (x instanceof Character) ? ((int)((Character)x)) : ((Integer)x).intValue();
+ if (k < 0) continue;
+ if (k==KeyEvent.VK_ALT) { hasMnemonic = true; accelMask = accelMask | InputEvent.ALT_MASK; continue; }
+ if (k==KeyEvent.VK_SHIFT) { hasMnemonic = true; accelMask = accelMask | InputEvent.SHIFT_MASK; continue; }
+ if (!hasMnemonic) { m.setMnemonic(k); hasMnemonic=true; } else m.setAccelerator(KeyStroke.getKeyStroke(k, accelMask));
+ }
+ if (x instanceof ActionListener) m.addActionListener((ActionListener)x);
+ if (x instanceof Icon) m.setIcon((Icon)x);
+ if (x instanceof Boolean) m.setEnabled((Boolean)x);
+ }
+ if (parent!=null) parent.add(m);
+ return m;
+ }
+
+ /** This method minimizes the window. */
+ public static void minimize(JFrame frame) { frame.setExtendedState(JFrame.ICONIFIED); }
+
+ /** This method alternatingly maximizes or restores the window. */
+ public static void zoom(JFrame frame) {
+ int both = JFrame.MAXIMIZED_BOTH;
+ frame.setExtendedState((frame.getExtendedState() & both)!=both ? both : JFrame.NORMAL);
+ }
+
+ /** Make the frame visible, non-iconized, and focused. */
+ public static void show(JFrame frame) {
+ frame.setVisible(true);
+ frame.setExtendedState(frame.getExtendedState() & ~JFrame.ICONIFIED);
+ frame.requestFocus();
+ frame.toFront();
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java
new file mode 100644
index 00000000..d243eb5d
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java
@@ -0,0 +1,67 @@
+/* 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.alloy4;
+
+import java.io.Serializable;
+
+/** Immutable; stores a pair of object references; Pair.equals() compares by calling equals() on both components.
+ *
+ *
Thread Safety: Safe (since objects of this class are immutable).
+ */
+
+public final class Pair implements Serializable {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The first half of the pair. */
+ public final A a;
+
+ /** The second half of the pair. */
+ public final B b;
+
+ /** Constructs a new Pair object (a,b). */
+ public Pair(A a, B b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ /** If "a" and "b" are both String, concatename them with a space; if only one is, return it; else call toString() on them. */
+ @Override public String toString() {
+ if (a instanceof String) { if (b instanceof String) return a+" "+b; else return (String)a; }
+ if (b instanceof String) return (String)b;
+ if (a==null) {
+ return (b!=null) ? b.toString() : "";
+ } else {
+ return (b!=null) ? (a+" "+b) : a.toString();
+ }
+ }
+
+ /** Returns a hashcode based on (a==null?0:a.hashCode()) and (b==null?0:b.hashCode()). */
+ @Override public int hashCode() {
+ int i = (a==null) ? 0 : a.hashCode();
+ int j = (b==null) ? 0 : b.hashCode();
+ return i*173123 + j;
+ }
+
+ /** Pairs (a1, b1) and (a2, b2) are equal iff (a1==null ? a2==null : a1.equals(a2)) and (b1==null ? b2==null : b1.equals(b2)) */
+ @Override public boolean equals(Object that) {
+ if (this==that) return true;
+ if (!(that instanceof Pair)) return false;
+ Pair,?> p = (Pair,?>)that;
+ return (a==null ? p.a==null : a.equals(p.a)) && (b==null ? p.b==null : b.equals(p.b));
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java
new file mode 100644
index 00000000..1e83c76d
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java
@@ -0,0 +1,138 @@
+/* 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.alloy4;
+
+import java.io.Serializable;
+
+/** Immutable; stores the filename and line/column position.
+ *
+ * Invariant: filename!=null && x>0 && y>0 && ((y2>y && x2>0) || (y2==y && x2>=x))
+ *
+ *
Thread Safety: Safe (since objects of this class are immutable).
+ */
+
+public final class Pos implements Serializable {
+
+ /** To make sure the serialization form is stable. */
+ private static final long serialVersionUID = 0;
+
+ /** The filename (it can be an empty string if unknown) */
+ public final String filename;
+
+ /** The starting column position (from 1..) */
+ public final int x;
+
+ /** The starting row position (from 1..) */
+ public final int y;
+
+ /** The ending column position (from 1..) */
+ public final int x2;
+
+ /** The ending row position (from 1..) */
+ public final int y2;
+
+ /** The default "unknown" location. */
+ public static final Pos UNKNOWN = new Pos("",1,1);
+
+ /** Constructs a new Pos object.
+ * @param filename - the filename (it can be an empty string if unknown)
+ * @param x - the column position (from 1..)
+ * @param y - the row position (from 1..)
+ */
+ public Pos(String filename, int x, int y) {
+ this.filename = (filename==null ? "" : filename);
+ this.x = (x>0 ? x : 1);
+ this.y = (y>0 ? y : 1);
+ this.x2 = this.x;
+ this.y2 = this.y;
+ }
+
+ /** Constructs a new Pos object.
+ * @param filename - the filename (it can be an empty string if unknown)
+ * @param x - the starting column position (from 1..)
+ * @param y - the starting row position (from 1..)
+ * @param x2 - the ending column position (from 1..)
+ * @param y2 - the ending row position (from 1..)
+ */
+ public Pos(String filename, int x, int y, int x2, int y2) {
+ this.filename = (filename==null ? "" : filename);
+ this.x = (x>0 ? x : 1);
+ this.y = (y>0 ? y : 1);
+ if (y2<(this.y)) y2=this.y;
+ if (y2==this.y) {
+ if (x2<(this.x)) x2=this.x;
+ } else {
+ if (x2<1) x2=1;
+ }
+ this.x2 = x2;
+ this.y2 = y2;
+ }
+
+ /** Return a new position that merges this and that (it is assumed that the two Pos objects have same filename)
+ * @param that - the other position object
+ */
+ public Pos merge(Pos that) {
+ if (that==null || that==UNKNOWN || that==this) return this;
+ if (this==UNKNOWN) return that;
+ int x=this.x, y=this.y, x2=that.x2, y2=that.y2;
+ if (that.yy2 || (this.y2==y2 && this.x2>x2)) {
+ x2=this.x2;
+ y2=this.y2;
+ }
+ if (x==this.x && y==this.y && x2==this.x2 && y2==this.y2) return this; // avoid creating unnecessary new object
+ if (x==that.x && y==that.y && x2==that.x2 && y2==that.y2) return that; // avoid creating unnecessary new object
+ return new Pos(filename, x, y, x2, y2);
+ }
+
+ /** Returns true if neither argument is null nor UNKNOWN,
+ * and that the ending position of "a" is before the starting position of "b".
+ */
+ public static boolean before(Pos a, Pos b) {
+ if (a==null || a==Pos.UNKNOWN || b==null || b==Pos.UNKNOWN || !a.filename.equals(b.filename)) return false;
+ return a.y2=0) f=f.substring(a+1);
+ if (f.length()==0) return "line "+y+", column "+x; else return "line "+y+", column "+x+", filename="+f;
+ }
+
+ /** Returns a String representation of this position value. */
+ @Override public String toString() {
+ if (filename.length()==0) return "line "+y+", column "+x; else return "line "+y+", column "+x+", filename="+filename;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java
new file mode 100644
index 00000000..eed69b79
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java
@@ -0,0 +1,96 @@
+/* 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.alloy4;
+
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.WindowListener;
+import java.awt.event.WindowEvent;
+import javax.swing.AbstractAction;
+import javax.swing.event.CaretEvent;
+import javax.swing.event.CaretListener;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+/** This class converts a Runnable into an AbstractAction, WindowListener, CaretListener, and MenuListener also. */
+
+public abstract class Runner extends AbstractAction implements Runnable, WindowListener, MenuListener, CaretListener, FocusListener {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** Constructs a new runner; you should override the run() and run(arg) method to customize it. */
+ public Runner() { }
+
+ /** This method should be overriden to provide the default action that this Runner would perform. */
+ public abstract void run();
+
+ /** This method should be overriden to provide the default action that this Runner would perform given an argument. */
+ public abstract void run(Object arg);
+
+ /** This method is defined in java.awt.event.ActionListener; (this implementation calls this.run()) */
+ public final void actionPerformed(ActionEvent e) { run(); }
+
+ /** This method is defined in javax.swing.event.MenuListener; (this implementation calls this.run()) */
+ public final void menuSelected(MenuEvent e) { run(); }
+
+ /** This method is defined in javax.swing.event.MenuListener; (this implementation does nothing) */
+ public final void menuDeselected(MenuEvent e) { }
+
+ /** This method is defined in javax.swing.event.MenuListener; (this implementation does nothing) */
+ public final void menuCanceled(MenuEvent e) { }
+
+ /** This method is defined in java.awt.event.CaretListener; (this implementation calls this.run()) */
+ public final void caretUpdate(CaretEvent e) { run(); }
+
+ /** This method is defined in java.awt.event.FocusListener; (this implementation calls this.run()) */
+ public final void focusGained(FocusEvent e) { run(); }
+
+ /** This method is defined in java.awt.event.FocusListener; (this implementation does nothing) */
+ public final void focusLost(FocusEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation calls this.run()) */
+ public final void windowClosing(WindowEvent e) { run(); }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowClosed(WindowEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowOpened(WindowEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowIconified(WindowEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowDeiconified(WindowEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowActivated(WindowEvent e) { }
+
+ /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */
+ public final void windowDeactivated(WindowEvent e) { }
+
+ /** This helper method returns a Runnable whose run() method will call window.dispose() */
+ public static final Runner createDispose(final Window window) {
+ return new Runner() {
+ private static final long serialVersionUID = 0;
+ public final void run() { window.dispose(); }
+ public final void run(Object arg) { window.dispose(); }
+ };
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java
new file mode 100644
index 00000000..1584e1ff
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java
@@ -0,0 +1,221 @@
+/* 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.alloy4;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import edu.mit.csail.sdg.alloy4.ConstList.TempList;
+
+/** This list allows add() but disallows remove() and set(); null values are allowed.
+ *
+ *
+ * By making this sacrifice, we are able to provide a very cheap "duplicate method"
+ * that simulates making a copy without actually making a copy.
+ *
+ *
+ * Furthermore, this class's iterator allows concurrent insertion and iteration
+ * (that is, we can iterate over the list while adding elements to the list at the same time).
+ * The iterator is guaranteed to iterate over exactly the elements
+ * that existed at the time that the iterator was created.
+ *
+ *
Thread Safety: Safe.
+ *
+ * @param - the type of element
+ */
+
+public final class SafeList implements Serializable, Iterable {
+
+ /** This ensures the class can be serialized reliably. */
+ private static final long serialVersionUID = 0;
+
+ /** The actual list of elements; it will be shared by an original SafeList and all its unmodifiable copies. */
+ private final List list;
+
+ /** If negative, that means this instance is mutable; otherwise, it is the list size at the time of the copy. */
+ private final int max;
+
+ /** Constructs a modifiable empty list. */
+ public SafeList() {
+ list = new ArrayList();
+ max = (-1);
+ }
+
+ /** Constructs a modifiable empty list with the initial capacity. */
+ public SafeList(int initialCapacity) {
+ list = new ArrayList(initialCapacity);
+ max = (-1);
+ }
+
+ /** Constructs a modifiable list containing the elements from the given collection. */
+ public SafeList(Collection extends T> initialValue) {
+ list = new ArrayList(initialValue);
+ max = (-1);
+ }
+
+ /** Constructs a modifiable list containing the elements from the given iterable. */
+ public SafeList(Iterable extends T> initialValue) {
+ list = new ArrayList();
+ max = (-1);
+ for(T obj: initialValue) list.add(obj);
+ }
+
+ /** Private constructor for assigning exact values to "list" and "max". */
+ private SafeList(List list, int max) {
+ this.list = list;
+ this.max = max;
+ }
+
+ /** Constructs an unmodifiable copy of an existing SafeList. */
+ public SafeList dup() {
+ synchronized(SafeList.class) { return new SafeList(list, size()); }
+ }
+
+ /** Constructs a modifiable ArrayList containing the same elements as this list. */
+ public List makeCopy() {
+ synchronized(SafeList.class) {
+ int n = size();
+ ArrayList ans = new ArrayList(n);
+ for(int i=0; i makeConstList() {
+ synchronized(SafeList.class) {
+ int n = size();
+ TempList ans = new TempList(n);
+ for(int i=0; i b;
+ if (that instanceof List) { n=((List)that).size(); if (n!=size()) return false; b=((List)that).iterator(); }
+ else if (that instanceof SafeList) { n=((SafeList)that).size(); if (n!=size()) return false; b=((SafeList)that).iterator(); }
+ else return false;
+ Iterator> a=iterator();
+ for(int i=0; i=0) throw new UnsupportedOperationException(); else return list.add(item);
+ }
+ }
+
+ /** Add a collection of elements into the list. */
+ public void addAll(Collection extends T> items) {
+ synchronized(SafeList.class) {
+ if (max>=0) throw new UnsupportedOperationException();
+ list.addAll(items);
+ }
+ }
+
+ /** Get an element from the list. */
+ public T get(int i) {
+ synchronized(SafeList.class) {
+ if (max>=0 && i>=max) throw new IndexOutOfBoundsException(); else return list.get(i);
+ }
+ }
+
+ /** Returns the size of the list. */
+ public int size() {
+ synchronized(SafeList.class) {
+ if (max>=0) return max; else return list.size();
+ }
+ }
+
+ /** Returns true if the list is empty. */
+ public boolean isEmpty() {
+ return size()==0;
+ }
+
+ /** Returns an iterator that iterates over elements in this list
+ * (in the order that they were inserted).
+ *
+ * Note: This iterator's remove() method always throws UnsupportedOperationException.
+ *
+ *
Note: This iterator always returns exactly the list of elements that existed
+ * at the time that the iterator was created (even if the list is modified after that point).
+ */
+ public Iterator iterator() {
+ synchronized(SafeList.class) {
+ return new Iterator() {
+ private final int imax = (max>=0 ? max : list.size());
+ private int now = 0;
+ public final T next() {
+ if (now >= imax) throw new NoSuchElementException();
+ synchronized(SafeList.class) {
+ T answer = list.get(now);
+ now++;
+ return answer;
+ }
+ }
+ public final boolean hasNext() { return now < imax; }
+ public final void remove() { throw new UnsupportedOperationException(); }
+ };
+ }
+ }
+
+ /** Returns a String representation of this list. */
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder("[");
+ boolean first = true;
+ for(Object x: this) {
+ if (first) first=false; else sb.append(", ");
+ if (x==this) sb.append("(this collection)"); else sb.append(x);
+ }
+ return sb.append(']').toString();
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java
new file mode 100644
index 00000000..f0b600e0
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java
@@ -0,0 +1,94 @@
+/* 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.alloy4;
+
+import java.io.InputStream;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/** This provides a convenience wrapper around a Process object.
+ *
+ * To launch a subprocess, simply write Subprocess.exec(timeout, args);
+ *
+ *
Thread Safety: Safe.
+ */
+
+public final class Subprocess {
+
+ /** Timer used to schedule a timeout for the process. */
+ private static final Timer stopper = new Timer();
+
+ /** This field will store the program output (or "" if an error occurred) */
+ private String stdout = null;
+
+ /** This field will store an error message (or "" if no error occurred) */
+ private String stderr = null;
+
+ /** Constructor is private since we only allow the static method exec() to construct objects of this class. */
+ private Subprocess() { }
+
+ /** Executes the given command line, wait for its completion, then return its output.
+ * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed
+ * @param p - preconstructed Process object
+ */
+ private static String exec (final long timeLimit, final Process p) throws Exception {
+ final Subprocess pro = new Subprocess();
+ final InputStream f1 = p.getInputStream(), f2 = p.getErrorStream();
+ TimerTask stoptask = new TimerTask() {
+ public void run() {
+ synchronized(pro) { if (pro.stdout!=null && pro.stderr!=null) return; pro.stdout=""; pro.stderr="Error: timeout"; }
+ p.destroy();
+ }
+ };
+ synchronized(Subprocess.class) { stopper.schedule(stoptask, timeLimit); }
+ new Thread(new Runnable() {
+ public void run() {
+ String err = null;
+ try { if (f2.read()>=0) err="Error: stderr"; } catch(Throwable ex) { err="Error: "+ex; }
+ synchronized(pro) { if (err!=null) {pro.stdout=""; pro.stderr=err;} else if (pro.stderr==null) pro.stderr=""; }
+ }
+ }).start();
+ p.getOutputStream().close();
+ StringBuilder output = new StringBuilder();
+ byte[] buf = new byte[8192];
+ while(true) {
+ int n = f1.read(buf);
+ if (n<0) break; else for(int i=0; iThread Safety: Safe.
+ */
+
+public final class UniqueNameGenerator {
+
+ /** This stores the set of names we've generated so far. */
+ private final Set names = new HashSet();
+
+ /** Construct a UniqueNameGenerator with a blank history. */
+ public UniqueNameGenerator() { }
+
+ /** Regard the provided name as "seen".
+ * For convenience, it returns the argument as the return value.
+ */
+ public synchronized String seen(String name) { names.add(name); return name; }
+
+ /** Queries whether the provided name has been "seen" or not. */
+ public synchronized boolean hasSeen(String name) { return names.contains(name); }
+
+ /** Clear the history of previously generated names. */
+ public synchronized void clear() { names.clear(); }
+
+ /** Generate a unique name based on the input name.
+ *
+ *
Specifically: if the name has not been generated/seen already by this generator,
+ * then it is returned as is. Otherwise, we append ' to it until the name becomes unique.
+ */
+ public synchronized String make(String name) {
+ while(!names.add(name)) name=name+"'";
+ return name;
+ }
+}
diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java
new file mode 100644
index 00000000..c621bdcd
--- /dev/null
+++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java
@@ -0,0 +1,601 @@
+/* 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.alloy4;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.prefs.Preferences;
+import edu.mit.csail.sdg.alloy4.ConstList.TempList;
+
+/** This provides useful static methods for I/O and XML operations.
+ *
+ *