/* 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.alloy4whole; import static edu.mit.csail.sdg.alloy4.OurUtil.menu; import static edu.mit.csail.sdg.alloy4.OurUtil.menuItem; import static java.awt.event.KeyEvent.VK_A; import static java.awt.event.KeyEvent.VK_ALT; import static java.awt.event.KeyEvent.VK_E; import static java.awt.event.KeyEvent.VK_PAGE_DOWN; import static java.awt.event.KeyEvent.VK_PAGE_UP; import static java.awt.event.KeyEvent.VK_SHIFT; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Scanner; import java.util.Set; import java.util.prefs.Preferences; import javax.swing.Box; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JEditorPane; 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.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.html.HTMLDocument; import kodkod.engine.fol2sat.HigherOrderDeclException; import edu.mit.csail.sdg.alloy4.A4Reporter; import edu.mit.csail.sdg.alloy4.Computer; import edu.mit.csail.sdg.alloy4.Err; import edu.mit.csail.sdg.alloy4.ErrorFatal; import edu.mit.csail.sdg.alloy4.ErrorType; import edu.mit.csail.sdg.alloy4.Listener; import edu.mit.csail.sdg.alloy4.MacUtil; import edu.mit.csail.sdg.alloy4.MailBug; import edu.mit.csail.sdg.alloy4.OurAntiAlias; import edu.mit.csail.sdg.alloy4.OurBorder; import edu.mit.csail.sdg.alloy4.OurCombobox; import edu.mit.csail.sdg.alloy4.OurDialog; import edu.mit.csail.sdg.alloy4.OurSyntaxWidget; import edu.mit.csail.sdg.alloy4.OurTabbedSyntaxWidget; import edu.mit.csail.sdg.alloy4.OurTree; import edu.mit.csail.sdg.alloy4.OurUtil; import edu.mit.csail.sdg.alloy4.Pair; import edu.mit.csail.sdg.alloy4.Pos; import edu.mit.csail.sdg.alloy4.Runner; import edu.mit.csail.sdg.alloy4.Subprocess; import edu.mit.csail.sdg.alloy4.Util; import edu.mit.csail.sdg.alloy4.Util.BooleanPref; import edu.mit.csail.sdg.alloy4.Util.IntPref; import edu.mit.csail.sdg.alloy4.Util.StringPref; import edu.mit.csail.sdg.alloy4.Version; import edu.mit.csail.sdg.alloy4.WorkerEngine; import edu.mit.csail.sdg.alloy4.WorkerEngine.WorkerCallback; import edu.mit.csail.sdg.alloy4.XMLNode; import edu.mit.csail.sdg.alloy4compiler.ast.Browsable; import edu.mit.csail.sdg.alloy4compiler.ast.Command; import edu.mit.csail.sdg.alloy4compiler.ast.Expr; import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; import edu.mit.csail.sdg.alloy4compiler.ast.Module; import edu.mit.csail.sdg.alloy4compiler.ast.Sig; import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; import edu.mit.csail.sdg.alloy4compiler.sim.SimInstance; import edu.mit.csail.sdg.alloy4compiler.sim.SimTuple; import edu.mit.csail.sdg.alloy4compiler.sim.SimTupleset; import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; import edu.mit.csail.sdg.alloy4compiler.translator.A4Options.SatSolver; import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; import edu.mit.csail.sdg.alloy4viz.VizGUI; import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleCallback1; import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleTask1; import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleTask2; /** Simple graphical interface for accessing various features of the analyzer. * * <p> Except noted below, methods in this class can only be called by the AWT event thread. * * <p> The methods that might get called from other threads are: * <br> (1) the run() method in SatRunner is launched from a fresh thread * <br> (2) the run() method in the instance watcher (in constructor) is launched from a fresh thread */ public final class SimpleGUI implements ComponentListener, Listener { /** The latest welcome screen; each time we update the welcome screen, we increment this number. */ private static final int welcomeLevel = 2; // Verify that the graphics environment is set up static { try { GraphicsEnvironment.getLocalGraphicsEnvironment(); } catch(Throwable ex) { System.err.println("Unable to start the graphical environment."); System.err.println("If you're on Mac OS X:"); System.err.println(" Please make sure you are running as the current local user."); System.err.println("If you're on Linux or FreeBSD:"); System.err.println(" Please make sure your X Windows is configured."); System.err.println(" You can verify this by typing \"xhost\"; it should not give an error message."); System.err.flush(); System.exit(1); } } //======== The Preferences ======================================================================================// //======== Note: you must make sure each preference has a unique key ============================================// /** The list of allowable memory sizes. */ private List<Integer> allowedMemorySizes; /** True if Alloy Analyzer should let warning be nonfatal. */ private static final BooleanPref WarningNonfatal = new BooleanPref("WarningNonfatal"); /** True if Alloy Analyzer should automatically visualize the latest instance. */ private static final BooleanPref AutoVisualize = new BooleanPref("AutoVisualize"); /** True if Alloy Analyzer should insist on antialias. */ private static final BooleanPref AntiAlias = new BooleanPref("AntiAlias"); /** True if Alloy Analyzer should record the raw Kodkod input and output. */ private static final BooleanPref RecordKodkod = new BooleanPref("RecordKodkod"); /** True if Alloy Analyzer should enable the new Implicit This name resolution. */ private static final BooleanPref ImplicitThis = new BooleanPref("ImplicitThis"); /** True if Alloy Analyzer should not report models that overflow. */ private static final BooleanPref NoOverflow = new BooleanPref("NoOverflow"); /** The latest X corrdinate of the Alloy Analyzer's main window. */ private static final IntPref AnalyzerX = new IntPref("AnalyzerX",0,-1,65535); /** The latest Y corrdinate of the Alloy Analyzer's main window. */ private static final IntPref AnalyzerY = new IntPref("AnalyzerY",0,-1,65535); /** The latest width of the Alloy Analyzer's main window. */ private static final IntPref AnalyzerWidth = new IntPref("AnalyzerWidth",0,-1,65535); /** The latest height of the Alloy Analyzer's main window. */ private static final IntPref AnalyzerHeight = new IntPref("AnalyzerHeight",0,-1,65535); /** The latest font size of the Alloy Analyzer. */ private static final IntPref FontSize = new IntPref("FontSize",9,12,72); /** The latest font name of the Alloy Analyzer. */ private static final StringPref FontName = new StringPref("FontName","Lucida Grande"); /** The latest tab distance of the Alloy Analyzer. */ private static final IntPref TabSize = new IntPref("TabSize",1,2,16); /** The latest welcome screen that the user has seen. */ private static final IntPref Welcome = new IntPref("Welcome",0,0,1000); /** Whether syntax highlighting should be disabled or not. */ private static final BooleanPref SyntaxDisabled = new BooleanPref("SyntaxHighlightingDisabled"); /** The number of recursion unrolls. */ private static final IntPref Unrolls = new IntPref("Unrolls", -1, -1, 3); /** The skolem depth. */ private static final IntPref SkolemDepth = new IntPref("SkolemDepth3", 0, 1, 4); /** The unsat core minimization strategy. */ private static final IntPref CoreMinimization = new IntPref("CoreMinimization",0,2,2); /** The unsat core granularity. */ private static final IntPref CoreGranularity = new IntPref("CoreGranularity",0,0,3); /** The amount of memory (in M) to allocate for Kodkod and the SAT solvers. */ private static final IntPref SubMemory = new IntPref("SubMemory",16,768,65535); /** The amount of stack (in K) to allocate for Kodkod and the SAT solvers. */ private static final IntPref SubStack = new IntPref("SubStack",16,8192,65536); /** The first file in Alloy Analyzer's "open recent" list. */ private static final StringPref Model0 = new StringPref("Model0"); /** The second file in Alloy Analyzer's "open recent" list. */ private static final StringPref Model1 = new StringPref("Model1"); /** The third file in Alloy Analyzer's "open recent" list. */ private static final StringPref Model2 = new StringPref("Model2"); /** The fourth file in Alloy Analyzer's "open recent" list. */ private static final StringPref Model3 = new StringPref("Model3"); /** This enum defines the set of possible message verbosity levels. */ private enum Verbosity { /** Level 0. */ DEFAULT("0", "low"), /** Level 1. */ VERBOSE("1", "medium"), /** Level 2. */ DEBUG("2", "high"), /** Level 3. */ FULLDEBUG("3", "debug only"); /** Returns true if it is greater than or equal to "other". */ public boolean geq(Verbosity other) { return ordinal() >= other.ordinal(); } /** This is a unique String for this value; it should be kept consistent in future versions. */ private final String id; /** This is the label that the toString() method will return. */ private final String label; /** Constructs a new Verbosity value with the given id and label. */ private Verbosity(String id, String label) { this.id=id; this.label=label; } /** Given an id, return the enum value corresponding to it (if there's no match, then return DEFAULT). */ private static Verbosity parse(String id) { for(Verbosity vb: values()) if (vb.id.equals(id)) return vb; return DEFAULT; } /** Returns the human-readable label for this enum value. */ @Override public final String toString() { return label; } /** Saves this value into the Java preference object. */ private void set() { Preferences.userNodeForPackage(Util.class).put("Verbosity",id); } /** Reads the current value of the Java preference object (if it's not set, then return DEFAULT). */ private static Verbosity get() { return parse(Preferences.userNodeForPackage(Util.class).get("Verbosity","")); } }; //===================================================================================================// /** The JFrame for the main window. */ private JFrame frame; /** The JFrame for the visualizer window. */ private VizGUI viz; /** The "File", "Edit", "Run", "Option", "Window", and "Help" menus. */ private JMenu filemenu, editmenu, runmenu, optmenu, windowmenu, windowmenu2, helpmenu; /** The toolbar. */ private JToolBar toolbar; /** The various toolbar buttons. */ private JButton runbutton, stopbutton, showbutton; /** The Splitpane. */ private JSplitPane splitpane; /** The JLabel that displays the current line/column position, etc. */ private JLabel status; /** Whether the editor has the focus, or the log window has the focus. */ private boolean lastFocusIsOnEditor = true; /** The text editor. */ private OurTabbedSyntaxWidget text; /** The "message panel" on the right. */ private SwingLogPanel log; /** The scrollpane containing the "message panel". */ private JScrollPane logpane; /** The last "find" that the user issued. */ private String lastFind = ""; /** The last find is case-sensitive or not. */ private boolean lastFindCaseSensitive = true; /** The last find is forward or not. */ private boolean lastFindForward = true; /** The icon for a "checked" menu item. */ private static final Icon iconYes = OurUtil.loadIcon("images/menu1.gif"); /** The icon for an "unchecked" menu item. */ private static final Icon iconNo = OurUtil.loadIcon("images/menu0.gif"); /** The system-specific file separator (forward-slash on UNIX, back-slash on Windows, etc.) */ private static final String fs = System.getProperty("file.separator"); /** The darker background color (for the MessageLog window and the Toolbar and the Status Bar, etc.) */ private static final Color background = new Color(0.9f, 0.9f, 0.9f); /** If subrunning==true: 0 means SAT solving; 1 means metamodel; 2 means enumeration. */ private int subrunningTask = 0; /** The amount of memory (in MB) currently allocated for this.subprocess */ private int subMemoryNow = 0; /** The amount of stack (in KB) currently allocated for this.subprocess */ private int subStackNow = 0; /** The list of commands (this field will be cleared to null when the text buffer is edited). */ private List<Command> commands = null; /** The latest executed command. */ private int latestCommand = 0; /** The current choices of SAT solver. */ private List<SatSolver> satChoices; /** The most recent Alloy version (as queried from alloy.mit.edu); -1 if alloy.mit.edu has not replied yet. */ private int latestAlloyVersion = (-1); /** The most recent Alloy version name (as queried from alloy.mit.edu); "unknown" if alloy.mit.edu has not replied yet. */ private String latestAlloyVersionName = "unknown"; /** If it's not "", then it is the XML filename for the latest satisfying instance or the latest metamodel. */ private String latestInstance = ""; /** If it's not "", then it is the latest instance or metamodel during the most recent click of "Execute". */ private String latestAutoInstance = ""; /** If true, that means the event handlers should return a Runner encapsulating them, rather than perform the actual work. */ private boolean wrap = false; //====== helper methods =================================================// /** Inserts "filename" into the "recently opened file list". */ private void addHistory(String filename) { String name0=Model0.get(), name1=Model1.get(), name2=Model2.get(); if (name0.equals(filename)) return; else {Model0.set(filename); Model1.set(name0);} if (name1.equals(filename)) return; else Model2.set(name1); if (name2.equals(filename)) return; else Model3.set(name2); } /** Sets the flag "lastFocusIsOnEditor" to be true. */ private Runner notifyFocusGained() { if (wrap) return wrapMe(); lastFocusIsOnEditor=true; return null; } /** Sets the flag "lastFocusIsOnEditor" to be false. */ void notifyFocusLost() { lastFocusIsOnEditor=false; } /** Updates the status bar at the bottom of the screen. */ private Runner notifyChange() { if (wrap) return wrapMe(); commands=null; if (text==null) return null; // If this was called prior to the "text" being fully initialized OurSyntaxWidget t = text.get(); if (Util.onMac()) frame.getRootPane().putClientProperty("windowModified", Boolean.valueOf(t.modified())); if (t.isFile()) frame.setTitle(t.getFilename()); else frame.setTitle("Alloy Analyzer "+Version.version()); toolbar.setBorder(new OurBorder(false, false, text.count()<=1, false)); int c = t.getCaret(); int y = t.getLineOfOffset(c)+1; int x = c - t.getLineStartOffset(y-1)+1; status.setText("<html> Line "+y+", Column "+x +(t.modified()?" <b style=\"color:#B43333;\">[modified]</b></html>":"</html>")); return null; } /** Helper method that returns a hopefully very short name for a file name. */ public static String slightlyShorterFilename(String name) { if (name.toLowerCase(Locale.US).endsWith(".als")) { int i=name.lastIndexOf('/'); if (i>=0) name=name.substring(i+1); i=name.lastIndexOf('\\'); if (i>=0) name=name.substring(i+1); return name.substring(0, name.length()-4); } else if (name.toLowerCase(Locale.US).endsWith(".xml")) { int i=name.lastIndexOf('/'); if (i>0) i=name.lastIndexOf('/', i-1); if (i>=0) name=name.substring(i+1); i=name.lastIndexOf('\\'); if (i>0) i=name.lastIndexOf('\\', i-1); if (i>=0) name=name.substring(i+1); return name.substring(0, name.length()-4); } return name; } /** Copy the required files from the JAR into a temporary directory. */ private void copyFromJAR() { // Compute the appropriate platform String os = System.getProperty("os.name").toLowerCase(Locale.US).replace(' ','-'); if (os.startsWith("mac-")) os="mac"; else if (os.startsWith("windows-")) os="windows"; String arch = System.getProperty("os.arch").toLowerCase(Locale.US).replace(' ','-'); if (arch.equals("powerpc")) arch="ppc-"+os; else arch=arch.replaceAll("\\Ai[3456]86\\z","x86")+"-"+os; if (os.equals("mac")) arch="x86-mac"; // our pre-compiled binaries are all universal binaries // Find out the appropriate Alloy directory final String platformBinary = alloyHome() + fs + "binary"; // Write a few test files try { (new File(platformBinary)).mkdirs(); Util.writeAll(platformBinary + fs + "tmp.cnf", "p cnf 3 1\n1 0\n"); } catch(Err er) { // The error will be caught later by the "berkmin" or "spear" test } // Copy the platform-dependent binaries Util.copy(true, false, platformBinary, arch+"/libminisat.so", arch+"/libminisatx1.so", arch+"/libminisat.jnilib", arch+"/libminisatprover.so", arch+"/libminisatproverx1.so", arch+"/libminisatprover.jnilib", arch+"/libzchaff.so", arch+"/libzchaffx1.so", arch+"/libzchaff.jnilib", arch+"/berkmin", arch+"/spear"); Util.copy(false, false, platformBinary, arch+"/minisat.dll", arch+"/minisatprover.dll", arch+"/zchaff.dll", arch+"/berkmin.exe", arch+"/spear.exe"); // Copy the model files Util.copy(false, true, alloyHome(), "models/book/appendixA/addressBook1.als", "models/book/appendixA/addressBook2.als", "models/book/appendixA/barbers.als", "models/book/appendixA/closure.als", "models/book/appendixA/distribution.als", "models/book/appendixA/phones.als", "models/book/appendixA/prison.als", "models/book/appendixA/properties.als", "models/book/appendixA/ring.als", "models/book/appendixA/spanning.als", "models/book/appendixA/tree.als", "models/book/appendixA/tube.als", "models/book/appendixA/undirected.als", "models/book/appendixE/hotel.thm", "models/book/appendixE/p300-hotel.als", "models/book/appendixE/p303-hotel.als", "models/book/appendixE/p306-hotel.als", "models/book/chapter2/addressBook1a.als", "models/book/chapter2/addressBook1b.als", "models/book/chapter2/addressBook1c.als", "models/book/chapter2/addressBook1d.als", "models/book/chapter2/addressBook1e.als", "models/book/chapter2/addressBook1f.als", "models/book/chapter2/addressBook1g.als", "models/book/chapter2/addressBook1h.als", "models/book/chapter2/addressBook2a.als", "models/book/chapter2/addressBook2b.als", "models/book/chapter2/addressBook2c.als", "models/book/chapter2/addressBook2d.als", "models/book/chapter2/addressBook2e.als", "models/book/chapter2/addressBook3a.als", "models/book/chapter2/addressBook3b.als", "models/book/chapter2/addressBook3c.als", "models/book/chapter2/addressBook3d.als", "models/book/chapter2/theme.thm", "models/book/chapter4/filesystem.als", "models/book/chapter4/grandpa1.als", "models/book/chapter4/grandpa2.als", "models/book/chapter4/grandpa3.als", "models/book/chapter4/lights.als", "models/book/chapter5/addressBook.als", "models/book/chapter5/lists.als", "models/book/chapter5/sets1.als", "models/book/chapter5/sets2.als", "models/book/chapter6/hotel.thm", "models/book/chapter6/hotel1.als", "models/book/chapter6/hotel2.als", "models/book/chapter6/hotel3.als", "models/book/chapter6/hotel4.als", "models/book/chapter6/mediaAssets.als", "models/book/chapter6/memory/abstractMemory.als", "models/book/chapter6/memory/cacheMemory.als", "models/book/chapter6/memory/checkCache.als", "models/book/chapter6/memory/checkFixedSize.als", "models/book/chapter6/memory/fixedSizeMemory.als", "models/book/chapter6/memory/fixedSizeMemory_H.als", "models/book/chapter6/ringElection.thm", "models/book/chapter6/ringElection1.als", "models/book/chapter6/ringElection2.als", "models/examples/algorithms/dijkstra.als", "models/examples/algorithms/dijkstra.thm", "models/examples/algorithms/messaging.als", "models/examples/algorithms/messaging.thm", "models/examples/algorithms/opt_spantree.als", "models/examples/algorithms/opt_spantree.thm", "models/examples/algorithms/peterson.als", "models/examples/algorithms/ringlead.als", "models/examples/algorithms/ringlead.thm", "models/examples/algorithms/s_ringlead.als", "models/examples/algorithms/stable_mutex_ring.als", "models/examples/algorithms/stable_mutex_ring.thm", "models/examples/algorithms/stable_orient_ring.als", "models/examples/algorithms/stable_orient_ring.thm", "models/examples/algorithms/stable_ringlead.als", "models/examples/algorithms/stable_ringlead.thm", "models/examples/case_studies/INSLabel.als", "models/examples/case_studies/chord.als", "models/examples/case_studies/chord2.als", "models/examples/case_studies/chordbugmodel.als", "models/examples/case_studies/com.als", "models/examples/case_studies/firewire.als", "models/examples/case_studies/firewire.thm", "models/examples/case_studies/ins.als", "models/examples/case_studies/iolus.als", "models/examples/case_studies/sync.als", "models/examples/case_studies/syncimpl.als", "models/examples/puzzles/farmer.als", "models/examples/puzzles/farmer.thm", "models/examples/puzzles/handshake.als", "models/examples/puzzles/handshake.thm", "models/examples/puzzles/hanoi.als", "models/examples/puzzles/hanoi.thm", "models/examples/systems/file_system.als", "models/examples/systems/file_system.thm", "models/examples/systems/javatypes_soundness.als", "models/examples/systems/lists.als", "models/examples/systems/lists.thm", "models/examples/systems/marksweepgc.als", "models/examples/systems/views.als", "models/examples/toys/birthday.als", "models/examples/toys/birthday.thm", "models/examples/toys/ceilingsAndFloors.als", "models/examples/toys/ceilingsAndFloors.thm", "models/examples/toys/genealogy.als", "models/examples/toys/genealogy.thm", "models/examples/toys/grandpa.als", "models/examples/toys/grandpa.thm", "models/examples/toys/javatypes.als", "models/examples/toys/life.als", "models/examples/toys/life.thm", "models/examples/toys/numbering.als", "models/examples/toys/railway.als", "models/examples/toys/railway.thm", "models/examples/toys/trivial.als", "models/examples/tutorial/farmer.als", "models/util/boolean.als", "models/util/graph.als", "models/util/integer.als", "models/util/natural.als", "models/util/ordering.als", "models/util/relation.als", "models/util/seqrel.als", "models/util/sequence.als", "models/util/sequniv.als", "models/util/ternary.als", "models/util/time.als" ); // Record the locations System.setProperty("alloy.theme0", alloyHome() + fs + "models"); System.setProperty("alloy.home", alloyHome()); } /** Called when this window is resized. */ public void componentResized(ComponentEvent e) { componentMoved(e); } /** Called when this window is moved. */ public void componentMoved(ComponentEvent e) { AnalyzerWidth.set(frame.getWidth()); AnalyzerHeight.set(frame.getHeight()); AnalyzerX.set(frame.getX()); AnalyzerY.set(frame.getY()); } /** Called when this window is shown. */ public void componentShown(ComponentEvent e) {} /** Called when this window is hidden. */ public void componentHidden(ComponentEvent e) {} /** Wraps the calling method into a Runnable whose run() will call the calling method with (false) as the only argument. */ private Runner wrapMe() { final String name; try { throw new Exception(); } catch(Exception ex) { name = ex.getStackTrace()[1].getMethodName(); } Method[] methods = getClass().getDeclaredMethods(); Method m=null; for(int i=0; i<methods.length; i++) if (methods[i].getName().equals(name)) { m=methods[i]; break; } final Method method=m; return new Runner() { private static final long serialVersionUID = 0; public void run() { try { method.setAccessible(true); method.invoke(SimpleGUI.this, new Object[]{}); } catch (Throwable ex) { ex = new IllegalArgumentException("Failed call to "+name+"()", ex); Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex); } } public void run(Object arg) { run(); } }; } /** Wraps the calling method into a Runnable whose run() will call the calling method with (false,argument) as the two arguments. */ private Runner wrapMe(final Object argument) { final String name; try { throw new Exception(); } catch(Exception ex) { name = ex.getStackTrace()[1].getMethodName(); } Method[] methods = getClass().getDeclaredMethods(); Method m=null; for(int i=0; i<methods.length; i++) if (methods[i].getName().equals(name)) { m=methods[i]; break; } final Method method=m; return new Runner() { private static final long serialVersionUID = 0; public void run(Object arg) { try { method.setAccessible(true); method.invoke(SimpleGUI.this, new Object[]{arg}); } catch (Throwable ex) { ex = new IllegalArgumentException("Failed call to "+name+"("+arg+")", ex); Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), ex); } } public void run() { run(argument); } }; } /** This variable caches the result of alloyHome() function call. */ private static String alloyHome = null; /** Find a temporary directory to store Alloy files; it's guaranteed to be a canonical absolute path. */ private static synchronized String alloyHome() { if (alloyHome!=null) return alloyHome; String temp=System.getProperty("java.io.tmpdir"); if (temp==null || temp.length()==0) OurDialog.fatal("Error. JVM need to specify a temporary directory using java.io.tmpdir property."); String username=System.getProperty("user.name"); File tempfile=new File(temp+File.separatorChar+"alloy4tmp40-"+(username==null?"":username)); tempfile.mkdirs(); String ans=Util.canon(tempfile.getPath()); if (!tempfile.isDirectory()) { OurDialog.fatal("Error. Cannot create the temporary directory "+ans); } if (!Util.onWindows()) { String[] args={"chmod", "700", ans}; try {Runtime.getRuntime().exec(args).waitFor();} catch (Throwable ex) {} // We only intend to make a best effort. } return alloyHome=ans; } /** Create an empty temporary directory for use, designate it "deleteOnExit", then return it. * It is guaranteed to be a canonical absolute path. */ private static String maketemp() { Random r=new Random(new Date().getTime()); while(true) { int i=r.nextInt(1000000); String dest = alloyHome()+File.separatorChar+"tmp"+File.separatorChar+i; File f=new File(dest); if (f.mkdirs()) { f.deleteOnExit(); return Util.canon(dest); } } } /** Return the number of bytes used by the Temporary Directory (or return -1 if the answer exceeds "long") */ private static long computeTemporarySpaceUsed() { long ans = iterateTemp(null,false); if (ans<0) return -1; else return ans; } /** Delete every file in the Temporary Directory. */ private static void clearTemporarySpace() { iterateTemp(null,true); // Also clear the temp dir from previous versions of Alloy4 String temp=System.getProperty("java.io.tmpdir"); if (temp==null || temp.length()==0) return; String username=System.getProperty("user.name"); if (username==null) username=""; for(int i=1; i<40; i++) iterateTemp(temp+File.separatorChar+"alloy4tmp"+i+"-"+username, true); } /** Helper method for performing either computeTemporarySpaceUsed() or clearTemporarySpace() */ private static long iterateTemp(String filename, boolean delete) { long ans=0; if (filename==null) filename = alloyHome()+File.separatorChar+"tmp"; File x = new File(filename); if (x.isDirectory()) { for(String subfile:x.list()) { long tmp=iterateTemp(filename+File.separatorChar+subfile, delete); if (ans>=0) ans=ans+tmp; } } else if (x.isFile()) { long tmp=x.length(); if (ans>=0) ans=ans+tmp; } if (delete) x.delete(); return ans; } //===============================================================================================================// /** This method refreshes the "file" menu. */ private Runner doRefreshFile() { if (wrap) return wrapMe(); try { wrap = true; filemenu.removeAll(); menuItem(filemenu, "New", 'N', 'N', doNew()); menuItem(filemenu, "Open...", 'O', 'O', doOpen()); if (!Util.onMac()) menuItem(filemenu, "Open Sample Models...", VK_ALT, 'O', doBuiltin()); else menuItem(filemenu, "Open Sample Models...", doBuiltin()); JMenu recentmenu; filemenu.add(recentmenu = new JMenu("Open Recent")); menuItem(filemenu, "Reload all", 'R', 'R', doReloadAll()); menuItem(filemenu, "Save", 'S', 'S', doSave()); if (Util.onMac()) menuItem(filemenu, "Save As...", VK_SHIFT, 'S', doSaveAs()); else menuItem(filemenu, "Save As...", 'A', doSaveAs()); menuItem(filemenu, "Close", 'W', 'W', doClose()); menuItem(filemenu, "Clear Temporary Directory", doClearTemp()); menuItem(filemenu, "Quit", 'Q', (Util.onMac() ? -1 : 'Q'), doQuit()); boolean found = false; for(Util.StringPref p: new Util.StringPref[]{ Model0, Model1, Model2, Model3 }) { String name = p.get(); if (name.length()>0) { found = true; menuItem(recentmenu, name, doOpenFile(name)); } } recentmenu.addSeparator(); menuItem(recentmenu, "Clear Menu", doClearRecent()); recentmenu.setEnabled(found); } finally { wrap = false; } return null; } /** This method performs File->New. */ private Runner doNew() { if (!wrap) { text.newtab(null); notifyChange(); doShow(); } return wrapMe(); } /** This method performs File->Open. */ private Runner doOpen() { if (wrap) return wrapMe(); File file=OurDialog.askFile(true, null, ".als", ".als files"); if (file!=null) { Util.setCurrentDirectory(file.getParentFile()); doOpenFile(file.getPath()); } return null; } /** This method performs File->OpenBuiltinModels. */ private Runner doBuiltin() { if (wrap) return wrapMe(); File file=OurDialog.askFile(true, alloyHome() + fs + "models", ".als", ".als files"); if (file!=null) { doOpenFile(file.getPath()); } return null; } /** This method performs File->ReloadAll. */ private Runner doReloadAll() { if (!wrap) text.reloadAll(); return wrapMe(); } /** This method performs File->ClearRecentFiles. */ private Runner doClearRecent() { if (!wrap) { Model0.set(""); Model1.set(""); Model2.set(""); Model3.set(""); } return wrapMe(); } /** This method performs File->Save. */ private Runner doSave() { if (!wrap) { String ans = text.save(false); if (ans==null) return null; notifyChange(); addHistory(ans); log.clearError(); } return wrapMe(); } /** This method performs File->SaveAs. */ private Runner doSaveAs() { if (!wrap) { String ans = text.save(true); if (ans==null) return null; notifyChange(); addHistory(ans); log.clearError(); } return wrapMe(); } /** This method clears the temporary files and then reinitialize the temporary directory. */ private Runner doClearTemp() { if (!wrap) { clearTemporarySpace(); copyFromJAR(); log.logBold("Temporary directory has been cleared.\n\n"); log.logDivider(); log.flush(); } return wrapMe(); } /** This method performs File->Close. */ private Runner doClose() { if (!wrap) text.close(); return wrapMe(); } /** This method performs File->Quit. */ private Runner doQuit() { if (!wrap) if (text.closeAll()) { try { WorkerEngine.stop(); } finally { System.exit(0); } } return wrapMe(); } //===============================================================================================================// /** This method refreshes the "edit" menu. */ private Runner doRefreshEdit() { if (wrap) return wrapMe(); try { wrap = true; boolean canUndo = text.get().canUndo(); boolean canRedo = text.get().canRedo(); editmenu.removeAll(); menuItem(editmenu, "Undo", 'Z', 'Z', doUndo(), canUndo); if (Util.onMac()) menuItem(editmenu, "Redo", VK_SHIFT, 'Z', doRedo(), canRedo); else menuItem(editmenu, "Redo", 'Y', 'Y', doRedo(), canRedo); editmenu.addSeparator(); menuItem(editmenu, "Cut", 'X', 'X', doCut()); menuItem(editmenu, "Copy", 'C', 'C', doCopy()); menuItem(editmenu, "Paste", 'V', 'V', doPaste()); editmenu.addSeparator(); menuItem(editmenu, "Go To..." , 'T', 'T', doGoto()); menuItem(editmenu, "Previous File" , VK_PAGE_UP, VK_PAGE_UP, doGotoPrevFile(), text.count()>1); menuItem(editmenu, "Next File" , VK_PAGE_DOWN, VK_PAGE_DOWN, doGotoNextFile(), text.count()>1); editmenu.addSeparator(); menuItem(editmenu, "Find...", 'F', 'F', doFind()); menuItem(editmenu, "Find Next", 'G', 'G', doFindNext()); } finally { wrap = false; } return null; } /** This method performs Edit->Undo. */ private Runner doUndo() { if (!wrap) text.get().undo(); return wrapMe(); } /** This method performs Edit->Redo. */ private Runner doRedo() { if (!wrap) text.get().redo(); return wrapMe(); } /** This method performs Edit->Copy. */ private Runner doCopy() { if (!wrap) { if (lastFocusIsOnEditor) text.get().copy(); else log.copy(); } return wrapMe(); } /** This method performs Edit->Cut. */ private Runner doCut() { if (!wrap && lastFocusIsOnEditor) text.get().cut(); return wrapMe(); } /** This method performs Edit->Paste. */ private Runner doPaste() { if (!wrap && lastFocusIsOnEditor) text.get().paste(); return wrapMe(); } /** This method performs Edit->Find. */ private Runner doFind() { if (wrap) return wrapMe(); JTextField x = OurUtil.textfield(lastFind,30); x.selectAll(); JCheckBox c = new JCheckBox("Case Sensitive?",lastFindCaseSensitive); c.setMnemonic('c'); JCheckBox b = new JCheckBox("Search Backward?",!lastFindForward); b.setMnemonic('b'); if (!OurDialog.getInput("Find", "Text:", x, " ", c, b)) return null; if (x.getText().length() == 0) return null; lastFind = x.getText(); lastFindCaseSensitive = c.getModel().isSelected(); lastFindForward = !b.getModel().isSelected(); doFindNext(); return null; } /** This method performs Edit->FindNext. */ private Runner doFindNext() { if (wrap) return wrapMe(); if (lastFind.length()==0) return null; OurSyntaxWidget t = text.get(); String all = t.getText(); int i = Util.indexOf(all, lastFind, t.getCaret()+(lastFindForward?0:-1),lastFindForward,lastFindCaseSensitive); if (i<0) { i=Util.indexOf(all, lastFind, lastFindForward?0:(all.length()-1), lastFindForward, lastFindCaseSensitive); if (i<0) { log.logRed("The specified search string cannot be found."); return null; } log.logRed("Search wrapped."); } else { log.clearError(); } if (lastFindForward) t.moveCaret(i, i+lastFind.length()); else t.moveCaret(i+lastFind.length(), i); t.requestFocusInWindow(); return null; } /** This method performs Edit->Goto. */ private Runner doGoto() { if (wrap) return wrapMe(); JTextField y = OurUtil.textfield("", 10); JTextField x = OurUtil.textfield("", 10); if (!OurDialog.getInput("Go To", "Line Number:", y, "Column Number (optional):", x)) return null; try { OurSyntaxWidget t = text.get(); int xx = 1, yy = Integer.parseInt(y.getText()), lineCount = t.getLineCount(); if (yy<1) return null; if (yy>lineCount) {log.logRed("This file only has "+lineCount+" line(s)."); return null;} if (x.getText().length()!=0) xx=Integer.parseInt(x.getText()); if (xx<1) {log.logRed("If the column number is specified, it must be 1 or greater."); return null;} int caret = t.getLineStartOffset(yy-1); int len = (yy==lineCount ? t.getText().length()+1 : t.getLineStartOffset(yy)) - caret; if (xx>len) xx=len; if (xx<1) xx=1; t.moveCaret(caret+xx-1, caret+xx-1); t.requestFocusInWindow(); } catch(NumberFormatException ex) { log.logRed("The number must be 1 or greater."); } catch(Throwable ex) { // This error is not important } return null; } /** This method performs Edit->GotoPrevFile. */ private Runner doGotoPrevFile() { if (wrap) return wrapMe(); else {text.prev(); return null;} } /** This method performs Edit->GotoNextFile. */ private Runner doGotoNextFile() { if (wrap) return wrapMe(); else {text.next(); return null;} } //===============================================================================================================// /** This method refreshes the "run" menu. */ private Runner doRefreshRun() { if (wrap) return wrapMe(); KeyStroke ac = KeyStroke.getKeyStroke(VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); try { wrap = true; runmenu.removeAll(); menuItem(runmenu, "Execute Latest Command", 'E', 'E', doExecuteLatest()); runmenu.add(new JSeparator()); menuItem(runmenu, "Show Latest Instance", 'L', 'L', doShowLatest(), latestInstance.length()>0); menuItem(runmenu, "Show Metamodel", 'M', 'M', doShowMetaModel()); if (Version.experimental) menuItem(runmenu, "Show Parse Tree", 'P', doShowParseTree()); menuItem(runmenu, "Open Evaluator", 'V', doLoadEvaluator()); } finally { wrap = false; } List<Command> cp = commands; if (cp==null) { try { cp=CompUtil.parseOneModule_fromString(text.get().getText()); } catch(Err e) { commands = null; runmenu.getItem(0).setEnabled(false); runmenu.getItem(3).setEnabled(false); text.shade(new Pos(text.get().getFilename(), e.pos.x, e.pos.y, e.pos.x2, e.pos.y2)); if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) log.logRed("Fatal Exception!" + e.dump() + "\n\n"); else log.logRed(e.toString()+"\n\n"); return null; } catch(Throwable e) { commands = null; runmenu.getItem(0).setEnabled(false); runmenu.getItem(3).setEnabled(false); log.logRed("Cannot parse the model.\n"+e.toString()+"\n\n"); return null; } commands=cp; } text.clearShade(); log.clearError(); // To clear any residual error message if (cp==null) { runmenu.getItem(0).setEnabled(false); runmenu.getItem(3).setEnabled(false); return null; } if (cp.size()==0) { runmenu.getItem(0).setEnabled(false); return null; } if (latestCommand>=cp.size()) latestCommand=cp.size()-1; runmenu.remove(0); try { wrap = true; for(int i=0; i<cp.size(); i++) { JMenuItem y = new JMenuItem(cp.get(i).toString(), null); y.addActionListener(doRun(i)); if (i==latestCommand) { y.setMnemonic(VK_E); y.setAccelerator(ac); } runmenu.add(y,i); } if (cp.size()>=2) { JMenuItem y = new JMenuItem("Execute All", null); y.setMnemonic(VK_A); y.addActionListener(doRun(-1)); runmenu.add(y,0); runmenu.add(new JSeparator(),1); } } finally { wrap = false; } return null; } /** This method executes a particular RUN or CHECK command. */ private Runner doRun(Integer commandIndex) { if (wrap) return wrapMe(commandIndex); final int index = commandIndex; if (WorkerEngine.isBusy()) return null; if (index==(-2)) subrunningTask=1; else subrunningTask=0; latestAutoInstance=""; if (index>=0) latestCommand=index; if (index==-1 && commands!=null) { latestCommand=commands.size()-1; if (latestCommand<0) latestCommand=0; } // To update the accelerator to point to the command actually chosen doRefreshRun(); OurUtil.enableAll(runmenu); if (commands==null) return null; if (commands.size()==0 && index!=-2 && index!=-3) { log.logRed("There are no commands to execute.\n\n"); return null; } int i=index; if (i>=commands.size()) i=commands.size()-1; SimpleCallback1 cb = new SimpleCallback1(this, null, log, Verbosity.get().ordinal(), latestAlloyVersionName, latestAlloyVersion); SimpleTask1 task = new SimpleTask1(); A4Options opt = new A4Options(); opt.tempDirectory = alloyHome() + fs + "tmp"; opt.solverDirectory = alloyHome() + fs + "binary"; opt.recordKodkod = RecordKodkod.get(); opt.noOverflow = NoOverflow.get(); opt.unrolls = Version.experimental ? Unrolls.get() : (-1); opt.skolemDepth = SkolemDepth.get(); opt.coreMinimization = CoreMinimization.get(); opt.coreGranularity = CoreGranularity.get(); opt.originalFilename = Util.canon(text.get().getFilename()); opt.solver = SatSolver.get(); task.bundleIndex = i; task.bundleWarningNonFatal = WarningNonfatal.get(); task.map = text.takeSnapshot(); task.options = opt.dup(); task.resolutionMode = (Version.experimental && ImplicitThis.get()) ? 2 : 1; task.tempdir = maketemp(); try { runmenu.setEnabled(false); runbutton.setVisible(false); showbutton.setEnabled(false); stopbutton.setVisible(true); int newmem = SubMemory.get(), newstack = SubStack.get(); if (newmem != subMemoryNow || newstack != subStackNow) WorkerEngine.stop(); if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) WorkerEngine.runLocally(task, cb); else WorkerEngine.run(task, newmem, newstack, alloyHome() + fs + "binary", "", cb); subMemoryNow = newmem; subStackNow = newstack; } catch(Throwable ex) { WorkerEngine.stop(); log.logBold("Fatal Error: Solver failed due to unknown reason.\n" + "One possible cause is that, in the Options menu, your specified\n" + "memory size is larger than the amount allowed by your OS.\n" + "Also, please make sure \"java\" is in your program path.\n"); log.logDivider(); log.flush(); doStop(2); } return null; } /** This method stops the current run or check (how==0 means DONE, how==1 means FAIL, how==2 means STOP). */ Runner doStop(Integer how) { if (wrap) return wrapMe(how); int h = how; if (h!=0) { if (h==2 && WorkerEngine.isBusy()) { WorkerEngine.stop(); log.logBold("\nSolving Stopped.\n"); log.logDivider(); } WorkerEngine.stop(); } runmenu.setEnabled(true); runbutton.setVisible(true); showbutton.setEnabled(true); stopbutton.setVisible(false); if (latestAutoInstance.length()>0) { String f=latestAutoInstance; latestAutoInstance=""; if (subrunningTask==2) viz.loadXML(f, true); else if (AutoVisualize.get() || subrunningTask==1) doVisualize("XML: "+f); } return null; } /** This method executes the latest command. */ private Runner doExecuteLatest() { if (wrap) return wrapMe(); doRefreshRun(); OurUtil.enableAll(runmenu); if (commands==null) return null; int n=commands.size(); if (n<=0) { log.logRed("There are no commands to execute.\n\n"); return null; } if (latestCommand>=n) latestCommand=n-1; if (latestCommand<0) latestCommand=0; return doRun(latestCommand); } /** This method displays the parse tree. */ private Runner doShowParseTree() { if (wrap) return wrapMe(); doRefreshRun(); OurUtil.enableAll(runmenu); if (commands!=null) { Module world = null; try { int resolutionMode = (Version.experimental && ImplicitThis.get()) ? 2 : 1; A4Options opt = new A4Options(); opt.tempDirectory = alloyHome() + fs + "tmp"; opt.solverDirectory = alloyHome() + fs + "binary"; opt.originalFilename = Util.canon(text.get().getFilename()); world = CompUtil.parseEverything_fromFile(A4Reporter.NOP, text.takeSnapshot(), opt.originalFilename, resolutionMode); } catch(Err er) { text.shade(er.pos); log.logRed(er.toString()+"\n\n"); return null; } world.showAsTree(this); } return null; } /** This method displays the meta model. */ private Runner doShowMetaModel() { if (wrap) return wrapMe(); doRefreshRun(); OurUtil.enableAll(runmenu); if (commands!=null) doRun(-2); return null; } /** This method displays the latest instance. */ private Runner doShowLatest() { if (wrap) return wrapMe(); if (latestInstance.length()==0) log.logRed("No previous instances are available for viewing.\n\n"); else doVisualize("XML: "+latestInstance); return null; } /** This method happens when the user tries to load the evaluator from the main GUI. */ private Runner doLoadEvaluator() { if (wrap) return wrapMe(); log.logRed("Note: the evaluator is now in the visualizer.\n" +"Just click the \"Evaluator\" toolbar button\n" +"when an instance is shown in the visualizer.\n"); log.flush(); return null; } //===============================================================================================================// /** This method refreshes the "Window" menu for either the SimpleGUI window (isViz==false) or the VizGUI window (isViz==true). */ private Runner doRefreshWindow(Boolean isViz) { if (wrap) return wrapMe(isViz); try { wrap = true; JMenu w = (isViz ? windowmenu2 : windowmenu); w.removeAll(); if (isViz) { viz.addMinMaxActions(w); } else { menuItem(w, "Minimize", 'M', doMinimize(), iconNo); menuItem(w, "Zoom", doZoom(), iconNo); } w.addSeparator(); int i = 0; for(String f: text.getFilenames()) { JMenuItem it = new JMenuItem("Model: "+slightlyShorterFilename(f)+(text.modified(i) ? " *" : ""), null); it.setIcon((f.equals(text.get().getFilename()) && !isViz) ? iconYes : iconNo); it.addActionListener(f.equals(text.get().getFilename()) ? doShow() : doOpenFile(f)); w.add(it); i++; } if (viz!=null) for(String f:viz.getInstances()) { JMenuItem it = new JMenuItem("Instance: "+viz.getInstanceTitle(f), null); it.setIcon((isViz && f.equals(viz.getXMLfilename())) ? iconYes : iconNo); it.addActionListener(doVisualize("XML: "+f)); w.add(it); } } finally { wrap = false; } return null; } /** This method minimizes the window. */ private Runner doMinimize() { if (wrap) return wrapMe(); else {OurUtil.minimize(frame); return null;} } /** This method alternatingly maximizes or restores the window. */ private Runner doZoom() { if (wrap) return wrapMe(); else {OurUtil.zoom(frame); return null;} } /** This method bring this window to the foreground. */ private Runner doShow() { if (wrap) return wrapMe(); OurUtil.show(frame); text.get().requestFocusInWindow(); return null; } //===============================================================================================================// /** This method refreshes the "Option" menu. */ private Runner doRefreshOption() { if (wrap) return wrapMe(); try { wrap = true; optmenu.removeAll(); menuItem(optmenu, "Welcome Message at Start Up: "+(Welcome.get() < welcomeLevel ? "Yes" : "No"), doOptWelcome()); // final SatSolver now = SatSolver.get(); final JMenu sat = new JMenu("SAT Solver: "+now); for(SatSolver sc:satChoices) { menuItem(sat, ""+sc, doOptSolver(sc), sc==now?iconYes:iconNo); } optmenu.add(sat); // menuItem(optmenu, "Warnings are Fatal: "+(WarningNonfatal.get()?"No":"Yes"), doOptWarning()); // final int mem = SubMemory.get(); final JMenu subMemoryMenu = new JMenu("Maximum Memory to Use: " + mem + "M"); for(int n: allowedMemorySizes) { menuItem(subMemoryMenu, ""+n+"M", doOptMemory(n), n==mem?iconYes:iconNo); } optmenu.add(subMemoryMenu); // final int stack = SubStack.get(); final JMenu subStackMenu = new JMenu("Maximum Stack to Use: " + stack + "k"); boolean debug = "yes".equals(System.getProperty("debug")); for(int n: new int[]{16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536}) { if (debug || n>=1024) menuItem(subStackMenu, ""+n+"k", doOptStack(n), n==stack?iconYes:iconNo); } optmenu.add(subStackMenu); // final Verbosity vnow = Verbosity.get(); final JMenu verb = new JMenu("Message Verbosity: "+vnow); for(Verbosity vb: Verbosity.values()) { menuItem(verb, ""+vb, doOptVerbosity(vb), vb==vnow?iconYes:iconNo); } optmenu.add(verb); // menuItem(optmenu, "Syntax Highlighting: "+(SyntaxDisabled.get()?"No":"Yes"), doOptSyntaxHighlighting()); // final int fontSize = FontSize.get(); final JMenu size = new JMenu("Font Size: "+fontSize); for(int n: new Integer[]{9,10,11,12,14,16,18,20,22,24,26,28,32,36,40,44,48,54,60,66,72}) { menuItem(size, ""+n, doOptFontsize(n), n==fontSize?iconYes:iconNo); } optmenu.add(size); // menuItem(optmenu, "Font: "+FontName.get()+"...", doOptFontname()); // if (Util.onMac() || Util.onWindows()) menuItem(optmenu, "Use anti-aliasing: Yes", false); else menuItem(optmenu, "Use anti-aliasing: "+(AntiAlias.get()?"Yes":"No"), doOptAntiAlias()); // final int tabSize = TabSize.get(); final JMenu tabSizeMenu = new JMenu("Tab Size: "+tabSize); for(int n=1; n<=12; n++) { menuItem(tabSizeMenu, ""+n, doOptTabsize(n), n==tabSize?iconYes:iconNo); } optmenu.add(tabSizeMenu); // final int skDepth = SkolemDepth.get(); final JMenu skDepthMenu = new JMenu("Skolem Depth: "+skDepth); for(int n=0; n<=4; n++) { menuItem(skDepthMenu, ""+n, doOptSkolemDepth(n), n==skDepth?iconYes:iconNo); } optmenu.add(skDepthMenu); // if (Version.experimental) { final int unrolls = Unrolls.get(); final JMenu unrollsMenu = new JMenu("Recursion Depth: "+(unrolls<0 ? "Disabled" : (""+unrolls))); for(int n=(-1); n<=3; n++) { menuItem(unrollsMenu, (n<0 ? "Disabled" : (""+n)), doOptUnrolls(n), n==unrolls?iconYes:iconNo); } optmenu.add(unrollsMenu); } // final int min = CoreMinimization.get(); final String[] minLabelLong=new String[]{"Slow (guarantees local minimum)", "Medium", "Fast (initial unsat core)"}; final String[] minLabelShort=new String[]{"Slow", "Medium", "Fast"}; final JMenu cmMenu = new JMenu("Unsat Core Minimization Strategy: "+minLabelShort[min]); for(int n=0; n<=2; n++) { menuItem(cmMenu, minLabelLong[n], doOptCore(n), n==min?iconYes:iconNo); } if (now!=SatSolver.MiniSatProverJNI) cmMenu.setEnabled(false); optmenu.add(cmMenu); // final int gran = CoreGranularity.get(); final String[] granLabelLong=new String[]{"Top-level conjuncts only", "Flatten the formula once at the beginning", "Flatten the formula at the beginning and after skolemizing", "In addition to flattening the formula twice, expand the quantifiers"}; final String[] granLabelShort=new String[]{"Top-level", "Flatten once", "Flatten twice", "Expand quantifiers"}; final JMenu cgMenu = new JMenu("Core Granularity: "+granLabelShort[gran]); for(int n=0; n<granLabelLong.length; n++) { menuItem(cgMenu, granLabelLong[n], doCoreGran(n), n==gran?iconYes:iconNo); } if (now!=SatSolver.MiniSatProverJNI) cgMenu.setEnabled(false); optmenu.add(cgMenu); // menuItem(optmenu, "Visualize Automatically: "+(AutoVisualize.get()?"Yes":"No"), doOptAutoVisualize()); menuItem(optmenu, "Record the Kodkod Input/Output: "+(RecordKodkod.get()?"Yes":"No"), doOptRecordKodkod()); if (Version.experimental) menuItem(optmenu, "Enable \"implicit this\" name resolution: "+(ImplicitThis.get()?"Yes":"No"), doOptImplicitThis()); if (Version.experimental) menuItem(optmenu, "Forbid Overflow: "+(NoOverflow.get()?"Yes":"No"), doOptNoOverflow()); } finally { wrap = false; } return null; } /** This method toggles the "show welcome message at startup" checkbox. */ private Runner doOptWelcome() { if (!wrap) Welcome.set(Welcome.get() < welcomeLevel ? welcomeLevel : 0); return wrapMe(); } /** This method toggles the "warning is fatal" checkbox. */ private Runner doOptWarning() { if (!wrap) WarningNonfatal.set(!WarningNonfatal.get()); return wrapMe(); } /** This method changes the SAT solver to the given solver. */ private Runner doOptSolver(SatSolver solver) { if (!wrap) solver.set(); return wrapMe(solver); } /** This method changes the amount of memory to use. */ private Runner doOptMemory(Integer size) { if (!wrap) SubMemory.set(size); return wrapMe(size); } /** This method changes the amount of stack to use. */ private Runner doOptStack(Integer size) { if (!wrap) SubStack.set(size); return wrapMe(size); } /** This method changes the message verbosity. */ private Runner doOptVerbosity(Verbosity verbosity) { if (!wrap) verbosity.set(); return wrapMe(verbosity); } /** This method changes the font name. */ private Runner doOptFontname() { if (wrap) return wrapMe(); int size=FontSize.get(); String f = OurDialog.askFont(); if (f.length()>0) { FontName.set(f); text.setFont(f, size, TabSize.get()); status.setFont(new Font(f, Font.PLAIN, size)); log.setFontName(f); } return null; } /** This method changes the font size. */ private Runner doOptFontsize(Integer size) { if (wrap) return wrapMe(size); int n=size; FontSize.set(n); String f = FontName.get(); text.setFont(f, n, TabSize.get()); status.setFont(new Font(f, Font.PLAIN, n)); log.setFontSize(n); viz.doSetFontSize(n); return null; } /** This method changes the tab size. */ private Runner doOptTabsize(Integer size) { if (!wrap) { TabSize.set(size.intValue()); text.setFont(FontName.get(), FontSize.get(), size.intValue()); } return wrapMe(size); } /** This method changes the number of unrolls. */ private Runner doOptUnrolls(Integer num) { if (!wrap) Unrolls.set(num.intValue()); return wrapMe(num); } /** This method changes the skolem depth. */ private Runner doOptSkolemDepth(Integer size) { if (!wrap) SkolemDepth.set(size.intValue()); return wrapMe(size); } /** This method changes the speed of unsat core minimization (larger integer means faster but less optimal). */ private Runner doOptCore(Integer speed) { if (!wrap) CoreMinimization.set(speed.intValue()); return wrapMe(speed); } /** This method changes the granularity of the unsat core (larger integer means more granular). */ private Runner doCoreGran(Integer gran) { if (!wrap) CoreGranularity.set(gran.intValue()); return wrapMe(gran); } /** This method toggles the "antialias" checkbox. */ private Runner doOptAntiAlias() { if (!wrap) { boolean newValue = !AntiAlias.get(); AntiAlias.set(newValue); OurAntiAlias.enableAntiAlias(newValue); } return wrapMe(); } /** This method toggles the "visualize automatically" checkbox. */ private Runner doOptAutoVisualize() { if (!wrap) AutoVisualize.set(!AutoVisualize.get()); return wrapMe(); } /** This method toggles the "record Kodkod input/output" checkbox. */ private Runner doOptRecordKodkod() { if (!wrap) RecordKodkod.set(!RecordKodkod.get()); return wrapMe(); } /** This method toggles the "enable new `implicit this' name resolution" checkbox. */ private Runner doOptImplicitThis() { if (!wrap) ImplicitThis.set(!ImplicitThis.get()); return wrapMe(); } private Runner doOptNoOverflow() { if (!wrap) NoOverflow.set(!NoOverflow.get()); return wrapMe(); } /** This method toggles the "syntax highlighting" checkbox. */ private Runner doOptSyntaxHighlighting() { if (!wrap) { boolean flag = SyntaxDisabled.get(); text.enableSyntax(flag); SyntaxDisabled.set(!flag); } return wrapMe(); } //===============================================================================================================// /** This method displays the about box. */ private Runner doAbout() { if (wrap) return wrapMe(); OurDialog.showmsg("About Alloy Analyzer " + Version.version(), OurUtil.loadIcon("images/logo.gif"), "Alloy Analyzer " + Version.version(), "Build date: " + Version.buildDate(), " ", "Lead developer: Felix Chang", "Engine developer: Emina Torlak", "Graphic design: Julie Pelaez", "Project lead: Daniel Jackson", " ", "Please post comments and questions to the Alloy Community Forum at http://alloy.mit.edu/", " ", "Thanks to: Ilya Shlyakhter, Manu Sridharan, Derek Rayside, Jonathan Edwards, Gregory Dennis,", "Robert Seater, Edmond Lau, Vincent Yeung, Sam Daitch, Andrew Yip, Jongmin Baek, Ning Song,", "Arturo Arizpe, Li-kuo (Brian) Lin, Joseph Cohen, Jesse Pavel, Ian Schechter, and Uriel Schafer." ); return null; } /** This method displays the help html. */ private Runner doHelp() { if (wrap) return wrapMe(); try { int w=OurUtil.getScreenWidth(), h=OurUtil.getScreenHeight(); final JFrame frame = new JFrame(); final JEditorPane html1 = new JEditorPane("text/html", ""); final JEditorPane html2 = new JEditorPane("text/html", ""); final HTMLDocument doc1 = (HTMLDocument) (html1.getDocument()); doc1.setAsynchronousLoadPriority(-1); final HTMLDocument doc2 = (HTMLDocument) (html2.getDocument()); doc2.setAsynchronousLoadPriority(-1); html1.setPage(this.getClass().getResource("/help/Nav.html")); html2.setPage(this.getClass().getResource("/help/index.html")); HyperlinkListener hl=new HyperlinkListener() { public final void hyperlinkUpdate(HyperlinkEvent e) { try { if (e.getEventType()!=HyperlinkEvent.EventType.ACTIVATED) return; if (e.getURL().getPath().endsWith("quit.htm")) { frame.dispose(); return; } HTMLDocument doc = (HTMLDocument) (html2.getDocument()); doc.setAsynchronousLoadPriority(-1); // So that we can catch any exception that may occur html2.setPage(e.getURL()); html2.requestFocusInWindow(); } catch(Throwable ex) { } } }; html1.setEditable(false); html1.setBorder(new EmptyBorder(3,3,3,3)); html1.addHyperlinkListener(hl); html2.setEditable(false); html2.setBorder(new EmptyBorder(3,3,3,3)); html2.addHyperlinkListener(hl); JScrollPane scroll1 = OurUtil.scrollpane(html1); JScrollPane scroll2 = OurUtil.scrollpane(html2); JSplitPane split = OurUtil.splitpane(JSplitPane.HORIZONTAL_SPLIT, scroll1, scroll2, 150); split.setResizeWeight(0d); frame.setTitle("Alloy Analyzer Online Guide"); frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(split, BorderLayout.CENTER); frame.pack(); frame.setSize(w-w/10, h-h/10); frame.setLocation(w/20, h/20); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.setVisible(true); html2.requestFocusInWindow(); } catch(Throwable ex) { return null; } return null; } /** This method displays the license box. */ private Runner doLicense() { if (wrap) return wrapMe(); final String JAR = Util.jarPrefix(); String alloytxt; try { alloytxt = Util.readAll(JAR + "LICENSES" + File.separator + "Alloy.txt"); } catch(IOException ex) { return null; } final JTextArea text = OurUtil.textarea(alloytxt, 15, 85, false, false, new EmptyBorder(2, 2, 2, 2), new Font("Monospaced", Font.PLAIN, 12)); final JScrollPane scroll = OurUtil.scrollpane(text, new LineBorder(Color.DARK_GRAY, 1)); final JComboBox combo = new OurCombobox(new String[]{"Alloy","Kodkod","JavaCup","SAT4J","ZChaff","MiniSat"}) { private static final long serialVersionUID = 0; @Override public void do_changed(Object value) { if (value instanceof String) { try { String content = Util.readAll(JAR + "LICENSES" + File.separator + value + ".txt"); text.setText(content); } catch(IOException ex) { text.setText("Sorry: an error has occurred in displaying the license file."); } } text.setCaretPosition(0); } }; OurDialog.showmsg("Copyright Notices", "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. We are extremely grateful to", "the authors of these packages for making their source code freely available.", " ", OurUtil.makeH(null, "See the copyright notice for: ", combo, null), " ", scroll ); return null; } /** This method changes the latest instance. */ void doSetLatest(String arg) { latestInstance = arg; latestAutoInstance = arg; } /** The color to use for functions/predicate/paragraphs that contains part of the unsat core. */ final Color supCoreColor = new Color(0.95f, 0.1f, 0.1f); /** The color to use for the unsat core. */ final Color coreColor = new Color(0.9f, 0.4f, 0.4f); /** The color to use for functions/predicate used by the Unsat core. */ final Color subCoreColor = new Color(0.9f, 0.7f, 0.7f); /** This method displays a particular instance or message. */ @SuppressWarnings("unchecked") Runner doVisualize(String arg) { if (wrap) return wrapMe(arg); text.clearShade(); if (arg.startsWith("MSG: ")) { // MSG: message OurDialog.showtext("Detailed Message", arg.substring(5)); } if (arg.startsWith("CORE: ")) { // CORE: filename String filename = Util.canon(arg.substring(6)); Pair<Set<Pos>,Set<Pos>> hCore; Set<Pos> lCore; InputStream is = null; ObjectInputStream ois = null; try { is = new FileInputStream(filename); ois = new ObjectInputStream(is); hCore = (Pair<Set<Pos>,Set<Pos>>) ois.readObject(); lCore = (Set<Pos>) ois.readObject(); } catch(Throwable ex) { log.logRed("Error reading or parsing the core \""+filename+"\"\n"); return null; } finally { Util.close(ois); Util.close(is); } text.clearShade(); text.shade(hCore.b, subCoreColor, false); text.shade(hCore.a, coreColor, false); // shade again, because if not all files were open, some shadings will have no effect text.shade(hCore.b, subCoreColor, false); text.shade(hCore.a, coreColor, false); } if (arg.startsWith("POS: ")) { // POS: x1 y1 x2 y2 filename Scanner s=new Scanner(arg.substring(5)); int x1=s.nextInt(), y1=s.nextInt(), x2=s.nextInt(), y2=s.nextInt(); String f=s.nextLine(); if (f.length()>0 && f.charAt(0)==' ') f=f.substring(1); // Get rid of the space after Y2 Pos p=new Pos(Util.canon(f), x1, y1, x2, y2); text.shade(p); } if (arg.startsWith("CNF: ")) { // CNF: filename String filename=Util.canon(arg.substring(5)); try { String text=Util.readAll(filename); OurDialog.showtext("Text Viewer", text); } catch(IOException ex) { log.logRed("Error reading the file \""+filename+"\"\n"); } } if (arg.startsWith("XML: ")) { // XML: filename viz.loadXML(Util.canon(arg.substring(5)), false); } return null; } /** This method opens a particular file. */ private Runner doOpenFile(String arg) { if (wrap) return wrapMe(arg); String f=Util.canon(arg); if (!text.newtab(f)) return null; if (text.get().isFile()) addHistory(f); doShow(); text.get().requestFocusInWindow(); log.clearError(); return null; } /** This object performs solution enumeration. */ private final Computer enumerator = new Computer() { public String compute(Object input) { final String arg = (String)input; OurUtil.show(frame); if (WorkerEngine.isBusy()) throw new RuntimeException("Alloy4 is currently executing a SAT solver command. Please wait until that command has finished."); SimpleCallback1 cb = new SimpleCallback1(SimpleGUI.this, viz, log, Verbosity.get().ordinal(), latestAlloyVersionName, latestAlloyVersion); SimpleTask2 task = new SimpleTask2(); task.filename = arg; try { WorkerEngine.run(task, SubMemory.get(), SubStack.get(), alloyHome() + fs + "binary", "", cb); // task.run(cb); } catch(Throwable ex) { WorkerEngine.stop(); log.logBold("Fatal Error: Solver failed due to unknown reason.\n" + "One possible cause is that, in the Options menu, your specified\n" + "memory size is larger than the amount allowed by your OS.\n" + "Also, please make sure \"java\" is in your program path.\n"); log.logDivider(); log.flush(); doStop(2); return arg; } subrunningTask=2; runmenu.setEnabled(false); runbutton.setVisible(false); showbutton.setEnabled(false); stopbutton.setVisible(true); return arg; } }; /** Converts an A4TupleSet into a SimTupleset object. */ private static SimTupleset convert(Object object) throws Err { if (!(object instanceof A4TupleSet)) throw new ErrorFatal("Unexpected type error: expecting an A4TupleSet."); A4TupleSet s = (A4TupleSet)object; if (s.size()==0) return SimTupleset.EMPTY; List<SimTuple> list = new ArrayList<SimTuple>(s.size()); int arity = s.arity(); for(A4Tuple t: s) { String[] array = new String[arity]; for(int i=0; i<t.arity(); i++) array[i] = t.atom(i); list.add(SimTuple.make(array)); } return SimTupleset.make(list); } /** Converts an A4Solution into a SimInstance object. */ private static SimInstance convert(Module root, A4Solution ans) throws Err { SimInstance ct = new SimInstance(root, ans.getBitwidth(), ans.getMaxSeq()); for(Sig s: ans.getAllReachableSigs()) { if (!s.builtin) ct.init(s, convert(ans.eval(s))); for(Field f: s.getFields()) if (!f.defined) ct.init(f, convert(ans.eval(f))); } for(ExprVar a:ans.getAllAtoms()) ct.init(a, convert(ans.eval(a))); for(ExprVar a:ans.getAllSkolems()) ct.init(a, convert(ans.eval(a))); return ct; } /** This object performs expression evaluation. */ private static Computer evaluator = new Computer() { private String filename = null; public final String compute(final Object input) throws Exception { if (input instanceof File) { filename = ((File)input).getAbsolutePath(); return ""; } if (!(input instanceof String)) return ""; final String str = (String)input; if (str.trim().length()==0) return ""; // Empty line Module root = null; A4Solution ans = null; try { Map<String,String> fc = new LinkedHashMap<String,String>(); XMLNode x = new XMLNode(new File(filename)); if (!x.is("alloy")) throw new Exception(); String mainname=null; for(XMLNode sub: x) if (sub.is("instance")) { mainname=sub.getAttribute("filename"); break; } if (mainname==null) throw new Exception(); for(XMLNode sub: x) if (sub.is("source")) { String name = sub.getAttribute("filename"); String content = sub.getAttribute("content"); fc.put(name, content); } root = CompUtil.parseEverything_fromFile(A4Reporter.NOP, fc, mainname, (Version.experimental && ImplicitThis.get()) ? 2 : 1); ans = A4SolutionReader.read(root.getAllReachableSigs(), x); for(ExprVar a:ans.getAllAtoms()) { root.addGlobal(a.label, a); } for(ExprVar a:ans.getAllSkolems()) { root.addGlobal(a.label, a); } } catch(Throwable ex) { throw new ErrorFatal("Failed to read or parse the XML file."); } try { Expr e = CompUtil.parseOneExpression_fromString(root, str); if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) { SimInstance simInst = convert(root, ans); return simInst.visitThis(e).toString() + (simInst.wasOverflow() ? " (OF)" : ""); } else return ans.eval(e).toString(); } catch(HigherOrderDeclException ex) { throw new ErrorType("Higher-order quantification is not allowed in the evaluator."); } } }; /** Returns true iff the output says "s SATISFIABLE" (while ignoring comment lines and value lines) */ private static boolean isSat(String output) { int i=0, n=output.length(); // skip COMMENT lines and VALUE lines while(i<n && (output.charAt(i)=='c' || output.charAt(i)=='v')) { while(i<n && (output.charAt(i)!='\r' && output.charAt(i)!='\n')) i++; while(i<n && (output.charAt(i)=='\r' || output.charAt(i)=='\n')) i++; continue; } return output.substring(i).startsWith("s SATISFIABLE"); } //====== Main Method ====================================================// /** Main method that launches the program; this method might be called by an arbitrary thread. */ public static void main(final String[] args) throws Exception { SwingUtilities.invokeLater(new Runnable() { public void run() { new SimpleGUI(args); } }); } //====== Constructor ====================================================// private static boolean loadLibrary(String library) { try { System.loadLibrary(library); return true; } catch(UnsatisfiedLinkError ex) { } try { System.loadLibrary(library+"x1"); return true; } catch(UnsatisfiedLinkError ex) { } try { System.loadLibrary(library+"x2"); return true; } catch(UnsatisfiedLinkError ex) { } try { System.loadLibrary(library+"x3"); return true; } catch(UnsatisfiedLinkError ex) { } try { System.loadLibrary(library+"x4"); return true; } catch(UnsatisfiedLinkError ex) { } try { System.loadLibrary(library+"x5"); return true; } catch(UnsatisfiedLinkError ex) { return false; } } /** Create a dummy task object for testing purpose. */ private static final WorkerEngine.WorkerTask dummyTask = new WorkerEngine.WorkerTask() { private static final long serialVersionUID = 0; public void run(WorkerCallback out) { } }; /** The constructor; this method will be called by the AWT event thread, using the "invokeLater" method. */ private SimpleGUI (final String[] args) { // Register an exception handler for uncaught exceptions MailBug.setup(); // Enable better look-and-feel if (Util.onMac() || Util.onWindows()) { System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Alloy Analyzer "+Version.version()); System.setProperty("com.apple.mrj.application.growbox.intrudes","true"); System.setProperty("com.apple.mrj.application.live-resize","true"); System.setProperty("com.apple.macos.useScreenMenuBar","true"); System.setProperty("apple.laf.useScreenMenuBar","true"); try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Throwable e) { } } // Figure out the desired x, y, width, and height int screenWidth=OurUtil.getScreenWidth(), screenHeight=OurUtil.getScreenHeight(); int width=AnalyzerWidth.get(); if (width<=0) width=screenWidth/10*8; else if (width<100) width=100; if (width>screenWidth) width=screenWidth; int height=AnalyzerHeight.get(); if (height<=0) height=screenHeight/10*8; else if (height<100) height=100; if (height>screenHeight) height=screenHeight; int x=AnalyzerX.get(); if (x<0) x=screenWidth/10; if (x>screenWidth-100) x=screenWidth-100; int y=AnalyzerY.get(); if (y<0) y=screenHeight/10; if (y>screenHeight-100) y=screenHeight-100; // Put up a slash screen final JFrame frame = new JFrame("Alloy Analyzer"); frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); frame.pack(); if (!Util.onMac() && !Util.onWindows()) { String gravity = System.getenv("_JAVA_AWT_WM_STATIC_GRAVITY"); if (gravity==null || gravity.length()==0) { // many Window managers do not respect ICCCM2; this should help avoid the Title Bar being shifted "off screen" if (x<30) { if (x<0) x=0; width=width-(30-x); x=30; } if (y<30) { if (y<0) y=0; height=height-(30-y); y=30; } } if (width<100) width=100; if (height<100) height=100; } frame.setSize(width,height); frame.setLocation(x,y); frame.setVisible(true); frame.setTitle("Alloy Analyzer "+Version.version()+" loading... please wait..."); final int windowWidth = width; // We intentionally call setVisible(true) first before settings the "please wait" title, // since we want the minimized window title on Linux/FreeBSD to just say Alloy Analyzer // Test the allowed memory sizes final WorkerEngine.WorkerCallback c = new WorkerEngine.WorkerCallback() { private final List<Integer> allowed = new ArrayList<Integer>(); private final List<Integer> toTry = new ArrayList<Integer>(Arrays.asList(256,512,768,1024,1536,2048,2560,3072,3584,4096)); private int mem; public synchronized void callback(Object msg) { if (toTry.size()==0) { SwingUtilities.invokeLater(new Runnable() { public void run() { SimpleGUI.this.frame=frame; SimpleGUI.this.finishInit(args, allowed, windowWidth); } }); return; } try { mem=toTry.remove(0); WorkerEngine.stop(); WorkerEngine.run(dummyTask, mem, 128, "", "", this); return; } catch(IOException ex) { fail(); } } public synchronized void done() { //System.out.println("Alloy4 can use "+mem+"M"); System.out.flush(); allowed.add(mem); callback(null); } public synchronized void fail() { //System.out.println("Alloy4 cannot use "+mem+"M"); System.out.flush(); callback(null); } }; c.callback(null); } private void finishInit(String[] args, List<Integer> initialAllowedMemorySizes, int width) { // Add the listeners try { wrap = true; frame.addWindowListener(doQuit()); } finally { wrap = false; } frame.addComponentListener(this); // initialize the "allowed memory sizes" array allowedMemorySizes = new ArrayList<Integer>(initialAllowedMemorySizes); int newmem = SubMemory.get(); if (!allowedMemorySizes.contains(newmem)) { int newmemlen = allowedMemorySizes.size(); if (allowedMemorySizes.contains(768) || newmemlen==0) SubMemory.set(768); // a nice default value else SubMemory.set(allowedMemorySizes.get(newmemlen-1)); } // Choose the appropriate font int fontSize=FontSize.get(); String fontName=FontName.get(); while(true) { if (!OurDialog.hasFont(fontName)) fontName="Lucida Grande"; else break; if (!OurDialog.hasFont(fontName)) fontName="Verdana"; else break; if (!OurDialog.hasFont(fontName)) fontName="Courier New"; else break; if (!OurDialog.hasFont(fontName)) fontName="Lucida Grande"; break; } FontName.set(fontName); // Copy required files from the JAR copyFromJAR(); final String binary = alloyHome() + fs + "binary"; // Create the menu bar JMenuBar bar = new JMenuBar(); try { wrap = true; filemenu = menu(bar, "&File", doRefreshFile()); editmenu = menu(bar, "&Edit", doRefreshEdit()); runmenu = menu(bar, "E&xecute", doRefreshRun()); optmenu = menu(bar, "&Options", doRefreshOption()); windowmenu = menu(bar, "&Window", doRefreshWindow(false)); windowmenu2 = menu(null, "&Window", doRefreshWindow(true)); helpmenu = menu(bar, "&Help", null); if (!Util.onMac()) menuItem(helpmenu, "About Alloy...", 'A', doAbout()); menuItem(helpmenu, "Quick Guide", 'Q', doHelp()); menuItem(helpmenu, "See the Copyright Notices...", 'L', doLicense()); } finally { wrap = false; } // Pre-load the visualizer viz = new VizGUI(false, "", windowmenu2, enumerator, evaluator); viz.doSetFontSize(FontSize.get()); // Create the toolbar try { wrap = true; toolbar = new JToolBar(); toolbar.setFloatable(false); if (!Util.onMac()) toolbar.setBackground(background); toolbar.add(OurUtil.button("New", "Starts a new blank model", "images/24_new.gif", doNew())); toolbar.add(OurUtil.button("Open", "Opens an existing model", "images/24_open.gif", doOpen())); toolbar.add(OurUtil.button("Reload", "Reload all the models from disk", "images/24_reload.gif", doReloadAll())); toolbar.add(OurUtil.button("Save", "Saves the current model", "images/24_save.gif", doSave())); toolbar.add(runbutton=OurUtil.button("Execute", "Executes the latest command", "images/24_execute.gif", doExecuteLatest())); toolbar.add(stopbutton=OurUtil.button("Stop", "Stops the current analysis", "images/24_execute_abort2.gif", doStop(2))); stopbutton.setVisible(false); toolbar.add(showbutton=OurUtil.button("Show", "Shows the latest instance", "images/24_graph.gif", doShowLatest())); toolbar.add(Box.createHorizontalGlue()); toolbar.setBorder(new OurBorder(false,false,false,false)); } finally { wrap = false; } // Choose the antiAlias setting OurAntiAlias.enableAntiAlias(AntiAlias.get()); // Create the message area logpane = OurUtil.scrollpane(null); log = new SwingLogPanel(logpane, fontName, fontSize, background, Color.BLACK, new Color(.7f,.2f,.2f), this); // Create the text area text = new OurTabbedSyntaxWidget(fontName, fontSize, TabSize.get()); text.listeners.add(this); text.enableSyntax(! SyntaxDisabled.get()); // Add everything to the frame, then display the frame Container all=frame.getContentPane(); all.setLayout(new BorderLayout()); all.removeAll(); JPanel lefthalf=new JPanel(); lefthalf.setLayout(new BorderLayout()); lefthalf.add(toolbar, BorderLayout.NORTH); text.addTo(lefthalf, BorderLayout.CENTER); splitpane = OurUtil.splitpane(JSplitPane.HORIZONTAL_SPLIT, lefthalf, logpane, width/2); splitpane.setResizeWeight(0.5D); status = OurUtil.make(OurAntiAlias.label(" "), new Font(fontName, Font.PLAIN, fontSize), Color.BLACK, background); status.setBorder(new OurBorder(true,false,false,false)); all.add(splitpane, BorderLayout.CENTER); all.add(status, BorderLayout.SOUTH); // Generate some informative log messages log.logBold("Alloy Analyzer "+Version.version()+" (build date: "+Version.buildDate()+")\n\n"); // If on Mac, then register an application listener try { wrap = true; if (Util.onMac()) MacUtil.registerApplicationListener(doShow(), doAbout(), doOpenFile(""), doQuit()); } finally { wrap = false; } // Add the new JNI location to the java.library.path try { System.setProperty("java.library.path", binary); // The above line is actually useless on Sun JDK/JRE (see Sun's bug ID 4280189) // The following 4 lines should work for Sun's JDK/JRE (though they probably won't work for others) String[] newarray = new String[]{binary}; java.lang.reflect.Field old = ClassLoader.class.getDeclaredField("usr_paths"); old.setAccessible(true); old.set(null,newarray); } catch (Throwable ex) { } // Testing the SAT solvers if (1==1) { satChoices = SatSolver.values().makeCopy(); // String test1 = Subprocess.exec(20000, new String[]{binary+fs+"berkmin", binary+fs+"tmp.cnf"}); // if (!isSat(test1)) satChoices.remove(SatSolver.BerkMinPIPE); satChoices.remove(SatSolver.BerkMinPIPE); String test2 = Subprocess.exec(20000, new String[]{binary+fs+"spear", "--model", "--dimacs", binary+fs+"tmp.cnf"}); if (!isSat(test2)) satChoices.remove(SatSolver.SpearPIPE); if (!loadLibrary("minisat")) { log.logBold("Warning: JNI-based SAT solver does not work on this platform.\n"); log.log("This is okay, since you can still use SAT4J as the solver.\n"+ "For more information, please visit http://alloy.mit.edu/alloy4/\n"); log.logDivider(); log.flush(); satChoices.remove(SatSolver.MiniSatJNI); } if (!loadLibrary("minisatprover")) satChoices.remove(SatSolver.MiniSatProverJNI); if (!loadLibrary("zchaff")) satChoices.remove(SatSolver.ZChaffJNI); SatSolver now = SatSolver.get(); if (!satChoices.contains(now)) { now=SatSolver.ZChaffJNI; if (!satChoices.contains(now)) now=SatSolver.SAT4J; now.set(); } if (now==SatSolver.SAT4J && satChoices.size()>3 && satChoices.contains(SatSolver.CNF) && satChoices.contains(SatSolver.KK)) { log.logBold("Warning: Alloy4 defaults to SAT4J since it is pure Java and very reliable.\n"); log.log("For faster performance, go to Options menu and try another solver like MiniSat.\n"); log.log("If these native solvers fail on your computer, remember to change back to SAT4J.\n"); log.logDivider(); log.flush(); } } // If the temporary directory has become too big, then tell the user they can "clear temporary directory". long space = computeTemporarySpaceUsed(); if (space<0 || space>=20*1024768) { if (space<0) log.logBold("Warning: Alloy4's temporary directory has exceeded 1024M.\n"); else log.logBold("Warning: Alloy4's temporary directory now uses "+(space/1024768)+"M.\n"); log.log("To clear the temporary directory,\n" +"go to the File menu and click \"Clear Temporary Directory\"\n"); log.logDivider(); log.flush(); } // Refreshes all the menu items doRefreshFile(); OurUtil.enableAll(filemenu); doRefreshEdit(); OurUtil.enableAll(editmenu); doRefreshRun(); OurUtil.enableAll(runmenu); doRefreshOption(); doRefreshWindow(false); OurUtil.enableAll(windowmenu); frame.setJMenuBar(bar); // Open the given file, if a filename is given in the command line for(String f:args) if (f.toLowerCase(Locale.US).endsWith(".als")) { File file = new File(f); if (file.exists() && file.isFile()) doOpenFile(file.getPath()); } // Update the title and status bar notifyChange(); text.get().requestFocusInWindow(); // Launch the welcome screen if needed if (!"yes".equals(System.getProperty("debug")) && Welcome.get() < welcomeLevel) { JCheckBox again = new JCheckBox("Show this message every time you start the Alloy Analyzer"); again.setSelected(true); OurDialog.showmsg("Welcome", "Thank you for using the Alloy Analyzer "+Version.version(), " ", "Version 4 of the Alloy Analyzer is a complete rewrite,", "offering improvements in robustness, performance and usability.", "Models written in Alloy 3 will require some small alterations to run in Alloy 4.", " ", "Here are some quick tips:", " ", "* Function calls now use [ ] instead of ( )", " For more details, please see http://alloy.mit.edu/alloy4/quickguide/", " ", "* The Execute button always executes the latest command.", " To choose which command to execute, go to the Execute menu.", " ", "* The Alloy Analyzer comes with a variety of sample models.", " To see them, go to the File menu and click Open Sample Models.", " ", again ); doShow(); if (!again.isSelected()) Welcome.set(welcomeLevel); } // Periodically ask the MailBug thread to see if there is a newer version or not final long now = System.currentTimeMillis(); final Timer t = new Timer(800, null); t.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int n = MailBug.latestBuildNumber(); // If beyond 3 seconds, then we should stop because the log message may run into other user messages if (System.currentTimeMillis() - now >= 3000 || n <= Version.buildNumber()) { t.stop(); return; } latestAlloyVersion = n; latestAlloyVersionName = MailBug.latestBuildName(); log.logBold("An updated version of the Alloy Analyzer has been released.\n"); log.log("Please visit alloy.mit.edu to download the latest version:\nVersion " + latestAlloyVersionName + "\n"); log.logDivider(); log.flush(); t.stop(); } }); t.start(); } /** {@inheritDoc} */ public Object do_action(Object sender, Event e) { if (sender instanceof OurTabbedSyntaxWidget) switch(e) { case FOCUSED: notifyFocusGained(); break; case STATUS_CHANGE: notifyChange(); break; } return true; } /** {@inheritDoc} */ public Object do_action(Object sender, Event e, Object arg) { if (sender instanceof OurTree && e==Event.CLICK && arg instanceof Browsable) { Pos p = ((Browsable)arg).pos(); if (p==Pos.UNKNOWN) p = ((Browsable)arg).span(); text.shade(p); } return true; } }