diff --git a/examples/Paxos/Consensus.tla b/examples/Paxos/Consensus.tla index 95b4da6950a4822b769b982c0df1de8b98a4fb15..622f289ed4e0408e848b54712d6ca61eaf42f07f 100644 --- a/examples/Paxos/Consensus.tla +++ b/examples/Paxos/Consensus.tla @@ -1,55 +1,55 @@ ------------------------------ MODULE Consensus ------------------------------ -EXTENDS Naturals, FiniteSets - -CONSTANT Value - (*************************************************************************) - (* The set of all values that can be chosen. *) - (*************************************************************************) - -VARIABLE chosen - (*************************************************************************) - (* The set of all values that have been chosen. *) - (*************************************************************************) - -(***************************************************************************) -(* The type-correctness invariant. *) -(***************************************************************************) -TypeOK == /\ chosen \subseteq Value - /\ IsFiniteSet(chosen) - -(***************************************************************************) -(* The initial predicate and next-state relation. *) -(***************************************************************************) -Init == chosen = {} - -Next == /\ chosen = {} - /\ \E v \in Value : chosen' = {v} - -(***************************************************************************) -(* The complete spec. *) -(***************************************************************************) -Spec == Init /\ [][Next]_chosen ------------------------------------------------------------------------------ -(***************************************************************************) -(* Safety: At most one value is chosen. *) -(***************************************************************************) -Inv == /\ TypeOK - /\ Cardinality(chosen) \leq 1 - -THEOREM Invariance == Spec => []Inv -<1>1. Init => Inv -<1>2. Inv /\ [Next]_chosen => Inv' -<1>3. QED - <2>1. Inv /\ [Next]_chosen => []Inv - BY <1>2 \* and a TLA proof rule - <2>2. QED - BY <1>1, <2>1 \* and simple logic ------------------------------------------------------------------------------ -(***************************************************************************) -(* Liveness: A value is eventually chosen. *) -(***************************************************************************) -Success == <>(chosen # {}) -LiveSpec == Spec /\ WF_chosen(Next) - -THEOREM LivenessTheorem == LiveSpec => Success -============================================================================= +----------------------------- MODULE Consensus ------------------------------ +EXTENDS Naturals, FiniteSets + +CONSTANT Value + (*************************************************************************) + (* The set of all values that can be chosen. *) + (*************************************************************************) + +VARIABLE chosen + (*************************************************************************) + (* The set of all values that have been chosen. *) + (*************************************************************************) + +(***************************************************************************) +(* The type-correctness invariant. *) +(***************************************************************************) +TypeOK == /\ chosen \subseteq Value + /\ IsFiniteSet(chosen) + +(***************************************************************************) +(* The initial predicate and next-state relation. *) +(***************************************************************************) +Init == chosen = {} + +Next == /\ chosen = {} + /\ \E v \in Value : chosen' = {v} + +(***************************************************************************) +(* The complete spec. *) +(***************************************************************************) +Spec == Init /\ [][Next]_chosen +----------------------------------------------------------------------------- +(***************************************************************************) +(* Safety: At most one value is chosen. *) +(***************************************************************************) +Inv == /\ TypeOK + /\ Cardinality(chosen) \leq 1 + +THEOREM Invariance == Spec => []Inv +<1>1. Init => Inv +<1>2. Inv /\ [Next]_chosen => Inv' +<1>3. QED + <2>1. Inv /\ [][Next]_chosen => []Inv + BY <1>2 \* and a TLA proof rule + <2>2. QED + BY <1>1, <2>1 \* and simple logic +----------------------------------------------------------------------------- +(***************************************************************************) +(* Liveness: A value is eventually chosen. *) +(***************************************************************************) +Success == <>(chosen # {}) +LiveSpec == Spec /\ WF_chosen(Next) + +THEOREM LivenessTheorem == LiveSpec => Success +============================================================================= diff --git a/general/cla/YOUR-NAME.txt.template b/general/cla/YOUR-NAME.txt.template new file mode 100644 index 0000000000000000000000000000000000000000..168773e493120597dd4e8a470639be97408e8267 --- /dev/null +++ b/general/cla/YOUR-NAME.txt.template @@ -0,0 +1,33 @@ +Thank you for your interest in the TLA+ project (http://tlaplus.codeplex.com/). In order for You (as defined below) to make intellectual property Contributions (as defined below) now or in the future to the TLA+ project, You must agree to this Contributor License Agreement ("CLA"). + +Please read this CLA carefully before accepting its terms. By accepting the CLA, You are agreeing to be bound by its terms. If You want to accept this CLA, complete the form completely, and when ready, include the form in your initial signed-off commit as a text file stored in general/cla/YOUR-NAME.txt. + +As used in this CLA: (i) “You" (or "Your") shall mean the entity that is making this Agreement with the TLA+ project; (ii) "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is submitted by You to the TLA+ project for inclusion in, or documentation of, any of the TLA+ project; and (iii) “Submit” (or “Submitted”) means any form of communication sent to the TLA+ project (e.g. the content You post on electronic mailing lists, forums, content management systems, source code control systems, code review systems, issue tracking systems, etc. that are used by the TLA+ project). + +This agreement applies to all Contributions You Submit. + +This CLA, and the license(s) associated with the TLA+ project You are contributing to, provides a license to Your Contributions to the TLA+ project and downstream consumers, but You still own Your Contributions, and except for the licenses provided for in this CLA, You reserve all right, title and interest in Your Contributions. + +The TLA+ project requires that each Contribution You Submit now or in the future to comply with the following four commitments. To confirm that You have read and agreed to each, please click on the box beside each one. + + - You will only Submit Contributions where You have authored 100% of the content. + - You will only Submit Contributions to which You have the necessary rights. This means that if You are employed You have received the necessary permissions from Your employer to make the Contributions. + - Whatever content You Contribute will be provided under the license(s) associated with the TLA+ project. You can see the license(s), or find links to the license(s) in the notices and file headers for the project. + - You understand and agree that TLA+ project and Your contributions are public, and that a record of the contribution (including all personal information You submit with it, including Your sign-off) is maintained indefinitely and may be redistributed consistent with the license(s) involved. + +You will promptly notify the TLA+ project if You become aware of any facts or circumstances that would make these commitments inaccurate in any way. +You represent that the information provided below is accurate. + +Full name: ______________________________________________________ + +(optional) Public name: _________________________________________ + +Mailing Address: ________________________________________________ + + ________________________________________________ + +Country: ______________________________________________________ + +Telephone: ______________________________________________________ + +E-Mail: ______________________________________________________ diff --git a/general/docs/microsoft-release.txt b/general/docs/microsoft-release.txt index b58073333210d25aecd492eaee67e862212b9b5e..8a3ef836bc68e028c6d9080bd98546e8e0769e75 100644 --- a/general/docs/microsoft-release.txt +++ b/general/docs/microsoft-release.txt @@ -209,6 +209,42 @@ TO GENERATE THE RELEASE: Queries to msrcpx or Chuck Needham (chuckne) ------------------------------------------------------------- +Version 1.5.2 - 21 December 2015 + - Quick access dialog to make models easily accessible to users (Ctrl+Shift-A). Additionally shows a spec's modules and (optionally) closed specs. + - Upon spec safe, automatically keep a file revision/history. Let users restore revision from history view. + - Do not offer to use built-in PDF viewer on Mac OS X. It is fundamentally broken and Mac's PDF viewer is more powerful anyway. + - Parse and size status line item's height too small. + - Limit the trace length to a fixed number of 1000 states by default. User can easily load more by clicking "Load more" in trace explorer. + - Show corresponding action for "back-to-state" cycle marker upon double click. + - Remove any symmetry definition when model is run in TraceExplorer. + - Allow users to add free-form comments to their models. + - Show warnings when modelchecking terminated with unexplored states left. + - Result page better indicates liveness checking stage (final vs. run on incomplete liveness graph). + - Stop NumberFormatException in "per minute" statistics. + - Fix bug where Toolbox reports a bogus invariant violation. + - Show Modules (files in spec dir) and Models as children of the root specification in the SpecExplorer. Group both under a virtual "modules" and "models" tree item. + - Show progress when opening a model from an existing log file. Supports cancellation if log file is large. + - Toolbox hangs on large TLC outputs during model checking and at startup. Now several orders of magnitude faster. + - Only reflect address part instead of full URL back on distributed TLC's setup webpage. (SECURITY) + - Allow only one Toolbox instance to be running. + - Open Module can open modules from foreign specs. + - Upgrade to Eclipse Foundation 4.5. + - Run liveness checking based on runtime ratio between safety and liveness checking. + - Add -userFile parameter to redirect Print/PrintT/... output to dedicated file instead of sysout. + - Show time it took to model check and to check liveness. + - Simulator (and DFIDChecker) now support TLCSet/TLCGet. + - Distributed TLC does not support TLCGet/TLCSet. Prints a warning upon startup. + - The ordering of records in TLC.Print and PrintT statement outputs can be wrong. + - Report Java assertion violations as such. Don't blame it on the user's spec or model. + - Do not throw a NullPointerException on action-less spec when coverage requested. + - Incorrect cmdline parameters cause NullPointerException. + - Race condition when printing the trace of an invariant violation. + - Delay adding successor states *after* corresponding invariants and implied actions have been checked. Previously, the early addition could potentially lead to multiple invariant/implied action violations being reported simultaneously iff TLC uses multiple workers (i.e. worker 1 adds a successor to the StateQueue *before* checking the invariants. Worker 2 would explore this state and find a invariant violation concurrently with worker 1). + - TLC never deletes old states from state queue causing the disk to fill up. + - Fixed minor PlusCal translator bug. + - Application Bundle packaging on Mac (Existing installations + have to be manually updated). + - Ended support for 32 bit Toolbox releases on Mac OSX. Version 1.5.1 - 1 June 2015 - Fix intermittent "Fingerprint is already on disk" TLC crash. - Fix conditions in state queue that can deadlock TLC. diff --git a/general/docs/roadmap.txt b/general/docs/roadmap.txt index 7ef377f15c976af1afa8652d323e7f1ae88ab59d..a8d0c791bee6899e5dabc57d2119779092ebc3dd 100644 --- a/general/docs/roadmap.txt +++ b/general/docs/roadmap.txt @@ -1,670 +1,681 @@ -To find the methods that Eclipse is using to implement something: - Alt-Shift-F1: - Brings up information about active editor of view or dialog. - Alt-Shift-F2 + click on menu & click on menu item: - Brings up information about implementation of some menu items. - -org.lamport.tla.toolbox.tool.tlc.ui - ui/editor - ModelEditor - Sets up the Model Editor, including its pages. The individual - pages are set up by the - MainModelPage, AdvancedModelPage, and ResultPage; - - loadData() initializes the editor. - validate() is called through some plug-in magic whenever - the spec is parsed (but probably only if the parse is correct) - and probably when the user has made some change in the model editor. - It is also run when the model editor is initialized. - -Various pieces of code are notified when the spec is opened, parsed, -and closed by listeners registered to the extension point -org.lamport.tla.toolbox.spec of the plugin org.lamport.tla.toolbox. -This extension point interacts via a class that extends the class -SpecLifecycleParticipant. So, finding all classes that extend this -should permit one to find every place that gets notified when the spec -is opened, closed, or parsed. However, this is all done by so many -levels of indirection that we have not succeeded in doing this. -For example, ModelEditor.validate() is called when the module is parsed, -and this must be through that extension point. But we have not discovered -the path from the extension point to that method. - -------- - -The spec status indicator in the lower-right corner is apparently -set by ParseStatusContributionItem . update(). - - ----------- -How to create a distribution -- This is obsolete. See the file -general/docs/microsoft-release.txt - -Go to org....product.standalone - This contains no functionality, but "builds application stuff". - -The following stuff needs to be done just once for each computer. - -Open standalone.product / Overview tab - Need to specify target platform. - By default, Eclipse uses itself as the target - platform. - Need to define a separate copy of the distribution - as the target platform. - - - 1. Unzip Eclipse platform from eclipse-rcp-galileo-win32.zip into somewhere as the - the target platform. I've put it in tla/target-platform - - 2. Go to Eclipse download page, download - Eclipse delta pack. Usually in - "Other Downloads". (search for "delta") - Extract it to a new directory. I've put it in tla/delta-pack, merging - with the stuff extracted above. - - 3. Go to Window/ preferences/Plug-in Development/Target Platform - - Hit Add. - Choose "nothing" & hit next - In Target Contact - Enter name, like "Galileo 3.5" - Location hit add: - select Installation & hit next - Select target-platform directory .../tla/target-platform/eclipse - and hit finish [included 487 of 487] - Hit add again - select Directory and hit next - select .../delta-pack/eclipse directory and hit - finish [61 of 61] - Hit finish. - Check "Galileo 3.5" in Target Platform to make it active. - Hit OK, and watch it rebuild. - -The following stuff needs to be done for each release. - - 1. Open standalone product / overview page - 1a. Select New button next to Product: and set - Application to org.lamport.tla.toolbox.application - - 2. Hit Synchronizing on the Testing section - 3. Hit on Eclipse Product Export Wizard - (export to windows, linux stk/x86, macosx cocoa/x86) ------------------------------------------------------------------------------ -How to put a new version up on the MSR-INRIA Web site. (This is also obsolete.) - -1. Go to the web site's project page. -2. Login -3. Select "contents", right-click on the html release page, and open it. -4. Select edit, and choose "html" on the editing panel to get a reasonable - format. -5. Make any necessary changes to the page and save them. -6. Go back to the "contents" page and use it to upload the new versions. - They will appear in the same directory as the html page, with the - name by which they were uploaded. -7. If you created a new file, go to Properties and click the box that - makes it not appear in the navigation pane. Save. - -The current preliminary release web page is reachable from the outside -world at: - -http://www.msr-inria.inria.fr/Projects/tools-for-formal-specs/preliminary-release/ - ------------------------------------------------------------------------------- -HELP - -INTRO -Intro page in : org.lamport.tla.toolbox.product.standalone / intro/root.xhtml. - Source page for each topic is in org.lamport.tla.toolbox.product.standalone /intro/*.xhtml - Each such topic page has an href link whose id field must be registered (linking - it to the page) by adding the appropriate stuff to - org.lamport.tla.toolbox.product.standalone /intro/introContent.xml - -ATTACHING A PAGE TO THE HELP BUTTON - -org.lamport.tla.toolbox.editor.basic/helpContexts.xml contains a <context> entry with - id "main_editor_window" : - <context id="main_editor_window"> - <description>This is a the basic editor. It is used to edit the TLA+ module files.</description> - <topic label="Understanding Problems" href="../org.lamport.tla.toolbox.doc/html/reference/maintopic.html" /> - </context> - -The constructor of TLAEditor contains - setHelpContextId("org.lamport.tla.toolbox.editor.basic.main_editor_window"); - - org.lamport.tla.toolbox.editor.basic/plugin.xml contains this relevant extensions: - - <extension - point="org.eclipse.help.contexts"> - <contexts - file="helpContexts.xml" - plugin="org.lamport.tla.toolbox.editor.basic"> - </contexts> - </extension> - -PUTTING A PAGE IN THE TABLE OF CONTENTS - -To add a top-level section in the toc, add something like - - <topic label="Concepts"> - <anchor id="concepts"/> - </topic> - -to org.lamport.tla.toolbox.doc/toc.xml . The label is the toc entry. The anchor -is used to attach things to that entry. Also, add to -org.lamport.tla.toolbox.doc/plugin.xml the indicated <toc> ... </toc> entry: - - <plugin> - <extension - point="org.eclipse.help.toc"> - ... - <toc - file="tocconcepts.xml"> - </toc> - ... - </extension> - </plugin> - -Subtopics are attached in - org.lamport.tla.toolbox.doc/tocconcepts.xml, which contains - - <toc label="Concepts" link_to="toc.xml#concepts"> - <topic label="Model" href="html/concepts/model.html"> - </topic> - <topic href="html/concepts/specification.html" label="Specification"> - </topic> - <topic href="html/concepts/module.html" label="Module"> - </topic> - </toc> - -The toc label seems to be irrelevant. The topic labels specify the TOC subtopics. - -To get a subtopic, put a <topic label=... href=... /> inside a topic - - - -ATTACHING A HELP BUTTON TO AN EDITOR/PAGE/DIALOGUE - -The help button on the Eclipse plug.xml editor uses lines 82-91 of -PDEFormPage class in package org.eclipse.pde.internal.ui.editor to attach the button. -This was found by trying Shift-Alt-F1 on the page in the Eclipse editor and going to -the ExtensionsPage, which extends PDEFormPage. - --------------------------------------------------------------- -GETTING NOTIFIED WHEN A MODULE IS SAVED - -When a module is saved, the performSave() of the org.tla.toolbox.editor.basic.TLAEditor object -is called. There's a default method in a superclass, but one can override it to -do whatever else you want to do on a save. There's also a performSaveAs() method. --------------------------------------------------------------- -RAISING AN ERROR OR WARNING WINDOW - - MessageDialog.openWarning(UIHelper.getShellProvider().getShell(), window-title, message) - -If that doesn't work (probably throwing a null-pointer exception), see -ResourceHelper.ErrorMessageRunnable. ------------------------------------------------------------- -LOGGING AN ERROR ON THE TOOLBOX'S LOG - - Activator.getDefault().logError ------------------------------------------------------------- -ADDING A BUTTON TO A MENU - -In the Extensions tab of the appropriate plugin.xml file: - -- Under org.eclipse.ui.menus, add a command to the appropriate - menuContribution item for the menu item's group. The commandId - ties the menu item to its handler. - -- Under org.eclipse.ui.handlers, add a handler that extends - AbstractHandler and implements IHandler, where the execute method - specifies what selecting the item should do. ------------------------------------------------------------- - -ASSIGNMENT OBJECTS AND WIZARDS - -An assignment object: - - Is used for constant assignments and for definition overrides. - - For the constant assignments, it is constructed by the loadData - method of MainModlePage in the call of - FormHelper.setSerializedInput, which puts it into the TableViewer - MainModelPage.constantTable - -The user can edit an Assignment object with an AssignmentWizard. When -he clicks on the Add or Edit button of the Constant Assignments -section, it will create an AssignmentWizard. - -The button is added in the ValidateableTableSectionPart object (the -super for Validateable...), and fSelectionListener is added to it. -Note that the doAdd and doEdit methods are overridden by the methods -in the subclass ValidateableConstant. These methods call -doEditFormula, which creates the AssignmentWizard. - -(See www.eclipse.org about dialogs and wizards) - -The AssignmentWizard has two AssignmentWizardPages that are created in -the addPages() method. In AssignmentWizardPages there is the -createControl method, which is called when the page is added to the -Wizard. Note: createControl is constructing the widgets that form the -wizard page. They are children of container, which is a child of the -parent argument. The last line, setControl(container), sets the -container as the control associated with the wizard page. - -To implement the help button on the dialog, we need to set the help on -the container. - -Further information on how definition overrides are managed can be -found in the comments for -FilteredDefinitionSelectionDialog.fillContentProvider. - -------------------------------------------------------------------- -Explanation of various items in no particular order by DR - -SANY is executed in the private method parseModule in -ModuleParserLauncher. This method is the place to access all modules -that are ever parsed. The method itself will not necessarily be -called on every modules that is parsed, but within the method, an -Enumerate object is generated by the SANY java code. This object -contains all modules that were ultimately parsed because SANY parsed -the module for which parseModule() is called. These modules are -either extended or instanced by the module for which parseModule() is -called. - -There is a good explanation of resource change listeners in the -article "How You've Changed!" on www.eclipse.org/articles. In short, -the method resourceChanged in any implementation of a resource change -listener is called when a resource change event for which that -listener is registered occurs. That method can access an -IResourceDelta tree which gives all resources in the workspace that -have changed. This tree can be traversed using an implementation of -IResourceDeltaVisitor. - -ParserDependancyStorage gives access to all user modules that are -relevant to the spec based on the most recent parse. If the most -recent parse resulted in an error, then ParserDependancyStorage will -not contain the modules on which the root module depends or the root -module itself. This makes sense because without a successful parse, -it cannot be determined on which modules the root module depends. - -An IResource is the class that represents a resource in the -workspace. There is a good explanation of resources in the Eclipse -Help Platform Plug-in Developer Guide Programmer's Guide. A IResource -allows setting persistent and session properties. Persistent -properties will last over startup and shutdowns of the toolbox while -session properties will be erased when the toolbox is shut down. -Right now, the time of the last build for a resource is set as a -persistent property in the private parseModule method in -ModuleParserLauncher. - -The basic principle behind designing the widgets in the toolbox is to -have three components: a viewer, a content provider, and a class -that represents the data to appear. The viewer does not know how the -data is represented that it is to present. The content provider takes -instances of the class (Assignment for example) that represents the -data and adds it to the viewer. An ILabelProvider is used to -determine how these instances are displayed in a viewer. -------------------------------------------------------------------- -STRUCTURE OF THE prover plugin - -Command handlers that launch a proof construct and schedule a - -ProverJob. - The running ProverJob streams the prover's output to a - -BroadcastStreamListener. - This calls the constructors to create - ConsoleProverProcessOutputSink and TagBasedTLAPMOutputIncrementalParser - objects and calls their initializeSink methods to initialize them - and then calls appendText methods when prover's input arrives. - -TagBasedTLAPMOutputIncrementalParser - The appendText() method creates TLAPMMessage objects and calls - appropriate methods in ProverHelper to process the messages, according - to their type, which is one of: - ObligationNumberMessage: reports total number of obligations. - ObligationStatusMessage: reports status of obligation, starting with - status TO_BE_PROVED - StepStatusMessage: Reports status of a step. - -Specifying color preferences is done through two extension points: -(This may be somewhat obsolete with the introduction of ColorPredicate -objects.) - org.eclipse.core.resources.marker - specifies the type of a marker (by declaring a new type) - org.eclipse.ui.editors.annotationTypes - declares a new type of annotation and maps it to a marker type - org.eclipse.ui.editors.markerAnnotationSpecification - Have field for the annotationTypes, and a bunch of fields - for declaring the default appearance of an annotation in the editor. - -The status of a proof is maintained with a tree of StepTuple objects. - ------------------------------------------------------------------------- -PARSE LISTENERS - -There are two ways to listen for new parse results. - -1. Listen for spec parses. This requires extending the class - SpecLifecycleParticipant and declaring the extending class to the - extension point org.lamport.tla.toolbox.spec. - -2. Listen for any new parse results. This requires implementing the - interface IParseResultListener and adding the listener to the - singleton instance of the class ParseResultBroadcaster using the - method ParseResultBroadcaster.addParseResultListener(). The singleton - instance of ParseResultBroadcaster can be obtained by calling - ParseResultBroadcaster.getParseResultBroadcaster() . ------------------------------------------------------------------------------ -GOTO vs. OPEN DECLARATION - On 25 Jun 2010, LL renamed the Open Declaration command to be - Goto Declaration. However, the classes and methods are still - named as if the command were called Open Declaration. It doesn't - seem to be worth the effort to refactor everything. -------------------------------------------------------------------------------- -PREFERENCES - -Defining a New Preference Page ------------------------------- -Use the extension org.eclipse.ui.preferencePages. The category specifies -the preference page within which is appears in the hierarchical -preferences menu. The top-level TLA+ preference pages have -category toolbox.ui.preferences.GeneralPreferencePage. The class -needs to implement IWorkbenchPreferencePage. All the Toolbox preference -pages also extend FieldEditorPreferencePage. - - -Defining a New Preference --------------------------- -1. In IXPreferenceConstants, for - - X = Editor, TLA2TeX, ... - - give the preference a name that is a string and then give that name - a name such as - - public static final String PREFERENCE_NAME = "preferenceName"; - - For the Editor, there is no IXPreferenceConstants page, and - the name is defined in EditorPreferencePage - -2. In XPreferencePage - - add the appropriate Field editor in the createFieldEditors() method by - - addField(new YFieldEditor(IXPreferenceConstants.PREFERENCE_NAME, - "&Text on preference page", - getFieldEditorFParent())) - - for Y = Boolean, String, Double, etc. For some values of Y, there - are additional arguments to the constructor. - -3. In XPreferenceInitializer, set its default value by adding - - store.setDefault(PREFERENCE_NAME, default value) - - to the initializeDefaultPreferences() method, where - store is the IPreferenceStore object obtained by calling - - Activator.getDefault().getPreferenceStore() - - or the equivalent - - PreferenceStoreHelper.getInstancePreferenceStore() - - For the editor, there is no EditorInitializer, and the - initializeDefaultPreferences() method is in PreferenceInitializer - -Getting the Value of a Preference ---------------------------------- - store.getY(IXPreferenceConstants.PREFERENCE_NAME); - ------ - -NOTE: The following email from Dan gives some additional information about -preferences - -The initializeDefaultPreferences() method for a particular plugin -should be called the first time the preference store for that plugin -is created. Remember that each plugin automatically has its own -preference store. I believe that the preference store for a plugin is -not created when the plugin is loaded. I think that instead, the -preference store is created the first time that it is needed. For the -prover plugin preference store, the creation of the launch prover -dialog is the first time that it is needed because that store is used -to store the value of the widgets in that dialog. I would guess, and -correct me if I'm wrong, that the initializeDefaultPreferences() -method of TLCPreferenceInitializer would be called if you launch TLC -before ever opening the TLC preference page. In general, search for -when the method getPreferenceStore() is called for a particular -plugin's activator. I think that the first time this is called, the -preference store is created and initializeDefaultPreferences() will be -called for that plugin's preference initializer. - -The ProverUIActivator is used to initialize default values for the -prover preference page for a different reason. The prover preference -page must use the preference store from the class EditorUI (I think -this is the class, but check the constructor for ProverPreferencePage -to be sure). It must use thid preference store because the eclipse -editors know to look there for the colors of markers. This is a -different preference store than the one provided by the prover plugin. -Since the preferences on ProverPreferencePage are not stored in the -prover plugin preference store, the prover preference store may not -have been created before the prover is launched from the toolbox. As -a result, the initializeDefaultPreferences() method of -ProverPreferenceInitializer may not be called before the prover is -launched. We need to initialize the default values for the -ProverPreferencePage elsewhere. Since the prover cannot be launched -until after the start method of ProverUIActivator is called, that -method seems like a reasonable place to initialize the default -preferences for ProverPreferencePage. - -If you put additional TLAPM preferences on a subpage of -ProverPreferencePage, then you can use the preference store provided -by the prover plugin. Then you can initialize the default preferences -in the initializeDefaultPreferences() method of -ProverPreferenceInitializer. This is a confusing design. I should -have noted all of this in comments. - ------- - -NOTE: Prover color preferences are done differently. See Dan's comments -in the start method of ProverUIActivator. Also, here is what Dan wrote -about it in an email about a bug that was undiagnosed when he wrote it: - -The problem seems to be that the editor is not being notified when the -color of a leaf step marker is restored to the default value. I think -that when you restore default values, the editor is notified that the -color of non leaf steps has changed but is not notified that the color -of leaf steps has changed. Closing and reopening the editor forces -the editor to check the preferences for the correct color of leaf and -non leaf steps so that the correct color is displayed. I thought I -had fixed this problem, but apparently not. - -The step coloring markers are set up in the following way. There are -two marker types for each logical color. One marker type for a given -logical color is put on leaf steps that should have that logical -color. The other marker type is put on non leaf steps that should -have that logical color. Each marker type has a key in the editor -preference store that maps to the physical color that the marker -should take. - -The color field editor for logical color x on the TLAPS preference -page corresponds to the color preference key for the non leaf marker -type for logical color x. This means that when the value of that -field editor is changed, the value of the color preference key for the -non leaf marker type for logical color x is changed in the preference -store. Listeners are notified of such a change. The editor is -automatically one of the listeners, so it is notified that non leaf -markers for logical color x should change physical colors. - -However, this does not yet change the physical color value of the -color preference key for the leaf steps of logical color x. We do -this by making the ProverPreferencePage a listener to changes to -preference values. When ProverPreferencePage is notified of a change -to the value of the non leaf color for logical color x, it changes the -value of the leaf color for logical color x. The editor should again -be notified, so that the value of leaf markers changes. However, -there is something strange that occurs when restoring default values. -It sounds like the editor is not being notified. Much of this is -explained in the class comments for ProverPreferencePage and the -comments for the method propertyChange(), which is called when a -preference value in the editor preference store changes. These -comments should explain what I thought the fix was. Perhaps that -could provide some evidence into what is really going on. - ------------------------------------------------------------------------- -MARKERS - To see how the Show Uses marker type is declared, see - org.lamport.tla.tool.editor.basic/plugin.xml and search backwards - from the end for showUse. You can then go to the Extensions to see - where those declarations appear. - - The properties of the Show Uses marker are specified in its entry - under org.eclipse.ui.editors.markerAnnotationSpecification. In - addition to the color, note the overviewRulerPreferenceValue. Setting - that true makes the markers appear in the right-hand overview - column. - - See HowUsesHandler for the code needed to create and display - the markers. Note that for some unknown reason, you have to - use the IMarker.setAttributes(Map) method to set the location. - Just using the methods to set a single attribute at a time - sets those attributes but doesn't cause the marker to be shown. - Note also the EditorUtil.getMarkerPosition(IMarker) method to - find the current location of a marker. - - Here's how to make markers persistent (so they are remembered - when the Toolbox is closed): - On the extension point org.eclipse.core.resources.markers, which - declares a new marker type, you can right click to select New > - persistent. Set the value to be true to make that type persistent. - Note that this value is not inherited by subtypes of markers. - -HANDLING OF PARSING ERRORS WITH MARKERS - -The handling of parsing errors and the hyperlinking of TLC errors is -fairly simple. Parsing errors are handled using IMarkers that are -specified in the main plugin's plugin.xml file. Hyperlinking of TLC -errors is done using all of the static methods in the class -TLCUIHelper (except for setHelp()). - ------------------------------------------------------------------- -THE TRACE EXPLORER AND TLC ERROR WINDOW - -Some information about how error information is generated is contained -in an email from Dan Ricketts to LL on 11 Jan 2011. - ------------------------------------------------------------------- -PLATFORM-DEPENDENT CODE - -To put in platform dependent code, you use the method -org.eclipse.core.runtime.Platform.getOS(). It returns one of the -string constants in the Platform class. That method is used elsewhere -in the Toolbox. I cannot remember where off the top of my head, so -just search for it if you want to look at examples. ------------------------------------------------------------------------ -HOW TO IMPLEMENT LIBRARY DIRECTORIES - -The system property "TLA-Library" specifies an additional list -of directories, separated by a path-separator (";" on Windows, ":" on Unix), -in which SANY looks for modules (besides the spec's directory and the -standard modules' directory). The order of search seems to be (i) spec's -directory, (ii) library directories, in the order listed, (iii) standard modules' -directory. - -The Toolbox seems to call the parser from one of the multiple parseModule -methods in ModuleParserLauncher. It should be possible to add a list -of library directories by calling System.setProperty before calling -the Parser. - -To add a user-specifiable list of libraries (in particular for a -TLAPS library), add both a global and a spec-specific preference -to specify the library folder. To add a global preference, see the -"Defining a New Preference" note above. To add a spec preference, copy -what's done in SpecPropertyPage for the "PlusCal call arguments" to -create a field editor with which the user sets the preference named - - IPreferenceConstants.SOME_NAME_FOR_THE_PREFERENCE - -To get the spec-specific preference, it looks like one first gets the project -with - - IProject project = Activator.getSpecManager().getSpecLoaded().getProject() - -and then calls - - PreferenceStoreHelper.getProjectPreferenceStore(project). - getString(IPreferenceConstants.SOME_NAME_FOR_THE_PREFERENCE); - -If this doesn't work, see the TranslatorJob constructor. - -Note: The method ...toolbox.spec.Module.isStandardModule is currently used -to determine if something is a standard module, and to eliminate it from -some things shown to the user if it isn't. I suspect that many things will -break if the Toolbox tries to access any file not in the spec's directory--which -includes library files. I think that this will have to be modified -to return true for a library module. Figuring out how to do that -will probably require debugging to see what path names are returned -by the class's getAbsolutePath method and how they compare with -the library folder names. ----------------------------------------------------------------------- -SYNTAX COLORING (KEYWORDS, COMMENTS, ETC.) - -Setting the Colors. ------------------- -See TLASourceViewerConfiguration and TLAColorProvider. It should be easy -to make the colors settable by preferences, but probably only if you have to -restart the Toolbox to change the preferences. - -Partitioning a TLA+ Document. ------------------------- -Done by the TLAFastPartitioner calling TLAPartitionScanner methods. -See comments in TLAFastPartitioner. - -------------------------------------------------------------------------- -HOW TO GET A... - SpecObj - From IFile: ResourceHelper.getValidParseResult(iFile).getSpecObj() - Spec - From nothing (the current spec): - Activator.getSpecManager().getSpecLoaded() - Has not been tested; don't know if it really works. - From the name: Activator.getSpecManager().getSpecByName(specName) - IFile - From TLAEditor: ((FileEditorInput) tlaEditor.getEditorInput()).getFile() - From String moduleName (IFile) ResourceHelper.getResourceByModuleName(moduleName) - ModuleNode - From String moduleName: ResourceHelper.getModuleNode(moduleName) - String moduleName - From TLAEditor: tlaEditor.getModuleName() - From ModuleNode: moduleNode.getName() - ResourceHelper.getModuleNames() returns all user-module names - TLAEditor - with focus: EditorUtil.getTLAEditorWithFocus() - from IFile: See the comments preceding - IEditorPart editor = UIHelper... - in UIHelper. - Shell - Of the currently active window: UIHelper.getShellProvider().getShell) -------------------------------------------------------------------- -INDEX TO USEFUL COMMENTS IN THE CODE - -Open Declaration Command - Its implementation is described in TLAEditor$OpenDeclarationHandler. - This also describes the implementation of hyperlinks displayed by - holding Control key and moving over the module. - -Attaching a Listener to Something - The implementation of the graphing of columns of the "State space - progress" section of the Model Checking Results page adds the listeners - in ResultPage.createTableColumns. The listener and interesting - stuff is documented in ResultPageColumnListener subclass of - ResultPage. - -Popping up a dialog. - - For a simple yes/no choice dialog, see DeleteSpecHandler. - (No comments, but the code is obvious.) - - - For a yes/no choice dialog with an extra input that sets - a preference, see ForgetSpecHandler. - - - For a pop-up that handles typed input (and modifies what - it shows based on the input), see ShowDeclarationsHandler. +To Fix Eclipse if it can't find a bunch of imported classes: + Try re-setting the target platform. To do so, open the + TLAToolbox.target platform and click the "Set as Target Platform" + label in the right upper corner. After a while (Eclipse might + download stuff), it should re-compile your workspace. + If that doesn't work, select all the URLs shown in that window + and click on Update. + +To tell Eclipse where to find a new JRE: + Go to Window > Preferences > Java > Installed JREs you can add it + +To find the methods that Eclipse is using to implement something: + Alt-Shift-F1: + Brings up information about active editor of view or dialog. + Alt-Shift-F2 + click on menu & click on menu item: + Brings up information about implementation of some menu items. + +org.lamport.tla.toolbox.tool.tlc.ui + ui/editor + ModelEditor + Sets up the Model Editor, including its pages. The individual + pages are set up by the + MainModelPage, AdvancedModelPage, and ResultPage; + + loadData() initializes the editor. + validate() is called through some plug-in magic whenever + the spec is parsed (but probably only if the parse is correct) + and probably when the user has made some change in the model editor. + It is also run when the model editor is initialized. + +Various pieces of code are notified when the spec is opened, parsed, +and closed by listeners registered to the extension point +org.lamport.tla.toolbox.spec of the plugin org.lamport.tla.toolbox. +This extension point interacts via a class that extends the class +SpecLifecycleParticipant. So, finding all classes that extend this +should permit one to find every place that gets notified when the spec +is opened, closed, or parsed. However, this is all done by so many +levels of indirection that we have not succeeded in doing this. +For example, ModelEditor.validate() is called when the module is parsed, +and this must be through that extension point. But we have not discovered +the path from the extension point to that method. + +------- + +The spec status indicator in the lower-right corner is apparently +set by ParseStatusContributionItem . update(). + + +---------- +How to create a distribution -- This is obsolete. See the file +general/docs/microsoft-release.txt + +Go to org....product.standalone + This contains no functionality, but "builds application stuff". + +The following stuff needs to be done just once for each computer. + +Open standalone.product / Overview tab + Need to specify target platform. + By default, Eclipse uses itself as the target + platform. + Need to define a separate copy of the distribution + as the target platform. + + + 1. Unzip Eclipse platform from eclipse-rcp-galileo-win32.zip into somewhere as the + the target platform. I've put it in tla/target-platform + + 2. Go to Eclipse download page, download + Eclipse delta pack. Usually in + "Other Downloads". (search for "delta") + Extract it to a new directory. I've put it in tla/delta-pack, merging + with the stuff extracted above. + + 3. Go to Window/ preferences/Plug-in Development/Target Platform + + Hit Add. + Choose "nothing" & hit next + In Target Contact + Enter name, like "Galileo 3.5" + Location hit add: + select Installation & hit next + Select target-platform directory .../tla/target-platform/eclipse + and hit finish [included 487 of 487] + Hit add again + select Directory and hit next + select .../delta-pack/eclipse directory and hit + finish [61 of 61] + Hit finish. + Check "Galileo 3.5" in Target Platform to make it active. + Hit OK, and watch it rebuild. + +The following stuff needs to be done for each release. + + 1. Open standalone product / overview page + 1a. Select New button next to Product: and set + Application to org.lamport.tla.toolbox.application + + 2. Hit Synchronizing on the Testing section + 3. Hit on Eclipse Product Export Wizard + (export to windows, linux stk/x86, macosx cocoa/x86) +----------------------------------------------------------------------------- +How to put a new version up on the MSR-INRIA Web site. (This is also obsolete.) + +1. Go to the web site's project page. +2. Login +3. Select "contents", right-click on the html release page, and open it. +4. Select edit, and choose "html" on the editing panel to get a reasonable + format. +5. Make any necessary changes to the page and save them. +6. Go back to the "contents" page and use it to upload the new versions. + They will appear in the same directory as the html page, with the + name by which they were uploaded. +7. If you created a new file, go to Properties and click the box that + makes it not appear in the navigation pane. Save. + +The current preliminary release web page is reachable from the outside +world at: + +http://www.msr-inria.inria.fr/Projects/tools-for-formal-specs/preliminary-release/ + +------------------------------------------------------------------------------ +HELP + +INTRO +Intro page in : org.lamport.tla.toolbox.product.standalone / intro/root.xhtml. + Source page for each topic is in org.lamport.tla.toolbox.product.standalone /intro/*.xhtml + Each such topic page has an href link whose id field must be registered (linking + it to the page) by adding the appropriate stuff to + org.lamport.tla.toolbox.product.standalone /intro/introContent.xml + +ATTACHING A PAGE TO THE HELP BUTTON + +org.lamport.tla.toolbox.editor.basic/helpContexts.xml contains a <context> entry with + id "main_editor_window" : + <context id="main_editor_window"> + <description>This is a the basic editor. It is used to edit the TLA+ module files.</description> + <topic label="Understanding Problems" href="../org.lamport.tla.toolbox.doc/html/reference/maintopic.html" /> + </context> + +The constructor of TLAEditor contains + setHelpContextId("org.lamport.tla.toolbox.editor.basic.main_editor_window"); + + org.lamport.tla.toolbox.editor.basic/plugin.xml contains this relevant extensions: + + <extension + point="org.eclipse.help.contexts"> + <contexts + file="helpContexts.xml" + plugin="org.lamport.tla.toolbox.editor.basic"> + </contexts> + </extension> + +PUTTING A PAGE IN THE TABLE OF CONTENTS + +To add a top-level section in the toc, add something like + + <topic label="Concepts"> + <anchor id="concepts"/> + </topic> + +to org.lamport.tla.toolbox.doc/toc.xml . The label is the toc entry. The anchor +is used to attach things to that entry. Also, add to +org.lamport.tla.toolbox.doc/plugin.xml the indicated <toc> ... </toc> entry: + + <plugin> + <extension + point="org.eclipse.help.toc"> + ... + <toc + file="tocconcepts.xml"> + </toc> + ... + </extension> + </plugin> + +Subtopics are attached in + org.lamport.tla.toolbox.doc/tocconcepts.xml, which contains + + <toc label="Concepts" link_to="toc.xml#concepts"> + <topic label="Model" href="html/concepts/model.html"> + </topic> + <topic href="html/concepts/specification.html" label="Specification"> + </topic> + <topic href="html/concepts/module.html" label="Module"> + </topic> + </toc> + +The toc label seems to be irrelevant. The topic labels specify the TOC subtopics. + +To get a subtopic, put a <topic label=... href=... /> inside a topic + + + +ATTACHING A HELP BUTTON TO AN EDITOR/PAGE/DIALOGUE + +The help button on the Eclipse plug.xml editor uses lines 82-91 of +PDEFormPage class in package org.eclipse.pde.internal.ui.editor to attach the button. +This was found by trying Shift-Alt-F1 on the page in the Eclipse editor and going to +the ExtensionsPage, which extends PDEFormPage. + +-------------------------------------------------------------- +GETTING NOTIFIED WHEN A MODULE IS SAVED + +When a module is saved, the performSave() of the org.tla.toolbox.editor.basic.TLAEditor object +is called. There's a default method in a superclass, but one can override it to +do whatever else you want to do on a save. There's also a performSaveAs() method. +-------------------------------------------------------------- +RAISING AN ERROR OR WARNING WINDOW + + MessageDialog.openWarning(UIHelper.getShellProvider().getShell(), window-title, message) + +If that doesn't work (probably throwing a null-pointer exception), see +ResourceHelper.ErrorMessageRunnable. +------------------------------------------------------------ +LOGGING AN ERROR ON THE TOOLBOX'S LOG + + Activator.getDefault().logError +------------------------------------------------------------ +ADDING A BUTTON TO A MENU + +In the Extensions tab of the appropriate plugin.xml file: + +- Under org.eclipse.ui.menus, add a command to the appropriate + menuContribution item for the menu item's group. The commandId + ties the menu item to its handler. + +- Under org.eclipse.ui.handlers, add a handler that extends + AbstractHandler and implements IHandler, where the execute method + specifies what selecting the item should do. +------------------------------------------------------------ + +ASSIGNMENT OBJECTS AND WIZARDS + +An assignment object: + - Is used for constant assignments and for definition overrides. + - For the constant assignments, it is constructed by the loadData + method of MainModlePage in the call of + FormHelper.setSerializedInput, which puts it into the TableViewer + MainModelPage.constantTable + +The user can edit an Assignment object with an AssignmentWizard. When +he clicks on the Add or Edit button of the Constant Assignments +section, it will create an AssignmentWizard. + +The button is added in the ValidateableTableSectionPart object (the +super for Validateable...), and fSelectionListener is added to it. +Note that the doAdd and doEdit methods are overridden by the methods +in the subclass ValidateableConstant. These methods call +doEditFormula, which creates the AssignmentWizard. + +(See www.eclipse.org about dialogs and wizards) + +The AssignmentWizard has two AssignmentWizardPages that are created in +the addPages() method. In AssignmentWizardPages there is the +createControl method, which is called when the page is added to the +Wizard. Note: createControl is constructing the widgets that form the +wizard page. They are children of container, which is a child of the +parent argument. The last line, setControl(container), sets the +container as the control associated with the wizard page. + +To implement the help button on the dialog, we need to set the help on +the container. + +Further information on how definition overrides are managed can be +found in the comments for +FilteredDefinitionSelectionDialog.fillContentProvider. + +------------------------------------------------------------------- +Explanation of various items in no particular order by DR + +SANY is executed in the private method parseModule in +ModuleParserLauncher. This method is the place to access all modules +that are ever parsed. The method itself will not necessarily be +called on every modules that is parsed, but within the method, an +Enumerate object is generated by the SANY java code. This object +contains all modules that were ultimately parsed because SANY parsed +the module for which parseModule() is called. These modules are +either extended or instanced by the module for which parseModule() is +called. + +There is a good explanation of resource change listeners in the +article "How You've Changed!" on www.eclipse.org/articles. In short, +the method resourceChanged in any implementation of a resource change +listener is called when a resource change event for which that +listener is registered occurs. That method can access an +IResourceDelta tree which gives all resources in the workspace that +have changed. This tree can be traversed using an implementation of +IResourceDeltaVisitor. + +ParserDependancyStorage gives access to all user modules that are +relevant to the spec based on the most recent parse. If the most +recent parse resulted in an error, then ParserDependancyStorage will +not contain the modules on which the root module depends or the root +module itself. This makes sense because without a successful parse, +it cannot be determined on which modules the root module depends. + +An IResource is the class that represents a resource in the +workspace. There is a good explanation of resources in the Eclipse +Help Platform Plug-in Developer Guide Programmer's Guide. A IResource +allows setting persistent and session properties. Persistent +properties will last over startup and shutdowns of the toolbox while +session properties will be erased when the toolbox is shut down. +Right now, the time of the last build for a resource is set as a +persistent property in the private parseModule method in +ModuleParserLauncher. + +The basic principle behind designing the widgets in the toolbox is to +have three components: a viewer, a content provider, and a class +that represents the data to appear. The viewer does not know how the +data is represented that it is to present. The content provider takes +instances of the class (Assignment for example) that represents the +data and adds it to the viewer. An ILabelProvider is used to +determine how these instances are displayed in a viewer. +------------------------------------------------------------------- +STRUCTURE OF THE prover plugin + +Command handlers that launch a proof construct and schedule a + +ProverJob. + The running ProverJob streams the prover's output to a + +BroadcastStreamListener. + This calls the constructors to create + ConsoleProverProcessOutputSink and TagBasedTLAPMOutputIncrementalParser + objects and calls their initializeSink methods to initialize them + and then calls appendText methods when prover's input arrives. + +TagBasedTLAPMOutputIncrementalParser + The appendText() method creates TLAPMMessage objects and calls + appropriate methods in ProverHelper to process the messages, according + to their type, which is one of: + ObligationNumberMessage: reports total number of obligations. + ObligationStatusMessage: reports status of obligation, starting with + status TO_BE_PROVED + StepStatusMessage: Reports status of a step. + +Specifying color preferences is done through two extension points: +(This may be somewhat obsolete with the introduction of ColorPredicate +objects.) + org.eclipse.core.resources.marker + specifies the type of a marker (by declaring a new type) + org.eclipse.ui.editors.annotationTypes + declares a new type of annotation and maps it to a marker type + org.eclipse.ui.editors.markerAnnotationSpecification + Have field for the annotationTypes, and a bunch of fields + for declaring the default appearance of an annotation in the editor. + +The status of a proof is maintained with a tree of StepTuple objects. + +------------------------------------------------------------------------ +PARSE LISTENERS + +There are two ways to listen for new parse results. + +1. Listen for spec parses. This requires extending the class + SpecLifecycleParticipant and declaring the extending class to the + extension point org.lamport.tla.toolbox.spec. + +2. Listen for any new parse results. This requires implementing the + interface IParseResultListener and adding the listener to the + singleton instance of the class ParseResultBroadcaster using the + method ParseResultBroadcaster.addParseResultListener(). The singleton + instance of ParseResultBroadcaster can be obtained by calling + ParseResultBroadcaster.getParseResultBroadcaster() . +----------------------------------------------------------------------------- +GOTO vs. OPEN DECLARATION + On 25 Jun 2010, LL renamed the Open Declaration command to be + Goto Declaration. However, the classes and methods are still + named as if the command were called Open Declaration. It doesn't + seem to be worth the effort to refactor everything. +------------------------------------------------------------------------------- +PREFERENCES + +Defining a New Preference Page +------------------------------ +Use the extension org.eclipse.ui.preferencePages. The category specifies +the preference page within which is appears in the hierarchical +preferences menu. The top-level TLA+ preference pages have +category toolbox.ui.preferences.GeneralPreferencePage. The class +needs to implement IWorkbenchPreferencePage. All the Toolbox preference +pages also extend FieldEditorPreferencePage. + + +Defining a New Preference +-------------------------- +1. In IXPreferenceConstants, for + + X = Editor, TLA2TeX, ... + + give the preference a name that is a string and then give that name + a name such as + + public static final String PREFERENCE_NAME = "preferenceName"; + + For the Editor, there is no IXPreferenceConstants page, and + the name is defined in EditorPreferencePage + +2. In XPreferencePage + + add the appropriate Field editor in the createFieldEditors() method by + + addField(new YFieldEditor(IXPreferenceConstants.PREFERENCE_NAME, + "&Text on preference page", + getFieldEditorFParent())) + + for Y = Boolean, String, Double, etc. For some values of Y, there + are additional arguments to the constructor. + +3. In XPreferenceInitializer, set its default value by adding + + store.setDefault(PREFERENCE_NAME, default value) + + to the initializeDefaultPreferences() method, where + store is the IPreferenceStore object obtained by calling + + Activator.getDefault().getPreferenceStore() + + or the equivalent + + PreferenceStoreHelper.getInstancePreferenceStore() + + For the editor, there is no EditorInitializer, and the + initializeDefaultPreferences() method is in PreferenceInitializer + +Getting the Value of a Preference +--------------------------------- + store.getY(IXPreferenceConstants.PREFERENCE_NAME); + +----- + +NOTE: The following email from Dan gives some additional information about +preferences + +The initializeDefaultPreferences() method for a particular plugin +should be called the first time the preference store for that plugin +is created. Remember that each plugin automatically has its own +preference store. I believe that the preference store for a plugin is +not created when the plugin is loaded. I think that instead, the +preference store is created the first time that it is needed. For the +prover plugin preference store, the creation of the launch prover +dialog is the first time that it is needed because that store is used +to store the value of the widgets in that dialog. I would guess, and +correct me if I'm wrong, that the initializeDefaultPreferences() +method of TLCPreferenceInitializer would be called if you launch TLC +before ever opening the TLC preference page. In general, search for +when the method getPreferenceStore() is called for a particular +plugin's activator. I think that the first time this is called, the +preference store is created and initializeDefaultPreferences() will be +called for that plugin's preference initializer. + +The ProverUIActivator is used to initialize default values for the +prover preference page for a different reason. The prover preference +page must use the preference store from the class EditorUI (I think +this is the class, but check the constructor for ProverPreferencePage +to be sure). It must use thid preference store because the eclipse +editors know to look there for the colors of markers. This is a +different preference store than the one provided by the prover plugin. +Since the preferences on ProverPreferencePage are not stored in the +prover plugin preference store, the prover preference store may not +have been created before the prover is launched from the toolbox. As +a result, the initializeDefaultPreferences() method of +ProverPreferenceInitializer may not be called before the prover is +launched. We need to initialize the default values for the +ProverPreferencePage elsewhere. Since the prover cannot be launched +until after the start method of ProverUIActivator is called, that +method seems like a reasonable place to initialize the default +preferences for ProverPreferencePage. + +If you put additional TLAPM preferences on a subpage of +ProverPreferencePage, then you can use the preference store provided +by the prover plugin. Then you can initialize the default preferences +in the initializeDefaultPreferences() method of +ProverPreferenceInitializer. This is a confusing design. I should +have noted all of this in comments. + +------ + +NOTE: Prover color preferences are done differently. See Dan's comments +in the start method of ProverUIActivator. Also, here is what Dan wrote +about it in an email about a bug that was undiagnosed when he wrote it: + +The problem seems to be that the editor is not being notified when the +color of a leaf step marker is restored to the default value. I think +that when you restore default values, the editor is notified that the +color of non leaf steps has changed but is not notified that the color +of leaf steps has changed. Closing and reopening the editor forces +the editor to check the preferences for the correct color of leaf and +non leaf steps so that the correct color is displayed. I thought I +had fixed this problem, but apparently not. + +The step coloring markers are set up in the following way. There are +two marker types for each logical color. One marker type for a given +logical color is put on leaf steps that should have that logical +color. The other marker type is put on non leaf steps that should +have that logical color. Each marker type has a key in the editor +preference store that maps to the physical color that the marker +should take. + +The color field editor for logical color x on the TLAPS preference +page corresponds to the color preference key for the non leaf marker +type for logical color x. This means that when the value of that +field editor is changed, the value of the color preference key for the +non leaf marker type for logical color x is changed in the preference +store. Listeners are notified of such a change. The editor is +automatically one of the listeners, so it is notified that non leaf +markers for logical color x should change physical colors. + +However, this does not yet change the physical color value of the +color preference key for the leaf steps of logical color x. We do +this by making the ProverPreferencePage a listener to changes to +preference values. When ProverPreferencePage is notified of a change +to the value of the non leaf color for logical color x, it changes the +value of the leaf color for logical color x. The editor should again +be notified, so that the value of leaf markers changes. However, +there is something strange that occurs when restoring default values. +It sounds like the editor is not being notified. Much of this is +explained in the class comments for ProverPreferencePage and the +comments for the method propertyChange(), which is called when a +preference value in the editor preference store changes. These +comments should explain what I thought the fix was. Perhaps that +could provide some evidence into what is really going on. + +------------------------------------------------------------------------ +MARKERS + To see how the Show Uses marker type is declared, see + org.lamport.tla.tool.editor.basic/plugin.xml and search backwards + from the end for showUse. You can then go to the Extensions to see + where those declarations appear. + + The properties of the Show Uses marker are specified in its entry + under org.eclipse.ui.editors.markerAnnotationSpecification. In + addition to the color, note the overviewRulerPreferenceValue. Setting + that true makes the markers appear in the right-hand overview + column. + + See HowUsesHandler for the code needed to create and display + the markers. Note that for some unknown reason, you have to + use the IMarker.setAttributes(Map) method to set the location. + Just using the methods to set a single attribute at a time + sets those attributes but doesn't cause the marker to be shown. + Note also the EditorUtil.getMarkerPosition(IMarker) method to + find the current location of a marker. + + Here's how to make markers persistent (so they are remembered + when the Toolbox is closed): + On the extension point org.eclipse.core.resources.markers, which + declares a new marker type, you can right click to select New > + persistent. Set the value to be true to make that type persistent. + Note that this value is not inherited by subtypes of markers. + +HANDLING OF PARSING ERRORS WITH MARKERS + +The handling of parsing errors and the hyperlinking of TLC errors is +fairly simple. Parsing errors are handled using IMarkers that are +specified in the main plugin's plugin.xml file. Hyperlinking of TLC +errors is done using all of the static methods in the class +TLCUIHelper (except for setHelp()). + +------------------------------------------------------------------ +THE TRACE EXPLORER AND TLC ERROR WINDOW + +Some information about how error information is generated is contained +in an email from Dan Ricketts to LL on 11 Jan 2011. + +------------------------------------------------------------------ +PLATFORM-DEPENDENT CODE + +To put in platform dependent code, you use the method +org.eclipse.core.runtime.Platform.getOS(). It returns one of the +string constants in the Platform class. That method is used elsewhere +in the Toolbox. I cannot remember where off the top of my head, so +just search for it if you want to look at examples. +----------------------------------------------------------------------- +HOW TO IMPLEMENT LIBRARY DIRECTORIES + +The system property "TLA-Library" specifies an additional list +of directories, separated by a path-separator (";" on Windows, ":" on Unix), +in which SANY looks for modules (besides the spec's directory and the +standard modules' directory). The order of search seems to be (i) spec's +directory, (ii) library directories, in the order listed, (iii) standard modules' +directory. + +The Toolbox seems to call the parser from one of the multiple parseModule +methods in ModuleParserLauncher. It should be possible to add a list +of library directories by calling System.setProperty before calling +the Parser. + +To add a user-specifiable list of libraries (in particular for a +TLAPS library), add both a global and a spec-specific preference +to specify the library folder. To add a global preference, see the +"Defining a New Preference" note above. To add a spec preference, copy +what's done in SpecPropertyPage for the "PlusCal call arguments" to +create a field editor with which the user sets the preference named + + IPreferenceConstants.SOME_NAME_FOR_THE_PREFERENCE + +To get the spec-specific preference, it looks like one first gets the project +with + + IProject project = Activator.getSpecManager().getSpecLoaded().getProject() + +and then calls + + PreferenceStoreHelper.getProjectPreferenceStore(project). + getString(IPreferenceConstants.SOME_NAME_FOR_THE_PREFERENCE); + +If this doesn't work, see the TranslatorJob constructor. + +Note: The method ...toolbox.spec.Module.isStandardModule is currently used +to determine if something is a standard module, and to eliminate it from +some things shown to the user if it isn't. I suspect that many things will +break if the Toolbox tries to access any file not in the spec's directory--which +includes library files. I think that this will have to be modified +to return true for a library module. Figuring out how to do that +will probably require debugging to see what path names are returned +by the class's getAbsolutePath method and how they compare with +the library folder names. +---------------------------------------------------------------------- +SYNTAX COLORING (KEYWORDS, COMMENTS, ETC.) + +Setting the Colors. +------------------ +See TLASourceViewerConfiguration and TLAColorProvider. It should be easy +to make the colors settable by preferences, but probably only if you have to +restart the Toolbox to change the preferences. + +Partitioning a TLA+ Document. +------------------------ +Done by the TLAFastPartitioner calling TLAPartitionScanner methods. +See comments in TLAFastPartitioner. + +------------------------------------------------------------------------- +HOW TO GET A... + SpecObj + From IFile: ResourceHelper.getValidParseResult(iFile).getSpecObj() + Spec + From nothing (the current spec): + Activator.getSpecManager().getSpecLoaded() + Has not been tested; don't know if it really works. + From the name: Activator.getSpecManager().getSpecByName(specName) + IFile + From TLAEditor: ((FileEditorInput) tlaEditor.getEditorInput()).getFile() + From String moduleName (IFile) ResourceHelper.getResourceByModuleName(moduleName) + ModuleNode + From String moduleName: ResourceHelper.getModuleNode(moduleName) + String moduleName + From TLAEditor: tlaEditor.getModuleName() + From ModuleNode: moduleNode.getName() + ResourceHelper.getModuleNames() returns all user-module names + TLAEditor + with focus: EditorUtil.getTLAEditorWithFocus() + from IFile: See the comments preceding + IEditorPart editor = UIHelper... + in UIHelper. + Shell + Of the currently active window: UIHelper.getShellProvider().getShell) +------------------------------------------------------------------- +INDEX TO USEFUL COMMENTS IN THE CODE + +Open Declaration Command + Its implementation is described in TLAEditor$OpenDeclarationHandler. + This also describes the implementation of hyperlinks displayed by + holding Control key and moving over the module. + +Attaching a Listener to Something + The implementation of the graphing of columns of the "State space + progress" section of the Model Checking Results page adds the listeners + in ResultPage.createTableColumns. The listener and interesting + stuff is documented in ResultPageColumnListener subclass of + ResultPage. + +Popping up a dialog. + - For a simple yes/no choice dialog, see DeleteSpecHandler. + (No comments, but the code is obvious.) + + - For a yes/no choice dialog with an extra input that sets + a preference, see ForgetSpecHandler. + + - For a pop-up that handles typed input (and modifies what + it shows based on the input), see ShowDeclarationsHandler. diff --git a/org.lamport.tla.toolbox.doc/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.doc/META-INF/MANIFEST.MF index 89939ce998431311287a8062fc35a66a44add039..caf06fea8e722ae7f050fd461a68358ebfc8b67f 100644 --- a/org.lamport.tla.toolbox.doc/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.doc/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TLA+ Toolbox Help Bundle-SymbolicName: org.lamport.tla.toolbox.doc; singleton:=true -Bundle-Version: 1.5.1.qualifier +Bundle-Version: 1.5.2.qualifier Bundle-RequiredExecutionEnvironment: J2SE-1.4 Bundle-Vendor: Simon Zambrovski, Leslie Lamport Bundle-ActivationPolicy: lazy diff --git a/org.lamport.tla.toolbox.doc/html/cloudtlc/index.html b/org.lamport.tla.toolbox.doc/html/cloudtlc/index.html index 587e08db2fef5d91d86dbbfebfa873bb3607e0bc..422635bd70b426180390a3e90fde5f2e3ea0a21f 100644 --- a/org.lamport.tla.toolbox.doc/html/cloudtlc/index.html +++ b/org.lamport.tla.toolbox.doc/html/cloudtlc/index.html @@ -48,13 +48,18 @@ Using cloud based TLC launches compute instances at your cloud provider which ma </h1> <ul> <li> -Only supports a single cloud provider (<a class="URL" href="http://aws.amazon.com/ec2/">Amazon EC2</a>) as of now +Only supports two cloud providers (<a class="URL" href="http://aws.amazon.com/ec2/">Amazon EC2</a> and <a class="URL" href="https://azure.microsoft.com/en-us/">Microsoft Azure</a>) as of now +<ul> +<li> +On Azure, the VM instances just stoppes but does not <a class="URL" href="http://blogs.technet.com/b/gbanin/archive/2015/04/22/difference-between-the-states-of-azure-virtual-machines-stopped-and-stopped-deallocated.aspx">deallocate</a> automatically. Please make sure to manually shutdown the VM instance after TLC finishes. +</li> +</ul> </li> <li> Runs TLC in non-distributed mode on a single cloud instance only as of now </li> <li> -Only a single instance type (<a class="URL" href="http://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud#Instance_types">m4.2xlarge</a>) supported as of now +Only a single instance type per cloud (<a class="URL" href="http://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud#Instance_types">m4.2xlarge</a> and <a class="URL" href="https://azure.microsoft.com/en-us/pricing/details/virtual-machines/">D14</a>) supported as of now </li> <li> Cloud based distributed TLC cannot recover from a checkpoint @@ -83,8 +88,16 @@ export AWS_SECRET_ACCESS_KEY=6FDASAIG7DAS976TYDKHCGQAS5D\FA77 </li> -</ol> +Alternatively for Azure, after the one-time <a class="URL" href="https://azure.microsoft.com/en-us/documentation/articles/java-create-azure-website-using-java-sdk/#create-a-certificate">creation and installation of a certificate</a>, set: +<li> +<div class="listing"> +<pre class="listing">export AZURE_COMPUTE_CREDENTIALS=ThePasswordUsedForTheCertificate (The "password" supplied during certificate creation) +export AZURE_COMPUTE_IDENTITY=/absolute/path/to/the/azure.p12 +export AZURE_COMPUTE_SUBSCRIPTION=YourAzureSubscriptionId (Extract the ID from the <a class="URL" href="https://ms.portal.azure.com/#blade/Microsoft_Azure_Billing/SubscriptionsBlade">Azure portal under "Subscriptions"</a>) +</pre> +</div></li> +</ol> </li> <li> Create a specification and a model @@ -246,42 +259,96 @@ Figure 11 Final result loaded </li> </ol> + + <h1 class="Section"> <a class="toc" name="toc-Section-5">5</a> Common problems </h1> -<ul> -<li> -The Toolbox fails to start the cloud instance<ul> -<li> -Re-check your credentials -</li> -<li> -If your credentials are correct, please turn on debug logging (start the Toolbox executable with “toolbox -console -consolelog”) and send us the output. You might have encountered a bug in cloud based distributed TLC. -</li> - -</ul> - -</li> -<li> -The runtime statistics (web browser) indicate that TLC has finished model checking, but no result is sent via email.<ul> -<li> -Check your email spam folder if the model checking result has incorrectly been classified as spam -</li> -<li> -Another reason why mail delivery might fail, are too strict spam counter measures at the mail server level. You might want to try to use a different email address (domain part) in the future.<ul> -<li> -Copy & paste the MC.out content from the browser window into a plain text file and load it from there (see <a class="Reference" href="#enu:Import-the-MC.out">4↑</a>) -</li> +<ul> + <li> + The Toolbox fails to start the cloud instance + <ul> + <li> + Re-check your credentials + </li> + <li> + If your credentials are correct, please turn on debug logging (start the Toolbox executable with “toolbox -console -consolelog”) and send us the output. You might have encountered a bug in cloud based distributed TLC. + </li> + </ul> + </li> + + <li> + The cloud instance sends "system notification" emails warning that the remaining disc space is at a critical level: + <pre class="listing"> + <i>"tlc :: tlc :: Disk usage in percent + CRITICALs: / is 92.23..."</i> + </pre> + <ul> + <li> + A long running TLC process writes unexplored states to the system disk for later exploration. With very large models, this can potentially use up all of the available disk space. If this happens, TLC will crash. Thus, if you received such a warning, please create a checkpoint, <a class="URL" href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-add-volume-to-instance.html">attach a larger disk to your VM</a> and continue TLC by recovering from the checkpoint. + <ul> + <li> + <a class="URL" href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-connect-to-instance-linux.html#using-putty">Connect to the cloud instance with ssh</a> + </li> + <li> + <pre class="listing"> +# Download a jmx commandline client +wget http://crawler.archive.org/cmdline-jmxclient/cmdline-jmxclient-0.10.3.jar + </pre> + </li> + <li> + <pre class="listing"> +# Connect to the locally running TLC and have it create a checkpoint +java -jar cmdline-jmxclient-0.10.3.jar - localhost:5400 tlc2.tool:type=ModelChecker checkpoint + </pre> + </li> + <li> + Re-attach to the screen session running TLC and terminate TLC wth CTRL^C + </li> + <li> + <pre class="listing"> +# Restart TLC and have it recover from the checkpoint created +java -jar tla2tools.jar -recover /path/to/states/15-12-16-12-16-04/ + </pre> + </li> + </ul> + </li> + </ul> + </li> + <ul> + <li> + The cloud instance sends "system notification" email warnings directly after startup. + <ul> + <li> + Ignore these emails :-). + </li> + <li> + This happens primarily on Azure when the monitoring runs too earlier before the cloud instance is fully started. + </li> + </ul> + </li> + <li> + The runtime statistics (web browser) indicate that TLC has finished model checking, but no result is sent via email. + <ul> + <li> + Check your email spam folder if the model checking result has incorrectly been classified as spam + </li> + <li> + Another reason why mail delivery might fail, are too strict spam counter measures at the mail server level. You might want to try to use a different email address (domain part) in the future. For Azure, it works best to use an Outlook.com email address. + </li> + <ul> + <li> + Copy & paste the MC.out content from the browser window into a plain text file and load it from there (see <a class="Reference" href="#enu:Import-the-MC.out">4↑</a>) + </li> + </ul> + </ul> + </li> + </ul> </ul> -</li> - -</ul> -</li> -</ul> </div> </body> diff --git a/org.lamport.tla.toolbox.doc/html/model/about-models.html b/org.lamport.tla.toolbox.doc/html/model/about-models.html index a3fb8b15d383cdd317d01d738e962ebd68e1837e..09e709c6d63f9effc1cf2cc2a959df2bb78414b4 100644 --- a/org.lamport.tla.toolbox.doc/html/model/about-models.html +++ b/org.lamport.tla.toolbox.doc/html/model/about-models.html @@ -1,125 +1,139 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<!-- This is file org.lamport.tla.toobox.doc/html/model/about-models.html --> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> - <LINK href="../style.css" rel="stylesheet" type="text/css"> - -<title>Models</title> -</head> -<!-- a comment --> - -<body> -<h1>Models</h1> - - <p> -You run the TLC model checker on a <em>model</em> of your spec. The model -tells TLC what it should do. Here are the parts of the model -that you must explicitly choose:</p> -<ul> -<li>What the <a href="../spec/what-is.html">behavior spec</a> is.</li> - -<li>What TLC should check.</li> - -<li>What values to substitute for constant parameters.</li> -</ul> - <p> - The <em><a href="overview-page.html">Model Overview Page</a></em> - and <em><a href="advanced-page.html">Advanced Options Page</a></em> - help pages describe all the things that can go into a model. - </p> - -<p> - -A specification may contain multiple models. You can create a -new model, open an existing one, or create a clone (a copy with a new -name) of an existing model by clicking on the <samp>TLC Model -Checker</samp> menu. - -Alternatively, you can use the <a -href="../spec/opening-closing.html">Spec Explorer</a>. - -To create a new model, open the <a -href="../spec/opening-closing.html">Spec Explorer</a> and right-click -on the currently open spec. - -To open an existing model, open the spec's menu item (by clicking on -the <samp>+</samp> ) and double-clicking on the -model. - -Right-clicking on the model also gives you the options of renaming it, -deleting it, and creating a clone. </p> - -<p> -When you model check a spec, you should begin with a very small model--that is, a model for which the -values substituted for constants (and perhaps a <a href="advanced-page.html#state">constraint</a>) -yield a small set of reachable states. Before you've model checked it, your spec almost surely contains -quite a few errors. Using a small model, TLC can find the trivial ones very quickly. When it finds -no errors in a small model, you can then gradually increase the size of the model. -</p> - -<p> - -As you run your spec on larger and larger models, it's easy to forget -what you've done. - -Instead of changing a model and running it again, create a new model -and keep the old one. (This is easy to do by cloning the model, -as described above.) - -It will be easier to identify your models if you put the important -contents of the model in the model name--for example, you can name a -model </p> <pre> - N=3,Proc={a1,a2,a3} -</pre> -<A name="locking"> -To prevent accidentally running or editing a model, you can lock it. </A> - - -There is a lock icon and a key icon that you click to lock or -unlock the model. - -Those icons are in the upper-left part of each model-editor page. - - -The model is always locked while it is being run by TLC, and it remains -locked after TLC completes unless the run was shorter -than the - <a href="executing-tlc.html#auto-lock-time"><em>TLC Run Auto-Lock Time</em> preference</A>. - -This means that you can't view information in sections of a running -model that are closed. - -It's therefore a good idea to open all sections of the model -that you might want to look at before starting a TLC run that will -take a significant amount of time. - -If you forget to do this, you can clone the running model and examine -the clone. - - -<p> -The Toolbox saves a -copy of the version of the spec on which the model was run or checked, so you can look at it even after you -have changed the spec. -You can examine any modules in the saved version by clicking on the <i>Open Saved Module</i> command on the -<i>TLC Model Checker</i> menu when the model is selected. (The model is selected when one of its pages has the focus.) The -<a href="executing-tlc.html#MC">How TLC is Run</a> -help-page section tells you where -those saved modules are saved. - </p> -<hr> -<!-- delete rest of line to comment out -<dl> -<dt><b><font color=#0000c0>Subtopics</font></b></dt> -<dd> <A href=""> TOPIC </A></dd> -<dd> <A href=""> TOPIC </A></dd> -</dl> ---> -<!-- delete rest of line to comment out --> -<a href = "model.html">↑ Model Checking</a> -<!-- --> -</hr> - -</body> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<!-- This is file org.lamport.tla.toobox.doc/html/model/about-models.html --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <LINK href="../style.css" rel="stylesheet" type="text/css"> + +<title>Models</title> +</head> +<!-- a comment --> + +<body> +<h1>Models</h1> + + <p> +You run the TLC model checker on a <em>model</em> of your spec. The model +tells TLC what it should do. Here are the parts of the model +that you must explicitly choose:</p> +<ul> +<li>What the <a href="../spec/what-is.html">behavior spec</a> is.</li> + +<li>What TLC should check.</li> + +<li>What values to substitute for constant parameters.</li> +</ul> + <p> + The <em><a href="overview-page.html">Model Overview Page</a></em> + and <em><a href="advanced-page.html">Advanced Options Page</a></em> + help pages describe all the things that can go into a model. + </p> + +<p> + +A specification may contain multiple models. You can create a +new model, open an existing one, or create a clone (a copy with a new +name) of an existing model by clicking on the <samp>TLC Model +Checker</samp> menu. + + + +Alternatively, you can use the <a +href="../spec/opening-closing.html">Spec Explorer</a>. + +To create a new model, open the <a +href="../spec/opening-closing.html">Spec Explorer</a> and right-click +on the currently open spec. + +To open an existing model, open the spec's menu item (by clicking on +the <samp>+</samp> ) and double-clicking on the +model. + +Right-clicking on the model also gives you the options of renaming it, +deleting it, and creating a clone. </p> + +<p> +A convenient way to examine all the spec's models +is by selecting <a name="quick-access"><em>Quick Access</em></a> on the <em>Window</em> menu +or typing <code>Control+Shift+A</code>. This raises a window showing the models +and the beginning of their <em>Model Description</em> fields. Double-clicking +on a model opens it. +You can also open +imported modules from that window. Check out the window's <em>options</em> menu. +</p> + +<p> +When you model check a spec, you should begin with a very small model--that is, a model for which the +values substituted for constants (and perhaps a <a href="advanced-page.html#state">constraint</a>) +yield a small set of reachable states. Before you've model checked it, your spec almost surely contains +quite a few errors. Using a small model, TLC can find the trivial ones very quickly. When it finds +no errors in a small model, you can then gradually increase the size of the model. +</p> + +<p> + +As you run your spec on larger and larger models, it's easy to forget +what you've done. + +Instead of changing a model and running it again, create a new model +and keep the old one. (This is easy to do by cloning the model, +as described above.) + +You can identify your models with the <em>Model Description</em> field, +which is shown +It will be easier to identify your models if you put the important +contents of the model in the model name--for example, you can name a +model </p> <pre> + N=3,Proc={a1,a2,a3} +</pre> +<A name="locking"> +To prevent accidentally running or editing a model, you can lock it. </A> + + +There is a lock icon and a key icon that you click to lock or +unlock the model. + +Those icons are in the upper-left part of each model-editor page. + + +The model is always locked while it is being run by TLC, and it remains +locked after TLC completes unless the run was shorter +than the + <a href="executing-tlc.html#auto-lock-time"><em>TLC Run Auto-Lock Time</em> preference</A>. + +This means that you can't view information in sections of a running +model that are closed. + +It's therefore a good idea to open all sections of the model +that you might want to look at before starting a TLC run that will +take a significant amount of time. + +If you forget to do this, you can clone the running model and examine +the clone. + + +<p> +The Toolbox saves a +copy of the version of the spec on which the model was run or checked, so you can look at it even after you +have changed the spec. +You can examine any modules in the saved version by clicking on the <i>Open Saved Module</i> command on the +<i>TLC Model Checker</i> menu when the model is selected. (The model is selected when one of its pages has the focus.) The +<a href="executing-tlc.html#MC">How TLC is Run</a> +help-page section tells you where +those saved modules are saved. + </p> +<hr> +<!-- delete rest of line to comment out +<dl> +<dt><b><font color=#0000c0>Subtopics</font></b></dt> +<dd> <A href=""> TOPIC </A></dd> +<dd> <A href=""> TOPIC </A></dd> +</dl> +--> +<!-- delete rest of line to comment out --> +<a href = "model.html">↑ Model Checking</a> +<!-- --> +</hr> + +</body> </html> \ No newline at end of file diff --git a/org.lamport.tla.toolbox.doc/html/model/advanced-page.html b/org.lamport.tla.toolbox.doc/html/model/advanced-page.html index 8c057fc6ec47b3d19d2daabc16aed295f29a0ee4..2f5c0115eb4994ed0fffafd1c2dd1c9437a2e83b 100644 --- a/org.lamport.tla.toolbox.doc/html/model/advanced-page.html +++ b/org.lamport.tla.toolbox.doc/html/model/advanced-page.html @@ -268,7 +268,7 @@ Only sophisticated users who know what they are doing should specify other JVM a <h3>TLC command line parameters</h3> These are options given to TLC when it is run on the model. -An complete list of TLC options can be found in the +A complete list of TLC options can be found in the <code>tlatools > src > tlc2 > TLC.java</code> file in the <a href="https://tlaplus.codeplex.com/SourceControl/latest">CodePlex repository on diff --git a/org.lamport.tla.toolbox.doc/html/model/executing-tlc.html b/org.lamport.tla.toolbox.doc/html/model/executing-tlc.html index fa9e3dda8f05f73cd1fc39eaeba759f87ac5e1eb..94762a93b2c3bb9f695e9f2908f56804379d7136 100644 --- a/org.lamport.tla.toolbox.doc/html/model/executing-tlc.html +++ b/org.lamport.tla.toolbox.doc/html/model/executing-tlc.html @@ -239,8 +239,20 @@ then the model will remain <A href="about-models.html#locking">locked</A>. </p> + +<h3><A name="maximum-tail-length">Maximum Tail Length of Trace Explorer States</A></h3> +<p> + +The maximum number of states of a trace that is displayed in the Trace Explorer when +TLC reports an error. +If the trace has more states, only the last ones are shown. +Double-clicking at the beginning of the trace displays additional states. +The default value is 10000. + +</p> <h2><a name="crash">If Something Crashes</a></h2> +<!-- <p> If TLC crashes (a very rare occurrence), or if the Toolbox or your computer crashes and you restart the Toolbox, you could find the Toolbox in a state in which it says that @@ -251,7 +263,7 @@ TLC is still running even though it's not. If that happens, open the <a href="../spec/opening-closing.html"><em>Spec Explorer</em></a>, click on the <code>+</code> next to the open spec, right-click on the model in question, and select the <code>Repair</code> option. This should fix the problem. -</p> +</p> --> <p> If you stop the Toolbox in some abnormal way while it is running TLC--for example, diff --git a/org.lamport.tla.toolbox.doc/html/model/overview-page.html b/org.lamport.tla.toolbox.doc/html/model/overview-page.html index 8f05da03a2e6497507a81a34874f92a838ddfe75..4df87407a9a7d9e247d09aaccfe1b7f983665ef7 100644 --- a/org.lamport.tla.toolbox.doc/html/model/overview-page.html +++ b/org.lamport.tla.toolbox.doc/html/model/overview-page.html @@ -1,243 +1,248 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<!-- This is file org.lamport.tla.toobox.doc/html/model/overview-page.html --> - - -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> - <LINK href="../style.css" rel="stylesheet" type="text/css"> - -<title>Model Overview Page</title> -</head> -<!-- a comment here --> - -<body> -<h1>Model Overview Page</h1> - -<pre> -Contents - <a href="#what-is-behavior">What is the behavior spec?</a> - <a href="#what-to-check">What to check?</a> - <a href="#what-is-model">What is the model?</a> - <a href="#how-to-run">How to run?</a> - <a href="#distributed">Running in Distributed Mode</a> -</pre> - - - <P> -This model editor page is usually the only one you will use to create -and run your model. Here are what the different sections of the -page are for. - </P> - - -<h2><a name="what-is-behavior">What is the behavior spec?</a></h2> - -The behavior spec is the formula or pair of formulas that describe the -possible behaviors of the system or algorithm you want to check. (See -the <a href="../spec/what-is.html"><em>What Is A Spec?</em></a> help page.) -There are two ways to write the behavior spec: -<dl> -<dt><b>Init and Next</b></dt> -<dd>A pair of formulas that specify the initial state and the next-state relation, -respectively. -</dd> -</dd> -<dt><b>Single formula</b></dt> -<dd>A single temporal formula of the form <code>Init /\ [][Next]<sub>vars</sub> /\ F</code>, -where <code>Init</code> is the initial predicate, - <code>Next</sub></code> is the next-state -relation, <code>vars</code> -is the tuple of variables, -and <code>F</code> is an optional fairness formula. - -</dd> -</dl> -The only way to write a behavior spec that includes fairness is with a temporal formula. -<p> -You can also choose to specify <em>No behavior spec</em>. This is the only option -if the spec has no variables. With this option, TLC will just check assumptions and -evaluate a constant expression, if you have entered one in the -<a href="results-page.html#evaluate"><em>Evaluate Constant Expression</em> section</a> -of the <a href="results-page.html">Model Checking Results Page</a>. -</p> - -<h2><a name="what-to-check">What to check?</h2> -There are three kinds of properties of the behavior spec that TLC can check: -<h3>Deadlock</h3> - -A <em>deadlock</em> is said to occur in a state for which the next-state relation -allows no successor states. Termination is deadlock that is not considered an error. -If you want the behavior spec to allow termination, then you should uncheck the -deadlock option. (This is not necessary for the spec produced by translating a PlusCal algorithm, -because its next-state relation is written in a way that causes TLC not to consider normal -termination to be a deadlock.) - -<h3>Invariants</h3> - -An invariant is a state predicate that is true of all reachable states--that is, -states that can occur in a behavior allowed by the behavior spec. You can include -a list of invariants. The checking of each invariant can be enabled or disabled -by checking or unchecking its box. - - -<h3>Properties</h3> - -TLC can check if the behavior spec satisfies (implies) a temporal property, which -is expressed as a temporal-logic formula. You can specify a list of such properties, -each with a check-box for enabling or disabling its checking. - -<h2><a name="what-is-model">What is the model?</a></h2> - -The most basic part of a model is an assignment of values to declared constants. -To assign a value to a constant, either double-click on the constant or select it and click on the -<samp>Edit</samp> button. This will raise a pop-up dialog giving you the choice -of three ways to assign a value to it: -<dl> -<dt><b>Ordinary assignment</b></dt> -<dd>You can set the value of the constant to any constant TLA+ expression that -contains only symbols defined in the spec. The expression can even include -declared constants, as long as the value assigned to a constant does not depend -on that constant. (If there are circular dependencies, TLC will -produce a <samp>Java StackOverflowError</samp> error.) -</dd> -<br></br> -<dt><b>Model value</b></dt> -<dd>It assigns to the constant a model value of the same name. (See -the <a href="model-values.html"><em>Model Values and Symmetry</em></a> -help page.) -</dd> -<br></br> -<dt><b>Set of model values</b></dt> -<dd>You must enter comma-separated list of legal model-value names, -optionally enclosed by <code>{</code> -and <code>}</code>. You will have the option of making them -a symmetry set. -<p> -A <em>typed</em> model value is one whose name begins with a letter -and an underscore--for example, <code>p_42a</code>. If you -enter a set of model values that are not all of the same type, -you will have to click <samp>Next</samp> -to continue. You will then be given the choice of specifying a type -for the set you have just entered. For example, if you entered the -set <code>{2, a, b}</code> and choose the -type <code>t</code> , the constant will be assigned the -set <code>{t_2, t_a, t_b}</code> of model values. -Note that a number like <code>2</code> is not a -legal model value. -<p> See the <a href="model-values.html"><em>Model Values and Symmetry</em></a> -help page to learn about typed values and symmetry sets. - -</dd> -</dl> -<h2><a name="how-to-run">How to run?</a></h2> - -Here, you specify the following two aspects of how TLC should -be run. (See the <a href="advanced-page.html#launching">TLC Options</a> section -of the <a href="advanced-page.html"><em>Advanced Options Page</em></a> help page for -additional ways to run TLC.) - -<h3>TLC Parameters</h3> -There are two parameters that you can set here: - -<h4>Number of worker threads</h4> - -TLC's algorithm for computing the set (actually the graph) of reachable states -is highly parallelizable, and it can make good use of arbitrarily many processors. -This parameter specifies the number of separate threads that TLC will spawn to -perform that computation. You should not set it to be greater than the number of -separate processors (cores) on your computer; the Toolbox will warn you if you do. - -<h4>Fraction of physical memory allocated to TLC</h4> - -This determines how large a heap TLC will use. If you make it too small, TLC -could run out of heap space and crash. If you make it too large, your machine will not have -enough memory and everything will run slowly. The slider's color warns you if -you are giving TLC too little or too much memory. -TLC can keep the set of reachable states it has found -on disk, so having too many reachable states can't run it out of memory. -However, it runs much faster when it can keep those states in memory. -TLC can run out of heap space if it takes too much memory to represent the -set of initial states or the set of successor states of a single state. -If TLC does run out of memory in the middle of a long run, -you can give it more and restart it from a checkpoint. - -<p> - -Setting the this parameter too large may produce a -<em>Could not create the Java virtual machine</em> error. - - -<A name="checkpoint"></A><h3>Checkpoint Recovery</h3></A> - - <p> -TLC takes regular checkpoints, from which it can be restarted if it is -stopped for any reason--for example, if your computer crashes. - -The <em>Recover from checkpoint</em> option tells TLC to start from where it -was when the last checkpoint was taken. - -This option is enabled if the last time you ran TLC, it ran long enough to -produce a checkpoint. The Toolbox will fill in the <em>Checkpoint -id</em> for you. - </p> - <p> -Warning: If you exit the Toolbox, it will stop any executions of TLC that are -in process. - -However, it is possible to stop the Toolbox in some drastic fashion that -leaves TLC running as a background process. - -Restarting from a checkpoint while the TLC process that created it is still -running can cause the checkpoint to be destroyed, making recovery -impossible. - -If you have reason to believe TLC was not stopped, check to see if it is -still running before trying to recover from a checkpoint. - </p> - <p> -The checkpoint TLC produces after a short run does not take up -much space. - -However, if TLC finds an error after running for a long time, the -checkpoint files could take up a lot of space--sometimes on the order -of a gigabyte for a model that has run for several days. - -These files are deleted if the model is re-run and a new checkpoint is -produced, or if the model is validated when the -<em>Recover from checkpoint</em> option is not selected. - -When TLC has created a checkpoint, the <em>How to Run</em> section -of the Model Overview page displays how much storage the checkpoint -occupies. - -It also provides a button that you can click to delete the checkpoint. - </p> - -<p> -Checkpointing does not yet work when TLC is run in distributed mode. -</p> -<a name="distributed"></a><h3>Running in Distributed Mode</h3> - -<p> -TLC can be run with worker threads run on multiple machine, -considerably speeding up its execution. -See <a href="distributed-mode.html">Running TLC in Distributed Mode</a> - -</p> - -<hr> -<!-- delete rest of line to comment out --> -<dl> -<dt><b><font color=#0000c0>Subtopics</font></b></dt> -<dd> <A href="model-values.html"> Model Values and Symmetry </A></dd> -<dd> <a href="distributed-mode.html">Running TLC in Distributed Mode</a></dd> -</dl> -<!-- --> -<!-- delete rest of line to comment out --> -<a href = "creating-model.html">↑ Creating a Model</a> -<!-- --> -</hr> - -</body> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<!-- This is file org.lamport.tla.toobox.doc/html/model/overview-page.html --> + + +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <LINK href="../style.css" rel="stylesheet" type="text/css"> + +<title>Model Overview Page</title> +</head> +<!-- a comment here --> + +<body> +<h1>Model Overview Page</h1> + +<pre> +Contents + <a href="#model-description">Model description</a> + <a href="#what-is-behavior">What is the behavior spec?</a> + <a href="#what-to-check">What to check?</a> + <a href="#what-is-model">What is the model?</a> + <a href="#how-to-run">How to run?</a> + <a href="#distributed">Running in Distributed Mode</a> +</pre> + + + <P> +This model editor page is usually the only one you will use to create +and run your model. Here are what the different sections of the +page are for. + </P> + +<h2><a name="model-description">Model description</a></h2> + +Enter any text you want here. The first line of the text is +displayed in the <a href="about-models.html#quick-access">Quick Access</a> window's list of models. + +<h2><a name="what-is-behavior">What is the behavior spec?</a></h2> + +The behavior spec is the formula or pair of formulas that describe the +possible behaviors of the system or algorithm you want to check. (See +the <a href="../spec/what-is.html"><em>What Is A Spec?</em></a> help page.) +There are two ways to write the behavior spec: +<dl> +<dt><b>Init and Next</b></dt> +<dd>A pair of formulas that specify the initial state and the next-state relation, +respectively. +</dd> +</dd> +<dt><b>Single formula</b></dt> +<dd>A single temporal formula of the form <code>Init /\ [][Next]<sub>vars</sub> /\ F</code>, +where <code>Init</code> is the initial predicate, + <code>Next</sub></code> is the next-state +relation, <code>vars</code> +is the tuple of variables, +and <code>F</code> is an optional fairness formula. + +</dd> +</dl> +The only way to write a behavior spec that includes fairness is with a temporal formula. +<p> +You can also choose to specify <em>No behavior spec</em>. This is the only option +if the spec has no variables. With this option, TLC will just check assumptions and +evaluate a constant expression, if you have entered one in the +<a href="results-page.html#evaluate"><em>Evaluate Constant Expression</em> section</a> +of the <a href="results-page.html">Model Checking Results Page</a>. +</p> + +<h2><a name="what-to-check">What to check?</h2> +There are three kinds of properties of the behavior spec that TLC can check: +<h3>Deadlock</h3> + +A <em>deadlock</em> is said to occur in a state for which the next-state relation +allows no successor states. Termination is deadlock that is not considered an error. +If you want the behavior spec to allow termination, then you should uncheck the +deadlock option. (This is not necessary for the spec produced by translating a PlusCal algorithm, +because its next-state relation is written in a way that causes TLC not to consider normal +termination to be a deadlock.) + +<h3>Invariants</h3> + +An invariant is a state predicate that is true of all reachable states--that is, +states that can occur in a behavior allowed by the behavior spec. You can include +a list of invariants. The checking of each invariant can be enabled or disabled +by checking or unchecking its box. + + +<h3>Properties</h3> + +TLC can check if the behavior spec satisfies (implies) a temporal property, which +is expressed as a temporal-logic formula. You can specify a list of such properties, +each with a check-box for enabling or disabling its checking. + +<h2><a name="what-is-model">What is the model?</a></h2> + +The most basic part of a model is an assignment of values to declared constants. +To assign a value to a constant, either double-click on the constant or select it and click on the +<samp>Edit</samp> button. This will raise a pop-up dialog giving you the choice +of three ways to assign a value to it: +<dl> +<dt><b>Ordinary assignment</b></dt> +<dd>You can set the value of the constant to any constant TLA+ expression that +contains only symbols defined in the spec. The expression can even include +declared constants, as long as the value assigned to a constant does not depend +on that constant. (If there are circular dependencies, TLC will +produce a <samp>Java StackOverflowError</samp> error.) +</dd> +<br></br> +<dt><b>Model value</b></dt> +<dd>It assigns to the constant a model value of the same name. (See +the <a href="model-values.html"><em>Model Values and Symmetry</em></a> +help page.) +</dd> +<br></br> +<dt><b>Set of model values</b></dt> +<dd>You must enter comma-separated list of legal model-value names, +optionally enclosed by <code>{</code> +and <code>}</code>. You will have the option of making them +a symmetry set. +<p> +A <em>typed</em> model value is one whose name begins with a letter +and an underscore--for example, <code>p_42a</code>. If you +enter a set of model values that are not all of the same type, +you will have to click <samp>Next</samp> +to continue. You will then be given the choice of specifying a type +for the set you have just entered. For example, if you entered the +set <code>{2, a, b}</code> and choose the +type <code>t</code> , the constant will be assigned the +set <code>{t_2, t_a, t_b}</code> of model values. +Note that a number like <code>2</code> is not a +legal model value. +<p> See the <a href="model-values.html"><em>Model Values and Symmetry</em></a> +help page to learn about typed values and symmetry sets. + +</dd> +</dl> +<h2><a name="how-to-run">How to run?</a></h2> + +Here, you specify the following two aspects of how TLC should +be run. (See the <a href="advanced-page.html#launching">TLC Options</a> section +of the <a href="advanced-page.html"><em>Advanced Options Page</em></a> help page for +additional ways to run TLC.) + +<h3>TLC Parameters</h3> +There are two parameters that you can set here: + +<h4>Number of worker threads</h4> + +TLC's algorithm for computing the set (actually the graph) of reachable states +is highly parallelizable, and it can make good use of arbitrarily many processors. +This parameter specifies the number of separate threads that TLC will spawn to +perform that computation. You should not set it to be greater than the number of +separate processors (cores) on your computer; the Toolbox will warn you if you do. + +<h4>Fraction of physical memory allocated to TLC</h4> + +This determines how large a heap TLC will use. If you make it too small, TLC +could run out of heap space and crash. If you make it too large, your machine will not have +enough memory and everything will run slowly. The slider's color warns you if +you are giving TLC too little or too much memory. +TLC can keep the set of reachable states it has found +on disk, so having too many reachable states can't run it out of memory. +However, it runs much faster when it can keep those states in memory. +TLC can run out of heap space if it takes too much memory to represent the +set of initial states or the set of successor states of a single state. +If TLC does run out of memory in the middle of a long run, +you can give it more and restart it from a checkpoint. + +<p> + +Setting the this parameter too large may produce a +<em>Could not create the Java virtual machine</em> error. + + +<A name="checkpoint"></A><h3>Checkpoint Recovery</h3></A> + + <p> +TLC takes regular checkpoints, from which it can be restarted if it is +stopped for any reason--for example, if your computer crashes. + +The <em>Recover from checkpoint</em> option tells TLC to start from where it +was when the last checkpoint was taken. + +This option is enabled if the last time you ran TLC, it ran long enough to +produce a checkpoint. The Toolbox will fill in the <em>Checkpoint +id</em> for you. + </p> + <p> +Warning: If you exit the Toolbox, it will stop any executions of TLC that are +in process. + +However, it is possible to stop the Toolbox in some drastic fashion that +leaves TLC running as a background process. + +Restarting from a checkpoint while the TLC process that created it is still +running can cause the checkpoint to be destroyed, making recovery +impossible. + +If you have reason to believe TLC was not stopped, check to see if it is +still running before trying to recover from a checkpoint. + </p> + <p> +The checkpoint TLC produces after a short run does not take up +much space. + +However, if TLC finds an error after running for a long time, the +checkpoint files could take up a lot of space--sometimes on the order +of a gigabyte for a model that has run for several days. + +These files are deleted if the model is re-run and a new checkpoint is +produced, or if the model is validated when the +<em>Recover from checkpoint</em> option is not selected. + +When TLC has created a checkpoint, the <em>How to Run</em> section +of the Model Overview page displays how much storage the checkpoint +occupies. + +It also provides a button that you can click to delete the checkpoint. + </p> + +<p> +Checkpointing does not yet work when TLC is run in distributed mode. +</p> +<a name="distributed"></a><h3>Running in Distributed Mode</h3> + +<p> +TLC can be run with worker threads run on multiple machine, +considerably speeding up its execution. +See <a href="distributed-mode.html">Running TLC in Distributed Mode</a> + +</p> + +<hr> +<!-- delete rest of line to comment out --> +<dl> +<dt><b><font color=#0000c0>Subtopics</font></b></dt> +<dd> <A href="model-values.html"> Model Values and Symmetry </A></dd> +<dd> <a href="distributed-mode.html">Running TLC in Distributed Mode</a></dd> +</dl> +<!-- --> +<!-- delete rest of line to comment out --> +<a href = "creating-model.html">↑ Creating a Model</a> +<!-- --> +</hr> + +</body> </html> \ No newline at end of file diff --git a/org.lamport.tla.toolbox.doc/html/spec/editing-modules.html b/org.lamport.tla.toolbox.doc/html/spec/editing-modules.html index 297e1273a286e8a30201ba1172afb79d6989c595..5aaf02cfd3d2f80ba1b20ab201c1cc42a2d6e528 100644 --- a/org.lamport.tla.toolbox.doc/html/spec/editing-modules.html +++ b/org.lamport.tla.toolbox.doc/html/spec/editing-modules.html @@ -1,605 +1,622 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<!-- This is file org.lamport.tla.toobox.doc/html/spec/editing-modules.html --> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> - <LINK href="../style.css" rel="stylesheet" type="text/css"> - -<title>Editing Modules</title> -</head> -<!-- a comment --> - -<body> -<h1>Editing Modules</h1> -<pre> -Contents - <a href="#module">Module Editors</a> - <a href="#finding">Finding Definitions and Declarations and Their Uses</a> - <a href="#finding-pcal">Finding PlusCal Source Code</a> - <a href="#comments">Editing Comments</a> - <a href="#proofs">Editing and Viewing Proofs</a> - <a href="#misc">Miscellaneous Commands</a> - <a href="#key-bindings">Module Editor Key Bindings</a> - -</pre> -<h2><a name="module">Module Editors</a></h2> - <P> -When you first open a spec, the Toolbox shows the root module in a module editor view. -You can open additional modules in separate module editor views by clicking <samp>File/Open Module</samp>. -This will allow you to select a module, either from a list of modules that are or were part of the spec -or from a file browser. You can also create a new module file in the file browser. You can select a module -only from the same folder as the root-module file. -</P> - -<p> -If you have multiple module editors open, you can switch between them by clicking on the -editor's tab. A <samp>*</samp> in the tab means that there are unsaved changes -to the module. -Clicking on the <samp>X</samp> at the right of the tab deletes the module editor (but -not the module). If you accidentally delete a module editor, you can re-open the module as -described above. -</p> -<p> - It's sometimes useful to open two views on the same module, so you can view different parts of it -at the same time. You can do this by right-clicking on the module editor's tab and selecting -<samp>New Editor</samp>. See the <a href="../gettingstarted/views.html">Managing Views</a> help page -for how to put model editors where you want them in the Toolbox window. See the -<a href="pretty-printing.html">Pretty-Printing Modules</a> help page for how to view the pretty-printed -versions of your modules. -</p> - -<p>A module editor provides the usual text editing commands for editing the module. -There are -<samp>Save</samp> and <samp>Save As</samp> commands on the <samp>File</samp> -menu. (Typing <code>Control+s</code> also performs the save command.) The <samp>Edit</samp> -menu provides a few familiar commands. -<a href="#key-bindings">Below</a> is a list of available commands and the keys to which they are bound. - -</p> - -<p> -If you create a new module file with the Toolbox, the module editor will create -a modification history at the end of the file that begins with the line -<pre> - \* Modification History -</pre> -You can move the modification history anywhere you want in the file. - -If you created the file outside the Toolbox, you can put this history-beginning -line anywhere you want. - -However, the line must appear exactly as shown, with no spaces before -the <code>\*</code> and exactly one space after it. -</p> - -<h2><a name="finding">Finding Definitions, Declarations, and Their Uses</a></h2> -<p> -The module editor provides commands for finding the PlusCal code that - -finding the definition or declaration of a user-defined symbol, -for finding all uses of that definition or declaration, and for finding all symbols defined and declared -in the module. - - -For brevity, the names of all of these commands use the term <em>declaration</em> instead of <em>declaration -or definition</em>. - -These commands can be executed by right-clicking on the editor and selecting the command from the -popped-up menu, or with the keystroke indicated on the menu. - -The <samp>General/Keys</samp> item on -the <a href="../gettingstarted/preferences.html">Preference</a> menu allows you to change those -key bindings. - -The <em>Goto Declaration</em> -command can also be executed by <em>control + click</em>, as explained below. -</p> - -<p> -For the commands that find the declaration or uses of a symbol, the symbol is the one containing the cursor (caret) -or that the cursor is right before. - -It is not the one being pointed at by the mouse--except when using the <em>control + click</em> version of -the <em>Goto Declaration</em> command. -</p> - -<p> -Most of these commands work only if the spec is successfully parsed--that is, if its status (indicated at the bottom -of the window) is <em>parsed</em> (green). - -If the module has been modified since the spec was parsed, a command may not work properly. The <em>Goto Declaration</em> -command may not recognize the symbol, and a command that should highlight and/or jump to someplace in the module may highlight -or jump to the wrong place. - -However, if the <em>Show Uses</em> command is executed before the module is modified, then the highlighting and subsequent -<em>Goto Prev/Next Use</em> commands will work properly. -</p> - -<h3>Goto Declaration (F3)</h3> - -<p> -This command jumps to the definition or declaration of the chosen symbol, which must not be a built-in TLA+ symbol or -a symbol defined in a standard module. - -This could mean jumping to a different module. - -In addition to executing the command from a menu or by typing <code>F3</code>, you can also hold down the <code>Control</code> key -and move the mouse over the symbol. - -When the mouse is over a declared or user-defined symbol, the symbol will change appearance and turn into -a link that you can left-click on to jump to the symbol's declaration or definition. -</p> - - -<h3>Return From Goto (F4)</h3> - -This command jumps back to the location from which the most recent <em>Goto Declaration</em> -or <em>Goto PCal Source</em> command was executed. -The <em>Go back in history</em> command (<samp>Alt+LeftArrow</samp>) has the same effect -when executed immediately after the <em>Goto</em> command. - -<h3>Show Declarations (F5)</h3> - -<p> -This command raises a menu containing a list of all the symbols defined and declared in the current module. - -You can jump to a symbol's definition or declaration by selecting that symbol from the list. - -You can select an item from the list by clicking on it, or by selecting the desired item--with a combination of the -arrow keys and typing the first few letters of its name--and then typing <code>Enter</code>. -</p> - -<p>Typing <code>space</code> causes the menu to toggle between showing and hiding definitions that are -imported by instantiation. - -(More precisely, it applies to definitions that are imported with renaming and/or with substitution -for module parameters.) -</p> - -<h3><a name ="showUses">Show Uses (F6)</a></h3> -<p> -This command highlights all uses of the selected symbol and enables the <em>Goto Next Use</em> -and <em>Goto Prev Use</em> commands. - -If the symbol is used in more than one module, it -raises a menu allowing you to select the desired module from a list. - -You can select a module from the list by clicking on it, or by selecting it--with a combination of the -arrow keys and typing the first few letters of its name--and then typing <code>Enter</code>. -</p> -<p> -The highlighting of the uses is cleared by executing another <em>Show Uses</em> command or by re-parsing -the specification. - -Whether or not re-parsing the specification clears the highlighting is controlled by an editor -<a href="../gettingstarted/preferences.html">preference</a>. -</p> - -<h3>Goto Prev/Next Use (F7/F8)</h3> - -These commands jump to the previous or next (earlier or later in the module) use of the -symbol selected by the most recent <em>Show Uses</em> command. - -<h2><a name="finding-pcal">Finding PlusCal Source Code</a></h2> - -<p> -After you have successfully run the PlusCal translator to translate an algorithm, -the <em>Goto PCal Source</em> command (F10) allows you to jump from a selected region of -the TLA+ translation to the PlusCal code that generated it. Select a -region of the TLA+ specification, and the command jumps to and highlights approximately -the smallest syntactic unit of the PlusCal algorithm whose translation contains the -selected region. (You can select a single token by putting the cursor in or -next to it.) The command does nothing if that smallest syntactic unit is -the entire algorithm, or if the selected region does not lie within the algorithm's -translation. The <em>Return From Goto</em> command (F4) jumps back to the original -selection. -</p> - -<p> -Clicking on parsing error reports, and on error reports and some other -fields in a TLC model window, highlights and jumps to a region of the -TLA+ spec. If that region of the spec was produced by the PlusCal -translation, the <em>Goto PCal Source</em> command should then take you to the -appropriate part of the PlusCal source code. You can also -jump directly to the PlusCal source - by control-clicking (holding the <samp>CONTROL</samp> key while clicking) on the error report. -That will take you to the region of the TLA+ spec if it has no corresponding -PlusCal region. - -</p> - -<p> -The mapping from regions of the TLA+ translation to regions of the PlusCal source -is determined at the time of translation. If the PlusCal source, the -translation, or any part of the module lying between them is changed, then the -command may jump to the wrong place. It's best to put the translation immediately -after the comment containing the algorithm. If you omit the -<code>BEGIN/END TRANSLATION</code> comments, that is where the translation will be -put. -</p> - -<h2><a name="comments">Editing Comments</a></h2> - -<p> -The <a href="pretty-printing.html">TLA+ Pretty Printer</a> regards -a comment "boxed" as follows to be a sequence of paragraphs. -<pre> - (**************************************) - (* This is the *) - (* first paragraph. *) - (* *) - (* This text is a display *) - (* *) - (* This is another paragraph. *) - (**************************************) -</pre> -Such a box can be placed on a series of lines by itself, or -it can appear to the right of other text. -</p> - -<p> -The module editor provides the commands for creating and -editing boxed comments. - -They make boxed comments that extend from some specified point to a right -margin--a column specified by a -<a href="../gettingstarted/module-editor-preferences.html">preference.</a>. - -These commands are executed by typing two characters, the first of -which is <samp>Ctl+O</samp>. - -If you type <samp>Ctl+O</samp> and wait a moment, the list of all these -commands will appear. - -The commands can also be executed by right-clicking on the mouse. - -For these commands, the <em>current position</em> is the location where -the typing cursor is, not where the mouse is pointing. -</p> - -<p> -To create a comment, you first position cursor where you -want the top left-hand corner of the comment and execute the -<em>Start Boxed Comment</em> by typing <samp>Ctl+O Ctl+S</samp>. - -This produces the comment begin and end tokens like this: -<pre> -Beauty == TRUE (*********************************** - - ***********************************)</pre> -showing where the finished box will be positioned, and it leaves the cursor -between them in the first column. - -Type your comment like ordinary text, with all paragraph lines -starting in column 1, like this: - -<pre> -Beauty == TRUE (*********************************** -This is -a -very interesting formula, -because it expresses Keats' famous dictum - - Beauty is truth - and truth beauty. - -How much more than this do you really need to know? - ***********************************)</pre> -When you have finished the contents of the comment, execute -the <em>Format Comment</em> command by typing -<samp>Ctl+O Ctl+F</samp>. - -This will format each sequence of non-blank lines that all begin in column 1 -as a paragraph, with lines broken so the paragraphs will fit snugly in the -comment box--like this: - -<pre> -Beauty == TRUE (*********************************** -This is a very interesting -formula, because it expresses -Keats' famous dictum - - Beauty is truth - and truth beauty. - -How much more than this do you -really need to know? - ***********************************)</pre> - - -Note that lines that do not begin in column 1 are not changed. - -If it looks right, execute the <em>Box Comment</em> command by -typing <samp>Ctl+O Ctl+B</samp> to create the box, like this: - -<pre> -Beauty == TRUE (**********************************) - (* This is a very interesting *) - (* formula, because it expresses *) - (* Keats' famous dictum *) - (* *) - (* Beauty is truth *) - (* and truth beauty. *) - (* *) - (* How much more than this do you *) - (* really need to know? *) - (**********************************)</pre> - - - -If the result doesn't look right, type <samp>Ctl+Z</samp> to undo the <em>Box -Comment</em> command and do whatever manual formatting seems necessary. -Then execute <em>Box Comment</em> again. - -Both the formatting and the boxing can be done together with the -<em>Format and Box Comment</em> command by typing <samp>Ctl+O Ctl+O</samp>. - -</p> - -<p> -To modify an existing boxed comment, put the cursor inside the -comment box and execute the <em>Unbox Comment</em> command -by typing <samp>Ctl+O Ctl+U</samp>. You can then edit and -rebox it with the commands described above. -</p> - - -<h2><a name="proofs">Editing and Viewing Proofs</a></h2> - -Editor commands for hierarchically viewing proofs are -described in <a href="../prover/reading.html">Viewing Structured Proofs</a>. - -We hope eventually to provide commands to aid in writing -and editing proofs. - -We would appreciate any suggestions for what commands we should -implement. - -Meanwhile, <a href="#block-selection">block selection mode</a> may be -useful in copying and indenting parts of a proof. - -<h2><a name="misc">Miscellaneous Commands</a></h2> - -<h3><a name="paren">Goto Matching Parenthesis (Control+Shift+p)</a></h3> - -Placing the cursor just to the right of a parenthesis and executing this -command cause the cursor to jump to the right of the matching parenthesis. -The pairs of matching "parentheses" are: -<pre> - ( ) [ ] { } << >> (* *) -</pre> -The cursor does not move and an error message appears at the bottom -left of the Toolbox window if there is no matching parenthesis or -there are mismatched parentheses. Mismatched parentheses are highlighted. -(Look to the right of the scroll bar to find them if they are off the screen.) -End-of-line comments (started with <samp>\*</samp>) and strings are treated -as independent regions for parenthesis matching. - - -<h3><a name="block-selection">Toggle Block Selection Mode (Alt+Shift+a)</a></h3> - -This command causes the editor to enter or leave block-selection mode. -In block-selection mode, you can select a rectangular region (which may have zero width). -You can delete, type into, or copy and paste the region. Typing space or <code>LeftArrow</code> -(backspace) into a zero-width region allows you to move a block of text left or right, which -is useful for adjusting alignment. In general, typing a single character into -a zero-width region inserts that character on each line of the region. - -<p> -The module is displayed in Text Editor Block Selection Font -instead of Text Font when in Block Selection Mode. You can change those fonts by -selecting <samp>General / Appearance / Colors and Fonts</samp> on the -<a href="../gettingstarted/preferences.html">Preference</a> menu. - - -<h2><a name="key-bindings">Module Editor Key Bindings</a></h2> - -The following are the default key bindings for module editors. The <samp>General/Keys</samp> item on -the <a href="../gettingstarted/preferences.html">Preference</a> menu allows you to change those -key bindings. Its <em>Emacs</em> scheme binds a few commands to the keys used in the -Emacs editor, making the module editor almost, but not quite, completely unlike Emacs. <br></br> - -<TABLE frame=box rules=all> - <!-- style="font-style:italic;font-size:80%" --> -<!-- <CAPTION align=left><h3>Module Editor Key Bindings</h3></CAPTION> --> -<TBODY> -<COL align=left> <!-- style="font-family:consolas,courier"--> -<col align=left > -<TR> <TH colspan=2 align=left> <font color=000000> -Insert, Delete, and Move Text</font></TH> -<TR> <TD><CODE>Ctl+Shift+Enter</CODE></TD> <TD> Insert line above current line</TD></TR> - -<TR> <TD><CODE>Shift+Enter</CODE></TD> <TD> Insert line below current line</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+Del</CODE></TD> <TD> Delete to end of current line</TD></TR> - -<TR> <TD><CODE>Ctl+Backspace</CODE></TD> <TD> Delete to beginning of current or previous word</TD></TR> - -<TR> <TD><CODE>Ctl+Del</CODE></TD> <TD> Delete to end of current word</TD></TR> - -<TR> <TD><CODE>Alt+Up/DownArrow</CODE></TD> <TD> Move current line up/down</TD></TR> - -<TR> <TD><CODE>Shift+Tab</CODE></TD> <TD>If there are (at least) 4 spaces at beginning of line, - delete them</TD></TR> - -<TR> <TD><CODE>Ctl+/</CODE></TD> <TD>Comment out or uncomment selected lines</TD></TR> - -<TR> <TD><CODE>Ctl+Alt+J</CODE></TD> <TD> Join next line to current line</TD></TR> - -<TR><TD><CODE>Ctl+C</CODE></TD> <TD> Copy selected text</TD></TR> - -<TR><TD><CODE>Ctl+V</CODE></TD> <TD> Paste</TD></TR> - -<TR><TD><CODE>Ctl+Z</CODE></TD> <TD> Undo</TD></TR> - -<TR><TD><CODE>Ctl+A</CODE></TD> <TD> Select all</TD></TR> - -<TR><TD><CODE>Alt+Shift+A</CODE></TD> <TD><a href="#block-selection">Toggle block selection mode<a></TD></TR> - -<TR> <TD colspan=2> <BR> </TD></TR> -<TR> <TH colspan=2 align=left> <font color=000000>Moving Around</font></TH> -<TR> <TD colspan=2 align=left>For most of these commands, holding Shift down also selects the text.</TD> - -<TR> <TD><CODE>Home/End</CODE></TD> <TD> Go to beginning/end of current line</TD></TR> - -<TR> <TD><CODE>Ctl+Home/End</CODE></TD> <TD> Go to beginning/end of module</TD></TR> - -<!-- -<TR> <TD><CODE>Home</CODE></TD> <TD> Go to beginning of current line</TD></TR> -<TR> <TD><CODE>Ctl+Home</CODE></TD> <TD> Go to beginning of module</TD></TR> - --> - -<TR> <TD><CODE>Ctl+RightArrow</CODE></TD> <TD> Go to beginning of next word</TD></TR> - -<TR> <TD><CODE>Ctl+LeftArrow</CODE></TD> <TD> Go to beginning of current or previous word</TD></TR> - -<TR> <TD><CODE>Alt+RightArrow</CODE></TD> <TD> Go forward in history.</TD></TR> - -<TR> <TD><CODE>Alt+LeftArrow</CODE></TD> <TD> Go back in history.</TD></TR> - -<TR> <TD><CODE>Ctl+L</CODE></TD> <TD> Go to line [asks for line number]</TD></TR> - -<TR> <TD><CODE>Ctl+Q</CODE></TD> <TD> Go to last edit location</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+P</CODE></TD> <TD> <a href="#paren">Go to matching parenthesis</a></TD></TR> - -<TR> <TD><CODE>Ctl+Up/DownArrow</CODE></TD> <TD> Scroll up/down one line</TD></TR> -<TR> <TD colspan=2 align=left>The arrow and page-up/down keys behave as usual.</TD> - -<!-- -<TR> <TD colspan=2> <BR> </TD></TR> -<TR> <TH colspan=2 align=left> <font color=000000>Selecting Text</font></TH> ---> - -<TR> <TD colspan=2> <BR> </TD></TR> - -<TR> <TH colspan=2 align=left> <font color=000000>Find and Replace</font></TH> -<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Raise Find/Replace dialog</TD></TR> - -<TR> <TD><CODE>Ctl+K</CODE></TD> <TD> Find next (in forward direction) instance of last search item</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+K</CODE></TD> <TD> Find previous (in backwards direction) instance of last search item</TD></TR> - -<TR> <TD><CODE>Ctl+J</CODE></TD> <TD> Incremental forward find</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+J</CODE></TD> <TD> Incremental backwards find</TD></TR> - - - -<TR> <TD colspan=2> <BR> </TD></TR> - -<TR> <TH colspan=2 align=left> <font color=000000><a href="#finding">Finding Definitions and Declarations and Their Uses</a></font></TH> -<TR> <TD><CODE>F3</CODE></TD> <TD> Goto declaration or definition</TD></TR> -<TR> <TD><CODE>F4</CODE></TD> <TD> Return from goto declaration or definition</TD></TR> -<TR> <TD><CODE>F5</CODE></TD> <TD> Show declarations and definitions</TD></TR> -<TR> <TD><CODE>F6</CODE></TD> <TD> Show uses of declaration or definition</TD></TR> -<TR> <TD><CODE>F7</CODE></TD> <TD> Goto previous use of declaration or definition</TD></TR> -<TR> <TD><CODE>F7</CODE></TD> <TD> Goto next use of declaration or definition</TD></TR> - -<TR> <TD colspan=2> <BR> </TD></TR> - -<TR> <TH colspan=2 align=left> <font color=000000><a href="#comments">Editing Comments</a></font></TH> -<TR> <TD><CODE>Ctl+O Ctl+S</CODE></TD> <TD> Begin comment</TD></TR> - -<TR> <TD><CODE>Ctl+O Ctl+F</CODE></TD> <TD>Format comment</TD></TR> - -<TR> <TD><CODE>Ctl+O Ctl+B</CODE></TD> <TD>Box comment</TD></TR> - -<TR> <TD><CODE>Ctl+O Ctl+O</CODE></TD> <TD>Format and box comment</TD></TR> - -<TR> <TD><CODE>Ctl+O Ctl+U</CODE></TD> <TD>Unbox comment</TD></TR> - -<TR> <TD colspan=2> <BR> </TD></TR> - -<TR> <TH colspan=2 align=left> <font color=000000>Viewing Proofs</font></TH> -<TR> <TD><CODE>Ctl+G Ctl+S</CODE></TD> <TD>Show current subtree</TD></TR> - -<TR> <TD><CODE>Ctl+G Ctl+H</CODE></TD> <TD>Hide current subtree</TD></TR> - -<TR> <TD><CODE>Ctl+G Ctl+C</CODE></TD> <TD>Show children only</TD></TR> - -<TR> <TD><CODE>Ctl+G Ctl+F</CODE></TD> <TD>Focus on step</TD></TR> - -<TR> <TD><CODE>Ctl+G Ctl+A</CODE></TD> <TD>Show all proofs</TD></TR> -<TR> <TD><CODE>Ctl+G Ctl+N</CODE></TD> <TD>Hide all proofs</TD></TR> - -<TR> <TD colspan=2> <BR> </TD></TR> - -<TR> <TH colspan=2 align=left> <font color=000000>Other</font></TH></TR> - - - -<TR> <TD><CODE>Ctl+S</CODE></TD> <TD> Save module</TD></TR> - -<TR> <TD><CODE>Alt+X</CODE></TD> <TD> Exit toolbox</TD></TR> - -<TR> <TD><CODE>Ctl+M</CODE></TD> <TD> Maximize or unmaximize current editor or view</TD></TR> - -<TR> <TD><CODE>Ctl+R</CODE></TD> <TD> Parse module</TD></TR> - -<TR> <TD><CODE>Ctl+Alt+R</CODE></TD> <TD> Parse spec</TD></TR> - -<TR> <TD><CODE>F1</CODE></TD> <TD> Raise Help view</TD></TR> - -<TR> <TD><CODE>Ctl+PageUp/Down</CODE></TD> <TD> Go to previous/next editor view </TD></TR> - -<TR> <TD><CODE>Ctl+F6</CODE></TD> <TD> Go to another editor view (Use up/down arrow to select view, - then type Enter.)</TD></TR> - -<TR> <TD><CODE>Ctl+F7</CODE></TD> <TD> Go to another view (Use up/down arrow to select view, - then type Enter.)</TD></TR> - - <TR> <TD><CODE>Ctl+F10</CODE></TD> <TD>Can use to set line-numbering preference or go to - <samp>Preferences /General/Editors/Text Editors</samp></TD></TR> - - - - -<!-- -<TR> <TD><CODE>Ctl+Shift+R</CODE></TD> <TD> Open Resource. <BR> - This can open a resource from a different spec. - It should be disabled. - -<TR> <TD><CODE>Alt+Shift+N</CODE></TD> <TD> DOES NOTHING</TD></TR> - -<TR> <TD><CODE>Ctl+.</CODE></TD> <TD> Seems to do nothing</TD></TR> - -<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Next Editor</TD></TR> - -<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Next Page</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+F</CODE></TD> <TD> Previous Page</TD></TR> - -<TR> <TD><CODE>Ctl+Shift+F</CODE></TD> <TD> Previous Editor</TD></TR> - -<TR> <TD><CODE>Ctl+,</CODE></TD> <TD> SEEMS TO DO NOTHING</TD></TR> - -<TR> <TD><CODE>Ctl+.</CODE></TD> <TD> SEEMS TO DO NOTHING</TD></TR> - -<TR> <TD><CODE>Alt+PageDown</CODE></TD> <TD> Next SubTab (Goes from module tab to pdf tab)</TD></TR> - -<TR> <TD><CODE>Alt+PageUp </CODE></TD> <TD> Previous SubTab DOES NOTHING</TD></TR> -<TR> <TD><CODE>Ctl+N</CODE></TD> <TD> Raises a "New" menu that allows user to do weird things. Probably best to disable this.</TD></TR> - -<TR> <TD><CODE>Alt+F4</CODE></TD> <TD> Windows command to kill program.</TD></TR> ---> -</TBODY> -</TABLE> - -<hr> -<!-- delete rest of line to comment out -<dl> -<dt><b><font color=#0000c0>Topics</font></b></dt> -<dd> <A href=""> TOPIC </A></dd> -<dd> <A href=""> TOPIC </A></dd> -</dl> - --> -<!-- delete rest of line to comment out --> -<a href = "spec.html">↑ Managing Your Specifications</a> -<!-- --> -</hr> - - - -</body> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<!-- This is file org.lamport.tla.toobox.doc/html/spec/editing-modules.html --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <LINK href="../style.css" rel="stylesheet" type="text/css"> + +<title>Editing Modules</title> +</head> +<!-- a comment --> + +<body> +<h1>Editing Modules</h1> +<pre> +Contents + <a href="#module">Module Editors</a> + <a href="#finding">Finding Definitions and Declarations and Their Uses</a> + <a href="#finding-pcal">Finding PlusCal Source Code</a> + <a href="#comments">Editing Comments</a> + <a href="#proofs">Editing and Viewing Proofs</a> + <a href="#misc">Miscellaneous Commands</a> + <a href="#key-bindings">Module Editor Key Bindings</a> + +</pre> +<h2><a name="module">Module Editors</a></h2> + <P> +When you first open a spec, the Toolbox shows the root module in a module editor view. +You can open additional modules in separate module editor views by clicking <samp>File/Open Module</samp>. +This will allow you to select a module, either from a list of modules that are or were part of the spec +or from a file browser. You can also create a new module file in the file browser. You can select a module +only from the same folder as the root-module file. +</P> + +<p> +If you have multiple module editors open, you can switch between them by clicking on the +editor's tab. A <samp>*</samp> in the tab means that there are unsaved changes +to the module. +Clicking on the <samp>X</samp> at the right of the tab deletes the module editor (but +not the module). If you accidentally delete a module editor, you can re-open the module as +described above. +</p> +<p> + It's sometimes useful to open two views on the same module, so you can view different parts of it +at the same time. You can do this by right-clicking on the module editor's tab and selecting +<samp>New Editor</samp>. See the <a href="../gettingstarted/views.html">Managing Views</a> help page +for how to put model editors where you want them in the Toolbox window. See the +<a href="pretty-printing.html">Pretty-Printing Modules</a> help page for how to view the pretty-printed +versions of your modules. +</p> + +<p>A module editor provides the usual text editing commands for editing the module. +There are +<samp>Save</samp> and <samp>Save As</samp> commands on the <samp>File</samp> +menu. (Typing <code>Control+s</code> also performs the save command.) The <samp>Edit</samp> +menu provides a few familiar commands. +<a href="#key-bindings">Below</a> is a list of available commands and the keys to which they are bound. + +</p> + +<p> +If you create a new module file with the Toolbox, the module editor will create +a modification history at the end of the file that begins with the line +<pre> + \* Modification History +</pre> +You can move the modification history anywhere you want in the file. + +If you created the file outside the Toolbox, you can put this history-beginning +line anywhere you want. + +However, the line must appear exactly as shown, with no spaces before +the <code>\*</code> and exactly one space after it. +</p> + + +<p> +The Toolbox maintains a history of previous versions of a module. +It adds the current version to the history every time you save the module. +You can examine and revert to a version from the history by right-clicking on +in the module editor and selecting <code>Team</code>, <code>Compare With</code>, +or <code>Replace With</code>. + +You can also right-click on any module of the open specification in the + <a +href="../spec/opening-closing.html">Spec Explorer</a> + and select +<code>Show in History</code>. +</p> + +<h2><a name="finding">Finding Definitions, Declarations, and Their Uses</a></h2> +<p> +The module editor provides commands for finding the PlusCal code that + +finding the definition or declaration of a user-defined symbol, +for finding all uses of that definition or declaration, and for finding all symbols defined and declared +in the module. + + +For brevity, the names of all of these commands use the term <em>declaration</em> instead of <em>declaration +or definition</em>. + +These commands can be executed by right-clicking on the editor and selecting the command from the +popped-up menu, or with the keystroke indicated on the menu. + +The <samp>General/Keys</samp> item on +the <a href="../gettingstarted/preferences.html">Preference</a> menu allows you to change those +key bindings. + +The <em>Goto Declaration</em> +command can also be executed by <em>control + click</em>, as explained below. +</p> + +<p> +For the commands that find the declaration or uses of a symbol, the symbol is the one containing the cursor (caret) +or that the cursor is right before. + +It is not the one being pointed at by the mouse--except when using the <em>control + click</em> version of +the <em>Goto Declaration</em> command. +</p> + +<p> +Most of these commands work only if the spec is successfully parsed--that is, if its status (indicated at the bottom +of the window) is <em>parsed</em> (green). + +If the module has been modified since the spec was parsed, a command may not work properly. The <em>Goto Declaration</em> +command may not recognize the symbol, and a command that should highlight and/or jump to someplace in the module may highlight +or jump to the wrong place. + +However, if the <em>Show Uses</em> command is executed before the module is modified, then the highlighting and subsequent +<em>Goto Prev/Next Use</em> commands will work properly. +</p> + +<h3>Goto Declaration (F3)</h3> + +<p> +This command jumps to the definition or declaration of the chosen symbol, which must not be a built-in TLA+ symbol or +a symbol defined in a standard module. + +This could mean jumping to a different module. + +In addition to executing the command from a menu or by typing <code>F3</code>, you can also hold down the <code>Control</code> key +and move the mouse over the symbol. + +When the mouse is over a declared or user-defined symbol, the symbol will change appearance and turn into +a link that you can left-click on to jump to the symbol's declaration or definition. +</p> + + +<h3>Return From Goto (F4)</h3> + +This command jumps back to the location from which the most recent <em>Goto Declaration</em> +or <em>Goto PCal Source</em> command was executed. +The <em>Go back in history</em> command (<samp>Alt+LeftArrow</samp>) has the same effect +when executed immediately after the <em>Goto</em> command. + +<h3>Show Declarations (F5)</h3> + +<p> +This command raises a menu containing a list of all the symbols defined and declared in the current module. + +You can jump to a symbol's definition or declaration by selecting that symbol from the list. + +You can select an item from the list by clicking on it, or by selecting the desired item--with a combination of the +arrow keys and typing the first few letters of its name--and then typing <code>Enter</code>. +</p> + +<p>Typing <code>space</code> causes the menu to toggle between showing and hiding definitions that are +imported by instantiation. + +(More precisely, it applies to definitions that are imported with renaming and/or with substitution +for module parameters.) +</p> + +<h3><a name ="showUses">Show Uses (F6)</a></h3> +<p> +This command highlights all uses of the selected symbol and enables the <em>Goto Next Use</em> +and <em>Goto Prev Use</em> commands. + +If the symbol is used in more than one module, it +raises a menu allowing you to select the desired module from a list. + +You can select a module from the list by clicking on it, or by selecting it--with a combination of the +arrow keys and typing the first few letters of its name--and then typing <code>Enter</code>. +</p> +<p> +The highlighting of the uses is cleared by executing another <em>Show Uses</em> command or by re-parsing +the specification. + +Whether or not re-parsing the specification clears the highlighting is controlled by an editor +<a href="../gettingstarted/preferences.html">preference</a>. +</p> + +<h3>Goto Prev/Next Use (F7/F8)</h3> + +These commands jump to the previous or next (earlier or later in the module) use of the +symbol selected by the most recent <em>Show Uses</em> command. + +<h2><a name="finding-pcal">Finding PlusCal Source Code</a></h2> + +<p> +After you have successfully run the PlusCal translator to translate an algorithm, +the <em>Goto PCal Source</em> command (F10) allows you to jump from a selected region of +the TLA+ translation to the PlusCal code that generated it. Select a +region of the TLA+ specification, and the command jumps to and highlights approximately +the smallest syntactic unit of the PlusCal algorithm whose translation contains the +selected region. (You can select a single token by putting the cursor in or +next to it.) The command does nothing if that smallest syntactic unit is +the entire algorithm, or if the selected region does not lie within the algorithm's +translation. The <em>Return From Goto</em> command (F4) jumps back to the original +selection. +</p> + +<p> +Clicking on parsing error reports, and on error reports and some other +fields in a TLC model window, highlights and jumps to a region of the +TLA+ spec. If that region of the spec was produced by the PlusCal +translation, the <em>Goto PCal Source</em> command should then take you to the +appropriate part of the PlusCal source code. You can also +jump directly to the PlusCal source + by control-clicking (holding the <samp>CONTROL</samp> key while clicking) on the error report. +That will take you to the region of the TLA+ spec if it has no corresponding +PlusCal region. + +</p> + +<p> +The mapping from regions of the TLA+ translation to regions of the PlusCal source +is determined at the time of translation. If the PlusCal source, the +translation, or any part of the module lying between them is changed, then the +command may jump to the wrong place. It's best to put the translation immediately +after the comment containing the algorithm. If you omit the +<code>BEGIN/END TRANSLATION</code> comments, that is where the translation will be +put. +</p> + +<h2><a name="comments">Editing Comments</a></h2> + +<p> +The <a href="pretty-printing.html">TLA+ Pretty Printer</a> regards +a comment "boxed" as follows to be a sequence of paragraphs. +<pre> + (**************************************) + (* This is the *) + (* first paragraph. *) + (* *) + (* This text is a display *) + (* *) + (* This is another paragraph. *) + (**************************************) +</pre> +Such a box can be placed on a series of lines by itself, or +it can appear to the right of other text. +</p> + +<p> +The module editor provides the commands for creating and +editing boxed comments. + +They make boxed comments that extend from some specified point to a right +margin--a column specified by a +<a href="../gettingstarted/module-editor-preferences.html">preference.</a>. + +These commands are executed by typing two characters, the first of +which is <samp>Ctl+O</samp>. + +If you type <samp>Ctl+O</samp> and wait a moment, the list of all these +commands will appear. + +The commands can also be executed by right-clicking on the mouse. + +For these commands, the <em>current position</em> is the location where +the typing cursor is, not where the mouse is pointing. +</p> + +<p> +To create a comment, you first position cursor where you +want the top left-hand corner of the comment and execute the +<em>Start Boxed Comment</em> by typing <samp>Ctl+O Ctl+S</samp>. + +This produces the comment begin and end tokens like this: +<pre> +Beauty == TRUE (*********************************** + + ***********************************)</pre> +showing where the finished box will be positioned, and it leaves the cursor +between them in the first column. + +Type your comment like ordinary text, with all paragraph lines +starting in column 1, like this: + +<pre> +Beauty == TRUE (*********************************** +This is +a +very interesting formula, +because it expresses Keats' famous dictum + + Beauty is truth + and truth beauty. + +How much more than this do you really need to know? + ***********************************)</pre> +When you have finished the contents of the comment, execute +the <em>Format Comment</em> command by typing +<samp>Ctl+O Ctl+F</samp>. + +This will format each sequence of non-blank lines that all begin in column 1 +as a paragraph, with lines broken so the paragraphs will fit snugly in the +comment box--like this: + +<pre> +Beauty == TRUE (*********************************** +This is a very interesting +formula, because it expresses +Keats' famous dictum + + Beauty is truth + and truth beauty. + +How much more than this do you +really need to know? + ***********************************)</pre> + + +Note that lines that do not begin in column 1 are not changed. + +If it looks right, execute the <em>Box Comment</em> command by +typing <samp>Ctl+O Ctl+B</samp> to create the box, like this: + +<pre> +Beauty == TRUE (**********************************) + (* This is a very interesting *) + (* formula, because it expresses *) + (* Keats' famous dictum *) + (* *) + (* Beauty is truth *) + (* and truth beauty. *) + (* *) + (* How much more than this do you *) + (* really need to know? *) + (**********************************)</pre> + + + +If the result doesn't look right, type <samp>Ctl+Z</samp> to undo the <em>Box +Comment</em> command and do whatever manual formatting seems necessary. +Then execute <em>Box Comment</em> again. + +Both the formatting and the boxing can be done together with the +<em>Format and Box Comment</em> command by typing <samp>Ctl+O Ctl+O</samp>. + +</p> + +<p> +To modify an existing boxed comment, put the cursor inside the +comment box and execute the <em>Unbox Comment</em> command +by typing <samp>Ctl+O Ctl+U</samp>. You can then edit and +rebox it with the commands described above. +</p> + + +<h2><a name="proofs">Editing and Viewing Proofs</a></h2> + +Editor commands for hierarchically viewing proofs are +described in <a href="../prover/reading.html">Viewing Structured Proofs</a>. + +We hope eventually to provide commands to aid in writing +and editing proofs. + +We would appreciate any suggestions for what commands we should +implement. + +Meanwhile, <a href="#block-selection">block selection mode</a> may be +useful in copying and indenting parts of a proof. + +<h2><a name="misc">Miscellaneous Commands</a></h2> + +<h3><a name="paren">Goto Matching Parenthesis (Control+Shift+p)</a></h3> + +Placing the cursor just to the right of a parenthesis and executing this +command cause the cursor to jump to the right of the matching parenthesis. +The pairs of matching "parentheses" are: +<pre> + ( ) [ ] { } << >> (* *) +</pre> +The cursor does not move and an error message appears at the bottom +left of the Toolbox window if there is no matching parenthesis or +there are mismatched parentheses. Mismatched parentheses are highlighted. +(Look to the right of the scroll bar to find them if they are off the screen.) +End-of-line comments (started with <samp>\*</samp>) and strings are treated +as independent regions for parenthesis matching. + + +<h3><a name="block-selection">Toggle Block Selection Mode (Alt+Shift+a)</a></h3> + +This command causes the editor to enter or leave block-selection mode. +In block-selection mode, you can select a rectangular region (which may have zero width). +You can delete, type into, or copy and paste the region. Typing space or <code>LeftArrow</code> +(backspace) into a zero-width region allows you to move a block of text left or right, which +is useful for adjusting alignment. In general, typing a single character into +a zero-width region inserts that character on each line of the region. + +<p> +The module is displayed in Text Editor Block Selection Font +instead of Text Font when in Block Selection Mode. You can change those fonts by +selecting <samp>General / Appearance / Colors and Fonts</samp> on the +<a href="../gettingstarted/preferences.html">Preference</a> menu. + + +<h2><a name="key-bindings">Module Editor Key Bindings</a></h2> + +The following are the default key bindings for module editors. The <samp>General/Keys</samp> item on +the <a href="../gettingstarted/preferences.html">Preference</a> menu allows you to change those +key bindings. Its <em>Emacs</em> scheme binds a few commands to the keys used in the +Emacs editor, making the module editor almost, but not quite, completely unlike Emacs. <br></br> + +<TABLE frame=box rules=all> + <!-- style="font-style:italic;font-size:80%" --> +<!-- <CAPTION align=left><h3>Module Editor Key Bindings</h3></CAPTION> --> +<TBODY> +<COL align=left> <!-- style="font-family:consolas,courier"--> +<col align=left > +<TR> <TH colspan=2 align=left> <font color=000000> +Insert, Delete, and Move Text</font></TH> +<TR> <TD><CODE>Ctl+Shift+Enter</CODE></TD> <TD> Insert line above current line</TD></TR> + +<TR> <TD><CODE>Shift+Enter</CODE></TD> <TD> Insert line below current line</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+Del</CODE></TD> <TD> Delete to end of current line</TD></TR> + +<TR> <TD><CODE>Ctl+Backspace</CODE></TD> <TD> Delete to beginning of current or previous word</TD></TR> + +<TR> <TD><CODE>Ctl+Del</CODE></TD> <TD> Delete to end of current word</TD></TR> + +<TR> <TD><CODE>Alt+Up/DownArrow</CODE></TD> <TD> Move current line up/down</TD></TR> + +<TR> <TD><CODE>Shift+Tab</CODE></TD> <TD>If there are (at least) 4 spaces at beginning of line, + delete them</TD></TR> + +<TR> <TD><CODE>Ctl+/</CODE></TD> <TD>Comment out or uncomment selected lines</TD></TR> + +<TR> <TD><CODE>Ctl+Alt+J</CODE></TD> <TD> Join next line to current line</TD></TR> + +<TR><TD><CODE>Ctl+C</CODE></TD> <TD> Copy selected text</TD></TR> + +<TR><TD><CODE>Ctl+V</CODE></TD> <TD> Paste</TD></TR> + +<TR><TD><CODE>Ctl+Z</CODE></TD> <TD> Undo</TD></TR> + +<TR><TD><CODE>Ctl+A</CODE></TD> <TD> Select all</TD></TR> + +<TR><TD><CODE>Alt+Shift+A</CODE></TD> <TD><a href="#block-selection">Toggle block selection mode<a></TD></TR> + +<TR> <TD colspan=2> <BR> </TD></TR> +<TR> <TH colspan=2 align=left> <font color=000000>Moving Around</font></TH> +<TR> <TD colspan=2 align=left>For most of these commands, holding Shift down also selects the text.</TD> + +<TR> <TD><CODE>Home/End</CODE></TD> <TD> Go to beginning/end of current line</TD></TR> + +<TR> <TD><CODE>Ctl+Home/End</CODE></TD> <TD> Go to beginning/end of module</TD></TR> + +<!-- +<TR> <TD><CODE>Home</CODE></TD> <TD> Go to beginning of current line</TD></TR> +<TR> <TD><CODE>Ctl+Home</CODE></TD> <TD> Go to beginning of module</TD></TR> + --> + +<TR> <TD><CODE>Ctl+RightArrow</CODE></TD> <TD> Go to beginning of next word</TD></TR> + +<TR> <TD><CODE>Ctl+LeftArrow</CODE></TD> <TD> Go to beginning of current or previous word</TD></TR> + +<TR> <TD><CODE>Alt+RightArrow</CODE></TD> <TD> Go forward in history.</TD></TR> + +<TR> <TD><CODE>Alt+LeftArrow</CODE></TD> <TD> Go back in history.</TD></TR> + +<TR> <TD><CODE>Ctl+L</CODE></TD> <TD> Go to line [asks for line number]</TD></TR> + +<TR> <TD><CODE>Ctl+Q</CODE></TD> <TD> Go to last edit location</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+P</CODE></TD> <TD> <a href="#paren">Go to matching parenthesis</a></TD></TR> + +<TR> <TD><CODE>Ctl+Up/DownArrow</CODE></TD> <TD> Scroll up/down one line</TD></TR> +<TR> <TD colspan=2 align=left>The arrow and page-up/down keys behave as usual.</TD> + +<!-- +<TR> <TD colspan=2> <BR> </TD></TR> +<TR> <TH colspan=2 align=left> <font color=000000>Selecting Text</font></TH> +--> + +<TR> <TD colspan=2> <BR> </TD></TR> + +<TR> <TH colspan=2 align=left> <font color=000000>Find and Replace</font></TH> +<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Raise Find/Replace dialog</TD></TR> + +<TR> <TD><CODE>Ctl+K</CODE></TD> <TD> Find next (in forward direction) instance of last search item</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+K</CODE></TD> <TD> Find previous (in backwards direction) instance of last search item</TD></TR> + +<TR> <TD><CODE>Ctl+J</CODE></TD> <TD> Incremental forward find</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+J</CODE></TD> <TD> Incremental backwards find</TD></TR> + + + +<TR> <TD colspan=2> <BR> </TD></TR> + +<TR> <TH colspan=2 align=left> <font color=000000><a href="#finding">Finding Definitions and Declarations and Their Uses</a></font></TH> +<TR> <TD><CODE>F3</CODE></TD> <TD> Goto declaration or definition</TD></TR> +<TR> <TD><CODE>F4</CODE></TD> <TD> Return from goto declaration or definition</TD></TR> +<TR> <TD><CODE>F5</CODE></TD> <TD> Show declarations and definitions</TD></TR> +<TR> <TD><CODE>F6</CODE></TD> <TD> Show uses of declaration or definition</TD></TR> +<TR> <TD><CODE>F7</CODE></TD> <TD> Goto previous use of declaration or definition</TD></TR> +<TR> <TD><CODE>F7</CODE></TD> <TD> Goto next use of declaration or definition</TD></TR> + +<TR> <TD colspan=2> <BR> </TD></TR> + +<TR> <TH colspan=2 align=left> <font color=000000><a href="#comments">Editing Comments</a></font></TH> +<TR> <TD><CODE>Ctl+O Ctl+S</CODE></TD> <TD> Begin comment</TD></TR> + +<TR> <TD><CODE>Ctl+O Ctl+F</CODE></TD> <TD>Format comment</TD></TR> + +<TR> <TD><CODE>Ctl+O Ctl+B</CODE></TD> <TD>Box comment</TD></TR> + +<TR> <TD><CODE>Ctl+O Ctl+O</CODE></TD> <TD>Format and box comment</TD></TR> + +<TR> <TD><CODE>Ctl+O Ctl+U</CODE></TD> <TD>Unbox comment</TD></TR> + +<TR> <TD colspan=2> <BR> </TD></TR> + +<TR> <TH colspan=2 align=left> <font color=000000>Viewing Proofs</font></TH> +<TR> <TD><CODE>Ctl+G Ctl+S</CODE></TD> <TD>Show current subtree</TD></TR> + +<TR> <TD><CODE>Ctl+G Ctl+H</CODE></TD> <TD>Hide current subtree</TD></TR> + +<TR> <TD><CODE>Ctl+G Ctl+C</CODE></TD> <TD>Show children only</TD></TR> + +<TR> <TD><CODE>Ctl+G Ctl+F</CODE></TD> <TD>Focus on step</TD></TR> + +<TR> <TD><CODE>Ctl+G Ctl+A</CODE></TD> <TD>Show all proofs</TD></TR> +<TR> <TD><CODE>Ctl+G Ctl+N</CODE></TD> <TD>Hide all proofs</TD></TR> + +<TR> <TD colspan=2> <BR> </TD></TR> + +<TR> <TH colspan=2 align=left> <font color=000000>Other</font></TH></TR> + + + +<TR> <TD><CODE>Ctl+S</CODE></TD> <TD> Save module</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+A</CODE></TD> <TD> Open Quick Access window</TD></TR> + +<TR> <TD><CODE>Alt+X</CODE></TD> <TD> Exit toolbox</TD></TR> + +<TR> <TD><CODE>Ctl+M</CODE></TD> <TD> Maximize or unmaximize current editor or view</TD></TR> + +<TR> <TD><CODE>Ctl+R</CODE></TD> <TD> Parse module</TD></TR> + +<TR> <TD><CODE>Ctl+Alt+R</CODE></TD> <TD> Parse spec</TD></TR> + +<TR> <TD><CODE>F1</CODE></TD> <TD> Raise Help view</TD></TR> + +<TR> <TD><CODE>Ctl+PageUp/Down</CODE></TD> <TD> Go to previous/next editor view </TD></TR> + +<TR> <TD><CODE>Ctl+F6</CODE></TD> <TD> Go to another editor view (Use up/down arrow to select view, + then type Enter.)</TD></TR> + +<TR> <TD><CODE>Ctl+F7</CODE></TD> <TD> Go to another view (Use up/down arrow to select view, + then type Enter.)</TD></TR> + + <TR> <TD><CODE>Ctl+F10</CODE></TD> <TD>Can use to set line-numbering preference or go to + <samp>Preferences /General/Editors/Text Editors</samp></TD></TR> + + + + +<!-- +<TR> <TD><CODE>Ctl+Shift+R</CODE></TD> <TD> Open Resource. <BR> + This can open a resource from a different spec. + It should be disabled. + +<TR> <TD><CODE>Alt+Shift+N</CODE></TD> <TD> DOES NOTHING</TD></TR> + +<TR> <TD><CODE>Ctl+.</CODE></TD> <TD> Seems to do nothing</TD></TR> + +<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Next Editor</TD></TR> + +<TR> <TD><CODE>Ctl+F</CODE></TD> <TD> Next Page</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+F</CODE></TD> <TD> Previous Page</TD></TR> + +<TR> <TD><CODE>Ctl+Shift+F</CODE></TD> <TD> Previous Editor</TD></TR> + +<TR> <TD><CODE>Ctl+,</CODE></TD> <TD> SEEMS TO DO NOTHING</TD></TR> + +<TR> <TD><CODE>Ctl+.</CODE></TD> <TD> SEEMS TO DO NOTHING</TD></TR> + +<TR> <TD><CODE>Alt+PageDown</CODE></TD> <TD> Next SubTab (Goes from module tab to pdf tab)</TD></TR> + +<TR> <TD><CODE>Alt+PageUp </CODE></TD> <TD> Previous SubTab DOES NOTHING</TD></TR> +<TR> <TD><CODE>Ctl+N</CODE></TD> <TD> Raises a "New" menu that allows user to do weird things. Probably best to disable this.</TD></TR> + +<TR> <TD><CODE>Alt+F4</CODE></TD> <TD> Windows command to kill program.</TD></TR> +--> +</TBODY> +</TABLE> + +<hr> +<!-- delete rest of line to comment out +<dl> +<dt><b><font color=#0000c0>Topics</font></b></dt> +<dd> <A href=""> TOPIC </A></dd> +<dd> <A href=""> TOPIC </A></dd> +</dl> + --> +<!-- delete rest of line to comment out --> +<a href = "spec.html">↑ Managing Your Specifications</a> +<!-- --> +</hr> + + + +</body> </html> \ No newline at end of file diff --git a/org.lamport.tla.toolbox.doc/html/spec/pretty-printing.html b/org.lamport.tla.toolbox.doc/html/spec/pretty-printing.html index dd5519a7a0bbe1a9edd062c88528c4610d5cca42..c790e83340d370907f1c9a3d06e46f8bb72ad93c 100644 --- a/org.lamport.tla.toolbox.doc/html/spec/pretty-printing.html +++ b/org.lamport.tla.toolbox.doc/html/spec/pretty-printing.html @@ -78,7 +78,10 @@ The built-in viewer does not yet provide a toolbar. You can navigate with the <samp>Page Up</samp> and <samp>Page Down</samp> keys. -Use the <samp>+</samp> and <samp>-</samp> keys to zoom in and out. +Use the <samp>+</samp> and <samp>-</samp> keys to zoom in and out. + +The built-in viewer option is not offered on Mac OS X. + <h4>Shade comments</h4> Comments are shaded in gray. This is the default. The darkness of the shading is specified diff --git a/org.lamport.tla.toolbox.doc/html/spec/spec.html b/org.lamport.tla.toolbox.doc/html/spec/spec.html index 90a5c7b5dc5c28277fe36f7944dc71e528c3311c..c1e48429081d5f7292b9cca320945aefb2f27c87 100644 --- a/org.lamport.tla.toolbox.doc/html/spec/spec.html +++ b/org.lamport.tla.toolbox.doc/html/spec/spec.html @@ -48,11 +48,7 @@ help page. </p> <p> -You can run multiple instances of the Toolbox to work on different -specifications concurrently. - -Do not open the same specification in two different instances of the Toolbox -at the same time. +You can run only one instance of the Toolbox at a time. </p> <hr> <dl> diff --git a/org.lamport.tla.toolbox.doc/pom.xml b/org.lamport.tla.toolbox.doc/pom.xml index 00918419ce09ee13dfb9f5327201262aeaf9347a..78451b8ca64b36eb504bfda97e9bdfb2f2ec8fd4 100644 --- a/org.lamport.tla.toolbox.doc/pom.xml +++ b/org.lamport.tla.toolbox.doc/pom.xml @@ -11,6 +11,6 @@ </parent> <groupId>tlatoolbox</groupId> <artifactId>org.lamport.tla.toolbox.doc</artifactId> - <version>1.5.1-SNAPSHOT</version> + <version>1.5.2-SNAPSHOT</version> <packaging>eclipse-plugin</packaging> </project> diff --git a/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/TLAEditor.java b/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/TLAEditor.java index a4b1715680ffd198feb0cf895b29ceeaec1cd0b0..bad580ade2e0391ffa67acf53983b7f629102b5a 100644 --- a/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/TLAEditor.java +++ b/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/TLAEditor.java @@ -66,6 +66,7 @@ import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.actions.ContributionItemFactory; import org.eclipse.ui.contexts.IContextActivation; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.editors.text.EditorsUI; @@ -419,7 +420,7 @@ public class TLAEditor extends TextEditor if (items[i] instanceof MenuManager) { MenuManager subMenu = (MenuManager) items[i]; - if (subMenu.find("viewsShowIn") != null) + if (subMenu.find(ContributionItemFactory.VIEWS_SHOW_IN.getId()) != null) { menuManager.remove(subMenu); break; @@ -549,6 +550,9 @@ public class TLAEditor extends TextEditor */ public void updateFoldingStructure(List<Position> positions) { + if (annotationModel == null) { + return; + } Annotation[] annotations = new Annotation[positions.size()]; @@ -855,7 +859,12 @@ public class TLAEditor extends TextEditor { if (getSourceViewer() != null) { - getSourceViewer().setEditable(!EditorUtil.isReadOnly(((FileEditorInput) getEditorInput()).getFile())); + if (getEditorInput() instanceof IFileEditorInput) { + final IFileEditorInput fileEditorInput = (IFileEditorInput) getEditorInput(); + getSourceViewer().setEditable(!EditorUtil.isReadOnly(fileEditorInput.getFile())); + } else { + getSourceViewer().setEditable(false); + } } } diff --git a/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/proof/TLAProofFoldingStructureProvider.java b/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/proof/TLAProofFoldingStructureProvider.java index 45b4d4004df0b20a51c697fe4d6834627b8988bc..8aac5e74af396be5a6df0185ae0d53ceddb2cf57 100644 --- a/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/proof/TLAProofFoldingStructureProvider.java +++ b/org.lamport.tla.toolbox.editor.basic/src/org/lamport/tla/toolbox/editor/basic/proof/TLAProofFoldingStructureProvider.java @@ -6,9 +6,11 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Vector; import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IPath; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; @@ -17,10 +19,10 @@ import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.projection.ProjectionAnnotation; +import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.part.FileEditorInput; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.editor.basic.TLAEditor; -import org.lamport.tla.toolbox.editor.basic.TLAEditorActivator; import org.lamport.tla.toolbox.spec.parser.IParseResultListener; import org.lamport.tla.toolbox.spec.parser.ParseResult; import org.lamport.tla.toolbox.spec.parser.ParseResultBroadcaster; @@ -70,7 +72,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * of the statement which they prove. These proofs * do not fold. */ - private Vector foldPositions; + private Vector<TLAProofPosition> foldPositions; /** * Time stamp for last modification of the document * represented by the editor as returned by @@ -97,18 +99,20 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I canPerformFoldingCommands = true; this.editor = editor; this.document = editor.getDocumentProvider().getDocument(editor.getEditorInput()); - foldPositions = new Vector(); + foldPositions = new Vector<TLAProofPosition>(); // add this as listener to document to listen for changes document.addDocumentListener(this); // get a parse result if the parse result broadcaster has already stored one - ParseResult parseResult = ParseResultBroadcaster.getParseResultBroadcaster().getParseResult( - ((FileEditorInput) editor.getEditorInput()).getFile().getLocation()); - - if (parseResult != null) - { - newParseResult(parseResult); + if (editor.getEditorInput() instanceof IFileEditorInput) { + IFileEditorInput editorInput = (IFileEditorInput) editor.getEditorInput(); + IPath location = editorInput.getFile().getLocation(); + ParseResult parseResult = ParseResultBroadcaster.getParseResultBroadcaster().getParseResult(location); + if (parseResult != null) + { + newParseResult(parseResult); + } } // now listen to any new parse results @@ -131,8 +135,8 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * * @throws BadLocationException */ - private void computeProofFoldPositions(TheoremNode theoremNode, HashMap additions, List foldsInCurrentTree, - List previousFolds) throws BadLocationException + private void computeProofFoldPositions(TheoremNode theoremNode, Map<ProjectionAnnotation, TLAProofPosition> additions, List<TLAProofPosition> foldsInCurrentTree, + List<TLAProofPosition> previousFolds) throws BadLocationException { if (theoremNode.getProof() == null) @@ -167,9 +171,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * new folds for proof tree. */ TLAProofPosition matchingPosition = null; - for (Iterator it = previousFolds.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = previousFolds.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); // positions are considered the same if the beginning and end line are the same if (proofPosition.isSamePosition(proofNodeRegion, document)) @@ -235,6 +239,13 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I public void newParseResult(ParseResult parseResult) { + if (!(editor.getEditorInput() instanceof IFileEditorInput)) { + // input can be the file history which isn't an IFileEditorInput and + // thus cannot be parsed. There is no point parsing every revision + // of the file history anyway. + return; + } + /* * If the parsed resource for parseResult is in a different * directory than the file open in the editor, then parseResult @@ -243,12 +254,12 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * in the same directory. */ if (!parseResult.getParsedResource().getLocation().removeLastSegments(1).equals( - ((FileEditorInput) editor.getEditorInput()).getFile().getLocation().removeLastSegments(1))) + ((IFileEditorInput) editor.getEditorInput()).getFile().getLocation().removeLastSegments(1))) { return; } - String moduleName = ResourceHelper.getModuleName(((FileEditorInput) editor.getEditorInput()).getFile()); + String moduleName = ResourceHelper.getModuleName(((IFileEditorInput) editor.getEditorInput()).getFile()); // TLAEditorActivator.getDefault().logDebug("Proof structure provider for " + moduleName + " recieved a parse result."); @@ -302,8 +313,8 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I canPerformFoldingCommands = false; - HashMap additions = new HashMap(); - Vector foldsInCurrentTree = new Vector(); + Map<ProjectionAnnotation, TLAProofPosition> additions = new HashMap<ProjectionAnnotation, TLAProofPosition>(); + Vector<TLAProofPosition> foldsInCurrentTree = new Vector<TLAProofPosition>(); TheoremNode[] theorems = moduleNode.getTheorems(); @@ -331,9 +342,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I // compute array of annotations to be deleted Annotation[] deletions = new ProjectionAnnotation[foldPositions.size()]; int i = 0; - for (Iterator it = foldPositions.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); proofPosition.remove(document); deletions[i] = proofPosition.getAnnotation(); } @@ -348,9 +359,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I // then it makes sense to sort int currentOffset = -1; boolean isSorted = true; - for (Iterator it = foldPositions.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); if (proofPosition.getOffset() >= currentOffset) { currentOffset = proofPosition.getOffset(); @@ -363,19 +374,12 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I if (!isSorted) { - Collections.sort(foldPositions, new Comparator() { + Collections.sort(foldPositions, new Comparator<TLAProofPosition>() { - public int compare(Object arg0, Object arg1) - { - if (arg0 instanceof TLAProofPosition && arg1 instanceof TLAProofPosition) - { - return ((TLAProofPosition) arg0).getOffset() - ((TLAProofPosition) arg1).getOffset(); - } else - { - return 0; - } - } - }); + public int compare(TLAProofPosition arg0, TLAProofPosition arg1) { + return arg0.getOffset() - arg1.getOffset(); + } + }); } canPerformFoldingCommands = true; @@ -421,7 +425,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I { for (int i = 0; i < foldPositions.size(); i++) { - TLAProofPosition proofPosition = (TLAProofPosition) foldPositions.get(i); + TLAProofPosition proofPosition = foldPositions.get(i); if (proofPosition.containsBeforeProof(caretOffset, document)) { return true; @@ -443,24 +447,24 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * @param selection selection in the editor * @return */ - private boolean containedByProof(int caretOffset) - { - try - { - for (int i = 0; i < foldPositions.size(); i++) - { - TLAProofPosition proofPosition = (TLAProofPosition) foldPositions.get(i); - if (proofPosition.containsInProof(caretOffset, document)) - { - return true; - } - } - } catch (BadLocationException e) - { - Activator.getDefault().logError("Error computing if selection is in proof step.", e); - } - return false; - } +// private boolean containedByProof(int caretOffset) +// { +// try +// { +// for (int i = 0; i < foldPositions.size(); i++) +// { +// TLAProofPosition proofPosition = (TLAProofPosition) foldPositions.get(i); +// if (proofPosition.containsInProof(caretOffset, document)) +// { +// return true; +// } +// } +// } catch (BadLocationException e) +// { +// Activator.getDefault().logError("Error computing if selection is in proof step.", e); +// } +// return false; +// } /** * Returns whether or not the selection is at a proof step, theorem statement, or leaf proof. @@ -477,7 +481,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I { for (int i = 0; i < foldPositions.size(); i++) { - TLAProofPosition proofPosition = (TLAProofPosition) foldPositions.get(i); + TLAProofPosition proofPosition = foldPositions.get(i); if (proofPosition.containsInProofOrStatement(caretOffset, document)) { return true; @@ -606,10 +610,10 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I */ private void foldEverythingUnusable(int cursorOffset) { - Vector modifiedAnnotations = new Vector(); - for (Iterator it = foldPositions.iterator(); it.hasNext();) + Vector<Annotation> modifiedAnnotations = new Vector<Annotation>(); + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); try { if (proofPosition.containsInProofOrStatement(cursorOffset, document)) @@ -641,10 +645,10 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I */ private void foldAllProofs() { - Vector modifiedAnnotations = new Vector(); - for (Iterator it = foldPositions.iterator(); it.hasNext();) + Vector<Annotation> modifiedAnnotations = new Vector<Annotation>(); + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); if (!proofPosition.getAnnotation().isCollapsed()) { // should fold every proof @@ -660,10 +664,10 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I private void expandAllProofs() { - Vector modifiedAnnotations = new Vector(); - for (Iterator it = foldPositions.iterator(); it.hasNext();) + Vector<Annotation> modifiedAnnotations = new Vector<Annotation>(); + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); if (proofPosition.getAnnotation().isCollapsed()) { // should fold every proof @@ -683,7 +687,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I */ private void expandCurrentSubtree(int offset) { - ArrayList modifiedAnnotations = new ArrayList(); + List<Annotation> modifiedAnnotations = new ArrayList<Annotation>(); // find statement containing offset TLAProofPosition found = null; @@ -698,9 +702,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * This requires that the proof positions be sorted in ascending * order of offset. */ - for (Iterator it = foldPositions.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); try { if (found == null && proofPosition.containsBeforeProof(offset, document)) @@ -738,7 +742,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I */ private void hideCurrentSubtree(int offset) { - ArrayList modifiedAnnotations = new ArrayList(); + List<Annotation> modifiedAnnotations = new ArrayList<Annotation>(); // find statement containing offset TLAProofPosition found = null; @@ -753,9 +757,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * This requires that the proof positions be sorted in ascending * order of offset. */ - for (Iterator it = foldPositions.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); try { if (found == null && proofPosition.containsBeforeProof(offset, document)) @@ -794,7 +798,7 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I */ private void showImmediateDescendants(int offset) { - ArrayList modifiedAnnotations = new ArrayList(); + List<Annotation> modifiedAnnotations = new ArrayList<Annotation>(); // find statement containing offset TLAProofPosition found = null; @@ -809,9 +813,9 @@ public class TLAProofFoldingStructureProvider implements IParseResultListener, I * This requires that the proof positions be sorted in ascending * order of offset. */ - for (Iterator it = foldPositions.iterator(); it.hasNext();) + for (Iterator<TLAProofPosition> it = foldPositions.iterator(); it.hasNext();) { - TLAProofPosition proofPosition = (TLAProofPosition) it.next(); + TLAProofPosition proofPosition = it.next(); try { if (found == null && proofPosition.containsBeforeProof(offset, document)) diff --git a/org.lamport.tla.toolbox.feature.standalone/feature.xml b/org.lamport.tla.toolbox.feature.standalone/feature.xml index a1eae05aa28de806178cde45d1c3a9415187f0a6..1c1b0282c5f690af32fcbe606b1e956bf4d32de4 100644 --- a/org.lamport.tla.toolbox.feature.standalone/feature.xml +++ b/org.lamport.tla.toolbox.feature.standalone/feature.xml @@ -176,4 +176,18 @@ version="0.0.0" unpack="false"/> + <plugin + id="org.eclipse.team.ui" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + + <plugin + id="org.eclipse.team.core" + download-size="0" + install-size="0" + version="0.0.0" + unpack="false"/> + </feature> diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/AzureCloudTLCInstanceParameters.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/AzureCloudTLCInstanceParameters.java new file mode 100644 index 0000000000000000000000000000000000000000..24412bde304e46b32013dfe5bf2219fa51f0e7e2 --- /dev/null +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/AzureCloudTLCInstanceParameters.java @@ -0,0 +1,138 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.jcloud; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.jclouds.ContextBuilder; + +public class AzureCloudTLCInstanceParameters extends CloudTLCInstanceParameters { + + public AzureCloudTLCInstanceParameters(final String tlcParams) { + super(tlcParams.trim()); + } + + @Override + public String getJavaSystemProperties() { + return "-Dtlc2.tool.fp.FPSet.impl=tlc2.tool.fp.OffHeapDiskFPSet"; + } + + @Override + public String getJavaVMArgs() { + return "-Xmx32G -Xms32G -XX:MaxDirectMemorySize=64g"; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getTLCParameters() + */ + @Override + public String getTLCParameters() { + if (tlcParams.length() > 0) { + return "-workers 16 " + tlcParams; + } + return "-workers 16"; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getCloudProvier() + */ + @Override + public String getCloudProvider() { + return "azurecompute"; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getImageId() + */ + @Override + public String getImageId() { + return "b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_1-LTS-amd64-server-20150123-en-us-30GB"; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getHardwareId() + */ + @Override + public String getHardwareId() { + return "STANDARD_D14"; + // 16 cores + // 112GB + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getIdentity() + */ + @Override + public String getIdentity() { + final String identity = System.getenv("AZURE_COMPUTE_IDENTITY"); + Assert.isNotNull(identity); + return identity; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#getCredentials() + */ + @Override + public String getCredentials() { + final String credential = System.getenv("AZURE_COMPUTE_CREDENTIALS"); + Assert.isNotNull(credential); + return credential; + } + + private String getSubscriptionId() { + final String subscription = System.getenv("AZURE_COMPUTE_SUBSCRIPTION"); + Assert.isNotNull(subscription); + return subscription; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#validateCredentials() + */ + @Override + public IStatus validateCredentials() { + final String credential = System.getenv("AZURE_COMPUTE_CREDENTIALS"); + final String identity = System.getenv("AZURE_COMPUTE_IDENTITY"); + final String subscription = System.getenv("AZURE_COMPUTE_SUBSCRIPTION"); + if (credential == null || identity == null || subscription == null) { + return new Status(Status.ERROR, "org.lamport.tla.toolbox.jcloud", + "Invalid credentials, please check the environment variables " + + "(AZURE_COMPUTE_CREDENTIALS & AZURE_COMPUTE_IDENTITY " + + "and AZURE_COMPUTE_SUBSCRIPTION) are correctly " + + "set up and picked up by the Toolbox."); + } + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#mungeBuilder(org.jclouds.ContextBuilder) + */ + @Override + public void mungeBuilder(ContextBuilder builder) { + builder.endpoint("https://management.core.windows.net/" + getSubscriptionId()); + } +} diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudDistributedTLCJob.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudDistributedTLCJob.java index d884c2d54b149141b372c9608f73bfb8945feff5..5a063e6841517614f9141592a88ffdb018b1f7e8 100644 --- a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudDistributedTLCJob.java +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudDistributedTLCJob.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.jcloud; import static com.google.common.base.Predicates.not; @@ -19,7 +45,6 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.jclouds.ContextBuilder; -import org.jclouds.aws.ec2.reference.AWSEC2Constants; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.RunNodesException; @@ -44,15 +69,19 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; +/* + * TODO + * ==== + * - Reverse PTR records in DNS to make it less likely that emails coming out of the VM are classified as SPAM + * -- Azure has only support for it in its service API but not in JClouds + * -- AWS just has a form where users can request a PTR record + * - Send test mail during instance startup and communicate back to user on failure + */ public class CloudDistributedTLCJob extends Job { - private final String identity = System.getenv("AWS_ACCESS_KEY_ID"); - private final String credential = System - .getenv("AWS_SECRET_ACCESS_KEY"); - /** * The groupName has to be unique per job. This is how cloud instances are - * associated to this job. If two jobs use the same groupName, the will talk + * associated to this job. If two jobs use the same groupName, they will talk * to the same set of nodes. */ private final String groupNameUUID; @@ -65,36 +94,19 @@ public class CloudDistributedTLCJob extends Job { int numberOfWorkers, final Properties properties, CloudTLCInstanceParameters params) { super(aName); this.params = params; - groupNameUUID = aName + "-" + UUID.randomUUID().toString(); + //TODO groupNameUUID is used by some providers (azure) as a hostname/DNS name. Thus, format. + groupNameUUID = aName.toLowerCase() + "-" + UUID.randomUUID().toString(); props = properties; modelPath = aModelFolder.toPath(); } - - // http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AWSCredentials.html - private boolean validateAWSCredentials() { - // must not be null - if (identity != null && credential != null) { - // identity always starts with "AIKA" and has 20 chars - if (identity.matches("AKIA[a-zA-Z0-9]{16}")) { - // secret has 40 chars - return credential.length() == 40; - } - } - return false; - } @Override protected IStatus run(final IProgressMonitor monitor) { - monitor.beginTask("Starting TLC model checker in the cloud", 85); + monitor.beginTask("Starting TLC model checker in the cloud", 100); // Validate credentials and fail fast if null or syntactically incorrect - if (!validateAWSCredentials()) { - return new Status( - Status.ERROR, - "org.lamport.tla.toolbox.jcloud", - "Invalid credentials, please check the environment variables " - + "(AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY) are correctly " - + "set up and picked up by the Toolbox."); + if (!params.validateCredentials().equals(Status.OK_STATUS)) { + return params.validateCredentials(); } ComputeServiceContext context = null; @@ -112,8 +124,7 @@ public class CloudDistributedTLCJob extends Job { // example of specific properties, in this case optimizing image // list to only amazon supplied final Properties properties = new Properties(); - properties.setProperty(AWSEC2Constants.PROPERTY_EC2_AMI_QUERY, - params.getOwnerId()); + params.mungeProperties(properties); // Create compute environment in the cloud and inject an ssh // implementation. ssh is our means of communicating with the node. @@ -121,10 +132,11 @@ public class CloudDistributedTLCJob extends Job { .<AbstractModule> of(new SshjSshClientModule(), new SLF4JLoggingModule()); final ContextBuilder builder = ContextBuilder - .newBuilder(params.getCloudProvier()) - .credentials(identity, credential).modules(modules) + .newBuilder(params.getCloudProvider()) + .credentials(params.getIdentity(), params.getCredentials()).modules(modules) .overrides(properties); - + params.mungeBuilder(builder); + monitor.subTask("Initializing " + builder.getApiMetadata().getName()); context = builder.buildView(ComputeServiceContext.class); final ComputeService compute = context.getComputeService(); @@ -171,6 +183,21 @@ public class CloudDistributedTLCJob extends Job { return Status.CANCEL_STATUS; } + // Creating an entry in /etc/alias that makes sure system email sent + // to root ends up at the address given by the user. Note that this + // has to be done before postfix gets installed later. postfix + // re-generates the aliases file for us. + final String email = props.getProperty(TLCJobFactory.MAIL_ADDRESS); + monitor.subTask("Setting up root aliases to " + email + + " on all node(s)"); + compute.runScriptOnNodesMatching(inGroup(groupNameUUID), + exec("echo root: " + email + " >> /etc/aliases"), + new TemplateOptions().runAsRoot(true).wrapInInitScript(false)); + monitor.worked(10); + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + // Install custom tailored jmx2munin to monitor the TLC process. Can // either monitor standalone tlc2.TLC or TLCServer. monitor.subTask("Provisioning TLC Monitoring on all node(s)"); @@ -196,14 +223,33 @@ public class CloudDistributedTLCJob extends Job { // to the TLC process if logged in to the // instance directly (it's probably already // installed). - + "apt-get install screen -y"), + + "apt-get install screen -y && " + // postfix is an MTA that is used to send warnings + // produced by munin off of the system. E.g. + // if the hard disc becomes full, the user + // will receive an email giving her a chance + // to free space and prevent TLC from crashing. + + "apt-get install postfix heirloom-mailx -y"), new TemplateOptions().runAsRoot(true).wrapInInitScript( false)); monitor.worked(10); if (monitor.isCanceled()) { return Status.CANCEL_STATUS; } - + + // Install all security relevant system packages + monitor.subTask("Installing security relevant system packages (in background)"); + compute.runScriptOnNodesMatching( + inGroup(groupNameUUID), + exec("echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true | debconf-set-selections" + + " && " + + "apt-get install unattended-upgrades" + + " && " + + "/usr/bin/unattended-upgrades"), + new TemplateOptions().runAsRoot(true).wrapInInitScript( + false).blockOnComplete(false).blockUntilRunning(false)); + monitor.worked(5); + // Create /mnt/tlc and change permission to be world writable // Requires package 'apache2' to be already installed. apache2 // creates /var/www/html. @@ -235,7 +281,10 @@ public class CloudDistributedTLCJob extends Job { }; }; // Copy tlatools.jar to _one_ remote host (do not exhaust upload of - // the machine running the toolbox) + // the machine running the toolbox). + // TODO Share the tla2tools.jar with the worker nodes by making it + // available on the master's webserver for the clients to download. + // On the other hand this means we are making the spec world-readable. SshClient sshClient = context.utils().sshForNode().apply(master); sshClient.put("/mnt/tlc/tla2tools.jar", jarPayLoad); sshClient.disconnect(); @@ -292,7 +341,7 @@ public class CloudDistributedTLCJob extends Job { new TemplateOptions().runAsRoot(false).wrapInInitScript( true).blockOnComplete(false).blockUntilRunning(false)); monitor.worked(5); - + // Communicate result to user monitor.done(); final String hostname = Iterables.getOnlyElement(master.getPublicAddresses()); // master.getHostname() only returns internal name diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCInstanceParameters.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCInstanceParameters.java index df9d6e0043be408d3872372c8aae145336fa6197..14ec4ec1fdd7ec08bc8f2a17b049b67985dd22aa 100644 --- a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCInstanceParameters.java +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCInstanceParameters.java @@ -1,5 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.jcloud; +import java.util.Properties; + +import org.eclipse.core.runtime.IStatus; +import org.jclouds.ContextBuilder; + /** * This class serves two purposes. * @@ -8,17 +39,44 @@ package org.lamport.tla.toolbox.jcloud; */ public abstract class CloudTLCInstanceParameters { - public abstract String getOwnerId(); + protected final String tlcParams; + + public CloudTLCInstanceParameters(String tlcParams) { + this.tlcParams = tlcParams; + } - public abstract String getCloudProvier(); + public String getJavaSystemProperties() { + return "-Dtlc2.tool.fp.FPSet.impl=tlc2.tool.fp.OffHeapDiskFPSet"; + } + + public String getJavaVMArgs() { + return "-Xmx24G -Xms24G -XX:MaxDirectMemorySize=32g"; + } + + public String getTLCParameters() { + if (tlcParams.length() > 0) { + return "-workers 12 " + tlcParams; + } + return "-workers 12"; + } + + public abstract String getCloudProvider(); public abstract String getImageId(); public abstract String getHardwareId(); - public abstract String getJavaSystemProperties(); + public abstract String getIdentity(); - public abstract String getJavaVMArgs(); + public abstract String getCredentials(); - public abstract String getTLCParameters(); + public abstract IStatus validateCredentials(); + + public void mungeProperties(Properties properties) { + // Nothing to be done here + } + + public void mungeBuilder(ContextBuilder builder) { + // Nothing to be done here + } } diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCJobFactory.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCJobFactory.java index 80be93583f7890abfbcaccdd03ef201c8004703c..182bf2b26c98b852f980df0c6ae54fc56f55a9fd 100644 --- a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCJobFactory.java +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/CloudTLCJobFactory.java @@ -1,16 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2014 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.jcloud; import java.io.File; import java.util.Properties; +import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.jobs.Job; import org.lamport.tla.toolbox.tool.tlc.job.TLCJobFactory; public class CloudTLCJobFactory implements TLCJobFactory { + private static final String AZURECOMPUTE = "Azure"; + private static final String AWS_EC2 = "aws-ec2"; + @Override public Job getTLCJob(String aName, File aModelFolder, int numberOfWorkers, final Properties props, String tlcparams) { - return new CloudDistributedTLCJob(aName, aModelFolder, numberOfWorkers, props, new EC2CloudTLCInstanceParameters(tlcparams)); + Assert.isNotNull(aName); + if (AWS_EC2.equals(aName)) { + return new CloudDistributedTLCJob(aName, aModelFolder, numberOfWorkers, props, new EC2CloudTLCInstanceParameters(tlcparams)); + } else if (AZURECOMPUTE.equals(aName)) { + return new CloudDistributedTLCJob(aName, aModelFolder, numberOfWorkers, props, new AzureCloudTLCInstanceParameters(tlcparams)); + } + throw new IllegalArgumentException(aName + " is an unknown cloud"); } - } diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/EC2CloudTLCInstanceParameters.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/EC2CloudTLCInstanceParameters.java index 4adbde95c0cc0e47ef1bc159443f41c41a3145c2..47eede7eb8694c5884a1f07e30075d459eb3bb13 100644 --- a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/EC2CloudTLCInstanceParameters.java +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/EC2CloudTLCInstanceParameters.java @@ -1,32 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2014 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.jcloud; -public class EC2CloudTLCInstanceParameters extends CloudTLCInstanceParameters { +import java.util.Properties; - private final String tlcParams; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.jclouds.aws.ec2.reference.AWSEC2Constants; + +public class EC2CloudTLCInstanceParameters extends CloudTLCInstanceParameters { public EC2CloudTLCInstanceParameters(final String tlcParams) { - this.tlcParams = tlcParams.trim(); + super(tlcParams.trim()); } - @Override - public String getOwnerId() { + private String getOwnerId() { // ubuntu official return "owner-id=owner-id=099720109477;state=available;image-type=machine"; } @Override - public String getCloudProvier() { + public String getCloudProvider() { return "aws-ec2"; } @Override public String getImageId() { - // Ubuntu 64bit 14.04.2 Trusty paravirtual/instance-store release + // Ubuntu 64bit 14.04.3 Trusty paravirtual/instance-store release // https://cloud-images.ubuntu.com/releases/14.04/release/ // or http://cloud-images.ubuntu.com/locator/ec2/ // See http://aws.amazon.com/amazon-linux-ami/instance-type-matrix/ // for paravirtual vs. hvm - return "us-east-1/ami-3234315a"; + return "us-east-1/ami-0a7a3f60"; } @Override @@ -36,20 +65,40 @@ public class EC2CloudTLCInstanceParameters extends CloudTLCInstanceParameters { } @Override - public String getJavaSystemProperties() { - return "-Dtlc2.tool.fp.FPSet.impl=tlc2.tool.fp.OffHeapDiskFPSet"; + public String getIdentity() { + return System.getenv("AWS_ACCESS_KEY_ID"); } @Override - public String getJavaVMArgs() { - return "-Xmx24G -Xms24G -XX:MaxDirectMemorySize=32g"; + public String getCredentials() { + return System.getenv("AWS_SECRET_ACCESS_KEY"); } - + + // http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AWSCredentials.html @Override - public String getTLCParameters() { - if (tlcParams.length() > 0) { - return "-workers 12 " + tlcParams; + public IStatus validateCredentials() { + // must not be null + if (getIdentity() != null && getCredentials() != null) { + // identity always starts with "AIKA" and has 20 chars + if (getIdentity().matches("AKIA[a-zA-Z0-9]{16}")) { + // secret has 40 chars + if (getCredentials().length() == 40) { + return Status.OK_STATUS; + } + } } - return "-workers 12"; + return new Status(Status.ERROR, "org.lamport.tla.toolbox.jcloud", + "Invalid credentials, please check the environment variables " + + "(AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY) are correctly " + + "set up and picked up by the Toolbox."); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.jcloud.CloudTLCInstanceParameters#mungeProperties(java.util.Properties) + */ + @Override + public void mungeProperties(Properties properties) { + properties.setProperty(AWSEC2Constants.PROPERTY_EC2_AMI_QUERY, + getOwnerId()); } } diff --git a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/JCloudActivator.java b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/JCloudActivator.java index 0ae4f6ea2342e314692fd3e07d85afbc589b2a21..7b35811e3f4cfa2e91794f36d25cf82600458305 100644 --- a/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/JCloudActivator.java +++ b/org.lamport.tla.toolbox.jclouds/src/org/lamport/tla/toolbox/jcloud/JCloudActivator.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.jcloud; import org.osgi.framework.BundleActivator; @@ -7,29 +33,9 @@ public class JCloudActivator implements BundleActivator { @Override public void start(BundleContext ctx) throws Exception { -// final Properties props = new Properties(); -// props.put("cloud", "aws-ec2"); -// ctx.registerService(TLCJob.class, new ServiceFactory<TLCJob>() { -// -// @Override -// public TLCJob getService(Bundle bundle, -// ServiceRegistration<TLCJob> registration) { -// return new CloudDistributedTLCJob(""); -// } -// -// @Override -// public void ungetService(Bundle bundle, -// ServiceRegistration<TLCJob> registration, TLCJob service) { -// } -// }, props); -// -// File path = new File("/home/markus/src/TLA/models/Grid5kPerformanceTest/Test.toolbox/k8l8n6"); -// Job j = new CloudDistributedTLCJob("foo", path, 1); -// j.schedule(); } @Override public void stop(BundleContext ctx) throws Exception { - } } diff --git a/org.lamport.tla.toolbox.jnlp/src/org/lamport/tla/toolbox/distributed/IndexServlet.java b/org.lamport.tla.toolbox.jnlp/src/org/lamport/tla/toolbox/distributed/IndexServlet.java index f411b2421bdab5a752e2c868dbe72a61b7629758..d76ebc6341f86733e01fd5bfe118a93d55a5cc0f 100644 --- a/org.lamport.tla.toolbox.jnlp/src/org/lamport/tla/toolbox/distributed/IndexServlet.java +++ b/org.lamport.tla.toolbox.jnlp/src/org/lamport/tla/toolbox/distributed/IndexServlet.java @@ -18,7 +18,7 @@ public class IndexServlet extends URLHttpServlet { @Override protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); - + resp.setContentType("text/html"); resp.getWriter().println("<!DOCTYPE html>"); @@ -26,12 +26,12 @@ public class IndexServlet extends URLHttpServlet { resp.getWriter().println( "<html><head>\n" + "<title>Distributed TLC</title>\n" + - "</head>"); + "</head><body>"); // boostrap JRE on Windows systems resp.getWriter().println( "<object codebase=\"http://java.sun.com/update/1.7.0/jinstall-7u80-windows-i586.cab#Version=7,0,0,0\" classid=\"clsid:5852F5ED-8BF4-11D4-A245-0080C6F74284\" height='0' width='0'>" + - "<param name=\"app\" value=\"" + url + "\"/>" + + "<param name=\"app\" value=\"" + addr + "\"/>" + "<param name=\"back\" value=\"false\"/>" + "</object>"); @@ -111,7 +111,7 @@ public class IndexServlet extends URLHttpServlet { "<li>\n" + "<p>Run from slave command line:</p>\n" + "<p><pre>" + - "javaws " + url + jnlpName + + "javaws " + addr + " " + jnlpName + "</pre></p>\n" + "</li>"); diff --git a/org.lamport.tla.toolbox.product.product/TLAToolbox.target b/org.lamport.tla.toolbox.product.product/TLAToolbox.target index fb7923cc7dc7c9000a5d5109d763b0a7411d1eb9..742b8cb150325bb7347420d41d69b71c313f09d7 100644 --- a/org.lamport.tla.toolbox.product.product/TLAToolbox.target +++ b/org.lamport.tla.toolbox.product.product/TLAToolbox.target @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> -<?pde version="3.8"?><target name="TLA+ Toolbox" sequenceNumber="62"> +<?pde version="3.8"?><target name="TLA+ Toolbox" sequenceNumber="70"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> -<unit id="org.eclipse.contribution.weaving.feature.group" version="2.2.3.e42x-RELEASE-20130625-1400"/> -<unit id="org.aspectj.weaver" version="1.7.3.20130613144500-a"/> -<repository location="http://download.eclipse.org/tools/ajdt/42/update"/> +<unit id="org.eclipse.contribution.weaving.feature.group" version="2.2.4.e45x-20150206-1600"/> +<unit id="org.aspectj.feature.group" version="1.8.5.20150128171000"/> +<repository location="http://download.eclipse.org/tools/ajdt/mars/update/"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="de.vonloesch.pdf4eclipse.feature.feature.group" version="1.0.1.201502251926"/> @@ -27,29 +27,29 @@ <unit id="org.eclipse.swtbot.eclipse.test.junit.feature.group" version="2.2.1.201402241301"/> <unit id="org.eclipse.swtbot.feature.group" version="2.2.1.201402241301"/> <unit id="org.eclipse.swtbot.eclipse.feature.group" version="2.2.1.201402241301"/> -<repository location="http://download.eclipse.org/technology/swtbot/releases/latest/"/> +<repository location="http://download.eclipse.org/technology/swtbot/releases/2.2.1/"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> <unit id="org.apache.jclouds.feature.feature.group" version="0.0.0"/> <repository location="http://lemmy.github.com/jclouds2p2/"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="false" includeMode="planner" includeSource="true" type="InstallableUnit"> -<unit id="org.eclipse.rcp.id" version="4.4.2.M20150204-1700"/> -<unit id="org.eclipse.help.feature.group" version="2.0.103.v20150204-1700"/> -<unit id="org.eclipse.equinox.core.sdk.feature.group" version="3.10.2.v20150203-1939"/> -<unit id="org.eclipse.rcp.sdk.id" version="4.4.2.M20150204-1700"/> -<unit id="org.eclipse.platform.ide" version="4.4.2.M20150204-1700"/> -<unit id="org.eclipse.rcp.configuration.feature.group" version="1.0.3.v20150204-1745"/> -<unit id="org.eclipse.emf.ecore.feature.group" version="2.10.2.v20150123-0348"/> -<unit id="org.eclipse.equinox.executable" version="3.6.102.v20150204-1316"/> -<unit id="org.eclipse.ecf.core.feature.feature.group" version="1.1.0.v20141221-2352"/> -<unit id="org.eclipse.e4.rcp.feature.group" version="1.3.101.v20150204-1353"/> -<unit id="org.eclipse.platform.sdk" version="4.4.2.M20150204-1700"/> -<unit id="org.eclipse.emf.common.feature.group" version="2.10.1.v20150123-0348"/> -<unit id="org.eclipse.equinox.sdk.feature.group" version="3.10.2.v20150204-1316"/> -<unit id="org.eclipse.core.runtime.feature.feature.group" version="1.1.2.v20150203-1939"/> -<unit id="org.eclipse.sdk.ide" version="4.4.2.M20150204-1700"/> -<repository location="http://download.eclipse.org/eclipse/updates/4.4/R-4.4.2-201502041700"/> +<unit id="org.eclipse.rcp.id" version="4.5.1.M20150904-0015"/> +<unit id="org.eclipse.help.feature.group" version="2.1.1.v20150904-0015"/> +<unit id="org.eclipse.equinox.core.sdk.feature.group" version="3.11.1.v20150831-1342"/> +<unit id="org.eclipse.rcp.sdk.id" version="4.5.1.M20150904-0015"/> +<unit id="org.eclipse.platform.ide" version="4.5.1.M20150904-0015"/> +<unit id="org.eclipse.rcp.configuration.feature.group" version="1.0.101.v20150904-0015"/> +<unit id="org.eclipse.emf.ecore.feature.group" version="2.11.1.v20150805-0538"/> +<unit id="org.eclipse.equinox.executable" version="3.6.200.v20150602-1417"/> +<unit id="org.eclipse.ecf.core.feature.feature.group" version="1.1.0.v20150810-1719"/> +<unit id="org.eclipse.e4.rcp.feature.group" version="1.4.0.v20150903-1804"/> +<unit id="org.eclipse.platform.sdk" version="4.5.1.M20150904-0015"/> +<unit id="org.eclipse.emf.common.feature.group" version="2.11.0.v20150805-0538"/> +<unit id="org.eclipse.equinox.sdk.feature.group" version="3.11.1.v20150831-1342"/> +<unit id="org.eclipse.core.runtime.feature.feature.group" version="1.1.101.v20150903-1804"/> +<unit id="org.eclipse.sdk.ide" version="4.5.1.M20150904-0015"/> +<repository location="http://download.eclipse.org/eclipse/updates/4.5/"/> </location> </locations> <targetJRE path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> diff --git a/org.lamport.tla.toolbox.product.product/org.lamport.tla.toolbox.product.product.product b/org.lamport.tla.toolbox.product.product/org.lamport.tla.toolbox.product.product.product index 9e01e6e514732fd9767f1593ce517b61e9df18cd..d0af1bd25cf69140657ebe0d52952d46f7796b9b 100644 --- a/org.lamport.tla.toolbox.product.product/org.lamport.tla.toolbox.product.product.product +++ b/org.lamport.tla.toolbox.product.product/org.lamport.tla.toolbox.product.product.product @@ -1,17 +1,17 @@ <?xml version="1.0" encoding="UTF-8"?> <?pde version="3.5"?> -<product name="TLA+ Toolbox" uid="org.lamport.tla.toolbox.product.product" id="org.lamport.tla.toolbox.product.standalone.product" application="org.lamport.tla.toolbox.application" version="1.5.1" useFeatures="true" includeLaunchers="true"> +<product name="TLA+ Toolbox" uid="org.lamport.tla.toolbox.product.product" id="org.lamport.tla.toolbox.product.standalone.product" application="org.lamport.tla.toolbox.application" version="1.5.2" useFeatures="true" includeLaunchers="true"> <aboutInfo> <image path="/org.lamport.tla.toolbox.product.standalone/images/splash_small.png"/> <text> TLA+ Toolbox provides a user interface for TLA+ Tools. -This is Version 1.5.1 of 1 June 2015 and includes: +This is Version 1.5.2 of 21 December 2015 and includes: - SANY Version 2.1 of 24 February 2014 - - TLC Version 2.07 of 1 June 2015 - - PlusCal Version 1.8 of 2 April 2013 + - TLC Version 2.08 of 21 December 2015 + - PlusCal Version 1.8 of 18 August 2015 - TLATeX Version 1.0 of 12 April 2013 Don't forget to click on help. You can learn about features that you never knew about or have forgotten. @@ -34,6 +34,7 @@ Please send us reports of problems or suggestions; see https://groups.google.com <windowImages i16="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_16.png" i32="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_32.png" i48="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_48.png" i64="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_64.png" i128="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_128.png" i256="/org.lamport.tla.toolbox/icons/full/etool16/tla_launch_check_wiz_256.png"/> + <launcher name="toolbox"> <solaris/> <win useIco="false"> @@ -59,6 +60,7 @@ Please send us reports of problems or suggestions; see https://groups.google.com <configurations> <plugin id="aws-ec2" autoStart="true" startLevel="4" /> + <plugin id="azurecompute" autoStart="true" startLevel="4" /> <plugin id="com.jcraft.jsch" autoStart="true" startLevel="4" /> <plugin id="ec2" autoStart="true" startLevel="4" /> <plugin id="jclouds-core" autoStart="true" startLevel="4" /> @@ -69,7 +71,7 @@ Please send us reports of problems or suggestions; see https://groups.google.com <plugin id="org.eclipse.equinox.http.registry" autoStart="true" startLevel="3" /> <plugin id="org.lamport.tla.toolbox.jclouds" autoStart="true" startLevel="4" /> <plugin id="sts" autoStart="true" startLevel="4" /> - <property name="eclipse.buildId" value="1.5.1" /> + <property name="eclipse.buildId" value="1.5.2" /> </configurations> <repositories> diff --git a/org.lamport.tla.toolbox.product.product/pom.xml b/org.lamport.tla.toolbox.product.product/pom.xml index ee6c51827a2bda72ad3e7d4a6596920232dbac96..30621dd4be57a9b1b4fb33ad23bb3892be411601 100644 --- a/org.lamport.tla.toolbox.product.product/pom.xml +++ b/org.lamport.tla.toolbox.product.product/pom.xml @@ -75,8 +75,12 @@ over features. The version segment has to be manually increment upon a release. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=357503#c22 --> - <archiveFileName>TLAToolbox-1.5.1</archiveFileName> + <archiveFileName>TLAToolbox-1.5.2</archiveFileName> <rootFolder>toolbox</rootFolder> + <!-- This causes the Toolbox to be packaged as a single Application Bundle + on Mac. Applciation Bundles are the default packaging for Mac where the single + file can simply be dropped into "Applications". --> + <rootFolders><macosx>TLA+ Toolbox.app</macosx></rootFolders> </product> </products> </configuration> diff --git a/org.lamport.tla.toolbox.product.standalone/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.product.standalone/META-INF/MANIFEST.MF index 9665635d4e2cc19977f950072f8b3386e17a8e01..5ff9affd3ff13e08902faaa4c77137a9366a4531 100644 --- a/org.lamport.tla.toolbox.product.standalone/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.product.standalone/META-INF/MANIFEST.MF @@ -13,7 +13,8 @@ Require-Bundle: org.eclipse.ui, org.eclipse.ui.forms, org.eclipse.ui.ide;bundle-version="3.4.1", org.eclipse.swt, - org.lamport.tla.toolbox;bundle-version="1.0.0", org.eclipse.equinox.p2.ui.sdk.scheduler;bundle-version="1.0.0" Bundle-ActivationPolicy: lazy Bundle-Activator: org.lamport.tla.toolbox.StandaloneActivator +Export-Package: org.lamport.tla.toolbox.lifecycle, + org.lamport.tla.toolbox.ui.intro diff --git a/org.lamport.tla.toolbox.product.standalone/images/splash_small.bmp b/org.lamport.tla.toolbox.product.standalone/images/splash_small.bmp new file mode 100644 index 0000000000000000000000000000000000000000..c4ea2eebc90bf0c5473b1038beda3ff0a5ac4a2f Binary files /dev/null and b/org.lamport.tla.toolbox.product.standalone/images/splash_small.bmp differ diff --git a/org.lamport.tla.toolbox.product.standalone/plugin.xml b/org.lamport.tla.toolbox.product.standalone/plugin.xml index 15f2d7a85f6a1987efd77e5c2fc72764753343a1..66f90b8f93e46005de8dbbb7beaa30a4f9b13498 100644 --- a/org.lamport.tla.toolbox.product.standalone/plugin.xml +++ b/org.lamport.tla.toolbox.product.standalone/plugin.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.2"?> <plugin> + <extension-point id="org.lamport.tla.toolbox.tool" name="Toolbox Life Cycle Participant" schema="schema/org.lamport.tla.toolbox.tool.exsd"/> <!-- --> <!-- Application --> @@ -29,7 +30,7 @@ </property> <property name="aboutText" - value="TLA+ Toolbox provides a user interface for TLA+ Tools. 

This is Version 1.5.1 of 1 June 2015 and includes:
 - SANY Version 2.1 of 24 February 2014
 - TLC Version 2.07 of 1 June 2015
 - PlusCal Version 1.8 of 2 April 2013
 - TLATeX Version 1.0 of 12 April 2013

Don't forget to click on help. You can learn about features that you never knew about or have forgotten.

Please send us reports of problems or suggestions; see https://groups.google.com/d/forum/tlaplus ."> + value="TLA+ Toolbox provides a user interface for TLA+ Tools. 

This is Version 1.5.2 of 21 December 2015 and includes:
 - SANY Version 2.1 of 24 February 2014
 - TLC Version 2.08 of 21 December 2015
 - PlusCal Version 1.8 of 18 August 2015
 - TLATeX Version 1.0 of 12 April 2013

Don't forget to click on help. You can learn about features that you never knew about or have forgotten.

Please send us reports of problems or suggestions; see https://groups.google.com/d/forum/tlaplus ."> </property> <property name="aboutImage" @@ -48,7 +49,7 @@ <extension point="org.eclipse.ui.intro"> <intro - class="org.lamport.tla.toolbox.ui.intro.ToolboxIntoPart" + class="org.lamport.tla.toolbox.ui.intro.ToolboxIntroPart" id="org.lamport.tla.toolbox.product.standalone.intro"> </intro> <introProductBinding diff --git a/org.lamport.tla.toolbox/schema/org.lamport.tla.toolbox.tool.exsd b/org.lamport.tla.toolbox.product.standalone/schema/org.lamport.tla.toolbox.tool.exsd similarity index 100% rename from org.lamport.tla.toolbox/schema/org.lamport.tla.toolbox.tool.exsd rename to org.lamport.tla.toolbox.product.standalone/schema/org.lamport.tla.toolbox.tool.exsd diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/Application.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/Application.java index ae1c1f46dd7e4cf8eadf7ef486daf300f0eb3312..25fa173b0a3c95b754811cf402bb70f624b2cf29 100644 --- a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/Application.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/Application.java @@ -1,8 +1,15 @@ // Copyright (c) Jan 30, 2012 Microsoft Corporation. All rights reserved. package org.lamport.tla.toolbox; +import java.io.File; +import java.io.IOException; + +import org.eclipse.core.runtime.Platform; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PlatformUI; @@ -11,7 +18,6 @@ import org.eclipse.ui.PlatformUI; * This class controls all aspects of the application's execution * * @author Simon Zambrovski - * @version $Id$ */ public class Application implements IApplication { @@ -35,6 +41,36 @@ public class Application implements IApplication { + " started without arguments."); } } + + // The call to getStateLocation makes sure the instance location gets + // initialized before we call .lock on it. + StandaloneActivator.getDefault().getStateLocation(); + final Location instanceLocation = Platform.getInstanceLocation(); + // Only allow a single Toolbox instance per workspace to prevent data + // corruption in the workspace files. + try { + if (!instanceLocation.lock()) { + final File workspaceDirectory = new File(Platform.getInstanceLocation().getURL().getFile()); + if (workspaceDirectory.exists()) { + MessageDialog.openError(PlatformUI.createDisplay().getActiveShell(), "Toolbox files cannot be locked", + NLS.bind( + "Could not launch the Toolbox because the associated workspace is currently in use by another Toolbox. Opening two instances on the same workspace leads to data corruption.\n\n" + + "If this is incorrect and there is no other Toolbox running, an earlier Toolbox terminated without releasing the lock. Please manually delete the lock file ''{0}'' and try restarting the Toolbox.", + workspaceDirectory.getAbsolutePath() + .concat(File.separator + ".metadata" + File.separator + ".lock"))); + } + // We showed an error to the user, lets do a "clean" (0) exit to + // not raise a second window with a detailed technical error. + System.exit(0); + } + } catch (IOException e) { + StandaloneActivator.getDefault().logError("Toolbox files cannot be locked", e); + MessageDialog.openError(PlatformUI.createDisplay().getActiveShell(), "Toolbox files cannot be locked", + "Could not launch the Toolbox because acquiring the associated workspace lock failed. We are sorry, this is a bug. Please get in contact with us."); + // We showed an error to the user, lets do a "clean" (0) exit to + // not raise a second window with a detailed technical error. + System.exit(0); + } Display display = PlatformUI.createDisplay(); try { diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchAdvisor.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchAdvisor.java index 638220ae16da62a474ba82ead38ccbab69eff407..1e3536e0f13da63a82c6423b38b69584d58e3e09 100644 --- a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchAdvisor.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchAdvisor.java @@ -9,24 +9,18 @@ import org.eclipse.ui.application.IWorkbenchWindowConfigurer; import org.eclipse.ui.application.WorkbenchAdvisor; import org.eclipse.ui.application.WorkbenchWindowAdvisor; import org.eclipse.ui.ide.IDE; -import org.lamport.tla.toolbox.tool.ToolboxHandle; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleException; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant; -import org.lamport.tla.toolbox.util.ToolboxLifecycleParticipantManger; -import org.lamport.tla.toolbox.util.UIHelper; +import org.lamport.tla.toolbox.ui.intro.ToolboxIntroPart; import org.osgi.framework.Bundle; /** * This workbench advisor creates the window advisor, and specifies * the perspective id for the initial window. * @author Simon Zambrovski - * @version $Id$ */ public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor { // TODO MOVE! - public static final String PERSPECTIVE_ID = "org.lamport.tla.toolbox.ui.perspective.initial"; public static final String IDE_PLUGIN = "org.eclipse.ui.ide"; public static final String PATH_OBJECT = "icons/full/obj16/"; public static final String PATH_WIZBAN = "icons/full/wizban/"; @@ -39,7 +33,6 @@ public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor * Image definition from org.eclipse.ui.internal.ide.IDEInternalWorkbenchImages#IMG_DLGBAN_SAVEAS_DLG */ public static final String IMG_DLGBAN_SAVEAS_DLG = "IMG_DLGBAN_SAVEAS_DLG"; - private ToolboxLifecycleParticipant[] registeredTools; public WorkbenchWindowAdvisor createWorkbenchWindowAdvisor(IWorkbenchWindowConfigurer configurer) { @@ -48,7 +41,7 @@ public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor public String getInitialWindowPerspectiveId() { - return PERSPECTIVE_ID; + return ToolboxIntroPart.PERSPECTIVE_ID; } /* @@ -80,41 +73,19 @@ public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor configurer.declareImage(symbolicName, desc, shared); } - public boolean preShutdown() - { - if (!ToolboxHandle.getInstanceStore().getBoolean(ToolboxHandle.I_RESTORE_LAST_SPEC)) - { - UIHelper.getActivePage().closeAllEditors(true); - UIHelper.switchPerspective(getInitialWindowPerspectiveId()); - } - - try - { - ToolboxLifecycleParticipantManger.terminate(registeredTools); - } catch (ToolboxLifecycleException e) - { - // TODO - e.printStackTrace(); - } - - return super.preShutdown(); - } + /* (non-Javadoc) + * @see org.eclipse.ui.application.WorkbenchAdvisor#preShutdown() + */ + public boolean preShutdown() { + ToolboxLifecycleParticipantManger.terminate(); + return super.preShutdown(); + } - public void postStartup() - { - super.postStartup(); - - try - { - registeredTools = ToolboxLifecycleParticipantManger.getRegisteredTools(); - ToolboxLifecycleParticipantManger.initialize(registeredTools); - } catch (ToolboxLifecycleException e) - { - // TODO - e.printStackTrace(); - } - - } - - + /* (non-Javadoc) + * @see org.eclipse.ui.application.WorkbenchAdvisor#postStartup() + */ + public void postStartup() { + super.postStartup(); + ToolboxLifecycleParticipantManger.initialize(); + } } diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchWindowAdvisor.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchWindowAdvisor.java index 8b856d4d30ba6d6549b8f7981666ab3ee747ec7f..61148738ee959d3c84f79b7b082460de0994d8a6 100644 --- a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchWindowAdvisor.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ApplicationWorkbenchWindowAdvisor.java @@ -15,13 +15,9 @@ import org.eclipse.ui.application.IActionBarConfigurer; import org.eclipse.ui.application.IWorkbenchWindowConfigurer; import org.eclipse.ui.application.WorkbenchWindowAdvisor; import org.eclipse.ui.internal.ide.EditorAreaDropAdapter; -import org.lamport.tla.toolbox.ui.navigator.ToolboxExplorer; -import org.lamport.tla.toolbox.ui.view.ProblemView; /** * Configuration of the main window - * @version $Id$ - * @author zambrovski */ public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor { @@ -61,6 +57,11 @@ public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor // @see http://bugzilla.tlaplus.net/show_bug.cgi?id=191 final List filters = new ArrayList(); filters.add("org.eclipse.compare"); + // The following three preferences are shown because the Toolbox uses + // the local history feature provided by o.e.team.ui + filters.add("org.eclipse.team.ui"); + filters.add("org.eclipse.ui.trace"); + filters.add("org.eclipse.jsch.ui"); // Clean the preferences final List elements = preferenceManager.getElements(PreferenceManager.POST_ORDER); @@ -82,14 +83,11 @@ public class ApplicationWorkbenchWindowAdvisor extends WorkbenchWindowAdvisor } } } + super.postWindowOpen(); // At this point in time we can be certain that the UI is fully // instantiated (views, editors, menus...). Thus, register // listeners that connect the UI to the workspace resources. - ProblemView.ResourceListener.init(); - ToolboxExplorer.ResourceListener.init(); - - super.postWindowOpen(); + ToolboxLifecycleParticipantManger.postWorkbenchWindowOpen(); } - } diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/StandaloneActivator.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/StandaloneActivator.java index 85fd9953b416d400040bc5f2590b4f1f45d33c9c..2d1c305fbf026eef396a00172dfceb87be34ba25 100644 --- a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/StandaloneActivator.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/StandaloneActivator.java @@ -2,16 +2,20 @@ package org.lamport.tla.toolbox; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ui.plugin.AbstractUIPlugin; + /** * @author Markus Alexander Kuppe */ -public class StandaloneActivator extends AbstractTLCActivator { +public class StandaloneActivator extends AbstractUIPlugin { + private static final int DEBUG_SEVERITY = -1; public static final String PLUGIN_ID = "org.lamport.tla.toolbox.product.standalone"; private static StandaloneActivator plugin; public StandaloneActivator() { - super(PLUGIN_ID); plugin = this; } @@ -23,4 +27,64 @@ public class StandaloneActivator extends AbstractTLCActivator { public static StandaloneActivator getDefault() { return plugin; } + + /** + * Writes a string and a cause into the error category of the log + * + * @param string + */ + public void logError(String message) { + getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, message)); + } + + /** + * Writes a string and a cause into the error category of the log + * + * @param string + * @param e + */ + public void logError(String message, Throwable cause) { + getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, message, cause)); + } + + /** + * Writes a string and a cause into the warning category of the log + * + * @param string + */ + public void logWarning(String message) { + getLog().log(new Status(IStatus.WARNING, PLUGIN_ID, message)); + } + + /** + * Writes a string and a cause into the warning category of the log + * + * @param string + */ + public void logWarning(String message, Throwable cause) { + getLog().log(new Status(IStatus.WARNING, PLUGIN_ID, message, cause)); + } + + /** + * Writes a string into some debugging place + */ + public void logDebug(String message) { + logDebug(message, null); + } + + /** + * Writes a string into some debugging place + */ + public void logDebug(String message, Throwable cause) { + getLog().log(new Status(IStatus.INFO, PLUGIN_ID, DEBUG_SEVERITY, message, cause)); + } + + /** + * Writes a string into the info category of the log + * + * @param string + */ + public void logInfo(String message) { + getLog().log(new Status(IStatus.INFO, PLUGIN_ID, message)); + } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/ToolboxLifecycleParticipantManger.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ToolboxLifecycleParticipantManger.java similarity index 66% rename from org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/ToolboxLifecycleParticipantManger.java rename to org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ToolboxLifecycleParticipantManger.java index cfcb4e20a4b054284a35dd1faac959ff630f3372..9ba934efad761df8e100e314b3faf11a42fbcc1a 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/ToolboxLifecycleParticipantManger.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ToolboxLifecycleParticipantManger.java @@ -1,17 +1,15 @@ -package org.lamport.tla.toolbox.util; +package org.lamport.tla.toolbox; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Platform; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleException; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; /** * Provides methods for accessing the extensions registered to the toolbox extension points * * @author Simon Zambrovski - * @version $Id$ */ public class ToolboxLifecycleParticipantManger { @@ -22,7 +20,7 @@ public class ToolboxLifecycleParticipantManger * Retrieves tools registered * @return */ - public static ToolboxLifecycleParticipant[] getRegisteredTools() throws ToolboxLifecycleException + private static ToolboxLifecycleParticipant[] getRegisteredTools() { IConfigurationElement[] decls = Platform.getExtensionRegistry().getConfigurationElementsFor(POINT); ToolboxLifecycleParticipant[] extensions = new ToolboxLifecycleParticipant[decls.length]; @@ -34,7 +32,8 @@ public class ToolboxLifecycleParticipantManger extensions[i] = (ToolboxLifecycleParticipant) decls[i].createExecutableExtension(CLASS_ATTR_NAME); } catch (CoreException e) { - throw new ToolboxLifecycleException("Error retrieving the registered tools", e); + StandaloneActivator.getDefault().logError(e.getMessage(), e); + return new ToolboxLifecycleParticipant[0]; } } return extensions; @@ -43,10 +42,10 @@ public class ToolboxLifecycleParticipantManger /** * Distributes the initialize message * @param participants - * @throws ToolboxLifecycleException */ - public static void initialize(ToolboxLifecycleParticipant[] participants) throws ToolboxLifecycleException + public static void initialize() { + final ToolboxLifecycleParticipant[] participants = getRegisteredTools(); Assert.isNotNull(participants); // Activator.getDefault().logDebug("Initializing the tools"); for (int i = 0; i < participants.length; i++) @@ -55,19 +54,26 @@ public class ToolboxLifecycleParticipantManger } } + public static void postWorkbenchWindowOpen() { + final ToolboxLifecycleParticipant[] participants = getRegisteredTools(); + Assert.isNotNull(participants); + for (int i = 0; i < participants.length; i++) { + participants[i].postWorkbenchWindowOpen(); + } + } + /** * Distributes the terminate message * @param participants - * @throws ToolboxLifecycleException */ - public static void terminate(ToolboxLifecycleParticipant[] participants) throws ToolboxLifecycleException + public static void terminate() { + final ToolboxLifecycleParticipant[] participants = getRegisteredTools(); Assert.isNotNull(participants); // Activator.getDefault().logDebug("Terminating the tools"); for (int i = 0; i < participants.length; i++) { participants[i].terminate(); } - } - + } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleParticipant.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/lifecycle/ToolboxLifecycleParticipant.java similarity index 50% rename from org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleParticipant.java rename to org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/lifecycle/ToolboxLifecycleParticipant.java index bd52518a84f2d480f708b488d8351cf745ef9e76..abaf9af45987b5d9e5889238f4778890c0569291 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleParticipant.java +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/lifecycle/ToolboxLifecycleParticipant.java @@ -1,31 +1,34 @@ -package org.lamport.tla.toolbox.tool; +package org.lamport.tla.toolbox.lifecycle; + +import org.eclipse.ui.IWorkbenchWindow; /** * Describes a basic interface for the tool contribution * * @author Simon Zambrovski - * @version $Id$ */ public abstract class ToolboxLifecycleParticipant { /** * Is called during toolbox initialization * The implementation is empty, subclasses may override - * @throws ToolboxLifecycleException */ - public void initialize() throws ToolboxLifecycleException - { - + public void initialize() { + // subclasses may override + } + + /** + * Is called when the {@link IWorkbenchWindow} has been opened. + */ + public void postWorkbenchWindowOpen() { + // subclasses may override } /** * Is called during termination of the toolbox. * The implementation is empty, subclasses may override - * - * @throws ToolboxLifecycleException */ - public void terminate() throws ToolboxLifecycleException - { - - } + public void terminate(){ + // subclasses may override + } } diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntroPart.java b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntroPart.java new file mode 100644 index 0000000000000000000000000000000000000000..4fea0972b82b40db8f5aaeee8d78774e94aca9ae --- /dev/null +++ b/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntroPart.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ +package org.lamport.tla.toolbox.ui.intro; + +import java.net.URL; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.resource.ColorDescriptor; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.help.IWorkbenchHelpSystem; +import org.eclipse.ui.intro.IIntroPart; +import org.eclipse.ui.part.IntroPart; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +public class ToolboxIntroPart extends IntroPart implements IIntroPart { + + public static final String PERSPECTIVE_ID = "org.lamport.tla.toolbox.ui.perspective.initial"; + + private Composite container; + + /** + * @wbp.parser.entryPoint + */ + public void createPartControl(Composite container) { + this.container = container; + createControl(container); + } + + public static void createControl(Composite container) { + Composite outerContainer = new Composite(container, SWT.NONE); + + // The local resource manager takes care of disposing images, fonts and + // colors when + // the outerContainer Composite is disposed. + final LocalResourceManager localResourceManager = new LocalResourceManager(JFaceResources.getResources(), + outerContainer); + + final GridLayout gridLayout = new GridLayout(); + gridLayout.numColumns = 2; + outerContainer.setLayout(gridLayout); + final Color backgroundColor = localResourceManager + .createColor(ColorDescriptor.createFrom(new RGB(255, 255, 228))); + outerContainer.setBackground(backgroundColor); + + /* Logo */ + final Label lblImage = new Label(outerContainer, SWT.NONE); + lblImage.setText("Invisible text"); + final Bundle bundle = FrameworkUtil.getBundle(ToolboxIntroPart.class); + final URL url = FileLocator.find(bundle, new Path("images/splash_small.bmp"), null); + final ImageDescriptor logoImage = ImageDescriptor.createFromURL(url); + lblImage.setImage(localResourceManager.createImage(logoImage)); + + /* Welcome header */ + final Label lblHeader = new Label(outerContainer, SWT.WRAP); + + // Double its font size + final FontDescriptor headerFontDescriptor = JFaceResources.getHeaderFontDescriptor(); + final FontData fontData = headerFontDescriptor.getFontData()[0]; + lblHeader.setFont(localResourceManager.createFont(headerFontDescriptor.setHeight(fontData.getHeight() * 2))); + + // Color value (taken from old style.css) + lblHeader.setForeground(localResourceManager.createColor(new RGB(0, 0, 192))); + + lblHeader.setLayoutData(new GridData(SWT.LEFT, SWT.BOTTOM, true, false, 1, 1)); + lblHeader.setText("Welcome to the TLA\u207A Toolbox"); + lblHeader.setBackground(backgroundColor); + + /* What is next section */ + + Label lblSeparator = new Label(outerContainer, SWT.NONE); + lblSeparator.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + + final StyledText styledWhatIsNext = new StyledText(outerContainer, SWT.WRAP | SWT.CENTER); + styledWhatIsNext.setBackground(backgroundColor); + final String whatIsnext = "There is no specification open. Click on Help if you're not sure what you should do next."; + styledWhatIsNext.setText(whatIsnext); + GridData gd_styledWhatIsNext = new GridData(GridData.FILL_HORIZONTAL); + gd_styledWhatIsNext.horizontalAlignment = SWT.LEFT; + gd_styledWhatIsNext.horizontalSpan = 2; + styledWhatIsNext.setLayoutData(gd_styledWhatIsNext); + + StyleRange winStyle = new StyleRange(); + winStyle.underline = true; + winStyle.underlineStyle = SWT.UNDERLINE_LINK; + + int[] winRange = { whatIsnext.indexOf("Help"), "Help".length() }; + StyleRange[] winStyles = { winStyle }; + styledWhatIsNext.setStyleRanges(winRange, winStyles); + + // link styled text to getting started guide + styledWhatIsNext.addListener(SWT.MouseDown, new Listener() { + public void handleEvent(Event event) { + IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem(); + helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/contents.html"); + } + }); + + /* Getting started text */ + + final StyledText styledGettingStarted = new StyledText(outerContainer, SWT.WRAP | SWT.CENTER); + styledGettingStarted.setBackground(backgroundColor); + final String lblString = "If this is the first time you have used the Toolbox, please read the Getting Started guide."; + styledGettingStarted.setText(lblString); + GridData gd_styledGettingStarted = new GridData(GridData.FILL_HORIZONTAL); + gd_styledGettingStarted.horizontalAlignment = SWT.LEFT; + gd_styledGettingStarted.horizontalSpan = 2; + styledGettingStarted.setLayoutData(gd_styledGettingStarted); + new Label(outerContainer, SWT.NONE); + new Label(outerContainer, SWT.NONE); + + StyleRange style = new StyleRange(); + style.underline = true; + style.underlineStyle = SWT.UNDERLINE_LINK; + + int[] range = { lblString.indexOf("Getting Started"), "Getting Started".length() }; + StyleRange[] styles = { style }; + styledGettingStarted.setStyleRanges(range, styles); + + // link styled text to getting started guide + styledGettingStarted.addListener(SWT.MouseDown, new Listener() { + public void handleEvent(Event event) { + IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem(); + helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/gettingstarted/gettingstarted.html"); + } + }); + + /* Toolbox version */ + final Label verticalFillUp = new Label(outerContainer, SWT.NONE); + verticalFillUp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true, 2, 1)); + verticalFillUp.setBackground(backgroundColor); + + final Label horizontalLine = new Label(outerContainer, SWT.SEPARATOR | SWT.HORIZONTAL); + horizontalLine.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); + + final Label lblVersion = new Label(outerContainer, SWT.WRAP); + lblVersion.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); + lblVersion.setText("Version 1.5.2 of 21 December 2015"); + lblVersion.setBackground(backgroundColor); + } + + public void standbyStateChanged(boolean standby) { + // do nothing for now (don't expect users to + // send welcome to standy) + } + + public void setFocus() { + container.setFocus(); + } +} diff --git a/org.lamport.tla.toolbox.test/pom.xml b/org.lamport.tla.toolbox.test/pom.xml index 45d0f5550a6b2cf6bc32c81d8d6feff9b99112de..1729cfa999936207bb97c6b389592251703d1581 100644 --- a/org.lamport.tla.toolbox.test/pom.xml +++ b/org.lamport.tla.toolbox.test/pom.xml @@ -48,21 +48,6 @@ <echo>[ProTip] (https://wiki.eclipse.org/SWTBot/Automate_test_execution#use_another_DISPLAY_to_save_time)</echo> <echo></echo> <echo></echo> - <echo></echo> - <echo>[Known Bug] ============================</echo> - <echo>[Known Bug] = Known Eclipse bug 449485 =</echo> - <echo>[Known Bug] ============================</echo> - <echo>[Known Bug] </echo> - <echo>[Known Bug] !ENTRY org.eclipse.e4.ui.workbench.swt 4 2 2015-02-09 11:29:57.483</echo> - <echo>[Known Bug] !MESSAGE Problems occurred when invoking code from plug-in: "org.eclipse.e4.ui.workbench.swt".</echo> - <echo>[Known Bug] !STACK 0</echo> - <echo>[Known Bug] org.eclipse.e4.core.di.InjectionException: org.eclipse.swt.SWTException: Widget is disposed</echo> - <echo>[Known Bug] ...</echo> - <echo>[Known Bug] Caused by: org.eclipse.swt.SWTException: Widget is disposed</echo> - <echo>[Known Bug] ...</echo> - <echo>[Known Bug] (see https://bugs.eclipse.org/449485)</echo> - <echo></echo> - <echo></echo> <echo></echo> </tasks> </configuration> @@ -86,6 +71,26 @@ <artifactId>org.eclipse.equinox.event</artifactId> <version>0.0.0</version> </dependency> + <dependency> + <type>p2-installable-unit</type> + <artifactId>org.apache.felix.gogo.command</artifactId> + <version>0.0.0</version> + </dependency> + <dependency> + <type>p2-installable-unit</type> + <artifactId>org.apache.felix.gogo.runtime</artifactId> + <version>0.0.0</version> + </dependency> + <dependency> + <type>p2-installable-unit</type> + <artifactId>org.apache.felix.gogo.shell</artifactId> + <version>0.0.0</version> + </dependency> + <dependency> + <type>p2-installable-unit</type> + <artifactId>org.eclipse.equinox.console</artifactId> + <version>0.0.0</version> + </dependency> </dependencies> <bundleStartLevel> <bundle> @@ -93,6 +98,11 @@ <level>4</level> <autoStart>true</autoStart> </bundle> + <bundle> + <id>org.eclipse.equinox.console</id> + <level>4</level> + <autoStart>true</autoStart> + </bundle> </bundleStartLevel> </configuration> </plugin> diff --git a/org.lamport.tla.toolbox.tool.prover/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.tool.prover/META-INF/MANIFEST.MF index 9765c78d2d883d44d23f6abb3ae0d561432c4003..1ac78412a71a2e4da3e73a8e8861aeb73d88c339 100644 --- a/org.lamport.tla.toolbox.tool.prover/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.tool.prover/META-INF/MANIFEST.MF @@ -11,7 +11,8 @@ Require-Bundle: org.eclipse.ui, org.eclipse.debug.core;bundle-version="3.5.0", org.eclipse.ui.editors;bundle-version="3.5.0", org.eclipse.jface.text;bundle-version="3.5.0", - org.eclipse.ui.console + org.eclipse.ui.console, + org.lamport.tla.toolbox.product.standalone Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.ui.forms, diff --git a/org.lamport.tla.toolbox.tool.prover/src/org/lamport/tla/toolbox/tool/prover/ProverToolboxLifecycleParticipant.java b/org.lamport.tla.toolbox.tool.prover/src/org/lamport/tla/toolbox/tool/prover/ProverToolboxLifecycleParticipant.java index ad72443f0fbdead0ae360a8aec2ac312bff866ea..0ec0c115aa5bf75d26a84d0605631d34add47e59 100644 --- a/org.lamport.tla.toolbox.tool.prover/src/org/lamport/tla/toolbox/tool/prover/ProverToolboxLifecycleParticipant.java +++ b/org.lamport.tla.toolbox.tool.prover/src/org/lamport/tla/toolbox/tool/prover/ProverToolboxLifecycleParticipant.java @@ -1,7 +1,6 @@ package org.lamport.tla.toolbox.tool.prover; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleException; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; import org.lamport.tla.toolbox.tool.prover.ui.util.ProverHelper; /** @@ -22,10 +21,8 @@ public class ProverToolboxLifecycleParticipant extends ToolboxLifecycleParticipa /** * Is called during termination of the toolbox. * This implementation cancels all running prover jobs. - * - * @throws ToolboxLifecycleException */ - public void terminate() throws ToolboxLifecycleException + public void terminate() { ProverHelper.cancelProverJobs(true); diff --git a/org.lamport.tla.toolbox.tool.tla2tex.uitest/pom.xml b/org.lamport.tla.toolbox.tool.tla2tex.uitest/pom.xml index 3ece914cb4af05808ed90de4c23cfbf182ac2312..9a21a5b658187e0428086d031fcf6c4cf96fed58 100644 --- a/org.lamport.tla.toolbox.tool.tla2tex.uitest/pom.xml +++ b/org.lamport.tla.toolbox.tool.tla2tex.uitest/pom.xml @@ -31,10 +31,10 @@ <goal>install-file</goal> </goals> <configuration> - <file>${settings.localRepository}/p2/osgi/bundle/org.eclipse.equinox.weaving.hook/1.1.1.v20140821-1918/org.eclipse.equinox.weaving.hook-1.1.1.v20140821-1918.jar</file> + <file>${settings.localRepository}/p2/osgi/bundle/org.eclipse.equinox.weaving.hook/1.1.100.v20140821-1915/org.eclipse.equinox.weaving.hook-1.1.100.v20140821-1915.jar</file> <groupId>tlatoolbox</groupId> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> <packaging>jar</packaging> </configuration> </execution> @@ -106,7 +106,7 @@ <dependency> <type>p2-installable-unit</type> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> </dependency> <dependency> <type>p2-installable-unit</type> @@ -120,7 +120,7 @@ <frameworkExtension> <groupId>tlatoolbox</groupId> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> </frameworkExtension> </frameworkExtensions> diff --git a/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferenceInitializer.java b/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferenceInitializer.java index 539cfde197abdeb1e2ab59cbd776626497239f68..65e8a8d1004ca2dae7346e1975d1aaed867e0ec9 100644 --- a/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferenceInitializer.java +++ b/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferenceInitializer.java @@ -1,5 +1,6 @@ package org.lamport.tla.toolbox.tool.tla2tex.preference; +import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; import org.eclipse.jface.preference.IPreferenceStore; import org.lamport.tla.toolbox.tool.tla2tex.TLA2TeXActivator; @@ -21,5 +22,10 @@ public class TLA2TeXPreferenceInitializer extends AbstractPreferenceInitializer store.setDefault(ITLA2TeXPreferenceConstants.LATEX_COMMAND, "pdflatex"); store.setDefault(ITLA2TeXPreferenceConstants.GRAY_LEVEL, "0.85"); store.setDefault(ITLA2TeXPreferenceConstants.EMBEDDED_VIEWER, false); + if (Platform.getOS().equals(Platform.OS_MACOSX)) { + // Support for the built-in viewer has ended on MACOSX, thus disable + // even if user enabled it in an earlier Toolbox release. + store.setValue(ITLA2TeXPreferenceConstants.EMBEDDED_VIEWER, false); + } } } diff --git a/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferencePage.java b/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferencePage.java index e70a3cfc2277e365e664796a99158a9f73cb5a88..b0d32b2ec1c1a17d99af797134dd8a2d92714a59 100644 --- a/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferencePage.java +++ b/org.lamport.tla.toolbox.tool.tla2tex/src/org/lamport/tla/toolbox/tool/tla2tex/preference/TLA2TeXPreferencePage.java @@ -1,5 +1,6 @@ package org.lamport.tla.toolbox.tool.tla2tex.preference; +import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.FieldEditorPreferencePage; import org.eclipse.jface.preference.StringFieldEditor; @@ -36,9 +37,11 @@ public class TLA2TeXPreferencePage extends FieldEditorPreferencePage implements } protected void createFieldEditors() { - addField(new BooleanFieldEditor( - ITLA2TeXPreferenceConstants.EMBEDDED_VIEWER, - "&Use built-in PDF viewer", getFieldEditorParent())); + if (!Platform.getOS().equals(Platform.OS_MACOSX)) { + addField(new BooleanFieldEditor( + ITLA2TeXPreferenceConstants.EMBEDDED_VIEWER, + "&Use built-in PDF viewer", getFieldEditorParent())); + } addField(new BooleanFieldEditor( ITLA2TeXPreferenceConstants.SHADE_COMMENTS, "&Shade comments", getFieldEditorParent())); diff --git a/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/model/AssignmentTest.java b/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/model/AssignmentTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2e1fdf3864e80c5898c6b323c8aaf97a7424c466 --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/model/AssignmentTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.tool.tlc.model; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class AssignmentTest { + + @Test + public void test() { + final Assignment a = new Assignment("X", new String[0], "X"); + a.setModelValue(true); + assertEquals("X", a.prettyPrint()); + + final Assignment b = new Assignment("Y", new String[0], "{a1, b1}"); + b.setModelValue(true); + assertEquals("Y" + Assignment.ASSIGNMENT_SIGN + "{a1, b1}", b.prettyPrint()); + + final Assignment c = new Assignment("Z", new String[0], "{s1, s2}"); + c.setModelValue(true); + c.setSymmetric(true); + assertEquals("Z" + Assignment.ASSIGNMENT_SIGN + "s{s1, s2}", c.prettyPrint()); + + final Assignment d = new Assignment("W", new String[0], "1"); + assertEquals("W" + Assignment.ASSIGNMENT_SIGN + "1", d.prettyPrint()); + } +} diff --git a/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelperTest.java b/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelperTest.java index d7957737d49f4ec45769ba66e971146c39f22c6d..3e04be4c0c8161109c93e289ff87e9890696838a 100644 --- a/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelperTest.java +++ b/org.lamport.tla.toolbox.tool.tlc.test/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelperTest.java @@ -1,15 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.util; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import junit.framework.Assert; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchDelegate; +import org.junit.Assert; +import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants; +import org.lamport.tla.toolbox.tool.tlc.model.Assignment; + import junit.framework.TestCase; /** * Test for toolkit methods - * @author Simon Zambrovski - * @version $Id$ */ public class ModelHelperTest extends TestCase { @@ -38,4 +79,177 @@ public class ModelHelperTest extends TestCase Matcher matcher2 = pattern.matcher(input2); Assert.assertFalse(matcher2.matches()); } + + public void testStripSymmetry() { + final List<String> serializedList = new ArrayList<String>(); + serializedList.add("Procs;;{A, B};1;1"); + + // Symmetry not stripped + List<Assignment> deserializeAssignmentList = ModelHelper.deserializeAssignmentList(serializedList); + assertEquals(1, deserializeAssignmentList.size()); + assertTrue(deserializeAssignmentList.get(0).isSymmetricalSet()); + + // Symmetry stripped + deserializeAssignmentList = ModelHelper.deserializeAssignmentList(serializedList, true); + assertEquals(1, deserializeAssignmentList.size()); + assertFalse(deserializeAssignmentList.get(0).isSymmetricalSet()); + } + + public void testPrettyPrintConstants2() throws CoreException { + final List<String> values = new ArrayList<String>(); + values.add("X;;X;1;0"); + values.add("Y;;{a1, b1};1;0"); + values.add("Z;;{s1, s2};1;1"); + values.add("W;;1;0;0"); + values.add("V;;V;1;0"); + values.add("U;;42;0;0"); + final ILaunchConfiguration config = new DummyLaunchConfig( + IModelConfigurationConstants.MODEL_PARAMETER_CONSTANTS, values); + + final String constants = ModelHelper.prettyPrintConstants(config, "\n"); + final String[] split = constants.split("\n"); + assertEquals(5, split.length); + assertEquals("U <- 42", split[0]); + assertEquals("W <- 1", split[1]); + assertEquals("Y <- {a1, b1}", split[2]); + assertEquals("Z <- s{s1, s2}", split[3]); + assertEquals("Model values: V, X", split[4]); + } + + private class DummyLaunchConfig implements ILaunchConfiguration { + + private final Map<String, List<String>> attributes = new HashMap<String, List<String>>(); + + public DummyLaunchConfig(String key, List<String> value) { + this.attributes.put(key, value); + } + + public <T> T getAdapter(Class<T> adapter) { + return null; + } + + public boolean contentsEqual(ILaunchConfiguration configuration) { + return false; + } + + public ILaunchConfigurationWorkingCopy copy(String name) throws CoreException { + return null; + } + + public void delete() throws CoreException { + } + + public boolean exists() { + return false; + } + + public boolean getAttribute(String attributeName, boolean defaultValue) throws CoreException { + return false; + } + + public int getAttribute(String attributeName, int defaultValue) throws CoreException { + return 0; + } + + public List<String> getAttribute(String attributeName, List<String> defaultValue) throws CoreException { + return this.attributes.get(attributeName); + } + + public Set<String> getAttribute(String attributeName, Set<String> defaultValue) throws CoreException { + return null; + } + + public Map<String, String> getAttribute(String attributeName, Map<String, String> defaultValue) + throws CoreException { + return null; + } + + public String getAttribute(String attributeName, String defaultValue) throws CoreException { + return null; + } + + public Map<String, Object> getAttributes() throws CoreException { + return null; + } + + public String getCategory() throws CoreException { + return null; + } + + public IFile getFile() { + return null; + } + + public IPath getLocation() { + return null; + } + + public IResource[] getMappedResources() throws CoreException { + return null; + } + + public String getMemento() throws CoreException { + return null; + } + + public String getName() { + return null; + } + + public Set<String> getModes() throws CoreException { + return null; + } + + public ILaunchDelegate getPreferredDelegate(Set<String> modes) throws CoreException { + return null; + } + + public ILaunchConfigurationType getType() throws CoreException { + return null; + } + + public ILaunchConfigurationWorkingCopy getWorkingCopy() throws CoreException { + return null; + } + + public boolean hasAttribute(String attributeName) throws CoreException { + return false; + } + + public boolean isLocal() { + return false; + } + + public boolean isMigrationCandidate() throws CoreException { + return false; + } + + public boolean isWorkingCopy() { + return false; + } + + public ILaunch launch(String mode, IProgressMonitor monitor) throws CoreException { + return null; + } + + public ILaunch launch(String mode, IProgressMonitor monitor, boolean build) throws CoreException { + return null; + } + + public ILaunch launch(String mode, IProgressMonitor monitor, boolean build, boolean register) + throws CoreException { + return null; + } + + public void migrate() throws CoreException { + } + + public boolean supportsMode(String mode) throws CoreException { + return false; + } + + public boolean isReadOnly() { + return false; + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.test/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.tool.tlc.ui.test/META-INF/MANIFEST.MF index 70826b790464e097908603d2d687df182f86bcea..19b581bb5bc24dd5a1449e7e8731bed4b0f4251f 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.test/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.tool.tlc.ui.test/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-SymbolicName: org.lamport.tla.toolbox.tool.tlc.ui.test;singleton:=true Bundle-Version: 1.0.0.qualifier Require-Bundle: org.lamport.tla.toolbox;bundle-version="1.0.0", org.eclipse.jface.text;bundle-version="3.6.1", - org.junit;bundle-version="3.8.2" + org.junit;bundle-version="3.8.2", + org.easymock;bundle-version="2.4.0" Import-Package: org.osgi.framework;version="1.3.0" Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-Vendor: Leslie Lamport, Markus Alexander Kuppe diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.test/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorViewTest.java b/org.lamport.tla.toolbox.tool.tlc.ui.test/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorViewTest.java new file mode 100644 index 0000000000000000000000000000000000000000..703a9b5a7f89e56e107d2475ccffcc46faf09b34 --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.ui.test/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorViewTest.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.tool.tlc.ui.view; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.easymock.EasyMock; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.junit.Ignore; +import org.junit.Test; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCModelLaunchDataProvider; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCState; +import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; +import org.lamport.tla.toolbox.tool.tlc.ui.preference.ITLCPreferenceConstants; +import org.lamport.tla.toolbox.util.UIHelper; + +public class TLCErrorViewTest { + + @SuppressWarnings("unchecked") + @Test//(timeout=10000) // 10 seconds timeout (should be sufficient even on old machines) + @Ignore + public void testLargeNumberOfStates() throws CoreException { + final String name = "testLargeNumberOfStates"; + + final IPath path = EasyMock.createNiceMock(IPath.class); + EasyMock.expect(path.removeFileExtension()).andReturn(path).anyTimes(); + EasyMock.expect(path.lastSegment()).andReturn(name).anyTimes(); + EasyMock.replay(path); + + final IProject project = EasyMock.createNiceMock(IProject.class); + EasyMock.expect(project.getName()).andReturn(name).anyTimes(); + EasyMock.replay(project); + + final IFile file = EasyMock.createNiceMock(IFile.class); + EasyMock.expect(file.findMarkers((String) EasyMock.anyObject(), EasyMock.anyBoolean(), EasyMock.anyInt())) + .andReturn(new IMarker[0]); + EasyMock.expect(file.getName()).andReturn(name).anyTimes(); + EasyMock.expect(file.getLocation()).andReturn(path).anyTimes(); + EasyMock.expect(file.getProject()).andReturn(project).anyTimes(); + EasyMock.replay(file); + + final ILaunchConfiguration launchConfig = EasyMock.createNiceMock(ILaunchConfiguration.class); + EasyMock.expect(launchConfig.getFile()).andReturn(file).anyTimes(); + EasyMock.expect(launchConfig.isWorkingCopy()).andReturn(false).anyTimes(); + EasyMock.expect(launchConfig.getAttribute((String) EasyMock.anyObject(), (List<String>) EasyMock.anyObject())) + .andReturn(new ArrayList<String>()).anyTimes(); + EasyMock.replay(launchConfig); + + // Cannot be mocked because it's not an interface. + final TLCModelLaunchDataProvider provider = new TLCModelLaunchDataProvider(launchConfig); + final List<TLCError> errors = new ArrayList<TLCError>(); + provider.setErrors(errors); + + final TLCError error = new TLCError(); + errors.add(error); + + for (int i = 1; i <= 10000; i++) { + final TLCState state = TLCState.parseState( + i + ": <Action line 1, col 1 to line 1, col 2 of module testLargeNumberOfStates>\nx = " + i, name); + error.addState(state); + } + + // show all states + TLCUIActivator.getDefault().getPreferenceStore().setValue(ITLCPreferenceConstants.I_TLC_TRACE_MAX_SHOW_ERRORS, + Integer.MAX_VALUE); + + final long before = System.currentTimeMillis(); + UIHelper.runUISync(new Runnable() { + public void run() { + TLCErrorView.updateErrorView(provider, launchConfig, true); + } + }); + assertTrue(before - System.currentTimeMillis() <= 10 * 1000); // maximally ten seconds + } +} diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/org.lamport.tla.toolbox.tool.tlc.ui.uitest.launch b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/org.lamport.tla.toolbox.tool.tlc.ui.uitest.launch index 08684b1b70213f0a2e6430b6df7c8d6a16433587..5baa451b9603b242553ad31eb53f6b00f5169958 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/org.lamport.tla.toolbox.tool.tlc.ui.uitest.launch +++ b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/org.lamport.tla.toolbox.tool.tlc.ui.uitest.launch @@ -26,17 +26,17 @@ <booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/> <stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/> <stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/> -<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> +<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.lamport.tla.toolbox.ui.handler.RenameSpecHandlerTest"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="org.lamport.tla.toolbox.tool.tlc.ui.uitest"/> <stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/> -<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms80m -Xmx512m -Dosgi.framework.extensions=org.eclipse.equinox.weaving.hook -Daj.weaving.verbose=true -Dorg.aspectj.weaver.showWeaveInfo=true -Dorg.aspectj.osgi.verbose=true -Dorg.lamport.tla.toolbox.tool.tlc.ui.test.PathToSpecB=${project_loc:org.lamport.tla.toolbox.uitest}/farsite/DistributedSystemModule.tla -Dorg.lamport.tla.toolbox.tool.tlc.ui.test.PathToSpecA=${project_loc:examples}/DieHard/DieHard.tla"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms80m -Xmx1024m -Dosgi.framework.extensions=org.eclipse.equinox.weaving.hook -Daj.weaving.verbose=true -Dorg.aspectj.weaver.showWeaveInfo=true -Dorg.aspectj.osgi.verbose=true -Dorg.lamport.tla.toolbox.tool.tlc.ui.test.PathToSpecB=${project_loc:org.lamport.tla.toolbox.uitest}/farsite/DistributedSystemModule.tla -Dorg.lamport.tla.toolbox.tool.tlc.ui.test.PathToSpecA=${project_loc:examples}/DieHard/DieHard.tla"/> <stringAttribute key="pde.version" value="3.3"/> <stringAttribute key="product" value="org.lamport.tla.toolbox.product.standalone.product"/> <booleanAttribute key="run_in_ui_thread" value="false"/> -<stringAttribute key="selected_target_plugins" value="com.ibm.icu@default:default,org.apache.log4j@default:default,org.apache.lucene.analysis@default:default,org.apache.lucene@default:default,org.aspectj.runtime@default:default,org.aspectj.weaver@default:default,org.eclipse.ant.core@default:default,org.eclipse.aspectj@default:default,org.eclipse.compare.core@default:default,org.eclipse.core.commands@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.expressions@default:default,org.eclipse.core.filebuffers@default:default,org.eclipse.core.filesystem.linux.x86_64@default:false,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.net.linux.x86_64@default:false,org.eclipse.core.net@default:default,org.eclipse.core.resources@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.core.runtime@default:true,org.eclipse.core.variables@default:default,org.eclipse.debug.core@default:default,org.eclipse.ecf.filetransfer@default:default,org.eclipse.ecf.identity@default:default,org.eclipse.ecf.provider.filetransfer.ssl@default:false,org.eclipse.ecf.provider.filetransfer@default:default,org.eclipse.ecf.ssl@default:false,org.eclipse.ecf@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.p2.core@default:default,org.eclipse.equinox.p2.engine@default:default,org.eclipse.equinox.p2.metadata.repository@default:default,org.eclipse.equinox.p2.metadata@default:default,org.eclipse.equinox.p2.repository@default:default,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.security@default:default,org.eclipse.equinox.weaving.aspectj@3:true,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.help.base@default:default,org.eclipse.help.ui@default:default,org.eclipse.help@default:default,org.eclipse.jdt.core@default:default,org.eclipse.jdt.debug@default:default,org.eclipse.jdt.junit.runtime@default:default,org.eclipse.jdt.launching@default:default,org.eclipse.jface.databinding@default:default,org.eclipse.jface.text@default:default,org.eclipse.jface@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.eclipse.sdk@default:default,org.eclipse.swt.gtk.linux.x86_64@default:false,org.eclipse.swt@default:default,org.eclipse.swtbot.eclipse.core@default:default,org.eclipse.swtbot.eclipse.finder@default:default,org.eclipse.swtbot.go@default:default,org.eclipse.swtbot.junit4_x@default:default,org.eclipse.swtbot.swt.finder@default:default,org.eclipse.team.core@default:default,org.eclipse.text@default:default,org.eclipse.ui.cheatsheets@default:default,org.eclipse.ui.console@default:default,org.eclipse.ui.editors@default:default,org.eclipse.ui.forms@default:default,org.eclipse.ui.ide.application@default:default,org.eclipse.ui.ide@default:default,org.eclipse.ui.intro@default:default,org.eclipse.ui.navigator@default:default,org.eclipse.ui.views@default:default,org.eclipse.ui.workbench.texteditor@default:default,org.eclipse.ui.workbench@default:default,org.eclipse.ui@default:default,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit*4.8.1.v4_8_1_v20100427-1100@default:default,org.junit4@default:default"/> -<stringAttribute key="selected_workspace_plugins" value="org.lamport.tla.toolbox.doc@default:default,org.lamport.tla.toolbox.editor.basic@default:default,org.lamport.tla.toolbox.product.standalone@default:default,org.lamport.tla.toolbox.tool.tlc.ui.uitest@default:default,org.lamport.tla.toolbox.tool.tlc.ui@default:default,org.lamport.tla.toolbox.tool.tlc@default:default,org.lamport.tla.toolbox.uitest@default:default,org.lamport.tla.toolbox@default:default,org.lamport.tlatools@default:default"/> +<stringAttribute key="selected_target_plugins" value="com.ibm.icu@default:default,javax.activation@default:default,javax.annotation@default:default,javax.mail@default:default,javax.xml@default:default,org.apache.batik.css@default:default,org.apache.batik.util.gui@default:default,org.apache.batik.util@default:default,org.apache.commons.jxpath@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.apache.log4j@default:default,org.apache.lucene.analysis@default:default,org.apache.lucene.core@default:default,org.apache.servicemix.bundles.javax-inject@default:default,org.aspectj.runtime@default:default,org.eclipse.ant.core@default:default,org.eclipse.compare.core@default:default,org.eclipse.compare@default:default,org.eclipse.core.commands@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.databinding.observable@default:default,org.eclipse.core.databinding.property@default:default,org.eclipse.core.databinding@default:default,org.eclipse.core.expressions@default:default,org.eclipse.core.filebuffers@default:default,org.eclipse.core.filesystem.java7@default:false,org.eclipse.core.filesystem.linux.x86_64@default:false,org.eclipse.core.filesystem@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.net.linux.x86_64@default:false,org.eclipse.core.net@default:default,org.eclipse.core.resources@default:default,org.eclipse.core.runtime.compatibility.registry@default:false,org.eclipse.core.runtime@default:true,org.eclipse.core.variables@default:default,org.eclipse.debug.core@default:default,org.eclipse.e4.core.commands@default:default,org.eclipse.e4.core.contexts@default:default,org.eclipse.e4.core.di.annotations@default:default,org.eclipse.e4.core.di.extensions@default:default,org.eclipse.e4.core.di@default:default,org.eclipse.e4.core.services@default:default,org.eclipse.e4.emf.xpath@default:default,org.eclipse.e4.ui.bindings@default:default,org.eclipse.e4.ui.css.core@default:default,org.eclipse.e4.ui.css.swt.theme@default:default,org.eclipse.e4.ui.css.swt@default:default,org.eclipse.e4.ui.di@default:default,org.eclipse.e4.ui.model.workbench@default:default,org.eclipse.e4.ui.services@default:default,org.eclipse.e4.ui.swt.gtk@default:false,org.eclipse.e4.ui.widgets@default:default,org.eclipse.e4.ui.workbench.addons.swt@default:default,org.eclipse.e4.ui.workbench.renderers.swt@default:default,org.eclipse.e4.ui.workbench.swt@default:default,org.eclipse.e4.ui.workbench3@default:default,org.eclipse.e4.ui.workbench@default:default,org.eclipse.ecf.filetransfer@default:default,org.eclipse.ecf.identity@default:default,org.eclipse.ecf.provider.filetransfer.ssl@default:false,org.eclipse.ecf.provider.filetransfer@default:default,org.eclipse.ecf.ssl@default:false,org.eclipse.ecf@default:default,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.change@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.bidi@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.equinox.ds@1:true,org.eclipse.equinox.event@default:default,org.eclipse.equinox.frameworkadmin.equinox@default:default,org.eclipse.equinox.frameworkadmin@default:default,org.eclipse.equinox.p2.artifact.repository@default:default,org.eclipse.equinox.p2.core@default:default,org.eclipse.equinox.p2.director@default:default,org.eclipse.equinox.p2.engine@default:default,org.eclipse.equinox.p2.garbagecollector@default:default,org.eclipse.equinox.p2.jarprocessor@default:default,org.eclipse.equinox.p2.metadata.repository@default:default,org.eclipse.equinox.p2.metadata@default:default,org.eclipse.equinox.p2.operations@default:default,org.eclipse.equinox.p2.repository@default:default,org.eclipse.equinox.p2.touchpoint.eclipse@default:default,org.eclipse.equinox.p2.ui.sdk.scheduler@default:default,org.eclipse.equinox.p2.ui@default:default,org.eclipse.equinox.p2.updatechecker@default:default,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.region@default:false,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.security.ui@default:default,org.eclipse.equinox.security@default:default,org.eclipse.equinox.simpleconfigurator.manipulator@default:default,org.eclipse.equinox.simpleconfigurator@1:true,org.eclipse.equinox.transforms.hook@default:false,org.eclipse.equinox.util@default:default,org.eclipse.equinox.weaving.aspectj@3:true,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.help.base@default:default,org.eclipse.help.ui@default:default,org.eclipse.help@default:default,org.eclipse.jdt.compiler.apt@default:false,org.eclipse.jdt.compiler.tool@default:false,org.eclipse.jdt.core@default:default,org.eclipse.jdt.debug@default:default,org.eclipse.jdt.junit.runtime@default:default,org.eclipse.jdt.launching@default:default,org.eclipse.jface.databinding@default:default,org.eclipse.jface.text@default:default,org.eclipse.jface@default:default,org.eclipse.ltk.core.refactoring@default:default,org.eclipse.ltk.ui.refactoring@default:default,org.eclipse.osgi.compatibility.state@default:false,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.eclipse.sdk@default:default,org.eclipse.swt.gtk.linux.x86_64@default:false,org.eclipse.swt@default:default,org.eclipse.swtbot.eclipse.core@default:default,org.eclipse.swtbot.eclipse.finder@default:default,org.eclipse.swtbot.go@default:default,org.eclipse.swtbot.junit4_x@default:default,org.eclipse.swtbot.swt.finder@default:default,org.eclipse.team.core@default:default,org.eclipse.team.ui@default:default,org.eclipse.text@default:default,org.eclipse.ui.cheatsheets@default:default,org.eclipse.ui.console@default:default,org.eclipse.ui.editors@default:default,org.eclipse.ui.forms@default:default,org.eclipse.ui.ide.application@default:default,org.eclipse.ui.ide@default:default,org.eclipse.ui.intro@default:default,org.eclipse.ui.navigator.resources@default:default,org.eclipse.ui.navigator@default:default,org.eclipse.ui.trace@default:default,org.eclipse.ui.views.properties.tabbed@default:default,org.eclipse.ui.views@default:default,org.eclipse.ui.workbench.texteditor@default:default,org.eclipse.ui.workbench@default:default,org.eclipse.ui@default:default,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit@default:default,org.sat4j.core@default:default,org.sat4j.pb@default:default,org.tukaani.xz@default:default,org.w3c.css.sac@default:default,org.w3c.dom.events@default:default,org.w3c.dom.smil@default:default,org.w3c.dom.svg@default:default"/> +<stringAttribute key="selected_workspace_plugins" value="org.lamport.tla.toolbox.doc@default:default,org.lamport.tla.toolbox.editor.basic@default:default,org.lamport.tla.toolbox.product.standalone@default:default,org.lamport.tla.toolbox.test@default:false,org.lamport.tla.toolbox.tool.tlc.test@default:false,org.lamport.tla.toolbox.tool.tlc.ui.test@default:false,org.lamport.tla.toolbox.tool.tlc.ui.uitest@default:default,org.lamport.tla.toolbox.tool.tlc.ui@default:default,org.lamport.tla.toolbox.tool.tlc@default:default,org.lamport.tla.toolbox.uitest@default:default,org.lamport.tla.toolbox@default:default,org.lamport.tlatools@default:default"/> <booleanAttribute key="show_selected_only" value="true"/> <stringAttribute key="templateConfig" value="${target_home}/configuration/config.ini"/> <stringAttribute key="testApplication" value="org.lamport.tla.toolbox.application"/> diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/pom.xml b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/pom.xml index 8d6e23def3617766ba6018edd6e54ec62bcfd96f..83a08606dd18f5e7600618935b4629659a050083 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/pom.xml +++ b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/pom.xml @@ -31,10 +31,10 @@ <goal>install-file</goal> </goals> <configuration> - <file>${settings.localRepository}/p2/osgi/bundle/org.eclipse.equinox.weaving.hook/1.1.1.v20140821-1918/org.eclipse.equinox.weaving.hook-1.1.1.v20140821-1918.jar</file> + <file>${settings.localRepository}/p2/osgi/bundle/org.eclipse.equinox.weaving.hook/1.1.100.v20140821-1915/org.eclipse.equinox.weaving.hook-1.1.100.v20140821-1915.jar</file> <groupId>tlatoolbox</groupId> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> <packaging>jar</packaging> </configuration> </execution> @@ -106,7 +106,7 @@ <dependency> <type>p2-installable-unit</type> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> </dependency> <dependency> <type>p2-installable-unit</type> @@ -120,7 +120,7 @@ <frameworkExtension> <groupId>tlatoolbox</groupId> <artifactId>org.eclipse.equinox.weaving.hook</artifactId> - <version>1.1.1.v20140821-1918</version> + <version>1.1.100.v20140821-1915</version> </frameworkExtension> </frameworkExtensions> diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/AbstractTest.java b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/AbstractTest.java index 3bfd4adc42ec5a0faeb1162c408eb71b4886e9a8..2df881c61892784ed2eb464173f14c4247d5e3f1 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/AbstractTest.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/AbstractTest.java @@ -20,7 +20,10 @@ import org.junit.Before; import org.lamport.tla.toolbox.test.RCPTestSetupHelper; public abstract class AbstractTest { - + + protected static final String SPEC_EXPLORER = "Spec Explorer"; + protected static final String TLA_SUFFIX = ".tla"; + /** * workbench handle used by tests to access UI elements */ diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/ModelCheckerTest.java b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/ModelCheckerTest.java index 9f62d3e4764e17b16a3f754850ac5bbf3ad8511b..bccdaa77cc491ed7d46cc44adfc0866d7e0c6234 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/ModelCheckerTest.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/tool/tlc/ui/test/ModelCheckerTest.java @@ -2,11 +2,13 @@ package org.lamport.tla.toolbox.tool.tlc.ui.test; import java.io.File; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner; -import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; import org.eclipse.swtbot.swt.finder.matchers.WithText; import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; import org.eclipse.swtbot.swt.finder.waits.Conditions; @@ -15,6 +17,7 @@ import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; import org.lamport.tla.toolbox.util.UIHelper; @RunWith(SWTBotJunit4ClassRunner.class) @@ -56,12 +59,26 @@ public class ModelCheckerTest extends AbstractTest { final String modelName = UIHelper.getActiveEditor().getTitle(); final IJobChangeListener listener = new DummyJobChangeListener(modelName); Job.getJobManager().addJobChangeListener(listener); - - // register job change listener to find out later if the job has been - // run start model checking by using the new F11 shortcut :) - bot.activeShell().pressShortcut(Keystrokes.F11); + + // start model checking by clicking the menu. This is more robust + // compared to the f11 keystroke which can get lost when fired during + // initialization of the model editor. + bot.menu("TLC Model Checker").menu("Run model").click(); // make unit test wait for model checker job to finish bot.waitUntil((ICondition) listener, SWTBotPreferences.TIMEOUT * 3); + + // Delete the newly created model again. It does not use the UI because + // SWTBot cannot handle the modal confirmation dialog do delete the + // model. + // Deleting the model is necessary because repeated test execution would + // leave huge numbers of model leftovers contributing to slowed down test + // execution (see SizeControlContribution for reason why). + try { + final ILaunchConfiguration ilc = ModelHelper.getModelByName(modelName); + ModelHelper.deleteModel(ilc, new NullProgressMonitor()); + } catch (CoreException e) { + e.printStackTrace(); + } } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/ui/handler/RenameSpecHandlerTest.java b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/ui/handler/RenameSpecHandlerTest.java index 0f1d51103a4cadc392daf95594532208d6cda30f..6a478fc5a70af7526c06cb2030f45f44a8a8769b 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/ui/handler/RenameSpecHandlerTest.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui.uitest/src/org/lamport/tla/toolbox/ui/handler/RenameSpecHandlerTest.java @@ -2,8 +2,6 @@ package org.lamport.tla.toolbox.ui.handler; import java.io.File; -import junit.framework.Assert; - import org.eclipse.core.runtime.AssertionFailedException; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.swt.widgets.MenuItem; @@ -14,6 +12,7 @@ import org.eclipse.swtbot.swt.finder.matchers.WithText; import org.eclipse.swtbot.swt.finder.waits.Conditions; import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -24,15 +23,14 @@ import org.lamport.tla.toolbox.tool.tlc.ui.test.AbstractTest; import org.lamport.tla.toolbox.tool.tlc.ui.test.ModelEditorOpenCondition; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; + /** * This test is placed in bundle ...tlc.ui.uitest because it renaming a spec * internally requires renaming all its models too. */ @RunWith(SWTBotJunit4ClassRunner.class) public class RenameSpecHandlerTest extends AbstractTest { - - private static final String SPEC_EXPLORER = "Spec Explorer"; - private static final String TLA_SUFFIX = ".tla"; + private static final String TEST_SPEC = "ToBeRenamedSpec"; private static final String TEST_MODEL = "Model_1"; @@ -118,7 +116,9 @@ public class RenameSpecHandlerTest extends AbstractTest { private void checkForModelExistenceUI(final SWTBotTreeItem treeItem) { try { treeItem.expand(); - treeItem.select(TEST_MODEL); + SWTBotTreeItem models = treeItem.getNode("models"); + models.expand(); + models.getNode(TEST_MODEL).select(); } catch(AssertionFailedException e) { Assert.fail(e.getMessage()); } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/.classpath b/org.lamport.tla.toolbox.tool.tlc.ui/.classpath index 3f7bf5da7198337daefd1069264a2c3c418658a9..bbd8b304fcf5fc337804d99b0c1f49e77d817c48 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/.classpath +++ b/org.lamport.tla.toolbox.tool.tlc.ui/.classpath @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="src" path="src"/> <classpathentry kind="output" path="class"/> diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/.settings/org.eclipse.jdt.core.prefs b/org.lamport.tla.toolbox.tool.tlc.ui/.settings/org.eclipse.jdt.core.prefs index b36e5726ae8c5158d6d86dca82e7e0a86a875af6..b1513f02a112a1e4efcea3b1013570093e4c79e1 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/.settings/org.eclipse.jdt.core.prefs +++ b/org.lamport.tla.toolbox.tool.tlc.ui/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,8 @@ -#Mon May 09 12:18:54 CEST 2011 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.compliance=1.7 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -71,4 +70,4 @@ org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disa org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.tool.tlc.ui/META-INF/MANIFEST.MF index 7742aefd64cd3fce5d98404fac87926c50f8167d..9dbec0b89e3aba4254c8b75dc9358c4ecf64be09 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.tool.tlc.ui/META-INF/MANIFEST.MF @@ -18,7 +18,7 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.ui.navigator;bundle-version="3.4.0", org.eclipse.core.expressions;bundle-version="3.4.100", javax.mail;bundle-version="1.4.0" -Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ActivationPolicy: lazy Import-Package: org.lamport.tla.toolbox.editor.basic Export-Package: org.lamport.tla.toolbox.tool.tlc.output;x-friends:="org.lamport.tla.toolbox.tool.tlc.ui.test", diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/add.gif b/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/add.gif new file mode 100644 index 0000000000000000000000000000000000000000..252d7ebcb8c74d6e5de66ef0eb8856622a0e9d89 Binary files /dev/null and b/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/add.gif differ diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/newstream_wiz.gif b/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/newstream_wiz.gif new file mode 100644 index 0000000000000000000000000000000000000000..045e32c58a139954fd62fa667b8180e02b6734de Binary files /dev/null and b/org.lamport.tla.toolbox.tool.tlc.ui/icons/full/newstream_wiz.gif differ diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/plugin.xml b/org.lamport.tla.toolbox.tool.tlc.ui/plugin.xml index 44d6d9d8d5fd53aabea168752a34b9e882792c4c..8174e143514da3eb19644da4b10648668643ba88 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/plugin.xml +++ b/org.lamport.tla.toolbox.tool.tlc.ui/plugin.xml @@ -159,6 +159,11 @@ optional="false"> </commandParameter> </command> + <command + description="Open a spec, module or model." + id="org.lamport.tla.toolbox.openElementSelection" + name="Filtered open element selection dialog"> + </command> </extension> @@ -170,7 +175,7 @@ <menu id="toolbox.toolmenus.tlc" label="TLC Model Checker" - mnemonic="M" + mnemonic="C" tooltip="TLC Model Checker Actions"> <command commandId="toolbox.tool.tlc.commands.model.new.delegate.current" @@ -254,6 +259,19 @@ </separator> </menu> </menuContribution> + + <!-- 'Window' menu --> + + <menuContribution + locationURI="menu:toolbox.window.menu?after=toolbox.window.view.separator"> + <command + commandId="org.lamport.tla.toolbox.openElementSelection" + label="Quick Access..." + mnemonic="A" + style="push"> + </command> + </menuContribution> + <menuContribution locationURI="menu:toolbox.toolmenus.tlc?after=org.lamport.tla.toolbox.tool.tlc.separator"> @@ -268,9 +286,17 @@ mode="FORCE_TEXT" style="push" tooltip="Runs TLC on the model."> - <visibleWhen - checkEnabled="false"> - </visibleWhen> + <visibleWhen + checkEnabled="true"> + <with + variable="activeWorkbenchWindow.activePerspective"> + <not> + <equals + value="org.lamport.tla.toolbox.ui.perspective.initial"> + </equals> + </not> + </with> + </visibleWhen> </command> <separator name="org.lamport.tla.toolbox.tool.tlc.ui.separator1" @@ -510,29 +536,6 @@ Console Facory <handler class="org.lamport.tla.toolbox.tool.tlc.handlers.NewModelHandlerSelectedDelegate" commandId="toolbox.tool.tlc.commands.model.new.delegate.selected"> - <activeWhen> - <and> - <with - variable="selection"> - <iterate - ifEmpty="true" - operator="and"> - <instanceof - value="org.lamport.tla.toolbox.spec.Spec"> - </instanceof> - <adapt - type="org.lamport.tla.toolbox.spec.Spec"> - <reference - definitionId="toolbox.isCurrentSpec"> - </reference> - </adapt> - </iterate> - </with> - <count - value="1"> - </count> - </and> - </activeWhen> </handler> <handler class="org.lamport.tla.toolbox.tool.tlc.handlers.NewModelHandlerCurrentDelegate" @@ -807,6 +810,10 @@ Console Facory </and> </activeWhen> </handler> + <handler + class="org.lamport.tla.toolbox.tool.tlc.handlers.TLAOpenElementSelectionDialogHandler" + commandId="org.lamport.tla.toolbox.openElementSelection"> + </handler> </extension> <extension point="org.eclipse.ui.preferencePages"> @@ -881,4 +888,13 @@ Console Facory commandId="toolbox.tool.tlc.commands.model.run" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/> </extension> + <extension + point="org.eclipse.ui.bindings"> + <key + commandId="org.lamport.tla.toolbox.openElementSelection" + contextId="org.eclipse.ui.contexts.window" + schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" + sequence="M1+M2+A"> + </key> + </extension> </plugin> diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/org/eclipse/ui/dialogs/FilteredItemsSelectionDialog.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/org/eclipse/ui/dialogs/FilteredItemsSelectionDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..02745e64d4fd35d5367d6bdb97fe851cc649ca64 --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/org/eclipse/ui/dialogs/FilteredItemsSelectionDialog.java @@ -0,0 +1,3206 @@ +/******************************************************************************* + * Copyright (c) 2000, 2014 IBM Corporation and others. + * 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 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Willian Mitsuda <wmitsuda@gmail.com> + * - Fix for bug 196553 - [Dialogs] Support IColorProvider/IFontProvider in FilteredItemsSelectionDialog + * Peter Friese <peter.friese@gentleware.com> + * - Fix for bug 208602 - [Dialogs] Open Type dialog needs accessible labels + * Simon Muschel <smuschel@gmx.de> - bug 258493 + * Lars Vogel <Lars.Vogel@gmail.com> - Bug 440810 + * Markus Alexander Kuppe <bugs.eclipse.org@lemmster.de> + * - bug 482482 - Allow subclasses of FilteredItemsSelectionDialog to nest list and extended area inside SashForm + *******************************************************************************/ +package org.lamport.org.eclipse.ui.dialogs; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.ProgressMonitorWrapper; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.LegacyActionTools; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IColorProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IFontProvider; +import org.eclipse.jface.viewers.ILabelDecorator; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.custom.ViewForm; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.ActiveShellExpression; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPreferenceConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.WorkbenchException; +import org.eclipse.ui.XMLMemento; +import org.eclipse.ui.dialogs.SearchPattern; +import org.eclipse.ui.dialogs.SelectionStatusDialog; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.internal.IWorkbenchGraphicConstants; +import org.eclipse.ui.internal.WorkbenchImages; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.internal.WorkbenchPlugin; +import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.statushandlers.StatusManager; + +/** + * Shows a list of items to the user with a text entry field for a string + * pattern used to filter the list of items. + * + * @since 3.3 + */ +public abstract class FilteredItemsSelectionDialog extends + SelectionStatusDialog { + + private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$ + + private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$ + + private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$ + + private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$ + + private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$ + + /** + * Represents an empty selection in the pattern input field (used only for + * initial pattern). + */ + public static final int NONE = 0; + + /** + * Pattern input field selection where caret is at the beginning (used only + * for initial pattern). + */ + public static final int CARET_BEGINNING = 1; + + /** + * Represents a full selection in the pattern input field (used only for + * initial pattern). + */ + public static final int FULL_SELECTION = 2; + + private Text pattern; + + private TableViewer list; + + private DetailsContentViewer details; + + /** + * It is a duplicate of a field in the CLabel class in DetailsContentViewer. + * It is maintained, because the <code>setDetailsLabelProvider()</code> + * could be called before content area is created. + */ + private ILabelProvider detailsLabelProvider; + + private ItemsListLabelProvider itemsListLabelProvider; + + private MenuManager menuManager; + + private MenuManager contextMenuManager; + + private boolean multi; + + private ToolBar toolBar; + + private ToolItem toolItem; + + private Label progressLabel; + + private ToggleStatusLineAction toggleStatusLineAction; + + private RemoveHistoryItemAction removeHistoryItemAction; + + private ActionContributionItem removeHistoryActionContributionItem; + + private IStatus status; + + private RefreshCacheJob refreshCacheJob; + + private RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob(); + + private Object[] currentSelection; + + private ContentProvider contentProvider; + + private FilterHistoryJob filterHistoryJob; + + private FilterJob filterJob; + + private ItemsFilter filter; + + private List lastCompletedResult; + + private ItemsFilter lastCompletedFilter; + + private String initialPatternText; + + private int selectionMode; + + private ItemsListSeparator itemsListSeparator; + + private static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + private boolean refreshWithLastSelection = false; + + private IHandlerActivation showViewHandler; + + /** + * Creates a new instance of the class. + * + * @param shell + * shell to parent the dialog on + * @param multi + * indicates whether dialog allows to select more than one + * position in its list of items + */ + public FilteredItemsSelectionDialog(Shell shell, boolean multi) { + super(shell); + this.multi = multi; + filterHistoryJob = new FilterHistoryJob(); + filterJob = new FilterJob(); + contentProvider = new ContentProvider(); + refreshCacheJob = new RefreshCacheJob(); + itemsListSeparator = new ItemsListSeparator( + WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel); + selectionMode = NONE; + } + + /** + * Creates a new instance of the class. Created dialog won't allow to select + * more than one item. + * + * @param shell + * shell to parent the dialog on + */ + public FilteredItemsSelectionDialog(Shell shell) { + this(shell, false); + } + + /** + * Adds viewer filter to the dialog items list. + * + * @param filter + * the new filter + */ + protected void addListFilter(ViewerFilter filter) { + contentProvider.addFilter(filter); + } + + /** + * Sets a new label provider for items in the list. If the label provider + * also implements {@link + * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider + * .IStyledLabelProvider}, the style text labels provided by it will be used + * provided that the corresponding preference is set. + * + * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS + * + * @param listLabelProvider + * the label provider for items in the list + */ + public void setListLabelProvider(ILabelProvider listLabelProvider) { + getItemsListLabelProvider().setProvider(listLabelProvider); + } + + /** + * Returns the label decorator for selected items in the list. + * + * @return the label decorator for selected items in the list + */ + private ILabelDecorator getListSelectionLabelDecorator() { + return getItemsListLabelProvider().getSelectionDecorator(); + } + + /** + * Sets the label decorator for selected items in the list. + * + * @param listSelectionLabelDecorator + * the label decorator for selected items in the list + */ + public void setListSelectionLabelDecorator( + ILabelDecorator listSelectionLabelDecorator) { + getItemsListLabelProvider().setSelectionDecorator( + listSelectionLabelDecorator); + } + + /** + * Returns the item list label provider. + * + * @return the item list label provider + */ + private ItemsListLabelProvider getItemsListLabelProvider() { + if (itemsListLabelProvider == null) { + itemsListLabelProvider = new ItemsListLabelProvider( + new LabelProvider(), null); + } + return itemsListLabelProvider; + } + + /** + * Sets label provider for the details field. + * + * For a single selection, the element sent to + * {@link ILabelProvider#getImage(Object)} and + * {@link ILabelProvider#getText(Object)} is the selected object, for + * multiple selection a {@link String} with amount of selected items is the + * element. + * + * @see #getSelectedItems() getSelectedItems() can be used to retrieve + * selected items and get the items count. + * + * @param detailsLabelProvider + * the label provider for the details field + */ + public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) { + this.detailsLabelProvider = detailsLabelProvider; + if (details != null) { + details.setLabelProvider(detailsLabelProvider); + } + } + + private ILabelProvider getDetailsLabelProvider() { + if (detailsLabelProvider == null) { + detailsLabelProvider = new LabelProvider(); + } + return detailsLabelProvider; + } + + + public void create() { + super.create(); + pattern.setFocus(); + } + + /** + * Restores dialog using persisted settings. The default implementation + * restores the status of the details line and the selection history. + * + * @param settings + * settings used to restore dialog + */ + protected void restoreDialog(IDialogSettings settings) { + boolean toggleStatusLine = true; + + if (settings.get(SHOW_STATUS_LINE) != null) { + toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE); + } + + toggleStatusLineAction.setChecked(toggleStatusLine); + + details.setVisible(toggleStatusLine); + + String setting = settings.get(HISTORY_SETTINGS); + if (setting != null) { + try { + IMemento memento = XMLMemento.createReadRoot(new StringReader( + setting)); + this.contentProvider.loadHistory(memento); + } catch (WorkbenchException e) { + // Simply don't restore the settings + StatusManager + .getManager() + .handle( + new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_restoreError, + e)); + } + } + } + + + public boolean close() { + this.filterJob.cancel(); + this.refreshCacheJob.cancel(); + this.refreshProgressMessageJob.cancel(); + if (showViewHandler != null) { + IHandlerService service = PlatformUI + .getWorkbench().getService(IHandlerService.class); + service.deactivateHandler(showViewHandler); + showViewHandler.getHandler().dispose(); + showViewHandler = null; + } + if (menuManager != null) + menuManager.dispose(); + if (contextMenuManager != null) + contextMenuManager.dispose(); + storeDialog(getDialogSettings()); + return super.close(); + } + + /** + * Stores dialog settings. + * + * @param settings + * settings used to store dialog + */ + protected void storeDialog(IDialogSettings settings) { + settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked()); + + XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS); + this.contentProvider.saveHistory(memento); + StringWriter writer = new StringWriter(); + try { + memento.save(writer); + settings.put(HISTORY_SETTINGS, writer.getBuffer().toString()); + } catch (IOException e) { + // Simply don't store the settings + StatusManager + .getManager() + .handle( + new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_storeError, + e)); + } + } + + /** + * Create a new header which is labelled by headerLabel. + * + * @param parent + * @return Label the label of the header + */ + private Label createHeader(Composite parent) { + Composite header = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + header.setLayout(layout); + + Label headerLabel = new Label(header, SWT.NONE); + headerLabel.setText((getMessage() != null && getMessage().trim() + .length() > 0) ? getMessage() + : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel); + headerLabel.addTraverseListener(new TraverseListener() { + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + pattern.setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + headerLabel.setLayoutData(gd); + + createViewMenu(header); + header.setLayoutData(gd); + return headerLabel; + } + + /** + * Create the labels for the list and the progress. Return the list label. + * + * @param parent + * @return Label + */ + private Label createLabels(Composite parent) { + Composite labels = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + labels.setLayout(layout); + + Label listLabel = new Label(labels, SWT.NONE); + listLabel + .setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel); + + listLabel.addTraverseListener(new TraverseListener() { + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + list.getTable().setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + listLabel.setLayoutData(gd); + + progressLabel = new Label(labels, SWT.RIGHT); + progressLabel.setLayoutData(gd); + + labels.setLayoutData(gd); + return listLabel; + } + + private void createViewMenu(Composite parent) { + toolBar = new ToolBar(parent, SWT.FLAT); + toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + + GridData data = new GridData(); + data.horizontalAlignment = GridData.END; + toolBar.setLayoutData(data); + + toolBar.addMouseListener(new MouseAdapter() { + + public void mouseDown(MouseEvent e) { + showViewMenu(); + } + }); + + // Just the image is easily missed. Thus, add an additional label. + toolItem.setText("Options"); + toolItem.setImage(WorkbenchImages + .getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU)); + toolItem + .setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu); + toolItem.addSelectionListener(new SelectionAdapter() { + + public void widgetSelected(SelectionEvent e) { + showViewMenu(); + } + }); + + menuManager = new MenuManager(); + + fillViewMenu(menuManager); + + IHandlerService service = PlatformUI.getWorkbench() + .getService(IHandlerService.class); + IHandler handler = new AbstractHandler() { + + public Object execute(ExecutionEvent event) { + showViewMenu(); + return null; + } + }; + showViewHandler = service.activateHandler( + IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler, + new ActiveShellExpression(getShell())); + } + + /** + * Fills the menu of the dialog. + * + * @param menuManager + * the menu manager + */ + protected void fillViewMenu(IMenuManager menuManager) { + toggleStatusLineAction = new ToggleStatusLineAction(); + menuManager.add(toggleStatusLineAction); + } + + private void showViewMenu() { + Menu menu = menuManager.createContextMenu(getShell()); + Rectangle bounds = toolItem.getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + } + + /** + * Hook that allows to add actions to the context menu. + * <p> + * Subclasses may extend in order to add other actions.</p> + * + * @param menuManager the context menu manager + * @since 3.5 + */ + protected void fillContextMenu(IMenuManager menuManager) { + List selectedElements= ((StructuredSelection)list.getSelection()).toList(); + + Object item= null; + + for (Iterator it= selectedElements.iterator(); it.hasNext();) { + item= it.next(); + if (item instanceof ItemsListSeparator || !isHistoryElement(item)) { + return; + } + } + + if (selectedElements.size() > 0) { + removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction); + + menuManager.add(removeHistoryActionContributionItem); + + } + } + + private void createPopupMenu() { + removeHistoryItemAction = new RemoveHistoryItemAction(); + removeHistoryActionContributionItem = new ActionContributionItem( + removeHistoryItemAction); + + contextMenuManager = new MenuManager(); + contextMenuManager.setRemoveAllWhenShown(true); + contextMenuManager.addMenuListener(new IMenuListener() { + + public void menuAboutToShow(IMenuManager manager) { + fillContextMenu(manager); + } + }); + + final Table table = list.getTable(); + Menu menu= contextMenuManager.createContextMenu(table); + table.setMenu(menu); + } + + /** + * Creates an extra content area, which will be located above the details. + * + * @param parent + * parent to create the dialog widgets in + * @return an extra content area + */ + protected abstract Control createExtendedContentArea(Composite parent); + + /** + * Creates the {@link Composite} into which the dialog area's TableViewer + * will be nested. + * + * @param parent + * parent to create the TableViewer in + * @return a Composite to hold the TableViewer. + */ + protected Composite createContentComposite(final Composite parent) { + final Composite comp = new Composite(parent, SWT.NONE); + comp.setLayoutData(new GridData(GridData.FILL_BOTH)); + + final GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginWidth = 0; + layout.marginHeight = 0; + comp.setLayout(layout); + + return comp; + } + + + protected Control createDialogArea(Composite parent) { + Composite dialogArea = (Composite) super.createDialogArea(parent); + + Composite content = new Composite(dialogArea, SWT.NONE); + GridData gd = new GridData(GridData.FILL_BOTH); + content.setLayoutData(gd); + + GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginWidth = 0; + layout.marginHeight = 0; + content.setLayout(layout); + + final Label headerLabel = createHeader(content); + + pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); + pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() { + + public void getName(AccessibleEvent e) { + e.result = LegacyActionTools.removeMnemonics(headerLabel + .getText()); + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL); + pattern.setLayoutData(gd); + + final Label listLabel = createLabels(content); + + final Composite comp = createContentComposite(content); + + list = new TableViewer(comp, + (multi ? SWT.MULTI : SWT.SINGLE) + | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL); + list.getTable().getAccessible().addAccessibleListener( + new AccessibleAdapter() { + + public void getName(AccessibleEvent e) { + if (e.childID == ACC.CHILDID_SELF) { + e.result = LegacyActionTools + .removeMnemonics(listLabel.getText()); + } + } + }); + list.setContentProvider(contentProvider); + list.setLabelProvider(getItemsListLabelProvider()); + list.setInput(new Object[0]); + list.setItemCount(contentProvider.getNumberOfElements()); + gd = new GridData(GridData.FILL_BOTH); + applyDialogFont(list.getTable()); + gd.heightHint= list.getTable().getItemHeight() * 15; + list.getTable().setLayoutData(gd); + + createPopupMenu(); + + pattern.addModifyListener(new ModifyListener() { + + public void modifyText(ModifyEvent e) { + applyFilter(); + } + }); + + pattern.addKeyListener(new KeyAdapter() { + + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + if (list.getTable().getItemCount() > 0) { + list.getTable().setFocus(); + } + } + } + }); + + list.addSelectionChangedListener(new ISelectionChangedListener() { + + public void selectionChanged(SelectionChangedEvent event) { + StructuredSelection selection = (StructuredSelection) event + .getSelection(); + handleSelected(selection); + } + }); + + list.addDoubleClickListener(new IDoubleClickListener() { + + public void doubleClick(DoubleClickEvent event) { + handleDoubleClick(); + } + }); + + list.getTable().addKeyListener(new KeyAdapter() { + + public void keyPressed(KeyEvent e) { + + if (e.keyCode == SWT.DEL) { + + List selectedElements = ((StructuredSelection) list + .getSelection()).toList(); + + Object item = null; + boolean isSelectedHistory = true; + + for (Iterator it = selectedElements.iterator(); it + .hasNext();) { + item = it.next(); + if (item instanceof ItemsListSeparator + || !isHistoryElement(item)) { + isSelectedHistory = false; + break; + } + } + if (isSelectedHistory) + removeSelectedItems(selectedElements); + + } + + if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0 + && (e.stateMask & SWT.CTRL) != 0) { + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + if (selection.size() == 1) { + Object element = selection.getFirstElement(); + if (element.equals(list.getElementAt(0))) { + pattern.setFocus(); + } + if (list.getElementAt(list.getTable() + .getSelectionIndex() - 1) instanceof ItemsListSeparator) + list.getTable().setSelection( + list.getTable().getSelectionIndex() - 1); + list.getTable().notifyListeners(SWT.Selection, + new Event()); + + } + } + + if (e.keyCode == SWT.ARROW_DOWN + && (e.stateMask & SWT.SHIFT) != 0 + && (e.stateMask & SWT.CTRL) != 0) { + + if (list + .getElementAt(list.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator) + list.getTable().setSelection( + list.getTable().getSelectionIndex() + 1); + list.getTable().notifyListeners(SWT.Selection, new Event()); + } + + } + }); + + createExtendedContentArea(comp); + + details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT); + details.setVisible(toggleStatusLineAction.isChecked()); + details.setContentProvider(new NullContentProvider()); + details.setLabelProvider(getDetailsLabelProvider()); + + applyDialogFont(content); + + restoreDialog(getDialogSettings()); + + if (initialPatternText != null) { + pattern.setText(initialPatternText); + } + + switch (selectionMode) { + case CARET_BEGINNING: + pattern.setSelection(0, 0); + break; + case FULL_SELECTION: + pattern.setSelection(0, initialPatternText.length()); + break; + } + + // apply filter even if pattern is empty (display history) + applyFilter(); + + return dialogArea; + } + + /** + * This method is a hook for subclasses to override default dialog behavior. + * The <code>handleDoubleClick()</code> method handles double clicks on + * the list of filtered elements. + * <p> + * Current implementation makes double-clicking on the list do the same as + * pressing <code>OK</code> button on the dialog. + */ + protected void handleDoubleClick() { + okPressed(); + } + + /** + * Refreshes the details field according to the current selection in the + * items list. + */ + private void refreshDetails() { + StructuredSelection selection = getSelectedItems(); + + switch (selection.size()) { + case 0: + details.setInput(null); + break; + case 1: + details.setInput(selection.getFirstElement()); + break; + default: + details + .setInput(NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected, + new Integer(selection.size()))); + break; + } + + } + + /** + * Handle selection in the items list by updating labels of selected and + * unselected items and refresh the details field using the selection. + * + * @param selection + * the new selection + */ + protected void handleSelected(StructuredSelection selection) { + IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, + IStatus.OK, EMPTY_STRING, null); + + Object[] lastSelection = currentSelection; + + currentSelection = selection.toArray(); + + if (selection.size() == 0) { + status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, + IStatus.ERROR, EMPTY_STRING, null); + + if (lastSelection != null + && getListSelectionLabelDecorator() != null) { + list.update(lastSelection, null); + } + + currentSelection = null; + + } else { + status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, + IStatus.ERROR, EMPTY_STRING, null); + + List items = selection.toList(); + + Object item = null; + IStatus tempStatus = null; + + for (Iterator it = items.iterator(); it.hasNext();) { + Object o = it.next(); + + if (o instanceof ItemsListSeparator) { + continue; + } + + item = o; + tempStatus = validateItem(item); + + if (tempStatus.isOK()) { + status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, + IStatus.OK, EMPTY_STRING, null); + } else { + status = tempStatus; + // if any selected element is not valid status is set to + // ERROR + break; + } + } + + if (lastSelection != null + && getListSelectionLabelDecorator() != null) { + list.update(lastSelection, null); + } + + if (getListSelectionLabelDecorator() != null) { + list.update(currentSelection, null); + } + } + + refreshDetails(); + updateStatus(status); + } + + + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings settings = getDialogSettings(); + IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS); + if (section == null) { + section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS); + section.put(DIALOG_HEIGHT, 500); + section.put(DIALOG_WIDTH, 600); + } + return section; + } + + /** + * Returns the dialog settings. Returned object can't be null. + * + * @return return dialog settings for this dialog + */ + protected abstract IDialogSettings getDialogSettings(); + + /** + * Refreshes the dialog - has to be called in UI thread. + */ + public void refresh() { + if (list != null && !list.getTable().isDisposed()) { + + List lastRefreshSelection = ((StructuredSelection) list + .getSelection()).toList(); + list.getTable().deselectAll(); + + list.setItemCount(contentProvider.getNumberOfElements()); + list.refresh(); + + if (list.getTable().getItemCount() > 0) { + // preserve previous selection + if (refreshWithLastSelection && lastRefreshSelection != null + && lastRefreshSelection.size() > 0) { + list.setSelection(new StructuredSelection( + lastRefreshSelection)); + } else { + refreshWithLastSelection = true; + list.getTable().setSelection(0); + list.getTable().notifyListeners(SWT.Selection, new Event()); + } + } else { + list.setSelection(StructuredSelection.EMPTY); + } + + } + + scheduleProgressMessageRefresh(); + } + + /** + * Updates the progress label. + * + * @deprecated + */ + @Deprecated + public void updateProgressLabel() { + scheduleProgressMessageRefresh(); + } + + /** + * Notifies the content provider - fires filtering of content provider + * elements. During the filtering, a separator between history and workspace + * matches is added. + * <p> + * This is a long running operation and should be called in a job. + * + * @param checkDuplicates + * <code>true</code> if data concerning elements duplication + * should be computed - it takes much more time than the standard + * filtering + * @param monitor + * a progress monitor or <code>null</code> if no monitor is + * available + */ + public void reloadCache(boolean checkDuplicates, IProgressMonitor monitor) { + if (list != null && !list.getTable().isDisposed() + && contentProvider != null) { + contentProvider.reloadCache(checkDuplicates, monitor); + } + } + + /** + * Schedule refresh job. + */ + public void scheduleRefresh() { + refreshCacheJob.cancelAll(); + refreshCacheJob.schedule(); + } + + /** + * Schedules progress message refresh. + */ + public void scheduleProgressMessageRefresh() { + if (filterJob.getState() != Job.RUNNING + && refreshProgressMessageJob.getState() != Job.RUNNING) + refreshProgressMessageJob.scheduleProgressRefresh(null); + } + + + protected void computeResult() { + + List selectedElements = ((StructuredSelection) list.getSelection()) + .toList(); + + List objectsToReturn = new ArrayList(); + + Object item = null; + + for (Iterator it = selectedElements.iterator(); it.hasNext();) { + item = it.next(); + + if (!(item instanceof ItemsListSeparator)) { + accessedHistoryItem(item); + objectsToReturn.add(item); + } + } + + setResult(objectsToReturn); + } + + /* + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus) + */ + + protected void updateStatus(IStatus status) { + this.status = status; + super.updateStatus(status); + } + + /* + * @see Dialog#okPressed() + */ + + protected void okPressed() { + if (status != null + && (status.isOK() || status.getCode() == IStatus.INFO)) { + super.okPressed(); + } + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. A full selection is used in the pattern + * input field. + * + * @param text + * initial pattern for the filter + * @see FilteredItemsSelectionDialog#FULL_SELECTION + */ + public void setInitialPattern(String text) { + setInitialPattern(text, FULL_SELECTION); + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. The <code>selectionMode</code> is used + * to choose selection type for the input field. + * + * @param text + * initial pattern for the filter + * @param selectionMode + * one of: {@link FilteredItemsSelectionDialog#NONE}, + * {@link FilteredItemsSelectionDialog#CARET_BEGINNING}, + * {@link FilteredItemsSelectionDialog#FULL_SELECTION} + */ + public void setInitialPattern(String text, int selectionMode) { + this.initialPatternText = text; + this.selectionMode = selectionMode; + } + + /** + * Gets initial pattern. + * + * @return initial pattern, or <code>null</code> if initial pattern is not + * set + */ + protected String getInitialPattern() { + return this.initialPatternText; + } + + /** + * Returns the current selection. + * + * @return the current selection + */ + protected StructuredSelection getSelectedItems() { + + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + List selectedItems = selection.toList(); + Object itemToRemove = null; + + for (Iterator it = selection.iterator(); it.hasNext();) { + Object item = it.next(); + if (item instanceof ItemsListSeparator) { + itemToRemove = item; + break; + } + } + + if (itemToRemove == null) + return new StructuredSelection(selectedItems); + // Create a new selection without the collision + List newItems = new ArrayList(selectedItems); + newItems.remove(itemToRemove); + return new StructuredSelection(newItems); + + } + + /** + * Validates the item. When items on the items list are selected or + * deselected, it validates each item in the selection and the dialog status + * depends on all validations. + * + * @param item + * an item to be checked + * @return status of the dialog to be set + */ + protected abstract IStatus validateItem(Object item); + + /** + * Creates an instance of a filter. + * + * @return a filter for items on the items list. Can be <code>null</code>, + * no filtering will be applied then, causing no item to be shown in + * the list. + */ + protected abstract ItemsFilter createFilter(); + + /** + * Applies the filter created by <code>createFilter()</code> method to the + * items list. When new filter is different than previous one it will cause + * refiltering. + */ + protected void applyFilter() { + + ItemsFilter newFilter = createFilter(); + + // don't apply filtering for patterns which mean the same, for example: + // *a**b and ***a*b + if (filter != null && filter.equalsFilter(newFilter)) { + return; + } + + filterHistoryJob.cancel(); + filterJob.cancel(); + + this.filter = newFilter; + + if (this.filter != null) { + filterHistoryJob.schedule(); + } + } + + /** + * Returns comparator to sort items inside content provider. Returned object + * will be probably created as an anonymous class. Parameters passed to the + * <code>compare(java.lang.Object, java.lang.Object)</code> are going to + * be the same type as the one used in the content provider. + * + * @return comparator to sort items content provider + */ + protected abstract Comparator getItemsComparator(); + + /** + * Fills the content provider with matching items. + * + * @param contentProvider + * collector to add items to. + * {@link FilteredItemsSelectionDialog.AbstractContentProvider#add(Object, FilteredItemsSelectionDialog.ItemsFilter)} + * only adds items that pass the given <code>itemsFilter</code>. + * @param itemsFilter + * the items filter + * @param progressMonitor + * must be used to report search progress. The state of this + * progress monitor reflects the state of the filtering process. + * @throws CoreException + */ + protected abstract void fillContentProvider( + AbstractContentProvider contentProvider, ItemsFilter itemsFilter, + IProgressMonitor progressMonitor) throws CoreException; + + /** + * Removes selected items from history. + * + * @param items + * items to be removed + */ + private void removeSelectedItems(List items) { + for (Iterator iter = items.iterator(); iter.hasNext();) { + Object item = iter.next(); + removeHistoryItem(item); + } + refreshWithLastSelection = false; + contentProvider.refresh(); + } + + /** + * Removes an item from history. + * + * @param item + * an item to remove + * @return removed item + */ + protected Object removeHistoryItem(Object item) { + return contentProvider.removeHistoryElement(item); + } + + /** + * Adds item to history. + * + * @param item + * the item to be added + */ + protected void accessedHistoryItem(Object item) { + contentProvider.addHistoryElement(item); + } + + /** + * Returns a history comparator. + * + * @return decorated comparator + */ + private Comparator getHistoryComparator() { + return new HistoryComparator(); + } + + /** + * Returns the history of selected elements. + * + * @return history of selected elements, or <code>null</code> if it is not + * set + */ + protected SelectionHistory getSelectionHistory() { + return this.contentProvider.getSelectionHistory(); + } + + /** + * Sets new history. + * + * @param selectionHistory + * the history + */ + protected void setSelectionHistory(SelectionHistory selectionHistory) { + if (this.contentProvider != null) + this.contentProvider.setSelectionHistory(selectionHistory); + } + + /** + * Indicates whether the given item is a history item. + * + * @param item + * the item to be investigated + * @return <code>true</code> if the given item exists in history, + * <code>false</code> otherwise + */ + public boolean isHistoryElement(Object item) { + return this.contentProvider.isHistoryElement(item); + } + + /** + * Indicates whether the given item is a duplicate. + * + * @param item + * the item to be investigated + * @return <code>true</code> if the item is duplicate, <code>false</code> + * otherwise + */ + public boolean isDuplicateElement(Object item) { + return this.contentProvider.isDuplicateElement(item); + } + + /** + * Sets separator label + * + * @param separatorLabel + * the label showed on separator + */ + public void setSeparatorLabel(String separatorLabel) { + this.itemsListSeparator = new ItemsListSeparator(separatorLabel); + } + + /** + * Returns name for then given object. + * + * @param item + * an object from the content provider. Subclasses should pay + * attention to the passed argument. They should either only pass + * objects of a known type (one used in content provider) or make + * sure that passed parameter is the expected one (by type + * checking like <code>instanceof</code> inside the method). + * @return name of the given item + */ + public abstract String getElementName(Object item); + + private class ToggleStatusLineAction extends Action { + + /** + * Creates a new instance of the class. + */ + public ToggleStatusLineAction() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction, + IAction.AS_CHECK_BOX); + } + + + public void run() { + details.setVisible(isChecked()); + } + } + + /** + * Only refreshes UI on the basis of an already sorted and filtered set of + * items. + * <p> + * Standard invocation scenario: + * <ol> + * <li>filtering job (<code>FilterJob</code> class extending + * <code>Job</code> class)</li> + * <li>cache refresh without checking for duplicates (<code>RefreshCacheJob</code> + * class extending <code>Job</code> class)</li> + * <li>UI refresh (<code>RefreshJob</code> class extending + * <code>UIJob</code> class)</li> + * <li>cache refresh with checking for duplicates (<cod>CacheRefreshJob</code> + * class extending <code>Job</code> class)</li> + * <li>UI refresh (<code>RefreshJob</code> class extending <code>UIJob</code> + * class)</li> + * </ol> + * The scenario is rather complicated, but it had to be applied, because: + * <ul> + * <li> refreshing cache is rather a long action and cannot be run in the UI - + * cannot be run in a UIJob</li> + * <li> refreshing cache checking for duplicates is twice as long as + * refreshing cache without checking for duplicates; results of the search + * could be displayed earlier</li> + * <li> refreshing the UI have to be run in a UIJob</li> + * </ul> + * + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.FilterJob + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshJob + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshCacheJob + */ + private class RefreshJob extends UIJob { + + /** + * Creates a new instance of the class. + */ + public RefreshJob() { + super(FilteredItemsSelectionDialog.this.getParentShell() + .getDisplay(), + WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob); + setSystem(true); + } + + + public IStatus runInUIThread(IProgressMonitor monitor) { + if (monitor.isCanceled()) + return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH, + IStatus.OK, EMPTY_STRING, null); + + if (FilteredItemsSelectionDialog.this != null) { + FilteredItemsSelectionDialog.this.refresh(); + } + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + } + + } + + /** + * Refreshes the progress message cyclically with 500 milliseconds delay. + * <code>RefreshProgressMessageJob</code> is strictly connected with + * <code>GranualProgressMonitor</code> and use it to to get progress + * message and to decide about break of cyclical refresh. + */ + private class RefreshProgressMessageJob extends UIJob { + + private GranualProgressMonitor progressMonitor; + + /** + * Creates a new instance of the class. + */ + public RefreshProgressMessageJob() { + super( + FilteredItemsSelectionDialog.this.getParentShell() + .getDisplay(), + WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob); + setSystem(true); + } + + + public IStatus runInUIThread(IProgressMonitor monitor) { + + if (!progressLabel.isDisposed()) + progressLabel.setText(progressMonitor != null ? progressMonitor + .getMessage() : EMPTY_STRING); + + if (progressMonitor == null || progressMonitor.isDone()) { + return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID, + IStatus.CANCEL, EMPTY_STRING, null); + } + + // Schedule cyclical with 500 milliseconds delay + schedule(500); + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + } + + /** + * Schedule progress refresh job. + * + * @param progressMonitor + * used during refresh progress label + */ + public void scheduleProgressRefresh( + GranualProgressMonitor progressMonitor) { + this.progressMonitor = progressMonitor; + // Schedule with initial delay to avoid flickering when the user + // types quickly + schedule(200); + } + + } + + /** + * A job responsible for computing filtered items list presented using + * <code>RefreshJob</code>. + * + * @see FilteredItemsSelectionDialog.RefreshJob + * + */ + private class RefreshCacheJob extends Job { + + private RefreshJob refreshJob = new RefreshJob(); + + /** + * Creates a new instance of the class. + */ + public RefreshCacheJob() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob); + setSystem(true); + } + + /** + * Stops the job and all sub-jobs. + */ + public void cancelAll() { + cancel(); + refreshJob.cancel(); + } + + + protected IStatus run(IProgressMonitor monitor) { + if (monitor.isCanceled()) { + return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH, + IStatus.CANCEL, EMPTY_STRING, null); + } + + if (FilteredItemsSelectionDialog.this != null) { + GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor( + monitor); + FilteredItemsSelectionDialog.this.reloadCache(true, + wrappedMonitor); + } + + if (!monitor.isCanceled()) { + refreshJob.schedule(); + } + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + + } + + + protected void canceling() { + super.canceling(); + contentProvider.stopReloadingCache(); + } + + } + + private class RemoveHistoryItemAction extends Action { + + /** + * Creates a new instance of the class. + */ + public RemoveHistoryItemAction() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction); + } + + + public void run() { + List selectedElements = ((StructuredSelection) list.getSelection()) + .toList(); + removeSelectedItems(selectedElements); + } + } + + private static boolean showColoredLabels() { + return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS); + } + + private class ItemsListLabelProvider extends StyledCellLabelProvider + implements ILabelProviderListener { + private ILabelProvider provider; + + private ILabelDecorator selectionDecorator; + + // Need to keep our own list of listeners + private ListenerList listeners = new ListenerList(); + + /** + * Creates a new instance of the class. + * + * @param provider + * the label provider for all items, not <code>null</code> + * @param selectionDecorator + * the decorator for selected items, can be <code>null</code> + */ + public ItemsListLabelProvider(ILabelProvider provider, + ILabelDecorator selectionDecorator) { + Assert.isNotNull(provider); + this.provider = provider; + this.selectionDecorator = selectionDecorator; + + setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider); + + provider.addListener(this); + + if (selectionDecorator != null) { + selectionDecorator.addListener(this); + } + } + + /** + * Sets new selection decorator. + * + * @param newSelectionDecorator + * new label decorator for selected items in the list + */ + public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) { + if (selectionDecorator != null) { + selectionDecorator.removeListener(this); + selectionDecorator.dispose(); + } + + selectionDecorator = newSelectionDecorator; + + if (selectionDecorator != null) { + selectionDecorator.addListener(this); + } + } + + /** + * Gets selection decorator. + * + * @return the label decorator for selected items in the list + */ + public ILabelDecorator getSelectionDecorator() { + return selectionDecorator; + } + + /** + * Sets new label provider. + * + * @param newProvider + * new label provider for items in the list, not + * <code>null</code> + */ + public void setProvider(ILabelProvider newProvider) { + Assert.isNotNull(newProvider); + provider.removeListener(this); + provider.dispose(); + + provider = newProvider; + + if (provider != null) { + provider.addListener(this); + } + + setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider); + } + + private Image getImage(Object element) { + if (element instanceof ItemsListSeparator) { + return WorkbenchImages + .getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR); + } + + return provider.getImage(element); + } + + private boolean isSelected(Object element) { + if (element != null && currentSelection != null) { + for (int i = 0; i < currentSelection.length; i++) { + if (element.equals(currentSelection[i])) + return true; + } + } + return false; + } + + private String getText(Object element) { + if (element instanceof ItemsListSeparator) { + return getSeparatorLabel(((ItemsListSeparator) element) + .getName()); + } + + String str = provider.getText(element); + if (selectionDecorator != null && isSelected(element)) { + return selectionDecorator.decorateText(str.toString(), element); + } + + return str; + } + + private StyledString getStyledText(Object element, + IStyledLabelProvider provider) { + StyledString string = provider.getStyledText(element); + + if (selectionDecorator != null && isSelected(element)) { + String decorated = selectionDecorator.decorateText(string + .getString(), element); + return StyledCellLabelProvider.styleDecoratedString(decorated, null, string); + // no need to add colors when element is selected + } + return string; + } + + + public void update(ViewerCell cell) { + Object element = cell.getElement(); + + if (!(element instanceof ItemsListSeparator) + && provider instanceof IStyledLabelProvider) { + IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider; + StyledString styledString = getStyledText(element, + styledLabelProvider); + + cell.setText(styledString.getString()); + cell.setStyleRanges(styledString.getStyleRanges()); + cell.setImage(styledLabelProvider.getImage(element)); + } else { + cell.setText(getText(element)); + cell.setImage(getImage(element)); + } + cell.setFont(getFont(element)); + cell.setForeground(getForeground(element)); + cell.setBackground(getBackground(element)); + + super.update(cell); + } + + private String getSeparatorLabel(String separatorLabel) { + Rectangle rect = list.getTable().getBounds(); + + int borderWidth = list.getTable().computeTrim(0, 0, 0, 0).width; + + int imageWidth = WorkbenchImages.getImage( + IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width; + + int width = rect.width - borderWidth - imageWidth; + + GC gc = new GC(list.getTable()); + gc.setFont(list.getTable().getFont()); + + int fSeparatorWidth = gc.getAdvanceWidth('-'); + int fMessageLength = gc.textExtent(separatorLabel).x; + + gc.dispose(); + + StringBuffer dashes = new StringBuffer(); + int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2; + for (int i = 0; i < chars; i++) { + dashes.append('-'); + } + + StringBuffer result = new StringBuffer(); + result.append(dashes); + result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$ + result.append(dashes); + return result.toString().trim(); + } + + + public void addListener(ILabelProviderListener listener) { + listeners.add(listener); + } + + + public void dispose() { + provider.removeListener(this); + provider.dispose(); + + if (selectionDecorator != null) { + selectionDecorator.removeListener(this); + selectionDecorator.dispose(); + } + + super.dispose(); + } + + + public boolean isLabelProperty(Object element, String property) { + if (provider.isLabelProperty(element, property)) { + return true; + } + if (selectionDecorator != null + && selectionDecorator.isLabelProperty(element, property)) { + return true; + } + return false; + } + + + public void removeListener(ILabelProviderListener listener) { + listeners.remove(listener); + } + + private Color getBackground(Object element) { + if (element instanceof ItemsListSeparator) { + return null; + } + if (provider instanceof IColorProvider) { + return ((IColorProvider) provider).getBackground(element); + } + return null; + } + + private Color getForeground(Object element) { + if (element instanceof ItemsListSeparator) { + return Display.getCurrent().getSystemColor( + SWT.COLOR_WIDGET_NORMAL_SHADOW); + } + if (provider instanceof IColorProvider) { + return ((IColorProvider) provider).getForeground(element); + } + return null; + } + + private Font getFont(Object element) { + if (element instanceof ItemsListSeparator) { + return null; + } + if (provider instanceof IFontProvider) { + return ((IFontProvider) provider).getFont(element); + } + return null; + } + + + public void labelProviderChanged(LabelProviderChangedEvent event) { + Object[] l = listeners.getListeners(); + for (int i = 0; i < listeners.size(); i++) { + ((ILabelProviderListener) l[i]).labelProviderChanged(event); + } + } + } + + /** + * Used in ItemsListContentProvider, separates history and non-history + * items. + */ + public class ItemsListSeparator { + + private String name; + + /** + * Creates a new instance of the class. + * + * @param name + * the name of the separator + */ + public ItemsListSeparator(String name) { + this.name = name; + } + + /** + * Returns the name of this separator. + * + * @return the name of the separator + */ + public String getName() { + return name; + } + } + + /** + * GranualProgressMonitor is used for monitoring progress of filtering + * process. It is used by <code>RefreshProgressMessageJob</code> to + * refresh progress message. State of this monitor illustrates state of + * filtering or cache refreshing process. + * + */ + private class GranualProgressMonitor extends ProgressMonitorWrapper { + + private String name; + + private String subName; + + private int totalWork; + + private double worked; + + private boolean done; + + /** + * Creates instance of <code>GranualProgressMonitor</code>. + * + * @param monitor + * progress to be wrapped + */ + public GranualProgressMonitor(IProgressMonitor monitor) { + super(monitor); + } + + /** + * Checks if filtering has been done + * + * @return true if filtering work has been done false in other way + */ + public boolean isDone() { + return done; + } + + + public void setTaskName(String name) { + super.setTaskName(name); + this.name = name; + this.subName = null; + } + + + public void subTask(String name) { + super.subTask(name); + this.subName = name; + } + + + public void beginTask(String name, int totalWork) { + super.beginTask(name, totalWork); + if (this.name == null) + this.name = name; + this.totalWork = totalWork; + refreshProgressMessageJob.scheduleProgressRefresh(this); + } + + + public void worked(int work) { + super.worked(work); + internalWorked(work); + } + + + public void done() { + done = true; + super.done(); + } + + + public void setCanceled(boolean b) { + done = b; + super.setCanceled(b); + } + + + public void internalWorked(double work) { + worked = worked + work; + } + + private String getMessage() { + if (done) + return ""; //$NON-NLS-1$ + + String message; + + if (name == null) { + message = subName == null ? "" : subName; //$NON-NLS-1$ + } else { + message = subName == null ? name + : NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage, + new Object[] { name, subName }); + } + if (totalWork == 0) + return message; + + return NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage, + new Object[] { + message, + new Integer( + (int) ((worked * 100) / totalWork)) }); + + } + + } + + /** + * Filters items history and schedule filter job. + */ + private class FilterHistoryJob extends Job { + + /** + * Filter used during the filtering process. + */ + private ItemsFilter itemsFilter; + + /** + * Creates new instance of receiver. + */ + public FilterHistoryJob() { + super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel); + setSystem(true); + } + + + protected IStatus run(IProgressMonitor monitor) { + + this.itemsFilter = filter; + + contentProvider.reset(); + + refreshWithLastSelection = false; + + contentProvider.addHistoryItems(itemsFilter); + + if (!(lastCompletedFilter != null && lastCompletedFilter + .isSubFilter(this.itemsFilter))) + contentProvider.refresh(); + + filterJob.schedule(); + + return Status.OK_STATUS; + } + + } + + /** + * Filters items in indicated set and history. During filtering, it + * refreshes the dialog (progress monitor and elements list). + * + * Depending on the filter, <code>FilterJob</code> decides which kind of + * search will be run inside <code>filterContent</code>. If the last + * filtering is done (last completed filter), is not null, and the new + * filter is a sub-filter ({@link FilteredItemsSelectionDialog.ItemsFilter#isSubFilter(FilteredItemsSelectionDialog.ItemsFilter)}) + * of the last, then <code>FilterJob</code> only filters in the cache. If + * it is the first filtering or the new filter isn't a sub-filter of the + * last one, a full search is run. + */ + private class FilterJob extends Job { + + /** + * Filter used during the filtering process. + */ + protected ItemsFilter itemsFilter; + + /** + * Creates new instance of FilterJob + */ + public FilterJob() { + super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel); + setSystem(true); + } + + + protected final IStatus run(IProgressMonitor parent) { + GranualProgressMonitor monitor = new GranualProgressMonitor(parent); + return doRun(monitor); + } + + /** + * Executes job using the given filtering progress monitor. A hook for + * subclasses. + * + * @param monitor + * progress monitor + * @return result of the execution + */ + protected IStatus doRun(GranualProgressMonitor monitor) { + try { + internalRun(monitor); + } catch (CoreException e) { + cancel(); + return new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_jobError, + e); + } + return Status.OK_STATUS; + } + + /** + * Main method for the job. + * + * @param monitor + * @throws CoreException + */ + private void internalRun(GranualProgressMonitor monitor) + throws CoreException { + try { + if (monitor.isCanceled()) + return; + + this.itemsFilter = filter; + + if (filter.getPattern().length() != 0) { + filterContent(monitor); + } + + if (monitor.isCanceled()) + return; + + contentProvider.refresh(); + } finally { + monitor.done(); + } + } + + /** + * Filters items. + * + * @param monitor + * for monitoring progress + * @throws CoreException + */ + protected void filterContent(GranualProgressMonitor monitor) + throws CoreException { + + if (lastCompletedFilter != null + && lastCompletedFilter.isSubFilter(this.itemsFilter)) { + + int length = lastCompletedResult.size() / 500; + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName, + length); + + for (int pos = 0; pos < lastCompletedResult.size(); pos++) { + + Object item = lastCompletedResult.get(pos); + if (monitor.isCanceled()) + break; + contentProvider.add(item, itemsFilter); + + if ((pos % 500) == 0) { + monitor.worked(1); + } + } + + } else { + + lastCompletedFilter = null; + lastCompletedResult = null; + + SubProgressMonitor subMonitor = null; + if (monitor != null) { + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName, + 100); + subMonitor = new SubProgressMonitor(monitor, 95); + + } + + fillContentProvider(contentProvider, itemsFilter, subMonitor); + + if (monitor != null && !monitor.isCanceled()) { + monitor.worked(2); + contentProvider.rememberResult(itemsFilter); + monitor.worked(3); + } + } + + } + + } + + /** + * History stores a list of key, object pairs. The list is bounded at a + * certain size. If the list exceeds this size the oldest element is removed + * from the list. An element can be added/renewed with a call to + * <code>accessed(Object)</code>. + * <p> + * The history can be stored to/loaded from an XML file. + */ + protected static abstract class SelectionHistory { + + private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$ + + private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$ + + private static final int MAX_HISTORY_SIZE = 60; + + private final Set historyList; + + private final String rootNodeName; + + private final String infoNodeName; + + private SelectionHistory(String rootNodeName, String infoNodeName) { + + historyList = Collections.synchronizedSet(new LinkedHashSet() { + + private static final long serialVersionUID = 0L; + + + public boolean add(Object arg0) { + if (this.size() >= MAX_HISTORY_SIZE) { + Iterator iterator = this.iterator(); + iterator.next(); + iterator.remove(); + } + return super.add(arg0); + } + + }); + + this.rootNodeName = rootNodeName; + this.infoNodeName = infoNodeName; + } + + /** + * Creates new instance of <code>SelectionHistory</code>. + */ + public SelectionHistory() { + this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME); + } + + /** + * Adds object to history. + * + * @param object + * the item to be added to the history + */ + public synchronized void accessed(Object object) { + historyList.remove(object); + historyList.add(object); + } + + /** + * Returns <code>true</code> if history contains object. + * + * @param object + * the item for which check will be executed + * @return <code>true</code> if history contains object + * <code>false</code> in other way + */ + public synchronized boolean contains(Object object) { + return historyList.contains(object); + } + + /** + * Returns <code>true</code> if history is empty. + * + * @return <code>true</code> if history is empty + */ + public synchronized boolean isEmpty() { + return historyList.isEmpty(); + } + + /** + * Remove element from history. + * + * @param element + * to remove form the history + * @return <code>true</code> if this list contained the specified + * element + */ + public synchronized boolean remove(Object element) { + return historyList.remove(element); + } + + /** + * Load history elements from memento. + * + * @param memento + * memento from which the history will be retrieved + */ + public void load(IMemento memento) { + + XMLMemento historyMemento = (XMLMemento) memento + .getChild(rootNodeName); + + if (historyMemento == null) { + return; + } + + IMemento[] mementoElements = historyMemento + .getChildren(infoNodeName); + for (int i = 0; i < mementoElements.length; ++i) { + IMemento mementoElement = mementoElements[i]; + Object object = restoreItemFromMemento(mementoElement); + if (object != null) { + historyList.add(object); + } + } + } + + /** + * Save history elements to memento. + * + * @param memento + * memento to which the history will be added + */ + public void save(IMemento memento) { + + IMemento historyMemento = memento.createChild(rootNodeName); + + Object[] items = getHistoryItems(); + for (int i = 0; i < items.length; i++) { + Object item = items[i]; + IMemento elementMemento = historyMemento + .createChild(infoNodeName); + storeItemToMemento(item, elementMemento); + } + + } + + /** + * Gets array of history items. + * + * @return array of history elements + */ + public synchronized Object[] getHistoryItems() { + return historyList.toArray(); + } + + /** + * Creates an object using given memento. + * + * @param memento + * memento used for creating new object + * + * @return the restored object + */ + protected abstract Object restoreItemFromMemento(IMemento memento); + + /** + * Store object in <code>IMemento</code>. + * + * @param item + * the item to store + * @param memento + * the memento to store to + */ + protected abstract void storeItemToMemento(Object item, IMemento memento); + + } + + /** + * Filters elements using SearchPattern by comparing the names of items with + * the filter pattern. + */ + protected abstract class ItemsFilter { + + protected SearchPattern patternMatcher; + + /** + * Creates new instance of ItemsFilter. + */ + public ItemsFilter() { + this(new SearchPattern()); + } + + /** + * Creates new instance of ItemsFilter. + * + * @param searchPattern + * the pattern to be used when filtering + */ + public ItemsFilter(SearchPattern searchPattern) { + patternMatcher = searchPattern; + String stringPattern = ""; //$NON-NLS-1$ + if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$ + stringPattern = pattern.getText(); + } + patternMatcher.setPattern(stringPattern); + } + + /** + * Check if the given filter is a sub-filter of this filter. The default + * implementation checks if the <code>SearchPattern</code> from the + * given filter is a sub-pattern of the one from this filter. + * <p> + * <i>WARNING: This method is <b>not</b> defined in reading order, i.e. + * <code>a.isSubFilter(b)</code> is <code>true</code> iff + * <code>b</code> is a sub-filter of <code>a</code>, and not + * vice-versa. </i> + * </p> + * + * @param filter + * the filter to be checked, or <code>null</code> + * @return <code>true</code> if the given filter is sub-filter of this + * filter, <code>false</code> if the given filter isn't a + * sub-filter or is <code>null</code> + * + * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern) + */ + public boolean isSubFilter(ItemsFilter filter) { + if (filter != null) { + return this.patternMatcher.isSubPattern(filter.patternMatcher); + } + return false; + } + + /** + * Checks whether the provided filter is equal to the current filter. + * The default implementation checks if <code>SearchPattern</code> + * from current filter is equal to the one from provided filter. + * + * @param filter + * filter to be checked, or <code>null</code> + * @return <code>true</code> if the given filter is equal to current + * filter, <code>false</code> if given filter isn't equal to + * current one or if it is <code>null</code> + * + * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern) + */ + public boolean equalsFilter(ItemsFilter filter) { + if (filter != null + && filter.patternMatcher.equalsPattern(this.patternMatcher)) { + return true; + } + return false; + } + + /** + * Checks whether the pattern's match rule is camel case. + * + * @return <code>true</code> if pattern's match rule is camel case, + * <code>false</code> otherwise + */ + public boolean isCamelCasePattern() { + return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH; + } + + /** + * Returns the pattern string. + * + * @return pattern for this filter + * + * @see SearchPattern#getPattern() + */ + public String getPattern() { + return patternMatcher.getPattern(); + } + + /** + * Returns the rule to apply for matching keys. + * + * @return an implementation-specific match rule + * + * @see SearchPattern#getMatchRule() for match rules returned by the + * default implementation + */ + public int getMatchRule() { + return patternMatcher.getMatchRule(); + } + + /** + * Matches text with filter. + * + * @param text + * the text to match with the filter + * @return <code>true</code> if text matches with filter pattern, + * <code>false</code> otherwise + */ + protected boolean matches(String text) { + return patternMatcher.matches(text); + } + + /** + * General method for matching raw name pattern. Checks whether current + * pattern is prefix of name provided item. + * + * @param item + * item to check + * @return <code>true</code> if current pattern is a prefix of name + * provided item, <code>false</code> if item's name is shorter + * than prefix or sequences of characters don't match. + */ + public boolean matchesRawNamePattern(Object item) { + String prefix = patternMatcher.getPattern(); + String text = getElementName(item); + + if (text == null) + return false; + + int textLength = text.length(); + int prefixLength = prefix.length(); + if (textLength < prefixLength) { + return false; + } + for (int i = prefixLength - 1; i >= 0; i--) { + if (Character.toLowerCase(prefix.charAt(i)) != Character + .toLowerCase(text.charAt(i))) + return false; + } + return true; + } + + /** + * Matches an item against filter conditions. + * + * @param item + * @return <code>true<code> if item matches against filter conditions, <code>false</code> + * otherwise + */ + public abstract boolean matchItem(Object item); + + /** + * Checks consistency of an item. Item is inconsistent if was changed or + * removed. + * + * @param item + * @return <code>true</code> if item is consistent, <code>false</code> + * if item is inconsistent + */ + public abstract boolean isConsistentItem(Object item); + + } + + /** + * An interface to content providers for + * <code>FilterItemsSelectionDialog</code>. + */ + protected abstract class AbstractContentProvider { + /** + * Adds the item to the content provider iff the filter matches the + * item. Otherwise does nothing. + * + * @param item + * the item to add + * @param itemsFilter + * the filter + * + * @see FilteredItemsSelectionDialog.ItemsFilter#matchItem(Object) + */ + public abstract void add(Object item, ItemsFilter itemsFilter); + } + + /** + * Collects filtered elements. Contains one synchronized, sorted set for + * collecting filtered elements. All collected elements are sorted using + * comparator. Comparator is returned by getElementComparator() method. + * Implementation of <code>ItemsFilter</code> is used to filter elements. + * The key function of filter used in to filtering is + * <code>matchElement(Object item)</code>. + * <p> + * The <code>ContentProvider</code> class also provides item filtering + * methods. The filtering has been moved from the standard TableView + * <code>getFilteredItems()</code> method to content provider, because + * <code>ILazyContentProvider</code> and virtual tables are used. This + * class is responsible for adding a separator below history items and + * marking each items as duplicate if its name repeats more than once on the + * filtered list. + */ + private class ContentProvider extends AbstractContentProvider implements + IStructuredContentProvider, ILazyContentProvider { + + private SelectionHistory selectionHistory; + + /** + * Raw result of the searching (unsorted, unfiltered). + * <p> + * Standard object flow: + * <code>items -> lastSortedItems -> lastFilteredItems</code> + */ + private Set items; + + /** + * Items that are duplicates. + */ + private Set duplicates; + + /** + * List of <code>ViewerFilter</code>s to be used during filtering + */ + private List filters; + + /** + * Result of the last filtering. + * <p> + * Standard object flow: + * <code>items -> lastSortedItems -> lastFilteredItems</code> + */ + private List lastFilteredItems; + + /** + * Result of the last sorting. + * <p> + * Standard object flow: + * <code>items -> lastSortedItems -> lastFilteredItems</code> + */ + private List lastSortedItems; + + /** + * Used for <code>getFilteredItems()</code> method canceling (when the + * job that invoked the method was canceled). + * <p> + * Method canceling could be based (only) on monitor canceling + * unfortunately sometimes the method <code>getFilteredElements()</code> + * could be run with a null monitor, the <code>reset</code> flag have + * to be left intact. + */ + private boolean reset; + + /** + * Creates new instance of <code>ContentProvider</code>. + */ + public ContentProvider() { + this.items = Collections.synchronizedSet(new HashSet(2048)); + this.duplicates = Collections.synchronizedSet(new HashSet(256)); + this.lastFilteredItems = new ArrayList(); + this.lastSortedItems = Collections.synchronizedList(new ArrayList( + 2048)); + } + + /** + * Sets selection history. + * + * @param selectionHistory + * The selectionHistory to set. + */ + public void setSelectionHistory(SelectionHistory selectionHistory) { + this.selectionHistory = selectionHistory; + } + + /** + * @return Returns the selectionHistory. + */ + public SelectionHistory getSelectionHistory() { + return selectionHistory; + } + + /** + * Removes all content items and resets progress message. + */ + public void reset() { + reset = true; + this.items.clear(); + this.duplicates.clear(); + this.lastSortedItems.clear(); + } + + /** + * Stops reloading cache - <code>getFilteredItems()</code> method. + */ + public void stopReloadingCache() { + reset = true; + } + + /** + * Adds filtered item. + * + * @param item + * @param itemsFilter + */ + + public void add(Object item, ItemsFilter itemsFilter) { + if (itemsFilter == filter) { + if (itemsFilter != null) { + if (itemsFilter.matchItem(item)) { + this.items.add(item); + } + } else { + this.items.add(item); + } + } + } + + /** + * Add all history items to <code>contentProvider</code>. + * + * @param itemsFilter + */ + public void addHistoryItems(ItemsFilter itemsFilter) { + if (this.selectionHistory != null) { + Object[] items = this.selectionHistory.getHistoryItems(); + for (int i = 0; i < items.length; i++) { + Object item = items[i]; + if (itemsFilter == filter) { + if (itemsFilter != null) { + if (itemsFilter.matchItem(item)) { + if (itemsFilter.isConsistentItem(item)) { + this.items.add(item); + } else { + this.selectionHistory.remove(item); + } + } + } + } + } + } + } + + /** + * Refresh dialog. + */ + public void refresh() { + scheduleRefresh(); + } + + /** + * Removes items from history and refreshes the view. + * + * @param item + * to remove + * + * @return removed item + */ + public Object removeHistoryElement(Object item) { + if (this.selectionHistory != null) + this.selectionHistory.remove(item); + if (filter == null || filter.getPattern().length() == 0) { + items.remove(item); + duplicates.remove(item); + this.lastSortedItems.remove(item); + } + + synchronized (lastSortedItems) { + Collections.sort(lastSortedItems, getHistoryComparator()); + } + return item; + } + + /** + * Adds item to history and refresh view. + * + * @param item + * to add + */ + public void addHistoryElement(Object item) { + if (this.selectionHistory != null) + this.selectionHistory.accessed(item); + if (filter == null || !filter.matchItem(item)) { + this.items.remove(item); + this.duplicates.remove(item); + this.lastSortedItems.remove(item); + } + synchronized (lastSortedItems) { + Collections.sort(lastSortedItems, getHistoryComparator()); + } + this.refresh(); + } + + /** + * @param item + * @return <code>true</code> if given item is part of the history + */ + public boolean isHistoryElement(Object item) { + if (this.selectionHistory != null) { + return this.selectionHistory.contains(item); + } + return false; + } + + /** + * Sets/unsets given item as duplicate. + * + * @param item + * item to change + * + * @param isDuplicate + * duplicate flag + */ + public void setDuplicateElement(Object item, boolean isDuplicate) { + if (this.items.contains(item)) { + if (isDuplicate) + this.duplicates.add(item); + else + this.duplicates.remove(item); + } + } + + /** + * Indicates whether given item is a duplicate. + * + * @param item + * item to check + * @return <code>true</code> if item is duplicate + */ + public boolean isDuplicateElement(Object item) { + return duplicates.contains(item); + } + + /** + * Load history from memento. + * + * @param memento + * memento from which the history will be retrieved + */ + public void loadHistory(IMemento memento) { + if (this.selectionHistory != null) + this.selectionHistory.load(memento); + } + + /** + * Save history to memento. + * + * @param memento + * memento to which the history will be added + */ + public void saveHistory(IMemento memento) { + if (this.selectionHistory != null) + this.selectionHistory.save(memento); + } + + /** + * Gets sorted items. + * + * @return sorted items + */ + private Object[] getSortedItems() { + if (lastSortedItems.size() != items.size()) { + synchronized (lastSortedItems) { + lastSortedItems.clear(); + lastSortedItems.addAll(items); + Collections.sort(lastSortedItems, getHistoryComparator()); + } + } + return lastSortedItems.toArray(); + } + + /** + * Remember result of filtering. + * + * @param itemsFilter + */ + public void rememberResult(ItemsFilter itemsFilter) { + List itemsList = Collections.synchronizedList(Arrays + .asList(getSortedItems())); + // synchronization + if (itemsFilter == filter) { + lastCompletedFilter = itemsFilter; + lastCompletedResult = itemsList; + } + + } + + + public Object[] getElements(Object inputElement) { + return lastFilteredItems.toArray(); + } + + public int getNumberOfElements() { + return lastFilteredItems.size(); + } + + + public void dispose() { + } + + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + + public void updateElement(int index) { + + FilteredItemsSelectionDialog.this.list.replace((lastFilteredItems + .size() > index) ? lastFilteredItems.get(index) : null, + index); + + } + + /** + * Main method responsible for getting the filtered items and checking + * for duplicates. It is based on the + * {@link FilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}. + * + * @param checkDuplicates + * <code>true</code> if data concerning elements + * duplication should be computed - it takes much more time + * than standard filtering + * + * @param monitor + * progress monitor + */ + public void reloadCache(boolean checkDuplicates, + IProgressMonitor monitor) { + + reset = false; + + if (monitor != null) { + // the work is divided into two actions of the same length + int totalWork = checkDuplicates ? 200 : 100; + + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob, + totalWork); + } + + // the TableViewer's root (the input) is treated as parent + + lastFilteredItems = Arrays.asList(getFilteredItems(list.getInput(), + monitor != null ? new SubProgressMonitor(monitor, 100) + : null)); + + if (reset || (monitor != null && monitor.isCanceled())) { + if (monitor != null) + monitor.done(); + return; + } + + if (checkDuplicates) { + checkDuplicates(monitor); + } + if (monitor != null) + monitor.done(); + } + + private void checkDuplicates(IProgressMonitor monitor) { + synchronized (lastFilteredItems) { + IProgressMonitor subMonitor = null; + int reportEvery = lastFilteredItems.size() / 20; + if (monitor != null) { + subMonitor = new SubProgressMonitor(monitor, 100); + subMonitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates, + 5); + } + HashMap helperMap = new HashMap(); + for (int i = 0; i < lastFilteredItems.size(); i++) { + if (reset + || (subMonitor != null && subMonitor.isCanceled())) + return; + Object item = lastFilteredItems.get(i); + + if (!(item instanceof ItemsListSeparator)) { + Object previousItem = helperMap.put( + getElementName(item), item); + if (previousItem != null) { + setDuplicateElement(previousItem, true); + setDuplicateElement(item, true); + } else { + setDuplicateElement(item, false); + } + } + + if (subMonitor != null && reportEvery != 0 + && (i + 1) % reportEvery == 0) + subMonitor.worked(1); + } + helperMap.clear(); + } + } + + /** + * Returns an array of items filtered using the provided + * <code>ViewerFilter</code>s with a separator added. + * + * @param parent + * the parent + * @param monitor + * progress monitor, can be <code>null</code> + * @return an array of filtered items + */ + protected Object[] getFilteredItems(Object parent, + IProgressMonitor monitor) { + int ticks = 100; + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements, + ticks); + if (filters != null) { + ticks /= (filters.size() + 2); + } else { + ticks /= 2; + } + + // get already sorted array + Object[] filteredElements = getSortedItems(); + + monitor.worked(ticks); + + // filter the elements using provided ViewerFilters + if (filters != null && filteredElements != null) { + for (Iterator iter = filters.iterator(); iter.hasNext();) { + ViewerFilter f = (ViewerFilter) iter.next(); + filteredElements = f.filter(list, parent, filteredElements); + monitor.worked(ticks); + } + } + + if (filteredElements == null || monitor.isCanceled()) { + monitor.done(); + return new Object[0]; + } + + ArrayList preparedElements = new ArrayList(); + boolean hasHistory = false; + + if (filteredElements.length > 0) { + if (isHistoryElement(filteredElements[0])) { + hasHistory = true; + } + } + + int reportEvery = filteredElements.length / ticks; + + // add separator + for (int i = 0; i < filteredElements.length; i++) { + Object item = filteredElements[i]; + + if (hasHistory && !isHistoryElement(item)) { + preparedElements.add(itemsListSeparator); + hasHistory = false; + } + + preparedElements.add(item); + + if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) { + monitor.worked(1); + } + } + + monitor.done(); + + return preparedElements.toArray(); + } + + /** + * Adds a filter to this content provider. For an example usage of such + * filters look at the project <code>org.eclipse.ui.ide</code>, class + * <code>org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter</code>. + * + * + * @param filter + * the filter to be added + */ + public void addFilter(ViewerFilter filter) { + if (filters == null) { + filters = new ArrayList(); + } + filters.add(filter); + // currently filters are only added when dialog is restored + // if it is changed, refreshing the whole TableViewer should be + // added + } + + } + + /** + * A content provider that does nothing. + */ + private class NullContentProvider implements IContentProvider { + + + public void dispose() { + } + + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + } + + /** + * DetailsContentViewer objects are wrappers for labels. + * DetailsContentViewer provides means to change label's image and text when + * the attached LabelProvider is updated. + */ + private class DetailsContentViewer extends ContentViewer { + + private CLabel label; + + /** + * Unfortunately, it was impossible to delegate displaying border to + * label. The <code>ViewForm</code> is used because + * <code>CLabel</code> displays shadow when border is present. + */ + private ViewForm viewForm; + + /** + * Constructs a new instance of this class given its parent and a style + * value describing its behavior and appearance. + * + * @param parent + * the parent component + * @param style + * SWT style bits + */ + public DetailsContentViewer(Composite parent, int style) { + viewForm = new ViewForm(parent, style); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + viewForm.setLayoutData(gd); + label = new CLabel(viewForm, SWT.FLAT); + label.setFont(parent.getFont()); + viewForm.setContent(label); + hookControl(label); + } + + /** + * Shows/hides the content viewer. + * + * @param visible + * if the content viewer should be visible. + */ + public void setVisible(boolean visible) { + viewForm.setVisible(visible); + GridData gd = (GridData) viewForm.getLayoutData(); + gd.exclude = !visible; + viewForm.getParent().layout(); + } + + + protected void inputChanged(Object input, Object oldInput) { + if (oldInput == null) { + if (input == null) { + return; + } + refresh(); + return; + } + + refresh(); + + } + + + protected void handleLabelProviderChanged( + LabelProviderChangedEvent event) { + if (event != null) { + refresh(event.getElements()); + } + } + + + public Control getControl() { + return label; + } + + + public ISelection getSelection() { + // not supported + return null; + } + + + public void refresh() { + Object input = this.getInput(); + if (input != null) { + ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); + doRefresh(labelProvider.getText(input), labelProvider + .getImage(input)); + } else { + doRefresh(null, null); + } + } + + /** + * Sets the given text and image to the label. + * + * @param text + * the new text or null + * @param image + * the new image + */ + private void doRefresh(String text, Image image) { + if ( text != null ) { + text = LegacyActionTools.escapeMnemonics(text); + } + label.setText(text); + label.setImage(image); + } + + + public void setSelection(ISelection selection, boolean reveal) { + // not supported + } + + /** + * Refreshes the label if currently chosen element is on the list. + * + * @param objs + * list of changed object + */ + private void refresh(Object[] objs) { + if (objs == null || getInput() == null) { + return; + } + Object input = getInput(); + for (int i = 0; i < objs.length; i++) { + if (objs[i].equals(input)) { + refresh(); + break; + } + } + } + } + + /** + * Compares items according to the history. + */ + private class HistoryComparator implements Comparator { + + + public int compare(Object o1, Object o2) { + boolean h1 = isHistoryElement(o1); + boolean h2 = isHistoryElement(o2); + if (h1 == h2) + return getItemsComparator().compare(o1, o2); + + if (h1) + return -2; + if (h2) + return +2; + + return 0; + } + + } + + + /** + * Get the control where the search pattern is entered. Any filtering should + * be done using an {@link ItemsFilter}. This control should only be + * accessed for listeners that wish to handle events that do not affect + * filtering such as custom traversal. + * + * @return Control or <code>null</code> if the pattern control has not + * been created. + */ + public Control getPatternControl() { + return pattern; + } + +} diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/DeleteModelHandler.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/DeleteModelHandler.java index ecfce32fbd5c7fc8694529589a22c8023a480e30..7cbc7d74610f7ce14f360a783686bc43a4be8e7d 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/DeleteModelHandler.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/DeleteModelHandler.java @@ -76,7 +76,7 @@ public class DeleteModelHandler extends AbstractHandler implements IHandler } } - // 3.) at this point, we are save to delete all models (user has + // 3.) at this point, we are safe to delete all models (user has // confirmed, no model is used in model checking // But there might be open editors corresponding to the models which have to be closed first final Iterator<ILaunchConfiguration> itr2 = iss.iterator(); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/NewModelHandlerSelectedDelegate.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/NewModelHandlerSelectedDelegate.java index e1ad889f2eb06a3b43154eed5ee24d0bfcc48ba0..ad6e6a0ec4b1efc72cbd4c63cb308e2c0c3bb2fe 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/NewModelHandlerSelectedDelegate.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/NewModelHandlerSelectedDelegate.java @@ -1,6 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.handlers; import java.util.HashMap; +import java.util.Map; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; @@ -8,40 +34,77 @@ import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IHandler; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.ISelectionService; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.HandlerUtil; import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.tool.tlc.ui.modelexplorer.ModelContentProvider; +import org.lamport.tla.toolbox.tool.tlc.ui.modelexplorer.ModelContentProvider.Group; import org.lamport.tla.toolbox.util.UIHelper; /** * Delegates the model creation for the spec selected from the toolbox explorer - * @author Simon Zambrovski - * @version $Id$ */ public class NewModelHandlerSelectedDelegate extends AbstractHandler implements IHandler { - /* (non-Javadoc) + /* (non-Javadoc) * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent) */ - public Object execute(ExecutionEvent event) throws ExecutionException - { - /* - * Try to get the spec from active navigator if any - */ - ISelection selection = HandlerUtil.getCurrentSelectionChecked(event); - if (selection != null && selection instanceof IStructuredSelection - && ((IStructuredSelection) selection).size() == 1) - { - Object selected = ((IStructuredSelection) selection).getFirstElement(); - if (selected instanceof Spec) - { - HashMap parameters = new HashMap(); - // fill the spec name for the handler - parameters.put(NewModelHandler.PARAM_SPEC_NAME, ((Spec) selected).getName()); - // delegate the call to the new model handler - UIHelper.runCommand(NewModelHandler.COMMAND_ID, parameters); - } - } - return null; - } + public Object execute(ExecutionEvent event) throws ExecutionException { + /* + * Try to get the spec from active navigator if any + */ + final ISelection selection = HandlerUtil.getCurrentSelectionChecked(event); + if (selection != null && selection instanceof IStructuredSelection + && ((IStructuredSelection) selection).size() == 1) { + Object selected = ((IStructuredSelection) selection).getFirstElement(); + if (selected instanceof ModelContentProvider.Group) { + // Convert the group to its corresponding spec + selected = ((Group) selected).getSpec(); + } + if (selected instanceof Spec) { + final Map<String, String> parameters = new HashMap<String, String>(); + // fill the spec name for the handler + parameters.put(NewModelHandler.PARAM_SPEC_NAME, ((Spec) selected).getName()); + // delegate the call to the new model handler + UIHelper.runCommand(NewModelHandler.COMMAND_ID, parameters); + } + } + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.core.commands.AbstractHandler#isEnabled() + */ + public boolean isEnabled() { + final ISelection selection = getSelection(); + if (selection instanceof IStructuredSelection) { + final IStructuredSelection iss = (IStructuredSelection) selection; + final Object selected = iss.getFirstElement(); + if (selected instanceof Spec && ((Spec) selected).isCurrentSpec()) { + return true; + } else if (selected instanceof ModelContentProvider.Group) { + final ModelContentProvider.Group group = (Group) selected; + return group.getSpec().isCurrentSpec(); + } + } + return false; + } + + private static ISelection getSelection() { + try { + IWorkbenchWindow ww = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (ww != null) { + ISelectionService service = ww.getSelectionService(); + if (service != null) { + return service.getSelection(); + } + } + } catch(IllegalStateException e) { + return null; + } + return null; + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/StartLaunchHandler.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/StartLaunchHandler.java index f5a952851e82ca5658431eb8bef148ce9702d7be..5697a4f8b9e0eb36d69f1fda756aa410a6a1dd18 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/StartLaunchHandler.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/StartLaunchHandler.java @@ -3,8 +3,11 @@ package org.lamport.tla.toolbox.tool.tlc.handlers; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Shell; @@ -57,6 +60,19 @@ public class StartLaunchHandler extends AbstractHandler { if (save) { // TODO decouple from ui thread ref.getEditor(true).doSave(new NullProgressMonitor()); + + // Wait for the AutoBuilder to parse the spec. + // Unless we wait here, the spec might actually in + // the unparsed state by the time we try to launch + // TLC. This launch will subsequently fail + // due to the spec's unparsed state. + try { + Job.getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD, new NullProgressMonitor()); + } catch (OperationCanceledException e) { + throw new ExecutionException(e.getMessage(), e); + } catch (InterruptedException e) { + throw new ExecutionException(e.getMessage(), e); + } } else { return null; } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/TLAOpenElementSelectionDialogHandler.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/TLAOpenElementSelectionDialogHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..3fe1869a2e781850f0c4bdb0b7e58b6f00964c80 --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/handlers/TLAOpenElementSelectionDialogHandler.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.tool.tlc.handlers; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.ui.handlers.HandlerUtil; +import org.lamport.org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; +import org.lamport.tla.toolbox.spec.Module; +import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.tool.tlc.ui.dialog.TLAFilteredItemsSelectionDialog; +import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; +import org.lamport.tla.toolbox.ui.handler.OpenModuleHandler; +import org.lamport.tla.toolbox.ui.handler.OpenSpecHandler; +import org.lamport.tla.toolbox.util.UIHelper; + +public class TLAOpenElementSelectionDialogHandler extends AbstractHandler implements IHandler { + + /* (non-Javadoc) + * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent) + */ + public Object execute(final ExecutionEvent event) throws ExecutionException { + final FilteredItemsSelectionDialog dialog = new TLAFilteredItemsSelectionDialog(HandlerUtil.getActiveShell(event)); + dialog.open(); + + final Object[] result = dialog.getResult(); + if (result != null && result.length == 1) { + final Map<String, String> parameters = new HashMap<String, String>(); + if (result[0] instanceof Module && ((Module) result[0]).isRoot()) { + parameters.put(OpenSpecHandler.PARAM_SPEC, ((Module) result[0]).getModuleName()); + UIHelper.runCommand(OpenSpecHandler.COMMAND_ID, parameters); + } else if (result[0] instanceof Module) { + parameters.put(OpenModuleHandler.PARAM_MODULE, ((Module) result[0]).getModuleName()); + UIHelper.runCommand(OpenModuleHandler.COMMAND_ID, parameters); + } else if (result[0] instanceof ILaunchConfiguration) { + parameters.put(OpenModelHandler.PARAM_MODEL_NAME, + ModelHelper.getModelName(((ILaunchConfiguration) result[0]).getFile())); + UIHelper.runCommand(OpenModelHandler.COMMAND_ID, parameters); + } else if (result[0] instanceof Spec) { + parameters.put(OpenSpecHandler.PARAM_SPEC, ((Spec) result[0]).getName()); + UIHelper.runCommand(OpenSpecHandler.COMMAND_ID, parameters); + } + } + return null; + } +} diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/LogFileReader.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/LogFileReader.java index 672591750cc89789ce09a4af860d3300587e022b..7539b9b2b274a35508417a1a1318a6e49e376546 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/LogFileReader.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/LogFileReader.java @@ -1,80 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; + import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.ui.editors.text.FileDocumentProvider; -import org.eclipse.ui.part.FileEditorInput; import org.lamport.tla.toolbox.tool.tlc.output.source.ITLCOutputSource; import org.lamport.tla.toolbox.tool.tlc.output.source.TagBasedTLCOutputIncrementalParser; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; /** * Reads the log file and parses the content + * * @author Simon Zambrovski - * @version $Id$ */ -public class LogFileReader -{ - private TagBasedTLCOutputIncrementalParser parser; - private IFile logFile; +public class LogFileReader { + + private final TagBasedTLCOutputIncrementalParser parser; + private final File logFile; - public LogFileReader(String name, IFile logFile, boolean isTraceExplorerLogFile) - { - this.logFile = logFile; - this.parser = new TagBasedTLCOutputIncrementalParser(name, ITLCOutputSource.PRIO_LOW, isTraceExplorerLogFile); - } + public LogFileReader(String name, IFile aLogFile, boolean isTraceExplorerLogFile) { + this.logFile = new File(aLogFile.getLocation().toOSString()); + this.parser = new TagBasedTLCOutputIncrementalParser(name, ITLCOutputSource.PRIO_LOW, isTraceExplorerLogFile, + TagBasedTLCOutputIncrementalParser.Mode.BATCH, this.logFile.length()); + } /** - * Reads the contents - */ - public void read() - { - FileEditorInput fileEditorInput = new FileEditorInput(logFile); - FileDocumentProvider fileDocumentProvider = new FileDocumentProvider(); - try - { - fileDocumentProvider.connect(fileEditorInput); - IDocument document = fileDocumentProvider.getDocument(fileEditorInput); - /* - * When the output file is somewhat large (several hundred KB) - * the toolbox hangs due to the parser inefficiently handling multiple lines at once. - * If the entire document representing that file - * is passed to the parser at once. We can send in pieces of it - * to solve this problem. - * - * We send in one line at a time. Note that IDocument lines - * are 0-based. - */ - for (int lineNum = 0; lineNum < document.getNumberOfLines(); lineNum++) - { - String line = document.get(document.getLineOffset(lineNum), document.getLineLength(lineNum)); - this.parser.addIncrement(line); - } - this.parser.done(); - } catch (CoreException e) - { - TLCUIActivator.getDefault().logError("Error accessing the TLC log file contents", e); - } catch (BadLocationException e) - { - TLCUIActivator.getDefault().logError("Error positioning in the TLC log file", e); - } finally - { - /* - * The document provider is not needed. Always disconnect it to avoid a memory leak. - * - * Keeping it connected only seems to provide synchronization of - * the document with file changes. That is not necessary in this context. - */ - fileDocumentProvider.disconnect(fileEditorInput); - } + * Reads the contents + */ + public void read(final IProgressMonitor monitor) throws IOException, BadLocationException { + BufferedReader reader = null; + try { + final long numberOfLines = getLineNumbers(); + + // I'm looking forward to the bug report where the int cast is + // relevant. + monitor.beginTask("Opening logfile "+logFile.getCanonicalPath(), (int) numberOfLines); + + reader = new BufferedReader(new FileReader(logFile)); + /* + * When the output file is somewhat large (several hundred KB) the + * toolbox hangs due to the parser inefficiently handling multiple + * lines at once. If the entire document representing that file is + * passed to the parser at once. We can send in pieces of it to + * solve this problem. + * + * We send in one line at a time. Note that IDocument lines are + * 0-based. + */ + for (int lineNum = 0; lineNum < numberOfLines; lineNum++) { + if (monitor.isCanceled()) { + this.parser.clear(); + return; + } + if (lineNum % 1000 == 0) { + monitor.worked(1000); + } + this.parser.addLine(reader.readLine().concat("\n")); + } + this.parser.done(); + monitor.worked(1); + } catch (BadLocationException e) { + TLCUIActivator.getDefault().logError("Error positioning in the TLC log file", e); + throw e; + } catch (FileNotFoundException e) { + TLCUIActivator.getDefault().logError("Error accessing the TLC log file contents", e); + throw e; + } catch (IOException e) { + TLCUIActivator.getDefault().logError("Error reading the TLC log file contents", e); + throw e; + } finally { + /* + * The document provider is not needed. Always disconnect it to + * avoid a memory leak. + * + * Keeping it connected only seems to provide synchronization of the + * document with file changes. That is not necessary in this + * context. + */ + monitor.done(); + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + TLCUIActivator.getDefault().logError("Error closing the TLC log file contents", e); + } + } + } + } - } + public long getLineNumbers() throws IOException { + final LineNumberReader reader = new LineNumberReader(new FileReader(logFile)); + reader.skip(Long.MAX_VALUE); + final int numberOfLines = reader.getLineNumber(); + reader.close(); + return numberOfLines; + } - public ITLCOutputSource getSource() - { - return this.parser.getSource(); - } + public ITLCOutputSource getSource() { + return this.parser.getSource(); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/StateSpaceInformationItem.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/StateSpaceInformationItem.java index 089c37b90f81bd61015827d40cf6523f5afb61a9..6bf6864af57d34d91dca68c23f2b286fc1a499c4 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/StateSpaceInformationItem.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/StateSpaceInformationItem.java @@ -1,5 +1,6 @@ package org.lamport.tla.toolbox.tool.tlc.output.data; +import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -24,8 +25,13 @@ public class StateSpaceInformationItem private long leftStates; private long spm; private long distinctSPM; + /** + * True if this is the most recent/up-to-date SSII holding the newest + * values. + */ + private boolean isMostRecent = true; - /** + /** * @param time * @param diameter * @param foundStates @@ -47,6 +53,20 @@ public class StateSpaceInformationItem this.distinctSPM = distinctSPM; } + /** + * @return the isMostRecent + */ + public boolean isMostRecent() { + return isMostRecent; + } + + /** + * @param isMostRecent the isMostRecent to set + */ + public void setMostRecent(boolean isMostRecent) { + this.isMostRecent = isMostRecent; + } + public final Date getTime() { return time; } @@ -136,17 +156,19 @@ public class StateSpaceInformationItem final Date time = SDF.parse(outputMessage.substring( i[1] + AT.length(), i[2])); + final NumberFormat nf = NumberFormat.getNumberInstance(); + final long diameter = Long.parseLong(outputMessage.substring(i[0] + OB.length(), i[1])); final long foundStates = Long.parseLong(outputMessage.substring( i[2] + COLON.length(), i[3])); - final long statesPerMinute = Long.parseLong(outputMessage - .substring(i[3] + GENERATED.length(), i[4]).replace(",", "")); + final long statesPerMinute = nf.parse(outputMessage + .substring(i[3] + GENERATED.length(), i[4]).replace(",", "")).longValue(); final long distinctStates = Long.parseLong(outputMessage.substring( i[4] + SPM.length(), i[5])); - final long distinctStatesPerMinute = Long.parseLong(outputMessage - .substring(i[5] + DISTINCT.length(), i[6]).replace(",", "")); + final long distinctStatesPerMinute = nf.parse(outputMessage + .substring(i[5] + DISTINCT.length(), i[6]).replace(",", "")).longValue(); final long leftStates = Long.parseLong(outputMessage.substring(i[6] + DISTINCT_SPM.length(), i[7])); @@ -209,4 +231,39 @@ public class StateSpaceInformationItem public final static SimpleDateFormat SDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (diameter ^ (diameter >>> 32)); + result = prime * result + (int) (distinctStates ^ (distinctStates >>> 32)); + result = prime * result + (int) (foundStates ^ (foundStates >>> 32)); + result = prime * result + (int) (leftStates ^ (leftStates >>> 32)); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + StateSpaceInformationItem other = (StateSpaceInformationItem) obj; + if (diameter != other.diameter) + return false; + if (distinctStates != other.distinctStates) + return false; + if (foundStates != other.foundStates) + return false; + if (leftStates != other.leftStates) + return false; + return true; + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCError.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCError.java index 6e7d6b326c2f7e8ba7ab01d0276b6edecc9bb02e..2d83d3e86b430d865d445214703189c4b246cbee 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCError.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCError.java @@ -1,37 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; +import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * Representation of the TLC error * @author Simon Zambrovski - * @version $Id$ */ public class TLCError { + public enum Length { + ALL, RESTRICTED; + } + private String message = ""; - private List<TLCState> states; + private LinkedList<TLCState> states = new LinkedList<TLCState>(); private TLCError cause; private int errorCode; + private int numberOfStatesToShow = Integer.MAX_VALUE; // no restriction by default - public TLCError() - { - + public void addState(TLCState state) { + addState(state, true); } /** * Add a state to a trace * @param state state to add */ - public void addState(TLCState state) + public void addState(TLCState state, boolean stateSortDirection) { - if (states == null) - { - states = new LinkedList<TLCState>(); + if (stateSortDirection) { + states.addFirst(state); + } else { + states.add(state); } - - states.add(state); } /** @@ -53,10 +82,94 @@ public class TLCError return message; } - public final List<TLCState> getStates() - { - return states; - } + public void restrictTraceTo(int numberOfStatesToShow) { + this.numberOfStatesToShow = numberOfStatesToShow; + } + + public boolean isTraceRestricted() { + return this.states.size() > this.numberOfStatesToShow; + } + + /** + * @return The amount of trace states currently hidden because of the + * restriction. + */ + public int getNumberOfRestrictedTraceStates() { + if (isTraceRestricted()) { + return this.states.size() - this.numberOfStatesToShow; + } else { + return 0; + } + } + + public int getTraceRestriction() { + return this.numberOfStatesToShow; + } + + public void reduceTraceRestrictionBy(int numberOfStatesToShow) { + this.numberOfStatesToShow += numberOfStatesToShow; + } + + public int getTraceSize(final int level) { + if (states.isEmpty()) { + return 0; + } + + TLCState representative = this.states.getFirst(); + if (representative.isInitialState()) { + representative = states.getLast(); + } + final int varCnt = representative.getVariableCount(level); + + if (numberOfStatesToShow < states.size()) { + return numberOfStatesToShow * (varCnt + 1); + } + return states.size() * (varCnt + 1); + } + + public int getTraceSize() { + return getTraceSize(0); + } + + public final List<TLCState> getStates() { + return getStates(Length.RESTRICTED); + } + + /** + * Returns the {@link TLCState}s that represent the trace of this + * {@link TLCError}. If {@link Length#ALL} is given, all {@link TLCState}s + * are returned regardless of any restriction imposed. Length.RESTRICTED + * returns the subList of states according to the restriction set. + * + * @param l + * @return + */ + public final List<TLCState> getStates(Length l) { + if (l == Length.ALL) { + return states; + } + if (states.size() > numberOfStatesToShow) { + // If only a sublist is requested, the current order of the list has + // to be taken into account. Otherwise, reversing the tree's sort + // order with a subList input will alternate between the states' + // head and tail. + // I.e. the tree starts with the state's tail. The user then changes + // the sort order which triggers a call to getStates again. However, + // because the underlying states list had been reversed in the + // meantime, it now returns the head. + if (states.getFirst().isInitialState()) { + // If the first state is the initial state, the list is sorted in + // ascending order. Thus, return the states' tail. Otherwise, head. + return states.subList(states.size() - numberOfStatesToShow, states.size()); + } + return states.subList(0, numberOfStatesToShow); + } + return states; + } + + public boolean isTraceEmpty() { + return numberOfStatesToShow == 0 || states.isEmpty(); + } public final int getErrorCode() { @@ -78,4 +191,7 @@ public class TLCError return this.states != null && !this.states.isEmpty(); } + public void reverseTrace() { + Collections.reverse(states); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFcnElementVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFcnElementVariableValue.java index 036fdb5eb84e93de7c64869d13f108a12927faa6..e4ddebbcb4a56ad3a2165bf77e12cee144794ef6 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFcnElementVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFcnElementVariableValue.java @@ -1,37 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Leslie Lamport - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; /** * Represents a pair of TLCVariableValue objects; used to hold individual - * mappings of a function--the individual ordered pairs if a function were - * a set of ordered pairs. The object O represents O.from @@ O.value + * mappings of a function--the individual ordered pairs if a function were a set + * of ordered pairs. The object O represents O.from @@ O.value + * * @author Leslie Lamport - * @version $Id$ */ -public class TLCFcnElementVariableValue extends TLCVariableValue -{ - protected TLCVariableValue from; +public class TLCFcnElementVariableValue extends TLCVariableValue { + protected final TLCVariableValue from; - public TLCVariableValue getFrom() - { + public TLCVariableValue getFrom() { return from; } - /** - * - */ - public TLCFcnElementVariableValue(TLCVariableValue fromVal, TLCVariableValue toVal) - { + public TLCFcnElementVariableValue(TLCVariableValue fromVal, TLCVariableValue toVal) { from = fromVal; value = toVal; } - public String toString() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toString() + */ + public String toString() { return from.toString() + " :> " + value.toString(); } - public String toSimpleString() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toSimpleString() + */ + public String toSimpleString() { return from.toSimpleString() + " :> " + ((TLCVariableValue) value).toSimpleString(); } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFunctionVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFunctionVariableValue.java index 592b0495782776b88f2f45a8bc754eae744cde47..cfd126e681107c97d42233b31a00ea60820dd0c6 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFunctionVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCFunctionVariableValue.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Leslie Lamport - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.List; @@ -6,38 +32,65 @@ import java.util.List; * Represents a function as a list of TLCFcnElementVariable objects * * @author Leslie Lamport - * @version $Id$ */ -public class TLCFunctionVariableValue extends TLCVariableValue -{ +public class TLCFunctionVariableValue extends TLCVariableValue implements TLCMultiVariableValue { private static final String[] DELIMETERS = { "(", " @@", ")" }; - private TLCFcnElementVariableValue[] elements; + private final TLCFcnElementVariableValue[] elements; + /** * @param fcnElements */ - public TLCFunctionVariableValue(List fcnElements) - { - this.value = fcnElements; - this.elements = innerGetFcnElements(); - } - - public Object getValue() - { - return elements; - } - - public TLCFcnElementVariableValue[] getFcnElements() { - return elements; - } - public TLCFcnElementVariableValue[] innerGetFcnElements() - { - return (TLCFcnElementVariableValue[]) ((List) this.value) - .toArray(new TLCFcnElementVariableValue[((List) this.value).size()]); - } - - public String toSimpleString() - { - return arrayToSimpleStringBuffer(getFcnElements(), DELIMETERS).toString(); - } + public TLCFunctionVariableValue(List fcnElements) { + this.value = fcnElements; + this.elements = innerGetFcnElements(); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#getValue() + */ + public Object getValue() { + return elements; + } + + public TLCFcnElementVariableValue[] getFcnElements() { + return elements; + } + + public TLCFcnElementVariableValue[] innerGetFcnElements() { + return (TLCFcnElementVariableValue[]) ((List) this.value) + .toArray(new TLCFcnElementVariableValue[((List) this.value).size()]); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toSimpleString() + */ + public String toSimpleString() { + return arrayToSimpleStringBuffer(getFcnElements(), DELIMETERS).toString(); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCMultiVariableValue#asList() + */ + public List<TLCVariableValue> asList() { + return (List<TLCVariableValue>) this.value; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#innerDiff(org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue) + */ + protected void innerDiff(TLCVariableValue other) { + /* + * FUNCTIONS We mark a record element as added or deleted if its + * label does not appear in one of the elements of the other record. + * We mark the element as changed, and call setInnerDiffInfo on the + * elements' values if elements with same label but different values + * appear in the two records. + */ + if (!(other instanceof TLCFunctionVariableValue)) { + return; + } + + setFcnElementArrayDiffInfo(this.elements, ((TLCFunctionVariableValue) other).elements); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCModelLaunchDataProvider.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCModelLaunchDataProvider.java index 8940fe708374169418d57bda1ae6d7e1287f8577..a341af774823ba75412134885347627a542435b2 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCModelLaunchDataProvider.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCModelLaunchDataProvider.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.ArrayList; @@ -13,15 +39,21 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationListener; +import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.DocumentRewriteSession; +import org.eclipse.jface.text.DocumentRewriteSessionType; import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.ui.editors.text.FileDocumentProvider; import org.eclipse.ui.part.FileEditorInput; +import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants; import org.lamport.tla.toolbox.tool.tlc.model.Formula; import org.lamport.tla.toolbox.tool.tlc.output.ITLCOutputListener; @@ -41,10 +73,11 @@ import tlc2.output.MP; /** * Container for the data about the model launch * @author Simon Zambrovski - * @version $Id$ */ -public class TLCModelLaunchDataProvider implements ITLCOutputListener +public class TLCModelLaunchDataProvider implements ITLCOutputListener, ILaunchConfigurationListener { + public static final String STATESORTORDER = "STATESORTORDER"; + public static final String NO_OUTPUT_AVAILABLE = "No user output is available"; public static final String NO_ERRORS = "No errors"; @@ -54,6 +87,7 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener public static final String RECOVERING = "Recovering from checkpoint"; public static final String COMPUTING_REACHABLE = "Computing reachable states"; public static final String CHECKPOINTING = "Checkpointing"; + public static final String CHECKING_LIVENESS_FINAL = "Checking final liveness"; public static final String CHECKING_LIVENESS = "Checking liveness"; public static final String SERVER_RUNNING = "Master waiting for workers"; public static final String SINGLE_WORKER_REGISTERED = " worker registered"; @@ -96,6 +130,10 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener protected Document userOutput; // calc output protected String constantExprEvalOutput; + /** + * Sort order in which states are sorted in the variable viewer + */ + private boolean stateSortDirection; // the model, which is represented by the current launch data provider private ILaunchConfiguration config; @@ -126,6 +164,7 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener public TLCModelLaunchDataProvider(ILaunchConfiguration config) { this.config = config; + // init provider, but not connect it to the source! initialize(); @@ -159,6 +198,11 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener userOutput = new Document(NO_OUTPUT_AVAILABLE); constantExprEvalOutput = ""; + final IDialogSettings dialogSettings = Activator.getDefault().getDialogSettings(); + stateSortDirection = dialogSettings.getBoolean(STATESORTORDER); + + // Register as a LCL to cache the LaunchConfig's attributes upon save/commit. + DebugPlugin.getDefault().getLaunchManager().addLaunchConfigurationListener(this); } /** @@ -249,7 +293,7 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener case MP.STATE: Assert.isNotNull(this.lastDetectedError, "The state encountered without the error describing the reason for it. This is a bug."); - this.lastDetectedError.addState(TLCState.parseState(outputMessage, getModelName())); + this.lastDetectedError.addState(TLCState.parseState(outputMessage, getModelName()), stateSortDirection); break; case MP.ERROR: case MP.TLCBUG: @@ -291,6 +335,7 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener this.lastDetectedError = null; } + // Order of case statements matters. There are no "break" statements because - by default - it should go to the document. switch (messageCode) { // Progress information case EC.TLC_VERSION: @@ -338,11 +383,17 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener setDocumentText(this.progressOutput, outputMessage, true); break; case EC.TLC_CHECKING_TEMPORAL_PROPS: - if (outputMessage.indexOf("complete") != 1) - { - this.setCurrentStatus(CHECKING_LIVENESS); - informPresenter(ITLCModelLaunchDataPresenter.CURRENT_STATUS); + if (outputMessage.contains("complete")) { + this.setCurrentStatus(CHECKING_LIVENESS_FINAL); + } else { + this.setCurrentStatus(CHECKING_LIVENESS); } + informPresenter(ITLCModelLaunchDataPresenter.CURRENT_STATUS); + setDocumentText(this.progressOutput, outputMessage, true); + break; + case EC.TLC_CHECKING_TEMPORAL_PROPS_END: + this.setCurrentStatus(COMPUTING_REACHABLE); + informPresenter(ITLCModelLaunchDataPresenter.CURRENT_STATUS); setDocumentText(this.progressOutput, outputMessage, true); break; case EC.TLC_CHECKPOINT_RECOVER_START: @@ -372,8 +423,9 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener case EC.TLC_PROGRESS_STATS_DFID: case EC.TLC_PROGRESS_SIMU: case EC.TLC_PROGRESS_STATS: - this.progressInformation.add(0, StateSpaceInformationItem.parse(outputMessage)); - informPresenter(ITLCModelLaunchDataPresenter.PROGRESS); + if (addOrReplaceProgressInformation(StateSpaceInformationItem.parse(outputMessage))) { + informPresenter(ITLCModelLaunchDataPresenter.PROGRESS); + } break; // Coverage information case EC.TLC_COVERAGE_START: @@ -488,10 +540,36 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener } /** + * @param latest + * @return true iff the presenter should be updated + */ + private boolean addOrReplaceProgressInformation(final StateSpaceInformationItem latest) { + if (!this.progressInformation.isEmpty()) { + final StateSpaceInformationItem head = this.progressInformation.get(0); + if (head.equals(latest)) { + this.progressInformation.set(0, latest); + return false; + } else { + this.progressInformation.add(0, latest); + if (this.progressInformation.size() > 1) { + // Set the predecessor to not be the most recent + // progress information. + this.progressInformation.get(1).setMostRecent(false); + } + return true; + } + } else { + this.progressInformation.add(latest); + return true; + } + } + + /** * Destroy and disconnect */ public void destroy() { + DebugPlugin.getDefault().getLaunchManager().removeLaunchConfigurationListener(this); TLCOutputSourceRegistry.getModelCheckSourceRegistry().disconnect(this); } @@ -603,7 +681,8 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener // some attributes are lists if (ModelHelper.isListAttribute(attributeName)) { - List<String> attributeValue = (List<String>) config.getAttribute(attributeName, new ArrayList<String>()); + final List<String> attributeValue = (List<String>) config + .getAttribute(attributeName, new ArrayList<String>(0)); int attributeNumber = (attributeIndex != null) ? attributeIndex.intValue() : 0; if (IModelConfigurationConstants.MODEL_PARAMETER_CONSTANTS.equals(attributeName) @@ -743,6 +822,9 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener return topError; } + private static final String CR = "\n"; + private static final String EMPTY = ""; + /** * Sets text to a document * @param document @@ -751,42 +833,40 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener * @throws BadLocationException * Has to be run from non-UI thread */ - public static synchronized void setDocumentText(final IDocument document, final String message, final boolean append) - { - final String CR = "\n"; - final String EMPTY = ""; - - UIHelper.runUIAsync(new Runnable() { - - public void run() - { - try - { - if (append) - { - if (document.getLength() == NO_OUTPUT_AVAILABLE.length()) - { - String content = document.get(0, NO_OUTPUT_AVAILABLE.length()); - if (content != null && NO_OUTPUT_AVAILABLE.equals(content)) - { - document.replace(0, document.getLength(), message - + ((message.endsWith(CR)) ? EMPTY : CR)); - } - } else - { - document.replace(document.getLength(), 0, message + ((message.endsWith(CR)) ? EMPTY : CR)); - } - } else - { - document.replace(0, document.getLength(), message + ((message.endsWith(CR)) ? EMPTY : CR)); - } - } catch (BadLocationException e) - { - - } - } - }); - } + public static synchronized void setDocumentText(final Document document, final String message, + final boolean append) { + + UIHelper.runUIAsync(new Runnable() { + public void run() { + try { + DocumentRewriteSession rewriteSession; + if (append && !isDefaultLabel(document)) { + rewriteSession = document.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL); + // append to existing document (0 length is valid and means message is going to be appended) + document.replace(document.getLength(), 0, message + ((message.endsWith(CR)) ? EMPTY : CR)); + } else { + rewriteSession = document.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL); + // replace of complete document + document.replace(0, document.getLength(), message + ((message.endsWith(CR)) ? EMPTY : CR)); + } + document.stopRewriteSession(rewriteSession); + } catch (BadLocationException ignored) { + } + } + }); + } + + /** + * @param document + * @return true iff the current content of the document is + * {@link TLCModelLaunchDataProvider#NO_OUTPUT_AVAILABLE} + * @throws BadLocationException + */ + private static boolean isDefaultLabel(final IDocument document) throws BadLocationException { + return document.getLength() == NO_OUTPUT_AVAILABLE.length() + && document.get(0, NO_OUTPUT_AVAILABLE.length()) != null + && NO_OUTPUT_AVAILABLE.equals(document.get(0, NO_OUTPUT_AVAILABLE.length())); + } /** * Connects this provider to the tlc output source registry. @@ -906,6 +986,11 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener return currentStatus; } + public boolean isDone() { + assert currentStatus.equals("Not running"); + return isDone; + } + /** * @param fingerprintCollisionProbability the fingerprintCollisionProbability to set */ @@ -971,4 +1056,29 @@ public class TLCModelLaunchDataProvider implements ITLCOutputListener // file (e.g. unit test) return ModelHelper.getModelName(getConfig().getFile()); } + + /* org.eclipse.debug.core.ILaunchConfigurationListener */ + + /* (non-Javadoc) + * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationAdded(org.eclipse.debug.core.ILaunchConfiguration) + */ + public void launchConfigurationAdded(final ILaunchConfiguration configuration) { + // Ignore + } + + /* (non-Javadoc) + * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationChanged(org.eclipse.debug.core.ILaunchConfiguration) + */ + public void launchConfigurationChanged(final ILaunchConfiguration configuration) { + // The moment the launch config is saved/committed, keep it. + this.config = configuration; + } + + /* (non-Javadoc) + * @see org.eclipse.debug.core.ILaunchConfigurationListener#launchConfigurationRemoved(org.eclipse.debug.core.ILaunchConfiguration) + */ + public void launchConfigurationRemoved(final ILaunchConfiguration configuration) { + // Ignore, should never happen during model checking because the ILC is locked. + this.config = null; + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCMultiVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCMultiVariableValue.java new file mode 100644 index 0000000000000000000000000000000000000000..2d11db82df069992223e69aaeb0275f6c2c1bc6e --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCMultiVariableValue.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.tool.tlc.output.data; + +import java.util.List; + +public interface TLCMultiVariableValue { + + List<TLCVariableValue> asList(); + +} diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCNamedVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCNamedVariableValue.java index 0e86868da09215cf21be48f4e58046d3457b5b83..d889320094071b0ebe26e7f0eddf4674591a0901 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCNamedVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCNamedVariableValue.java @@ -1,33 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; /** * Represents named values + * * @author Simon Zambrovski - * @version $Id$ */ -public class TLCNamedVariableValue extends TLCVariableValue -{ +public class TLCNamedVariableValue extends TLCVariableValue { private String name; - TLCNamedVariableValue(String name, TLCVariableValue value) - { + TLCNamedVariableValue(String name, TLCVariableValue value) { this.name = name; this.value = value; } - public String getName() - { + public String getName() { return name; } - public String toString() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toString() + */ + public String toString() { return this.name + " |-> " + value.toString(); } - public String toSimpleString() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toSimpleString() + */ + public String toSimpleString() { return this.name + " |-> " + ( (TLCVariableValue) value).toSimpleString(); } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCRecordVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCRecordVariableValue.java index 9626e2cb46e21bc61c7a25d75d22907380ed0a38..566c4ca3a403f358a2ae9557ad723bc64715945d 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCRecordVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCRecordVariableValue.java @@ -1,37 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.List; /** * @author Simon Zambrovski - * @version $Id$ */ -public class TLCRecordVariableValue extends TLCVariableValue -{ +public class TLCRecordVariableValue extends TLCVariableValue implements TLCMultiVariableValue { private static final String[] DELIMETERS = { "[", ",", "]" }; /** * @param recordPairs */ - public TLCRecordVariableValue(List recordPairs) - { + public TLCRecordVariableValue(List<TLCVariableValue> recordPairs) { this.value = recordPairs; } - public TLCNamedVariableValue[] getPairs() - { - return (TLCNamedVariableValue[]) ((List) this.value).toArray(new TLCNamedVariableValue[((List) this.value) - .size()]); - } + public TLCNamedVariableValue[] getPairs() { + return (TLCNamedVariableValue[]) ((List<TLCNamedVariableValue>) this.value) + .toArray(new TLCNamedVariableValue[((List<TLCNamedVariableValue>) this.value).size()]); + } - public Object getValue() - { - return getPairs(); - } + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#getValue() + */ + public Object getValue() { + return getPairs(); + } - public String toSimpleString() - { - return arrayToSimpleStringBuffer(getPairs(), DELIMETERS).toString(); - } + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toSimpleString() + */ + public String toSimpleString() { + return arrayToSimpleStringBuffer(getPairs(), DELIMETERS).toString(); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCMultiVariableValue#asList() + */ + public List<TLCVariableValue> asList() { + return (List<TLCVariableValue>) this.value; + } + + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#innerDiff(org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue) + */ + protected void innerDiff(TLCVariableValue other) { + /* + * RECORDS We mark a record element as added or deleted if its label + * does not appear in one of the elements of the other record. We + * mark the element as changed, and call setInnerDiffInfo on the + * elements' values if elements with same label but different values + * appear in the two records. + */ + if (!(other instanceof TLCRecordVariableValue)) { + return; + } + TLCVariableValue[] firstElts = this.getPairs(); + TLCVariableValue[] secondElts = ((TLCRecordVariableValue) other).getPairs(); + + String[] firstLHStrings = new String[firstElts.length]; + for (int i = 0; i < firstElts.length; i++) { + firstLHStrings[i] = ((TLCNamedVariableValue) firstElts[i]).getName(); + } + String[] secondLHStrings = new String[secondElts.length]; + for (int i = 0; i < secondElts.length; i++) { + secondLHStrings[i] = ((TLCNamedVariableValue) secondElts[i]).getName(); + } + + setElementArrayDiffInfo(firstElts, firstLHStrings, secondElts, secondLHStrings); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSequenceVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSequenceVariableValue.java index 82693d7830378d1deeeb7582e84a0f88eae00a6d..727aed0db9a2b1e2a71c3641a0af0dd2828d159f 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSequenceVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSequenceVariableValue.java @@ -1,9 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.List; -public class TLCSequenceVariableValue extends TLCVariableValue -{ +public class TLCSequenceVariableValue extends TLCVariableValue implements TLCMultiVariableValue { private static final String[] DELIMETERS = { "<<", ",", ">>" }; @@ -66,4 +91,78 @@ public class TLCSequenceVariableValue extends TLCVariableValue return arrayToSimpleStringBuffer(elementValues, DELIMETERS).toString(); } + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCMultiVariableValue#asList() + */ + public List<TLCVariableValue> asList() { + return (List<TLCVariableValue>) this.value; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#innerDiff(org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue) + */ + protected void innerDiff(TLCVariableValue other) { + /* + * SEQUENCES In general, it's not clear how differences between two + * sequences should be highlighted. We adopt the following + * preliminary approach: If one sequence is a proper initial prefix + * or suffix of the other, then the difference is interpreted as + * adding or deleting the appropriate sequence elements. Otherwise, + * the sequences are treated as functions. + * + * Note: If one sequence is both an initial prefix and a suffix of + * the other then we give preference to interpreting the operation + * as adding to the end or removing from the front. + */ + if (!(other instanceof TLCSequenceVariableValue)) { + return; + } + TLCFcnElementVariableValue[] firstElts = this.getElements(); + TLCFcnElementVariableValue[] secondElts = ((TLCSequenceVariableValue) other).getElements(); + if (firstElts.length == secondElts.length) { + setFcnElementArrayDiffInfo(firstElts, secondElts); + return; + } + + TLCFcnElementVariableValue[] shorter = firstElts; + TLCFcnElementVariableValue[] longer = secondElts; + boolean firstShorter = true; + if (firstElts.length > secondElts.length) { + longer = firstElts; + shorter = secondElts; + firstShorter = false; + } + boolean isPrefix = true; + for (int i = 0; i < shorter.length; i++) { + if (!((TLCVariableValue) shorter[i].getValue()).toSimpleString() + .equals(((TLCVariableValue) longer[i].getValue()).toSimpleString())) { + isPrefix = false; + break; + } + } + boolean isSuffix = true; + for (int i = 0; i < shorter.length; i++) { + if (!((TLCVariableValue) shorter[i].getValue()).toSimpleString().equals( + ((TLCVariableValue) longer[i + longer.length - shorter.length].getValue()).toSimpleString())) { + + isSuffix = false; + break; + } + } + /* + * If it's both a prefix and a suffix, we interpret the change as + * either adding to the end or deleting from the front. If it's + * neither, we treat the sequences as functions. + */ + if (isPrefix && isSuffix) { + if (firstShorter) { + isSuffix = false; + } else { + isPrefix = false; + } + } else if (!(isPrefix || isSuffix)) { + setFcnElementArrayDiffInfo(firstElts, secondElts); + return; + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSetVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSetVariableValue.java index 5324e1310143fb63e101e629a6714c5f63199c1f..c9ef9847f635baa239c4929874ac10d2c0246eff 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSetVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSetVariableValue.java @@ -1,32 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.List; -public class TLCSetVariableValue extends TLCVariableValue -{ +public class TLCSetVariableValue extends TLCVariableValue implements TLCMultiVariableValue { private static final String[] DELIMETERS = { "{", ",", "}" }; - TLCSetVariableValue(List values) - { + TLCSetVariableValue(List values) { this.value = values; - } - public Object getValue() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#getValue() + */ + public Object getValue() { return getElements(); } - public TLCVariableValue[] getElements() - { - List list = (List) value; - return (TLCVariableValue[]) list.toArray(new TLCVariableValue[list.size()]); - } + public TLCVariableValue[] getElements() { + List list = (List) value; + return (TLCVariableValue[]) list.toArray(new TLCVariableValue[list.size()]); + } - public String toSimpleString() - { - TLCVariableValue[] elements = getElements(); - return arrayToSimpleStringBuffer(elements, DELIMETERS).toString(); - } + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toSimpleString() + */ + public String toSimpleString() { + TLCVariableValue[] elements = getElements(); + return arrayToSimpleStringBuffer(elements, DELIMETERS).toString(); + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCMultiVariableValue#asList() + */ + public List<TLCVariableValue> asList() { + return (List<TLCVariableValue>) this.value; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#innerDiff(org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue) + */ + protected void innerDiff(TLCVariableValue other) { + /* + * SETS For two sets, the only meaningful changes are additions and + * deletions. + */ + if (!(other instanceof TLCSetVariableValue)) { + return; + } + TLCVariableValue[] firstElts = this.getElements(); + TLCVariableValue[] secondElts = ((TLCSetVariableValue) other).getElements(); + + for (int i = 0; i < firstElts.length; i++) { + boolean notfound = true; + int j = 0; + while (notfound && j < secondElts.length) { + if (firstElts[i].toSimpleString().equals(secondElts[j].toSimpleString())) { + notfound = false; + } + j++; + } + if (notfound) { + firstElts[i].setDeleted(); + } + } + + for (int i = 0; i < secondElts.length; i++) { + boolean notfound = true; + int j = 0; + while (notfound && j < firstElts.length) { + if (firstElts[j].toSimpleString().equals(secondElts[i].toSimpleString())) { + notfound = false; + } + j++; + } + if (notfound) { + secondElts[i].setAdded(); + } + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSimpleVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSimpleVariableValue.java index a19772d8581f5097d7fd51f1dfb3d43113ef7df3..c6fdfffb5c26c6c3c5ab1b6e3257733c66d64f50 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSimpleVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCSimpleVariableValue.java @@ -1,19 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; /** * Represents simple values + * * @author Simon Zambrovski - * @version $Id$ */ -public class TLCSimpleVariableValue extends TLCVariableValue -{ - protected TLCSimpleVariableValue(Object value) - { +public class TLCSimpleVariableValue extends TLCVariableValue { + protected TLCSimpleVariableValue(Object value) { this.value = value; } - public String toString() - { + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.output.data.TLCVariableValue#toString() + */ + public String toString() { return (String) this.value; } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCState.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCState.java index 98d994969bc0211cb04101374b0185cd9a821fb6..e7101ab7bb7b11b3edfe0ea1d58d9018f22c7363 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCState.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCState.java @@ -1,8 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; -import java.util.Vector; import org.lamport.tla.toolbox.tool.tlc.ui.util.IModuleLocatable; @@ -11,7 +38,6 @@ import tla2sany.st.Location; /** * Representation of the TLC state * @author Simon Zambrovski - * @version $Id$ */ public class TLCState implements IModuleLocatable { @@ -70,7 +96,10 @@ public class TLCState implements IModuleLocatable return STUTTERING_STATE(number, modelName); } else if (label.indexOf(BACK_TO_STATE) != -1) { - return BACK_TO_STATE(number, modelName); + final TLCState state = BACK_TO_STATE(number, modelName); + // See in MP.java case for EC.TLC_BACK_TO_STATE + state.setLocation(Location.parseLocation(label.substring(" Back to State: ".length(), label.length()))); + return state; } else { TLCState state = new TLCState(number, modelName); @@ -87,10 +116,9 @@ public class TLCState implements IModuleLocatable * @param variablesText * @return */ - private static TLCVariable[] parseVariables(String variablesText) - { + private static List<TLCVariable> parseVariables(String variablesText) { String[] lines = variablesText.split(CR); - Vector vars = new Vector(); + List<TLCVariable> vars = new ArrayList<TLCVariable>(); int index; // buffer for accumulating the state variable @@ -138,7 +166,22 @@ public class TLCState implements IModuleLocatable vars.add(var); } - return (TLCVariable[]) vars.toArray(new TLCVariable[vars.size()]); + Collections.sort(vars, new Comparator<TLCVariable>() { + public int compare(TLCVariable v1, TLCVariable v2) { + if (v1.isTraceExplorerVar() && v2.isTraceExplorerVar()) { + // both are variables. Compare the vars alphabetically. + return v1.getName().compareTo(v2.getName()); + } + if (v1.isTraceExplorerVar()) { + return -1; + } + if (v2.isTraceExplorerVar()) { + return 1; + } + return v1.getName().compareTo(v2.getName()); + } + }); + return vars; } private int number; @@ -146,7 +189,7 @@ public class TLCState implements IModuleLocatable private boolean isBackToState = false; private String label; private String variablesAsString; - private TLCVariable[] variables = new TLCVariable[0]; + private List<TLCVariable> variables = new ArrayList<TLCVariable>(0); /** * Contains the location of the action * which caused this state @@ -156,7 +199,8 @@ public class TLCState implements IModuleLocatable * The name of the model for which this * is a state. */ - private String modelName; + private final String modelName; + private boolean wasDiffed= false; /** * @@ -178,6 +222,10 @@ public class TLCState implements IModuleLocatable { return isBackToState; } + + public boolean isInitialState() { + return number == 1; + } public int getStateNumber() { @@ -194,14 +242,12 @@ public class TLCState implements IModuleLocatable this.label = label; } - public final List getVariablesAsList() - { - return Arrays.asList(variables); - } + public final List<TLCVariable> getVariablesAsList() { + return this.variables; + } - public final TLCVariable[] getVariables() - { - return variables; + public final TLCVariable[] getVariables() { + return variables.toArray(new TLCVariable[variables.size()]); } public String toString() @@ -243,9 +289,8 @@ public class TLCState implements IModuleLocatable * does. */ StringBuffer result = new StringBuffer(); - for (int i = 0; i < variables.length; i++) - { - TLCVariable var = variables[i]; + for (int i = 0; i < variables.size(); i++) { + TLCVariable var = variables.get(i); result.append("/\\ "); if (var.isTraceExplorerVar()) { @@ -275,4 +320,35 @@ public class TLCState implements IModuleLocatable { return modelName; } + + /** + * Set the data structures that cause highlighting of changes in the + * error trace. + */ + public void diff(final TLCState other) { + if (this == other || wasDiffed || other.isStuttering() || other.isBackToState()) { + // there are no variables in other + // because it is a stuttering or a back to state + // step + return; + } + wasDiffed = true; + final List<TLCVariable> predecessorVariables = this.getVariablesAsList(); + final List<TLCVariable> secondVariables = other.getVariablesAsList(); + for (int i = 0; i < predecessorVariables.size(); i++) { + final TLCVariableValue firstValue = predecessorVariables.get(i).getValue(); + final TLCVariableValue secondValue = secondVariables.get(i).getValue(); + firstValue.diff(secondValue); + } + } + + public int getVariableCount(int level) { + if (level > 1) { + throw new UnsupportedOperationException("not yet implemented"); + } + if (level == 1) { + return this.variables.size(); + } + return 0; + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariable.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariable.java index 061e6bd5bfd52fb227bac43106270af4d7aed267..19b52756f091da24327f7a6248ea610b6ae52663 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariable.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariable.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; public class TLCVariable @@ -74,5 +100,12 @@ public class TLCVariable { this.isTraceExplorerVar = isTraceExplorerVar; } - + + public int getChildCount() { + return value.getChildCount(); + } + + public final boolean isChanged() { + return value.isAdded() || value.isDeleted() || value.isChanged(); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariableValue.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariableValue.java index fb03f7dea252d6dcaccf178a009c3994cc4c2cb9..5b24b31f52c2a418d22d088cec6dd57c37734cc6 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariableValue.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TLCVariableValue.java @@ -1,3 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.data; import java.util.List; @@ -10,7 +36,6 @@ import org.eclipse.core.runtime.Assert; /** * A representation of a variable value * @author Simon Zambrovski - * @version $Id$ */ public abstract class TLCVariableValue { @@ -74,12 +99,16 @@ public abstract class TLCVariableValue { Assert.isNotNull(delimeters); - StringBuffer buffer = new StringBuffer(delimeters[0]); + StringBuffer buffer; if (elements.length == 0) { + buffer = new StringBuffer(3); + buffer.append(delimeters[0]); buffer.append(SPACE); } else { + buffer = new StringBuffer((elements.length * 3) + 2); + buffer.append(delimeters[0]); for (int i = 0; i < elements.length; i++) { buffer.append(elements[i].toString()); @@ -98,12 +127,16 @@ public abstract class TLCVariableValue { Assert.isNotNull(delimeters); - StringBuffer buffer = new StringBuffer(delimeters[0]); + StringBuffer buffer = null; if (elements.length == 0) { + buffer = new StringBuffer(3); + buffer.append(delimeters[0]); buffer.append(SPACE); } else { + buffer = new StringBuffer((elements.length * 3) + 2); + buffer.append(delimeters[0]); for (int i = 0; i < elements.length; i++) { if (elements[i] instanceof TLCVariableValue) @@ -151,7 +184,7 @@ public abstract class TLCVariableValue throw new VariableValueParseException(); } - List sequenceValues = new Vector(); + List<TLCVariableValue> sequenceValues = new Vector<TLCVariableValue>(); innerValue = innerParse(input); if (innerValue != null) { @@ -180,7 +213,7 @@ public abstract class TLCVariableValue return null; case OBRACKET: - List recordPairs = new Vector(); + List<TLCVariableValue> recordPairs = new Vector<TLCVariableValue>(); TLCVariableValue innerValue2; @@ -252,7 +285,7 @@ public abstract class TLCVariableValue case CBRACKET: return null; case OPAREN: - List fcnElements = new Vector(); + List<TLCVariableValue> fcnElements = new Vector<TLCVariableValue>(); TLCVariableValue domElement; @@ -319,7 +352,7 @@ public abstract class TLCVariableValue throw new VariableValueParseException(); case OCBRACE: - List setValues = new Vector(); + List<TLCVariableValue> setValues = new Vector<TLCVariableValue>(); innerValue = innerParse(input); if (innerValue != null) { @@ -419,6 +452,8 @@ public abstract class TLCVariableValue * TLCNamedVariableValue objects--and for TLCSimpleVariableValue * objects. */ protected String source = null; + + private short diffState = 0; public Object getValue() { @@ -444,6 +479,173 @@ public abstract class TLCVariableValue return value.toString(); } + public int getChildCount() { + if (this.value instanceof List) { + return ((List) this.value).size(); + } else if (this.value instanceof TLCVariableValue) { + return ((TLCVariableValue) this.value).getChildCount(); + } + return 0; + } + + /* + * Here are the three states that contain the objects + * representing rows in the table displaying the trace that should be + * highlighted. They have the following meanings: + * + * changed: Rows indicating values that have changed from the last + * state. Subobjects of the value column of such a row could also be + * highlighted. + * + * added: Rows that have been added to a value since the last state. + * + * deleted: Rows that are deleted in the following state. + * + * The same row can appear in both the deleted and the + * changed or added. In that case, it should be displayed as + * a changed or added row--since we can't do multicolored backgrounds to + * show that it is both. + */ + protected void setDeleted() { + diffState |= (1 << 2); + } + + /** + * @return true iff this value has been delete compared to the predecessor + * state's value in the error trace. + */ + public boolean isDeleted() { + return ((diffState>>2) & 1) != 0; + } + + protected void setAdded() { + diffState |= (1 << 1); + } + + /** + * @return true iff this value has been added compared to the predecessor + * state's value in the error trace. + */ + public boolean isAdded() { + return ((diffState>>1) & 1) != 0; + } + + protected void setChanged() { + diffState |= (1 << 0); + } + + /** + * @return true iff this value is different to the predecessor state's value + * in the error trace. + */ + public boolean isChanged() { + return ((diffState>>0) & 1) != 0; + } + + /** + * The recursive method called by innerDiff that diffs the subobjects of + * the variable value objects to indicate which rows of + * the hierarchical trace table should be highlighted to show the parts of + * the state that have changed. + */ + public void diff(TLCVariableValue other) { + if (!this.toSimpleString().equals(other.toSimpleString())) { + other.setChanged(); + if (this.getClass().equals(other.getClass())) { + // Only diff objects of identical types + innerDiff(other); + } + } + } + + protected void innerDiff(TLCVariableValue other) { + // nothing to be done generally, subclass may override + return; + } + + /** + * A method that sets the diff highlighting information for two arrays of + * either TLCFcnElementVariableValue or TLCNamedVariableValue objects, + * representing the value elements of twos values represented by + * TLCFunctionVariableValue, TLCRecordVariableValue, or + * TLCSequenceVariableValue objects. The parameters firstElts and secondElts + * are the two arrays, and firstLHStrings and secondLHStrings are the + * results of applying the toString or toSimpleString method to their first + * elements. In plain math, this means that we are doing a diff on two + * functions (possibly two records or two sequences) where the ...Strings + * arrays are string representations of the domain elements of each of the + * function elements. + * + * The HashSet arguments are the sets of element objects that are to be + * highlighted in the appropriate fashion. + * + * We mark a function element as added or deleted if its left-hand value + * does not appear in one of the elements of the other function. We mark the + * element as changed, and call setInnerDiffInfo on the elements' values if + * elements with the same left-hand values having different values appear in + * the two records. + */ + protected void setElementArrayDiffInfo(TLCVariableValue[] firstElts, String[] firstLHStrings, + TLCVariableValue[] secondElts, String[] secondLHStrings) { + + for (int i = 0; i < firstElts.length; i++) { + boolean notfound = true; + int j = 0; + while (notfound && j < secondElts.length) { + if (firstLHStrings[i].equals(secondLHStrings[j])) { + notfound = false; + TLCVariableValue first = (TLCVariableValue) firstElts[i].getValue(); + TLCVariableValue second = (TLCVariableValue) secondElts[j].getValue(); + if (!first.toSimpleString().equals(second.toSimpleString())) { + second.setChanged(); + if (first.getClass().equals(second.getClass())) { + // Only diff objects of identical types + first.innerDiff(second); + } + } + } + j++; + } + if (notfound) { + firstElts[i].setDeleted(); + } + } + + for (int i = 0; i < secondElts.length; i++) { + boolean notfound = true; + int j = 0; + while (notfound && j < firstElts.length) { + if (firstElts[j].toSimpleString().equals(secondElts[i].toSimpleString())) { + notfound = false; + } + j++; + } + if (notfound) { + secondElts[i].setAdded(); + } + } + } + + /** + * A method that sets the diff highlighting information for two arrays of + * TLCFcnElementVariableValue objects. The parameters firstElts and + * secondElts are the two arrays.In plain math, this means that we are doing + * a diff on two functions (possibly two sequences). This method calls + * setElementArrayDiffInfo to do the work. + */ + protected void setFcnElementArrayDiffInfo(TLCFcnElementVariableValue[] firstElts, + TLCFcnElementVariableValue[] secondElts) { + final String[] firstLHStrings = new String[firstElts.length]; + for (int i = 0; i < firstElts.length; i++) { + firstLHStrings[i] = firstElts[i].getFrom().toSimpleString(); + } + final String[] secondLHStrings = new String[secondElts.length]; + for (int i = 0; i < secondElts.length; i++) { + secondLHStrings[i] = secondElts[i].getFrom().toSimpleString(); + } + setElementArrayDiffInfo(firstElts, firstLHStrings, secondElts, secondLHStrings); + } + static class InputPair { String input; diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TraceExplorerDataProvider.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TraceExplorerDataProvider.java index ea5f6694ed7616a8c0fc1f5c3bf9660c9d0c3753..711b7b84852b9a2306d023de8fcf95269ceae8b4 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TraceExplorerDataProvider.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/data/TraceExplorerDataProvider.java @@ -20,6 +20,7 @@ import org.eclipse.jface.text.ITypedRegion; import org.eclipse.ui.editors.text.FileDocumentProvider; import org.eclipse.ui.part.FileEditorInput; import org.lamport.tla.toolbox.tool.tlc.launch.TraceExpressionInformationHolder; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError.Length; import org.lamport.tla.toolbox.tool.tlc.output.source.TLCOutputSourceRegistry; import org.lamport.tla.toolbox.tool.tlc.output.source.TLCRegion; import org.lamport.tla.toolbox.tool.tlc.output.source.TLCRegionContainer; @@ -38,7 +39,6 @@ import tlc2.output.EC; * of an error trace, if one is produced. * * @author Daniel Ricketts - * */ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider { @@ -48,7 +48,7 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider // the key is the variable name used for the expression, the value // is an instance of TraceExpressionInformationHolder corresponding // to the expression. - private Hashtable traceExpressionDataTable; + private Hashtable<String, TraceExpressionInformationHolder> traceExpressionDataTable; private static String TE_ERROR_HEADER = "Error(s) from running the Trace Explorer:\n"; public TraceExplorerDataProvider(ILaunchConfiguration config) @@ -91,14 +91,14 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider */ private void getTraceExpressionsInformation() { + if (traceExpressionDataTable == null) { + // getTraceExpressionsInformation is called implicitly when + // super(config) is called in the constructor. Thus, we have to + // initialize it here. + traceExpressionDataTable = new Hashtable<String, TraceExpressionInformationHolder>(); + } // erase existing information - if (traceExpressionDataTable == null) - { - traceExpressionDataTable = new Hashtable(); - } else - { - traceExpressionDataTable.clear(); - } + traceExpressionDataTable.clear(); /* * Retrieve the TE file and create a document provider. This document @@ -289,7 +289,7 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider private void processTraceForTraceExplorer() { // retrieve the error with a trace for which the trace explorer was run - TLCError originalErrorWithTrace = TraceExplorerHelper.getErrorOfOriginalTrace(getConfig()); + final TLCError originalErrorWithTrace = TraceExplorerHelper.getErrorOfOriginalTrace(getConfig()); if (originalErrorWithTrace == null) { // the trace explorer is meaningless if the original trace cannot be recovered @@ -312,14 +312,14 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider // retrieve the original trace // this is necessary for items (3) and (5) from the list in the // documentation for this method - List originalTrace = originalErrorWithTrace.getStates(); + List<TLCState> originalTrace = originalErrorWithTrace.getStates(Length.ALL); Assert.isNotNull(originalTrace, "Could not get original trace after running trace explorer. This is a bug."); // iterate through the errors to find one with a trace - Iterator it = getErrors().iterator(); + Iterator<TLCError> it = getErrors().iterator(); while (it.hasNext()) { - TLCError error = (TLCError) it.next(); + TLCError error = it.next(); if (error.hasTrace()) { @@ -342,16 +342,14 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider { error.setMessage(TE_ERROR_HEADER + error.getMessage()); } - + // a comparator used for sorting the variables within each // state so that the variables representing the trace explorer // expressions appear first in each state - Comparator varComparator = new Comparator() { + final Comparator<TLCVariable> varComparator = new Comparator<TLCVariable>() { - public int compare(Object arg0, Object arg1) + public int compare(TLCVariable var0, TLCVariable var1) { - TLCVariable var0 = (TLCVariable) arg0; - TLCVariable var1 = (TLCVariable) arg1; if ((var0.isTraceExplorerVar() && var1.isTraceExplorerVar()) || (!var0.isTraceExplorerVar() && !var1.isTraceExplorerVar())) { @@ -376,15 +374,15 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider // this is the trace produced by the run // of TLC for the trace explorer - List newTrace = error.getStates(); + List<TLCState> newTrace = error.getStates(); - Iterator newTraceIt = newTrace.iterator(); - Iterator originalTraceIt = originalTrace.iterator(); + Iterator<TLCState> newTraceIt = newTrace.iterator(); + Iterator<TLCState> originalTraceIt = originalTrace.iterator(); - TLCState currentStateNewTrace = (TLCState) newTraceIt.next(); + TLCState currentStateNewTrace = newTraceIt.next(); TLCState nextStateNewTrace = null; - TLCState currentStateOriginalTrace = (TLCState) originalTraceIt.next(); + TLCState currentStateOriginalTrace = originalTraceIt.next(); /* * The following while loop performs items 1-4 for some of the states. @@ -433,7 +431,7 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider // not represent a trace explorer expression // If the variable does represent a trace explorer expression, then the following // object will contain the variable name, the expression, and the level of the expression - TraceExpressionInformationHolder traceExpressionData = (TraceExpressionInformationHolder) traceExpressionDataTable + TraceExpressionInformationHolder traceExpressionData = traceExpressionDataTable .get(variableName.trim()); if (traceExpressionData != null) @@ -467,7 +465,7 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider currentStateNewTrace = nextStateNewTrace; - currentStateOriginalTrace = (TLCState) originalTraceIt.next(); + currentStateOriginalTrace = originalTraceIt.next(); } /* @@ -491,6 +489,8 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider newTrace.subList(originalTrace.size() - 1, newTrace.size()).clear(); } + error.restrictTraceTo(originalErrorWithTrace.getTraceRestriction()); + // new trace should now be no longer than the original trace Assert.isTrue(newTrace.size() <= originalTrace.size(), "States from trace explorer trace not removed properly."); @@ -529,7 +529,7 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider for (int i = 0; i < finalStateNewTraceVariables.length; i++) { - TraceExpressionInformationHolder traceExpressionData = (TraceExpressionInformationHolder) traceExpressionDataTable + TraceExpressionInformationHolder traceExpressionData = traceExpressionDataTable .get(finalStateNewTraceVariables[i].getName().trim()); if (traceExpressionData != null) @@ -576,13 +576,13 @@ public class TraceExplorerDataProvider extends TLCModelLaunchDataProvider */ if (successfulTEError != null) { - List originalErrors = TLCOutputSourceRegistry.getModelCheckSourceRegistry().getProvider(getConfig()) + List<TLCError> originalErrors = TLCOutputSourceRegistry.getModelCheckSourceRegistry().getProvider(getConfig()) .getErrors(); - List newErrors = new LinkedList(); - Iterator iterator = originalErrors.iterator(); + List<TLCError> newErrors = new LinkedList<TLCError>(); + Iterator<TLCError> iterator = originalErrors.iterator(); while (iterator.hasNext()) { - Object next = iterator.next(); + TLCError next = iterator.next(); if (next == originalErrorWithTrace) { newErrors.add(successfulTEError); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TLCOutputSourceRegistry.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TLCOutputSourceRegistry.java index ecee7dfc948a8d1891b58e9af0d3273a18533355..84fbd870d6cd9ae393b18b299704eca33e41df6f 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TLCOutputSourceRegistry.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TLCOutputSourceRegistry.java @@ -1,11 +1,21 @@ package org.lamport.tla.toolbox.tool.tlc.output.source; +import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.ui.progress.IProgressConstants; +import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.tool.tlc.output.ITLCOutputListener; import org.lamport.tla.toolbox.tool.tlc.output.LogFileReader; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCModelLaunchDataProvider; @@ -160,14 +170,30 @@ public class TLCOutputSourceRegistry // initialize the reader and read the content // this will create the parser // the parser will create a source and register in the registry - LogFileReader logFileReader = new LogFileReader(processName, logFile, isTraceExploreInstance); + final LogFileReader logFileReader = new LogFileReader(processName, logFile, isTraceExploreInstance); // retrieve the source source = logFileReader.getSource(); source.addTLCOutputListener(listener); // read in the data - logFileReader.read(); + // read in the data + final Job job = new WorkspaceJob("Logfile reader...") { + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + try { + logFileReader.read(monitor); + } catch (BadLocationException e) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage()); + } catch (IOException e) { + return new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage()); + } + return Status.OK_STATUS; + } + }; + job.setProperty(IProgressConstants.PROPERTY_IN_DIALOG, true); + job.setPriority(Job.LONG); + job.setUser(true); + job.schedule(); // from now on we should have a source for this model Assert.isTrue(this.sources.get(processName) != null); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TagBasedTLCOutputIncrementalParser.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TagBasedTLCOutputIncrementalParser.java index a8ecd4ea4f257132b98c82a22ec10511e780c735..4c678ad1ab72cb98dfd2a50613c013f5b7dcbad2 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TagBasedTLCOutputIncrementalParser.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/output/source/TagBasedTLCOutputIncrementalParser.java @@ -1,14 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.output.source; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.DocumentPartitioningChangedEvent; +import org.eclipse.jface.text.DocumentRewriteSessionType; +import org.eclipse.jface.text.GapTextStore; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.IDocumentPartitioningListener; import org.eclipse.jface.text.IDocumentPartitioningListenerExtension2; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextStore; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.rules.FastPartitioner; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; @@ -46,10 +75,23 @@ import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; * does the work in analyzing the partitions. See that method for the implementation. * * @author Simon Zambrovski - * @version $Id$ */ public class TagBasedTLCOutputIncrementalParser { + + /** + * In batch mode, all lines are added to the document before the parser + * begins its work (see TLCOutputPartitionChangeListener). This is i.e. + * useful where an existing log file is processed. Conversely, incremental + * mode parses each line immediately when added via addIncrement/addLine. + * This mode should be used when {@link TagBasedTLCOutputIncrementalParser} + * is attached to a process sink/running TLC model checker and is supposed + * to show its progress. + */ + public enum Mode { + BATCH, INCREMENTAL; + } + /** * The offset of the end of the last * start or end tag seen by the previous run of @@ -66,6 +108,15 @@ public class TagBasedTLCOutputIncrementalParser class TLCOutputPartitionChangeListener implements IDocumentPartitioningListener, IDocumentPartitioningListenerExtension2 { + private final Mode mode; + + public TLCOutputPartitionChangeListener(Mode mode) { + this.mode = mode; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.text.IDocumentPartitioningListener#documentPartitioningChanged(org.eclipse.jface.text.IDocument) + */ public void documentPartitioningChanged(IDocument document) { } @@ -245,8 +296,15 @@ public class TagBasedTLCOutputIncrementalParser } // Step 6 - if (offsetToRemove > 0) + if (mode == Mode.INCREMENTAL && offsetToRemove > 0) { + // if mode is BATCH, this call would just replace the + // Documents complete contents wrapped in several + // categories. Since we are going to throw the Document away + // anyway, there is no point in removing categories. + // Removing categories takes a long time because the + // implementation isn't meant for a huge number of + // categories. document.replace(0, offsetToRemove, ""); } } catch (BadLocationException e) @@ -263,10 +321,14 @@ public class TagBasedTLCOutputIncrementalParser * @param prio * @param isTraceExplorer TODO */ - public TagBasedTLCOutputIncrementalParser(String name, int prio, boolean isTraceExplorer) + public TagBasedTLCOutputIncrementalParser(String name, int prio, boolean isTraceExplorer) { + this(name, prio, isTraceExplorer, Mode.INCREMENTAL, LargeTextStoreDocument.SIZE_UNKNOWN); + } + + public TagBasedTLCOutputIncrementalParser(String name, int prio, boolean isTraceExplorer, Mode mode, final long size) { - // create the document - document = new Document(); + // create the document + document = new LargeTextStoreDocument(size); this.analyzer = new TagBasedTLCAnalyzer(document); this.source = new CachingTLCOutputSource(name, prio); @@ -279,7 +341,7 @@ public class TagBasedTLCOutputIncrementalParser // now register the listener, responsible for evaluating the partitioning information - document.addDocumentPartitioningListener(new TLCOutputPartitionChangeListener()); + document.addDocumentPartitioningListener(new TLCOutputPartitionChangeListener(mode)); /* * Register the process source @@ -293,6 +355,17 @@ public class TagBasedTLCOutputIncrementalParser TLCOutputSourceRegistry.getTraceExploreSourceRegistry().addTLCOutputSource(this.source); } else { + if (mode == Mode.BATCH) { + // TLC always appends to the document. Therefore, we can tell the + // document to use a more efficient rewrite mode which reduces the time + // taken to execute replace operations from minutes and hours to + // seconds. + // Its down side is that the ResultPage is only updated when the + // complete log file is fully parsed, whereas in incremental + // mode, it (potentially) updates after each line. + document.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL); + } + TLCOutputSourceRegistry.getModelCheckSourceRegistry().addTLCOutputSource(this.source); } } @@ -323,6 +396,21 @@ public class TagBasedTLCOutputIncrementalParser document.replace(document.getLength(), 0, text); } + + /** + * Contrary to addIncrement(String), this method does not verify that the + * input terminates with the newline separator. It is up to the caller to + * only provide valid input. + */ + public void addLine(String text) throws BadLocationException + { + // don't waste time, skip empty or new lines + if (text == null || text.length() == 0 || text.equals("\n")) { + return; + } + + document.replace(document.getLength(), 0, text); + } IDocument getDocument() { return document; @@ -337,6 +425,9 @@ public class TagBasedTLCOutputIncrementalParser */ public void done() { + if (this.document.getActiveRewriteSession() != null) { + this.document.stopRewriteSession(this.document.getActiveRewriteSession()); + } this.source.onDone(); } @@ -348,4 +439,42 @@ public class TagBasedTLCOutputIncrementalParser return this.source; } + public void clear() throws BadLocationException { + this.document.replace(0, this.document.getLength(), ""); + if (this.document.getActiveRewriteSession() != null) { + this.document.stopRewriteSession(this.document.getActiveRewriteSession()); + } + } + + /** + * {@link Document} targets source code documents of a few thousand lines. + * It quickly becomes inefficient for large inputs like e.g. a TLC log file + * can be. + * <p> + * {@link LargeTextStoreDocument} thus drastically increases the min and max + * sizes of the {@link Document}'s underlying {@link ITextStore}. + * Experiments revealed that this optimization significantly increases the + * {@link TagBasedTLCOutputIncrementalParser} performance. + * <p> + * As we know the size of the log file a-prior, the {@link ITextStore}'s + * internal storage can be sized up-front to match the input's size. + * <p> + * Generally though, it's stupid to keep the entire log file in memory. The + * *incremental* parser should rather discard seen content after processing + * to free memory right away. However, this is larger refactoring. + */ + private class LargeTextStoreDocument extends Document { + + public static final long SIZE_UNKNOWN = -1; + + public LargeTextStoreDocument(long size) { + if (size != SIZE_UNKNOWN) { + Assert.isLegal(size >= 0, "Negative file size"); + if (size > Integer.MAX_VALUE) { + size = Integer.MAX_VALUE - 1; + } + setTextStore(new GapTextStore((int) size, (int) size + 1, 0.1f)); + } + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerComposite.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerComposite.java index e692a10183436adfea88aadb7b16e84e6660d4e1..a2cfc811c71ed9297a878ee038d191f0174a8186 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerComposite.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerComposite.java @@ -1,14 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Dan Rickett - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.tool.tlc.traceexplorer; -import java.util.List; import java.util.Vector; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.jface.dialogs.MessageDialog; @@ -71,9 +98,6 @@ import org.lamport.tla.toolbox.util.UIHelper; * {@link TraceExplorerComposite#doExplore()} is called when the user clicks the explore button. * * {@link TraceExplorerComposite#doRestore()} is called when the user clicks the explore button. - * - * @author drickett - * */ public class TraceExplorerComposite { @@ -331,7 +355,8 @@ public class TraceExplorerComposite // add a formula if (formula != null) { - Vector input = ((Vector) tableViewer.getInput()); + @SuppressWarnings("unchecked") + Vector<Formula> input = ((Vector<Formula>) tableViewer.getInput()); input.add(formula); tableViewer.setInput(input); if (tableViewer instanceof CheckboxTableViewer) @@ -445,25 +470,27 @@ public class TraceExplorerComposite final ILaunchConfigurationWorkingCopy workingCopy = modelConfig.getWorkingCopy(); - List trace = view.getTrace(); - // if the trace is empty, then do nothing - if (trace.size() > 0) + if (!view.getTrace().isTraceEmpty()) { // TraceExplorerHelper.serializeTrace(modelConfig); - // Wrap the launch in a WorkspaceRunnable to guarantee that the + // Wrap the launch in a WorkspaceJob to guarantee that the // operation is executed atomically from the workspace perspective. - // If the runnable would be omitted, the launch can become interleaved with + // If the job and rule would be omitted, the launch can become interleaved with // workspace (autobuild) jobs triggered by IResourceChange events. // The Toolbox's IResourceChangeListeners reacting to resource change events // run the SANY parser and SANY does not support concurrent execution. - final IWorkspace workspace = ResourcesPlugin.getWorkspace(); - workspace.run(new IWorkspaceRunnable() { - public void run(IProgressMonitor monitor) throws CoreException { + final Job job = new WorkspaceJob("Exploring the trace...") { + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { workingCopy.doSave().launch(TraceExplorerDelegate.MODE_TRACE_EXPLORE, monitor, true); + return Status.OK_STATUS; } - }, new NullProgressMonitor()); + }; + // Lock the entire workspace. + job.setRule(ResourcesPlugin.getWorkspace().getRoot()); + job.setUser(true); + job.schedule(); } } catch (CoreException e) @@ -508,7 +535,7 @@ public class TraceExplorerComposite } if (buttonExplore != null) { - buttonExplore.setEnabled(view.getTrace() != null && view.getTrace().size() > 0 + buttonExplore.setEnabled(view.getTrace() != null && !view.getTrace().isTraceEmpty() && tableViewer.getCheckedElements().length > 0); } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerHelper.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerHelper.java index d7262fd7bfcb2be23d1ec3fa0e21f5c20cc2ba97..136d572bbc28ff6f82a3241630cb74c87165e3ae 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerHelper.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/traceexplorer/TraceExplorerHelper.java @@ -14,6 +14,7 @@ import org.eclipse.debug.core.ILaunchConfiguration; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCModelLaunchDataProvider; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCState; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError.Length; import org.lamport.tla.toolbox.tool.tlc.output.source.TLCOutputSourceRegistry; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; @@ -44,13 +45,13 @@ public class TraceExplorerHelper */ TLCModelLaunchDataProvider originalTraceProvider = TLCOutputSourceRegistry.getModelCheckSourceRegistry() .getProvider(config); - List errors = originalTraceProvider.getErrors(); + List<TLCError> errors = originalTraceProvider.getErrors(); if (errors != null) { - Iterator it = errors.iterator(); + Iterator<TLCError> it = errors.iterator(); while (it.hasNext()) { - TLCError error = (TLCError) it.next(); + TLCError error = it.next(); if (error.hasTrace()) { return error; @@ -70,9 +71,9 @@ public class TraceExplorerHelper { try { - List trace = getErrorOfOriginalTrace(config).getStates(); + List<TLCState> trace = getErrorOfOriginalTrace(config).getStates(Length.ALL); Assert.isNotNull(trace); - Iterator it = trace.iterator(); + Iterator<TLCState> it = trace.iterator(); IFile traceSourceFile = ModelHelper.getTraceSourceFile(config); ModelHelper.createOrClearFiles(new IFile[] { traceSourceFile }, new NullProgressMonitor()); while (it.hasNext()) diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/FilteredDefinitionSelectionDialog.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/FilteredDefinitionSelectionDialog.java index dec5fd1c27aff900b7cf4eaacf669f129911aab8..84c51a15cea5d167852c523b7591bb3323a59e2b 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/FilteredDefinitionSelectionDialog.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/FilteredDefinitionSelectionDialog.java @@ -1,8 +1,6 @@ package org.lamport.tla.toolbox.tool.tlc.ui.dialog; import java.util.Comparator; -import java.util.List; -import java.util.Vector; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; @@ -18,7 +16,6 @@ import org.eclipse.ui.IMemento; import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; import org.lamport.tla.toolbox.tool.ToolboxHandle; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; -import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; import tla2sany.modanalyzer.SpecObj; import tla2sany.semantic.ModuleNode; @@ -210,15 +207,12 @@ public class FilteredDefinitionSelectionDialog extends FilteredItemsSelectionDia /* (non-Javadoc) * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#getItemsComparator() */ - protected Comparator getItemsComparator() + protected Comparator<OpDefNode> getItemsComparator() { - return new Comparator() { + return new Comparator<OpDefNode>() { // group by modules, sort by user modules first, then by module name and then then by operator name - public int compare(Object arg0, Object arg1) + public int compare(OpDefNode node0, OpDefNode node1) { - OpDefNode node0 = (OpDefNode) arg0; - OpDefNode node1 = (OpDefNode) arg1; - ModuleNode module0 = node0.getOriginallyDefinedInModuleNode(); ModuleNode module1 = node1.getOriginallyDefinedInModuleNode(); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/TLAFilteredItemsSelectionDialog.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/TLAFilteredItemsSelectionDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..940b43607797e3695c74cf58fe3a641ea5105c1b --- /dev/null +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/dialog/TLAFilteredItemsSelectionDialog.java @@ -0,0 +1,510 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.tool.tlc.ui.dialog; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE.SharedImages; +import org.eclipse.ui.navigator.IDescriptionProvider; +import org.lamport.org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; +import org.lamport.tla.toolbox.Activator; +import org.lamport.tla.toolbox.spec.Module; +import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants; +import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; +import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; + +public class TLAFilteredItemsSelectionDialog extends FilteredItemsSelectionDialog { + + private static final String SHOW_CONSTANTS = "ShowConstants"; //$NON-NLS-1$ + + private static final String SHOW_CLOSED_SPECS = "ShowClosedSpecs"; //$NON-NLS-1$ + + private static final String SASH_RATIO_TOP = "SashRatioTop"; //$NON-NLS-1$ + + private static final String SASH_RATIO_BOTTOM = "SashRatioBottom"; //$NON-NLS-1$ + + private static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + private static final Image ModelImage = TLCUIActivator.getImageDescriptor("/icons/full/choice_sc_obj.gif") + .createImage(); + + private final ItemsListSeparator modulesSep = new ItemsListSeparator("Modules"); //$NON-NLS-1$ + + private final ItemsListSeparator specsSep = new ItemsListSeparator("Closed Specs"); //$NON-NLS-1$ + + private final ToggleShowAction toggleShowConstantsAction = new ToggleShowAction("Show spec constants", getDialogSettings().getBoolean(SHOW_CONSTANTS)); + + private final ToggleShowAction toggleShowSpecAction = new ToggleShowAction("Show closed specs", getDialogSettings().getBoolean(SHOW_CLOSED_SPECS)); + + private SourceViewer sourceViewer; + + private SashForm sashForm; + + public TLAFilteredItemsSelectionDialog(final Shell shell) { + super(shell, false); + setInitialPattern("?"); // https://bugs.eclipse.org/308689 + setListLabelProvider(new TableLabelProvider()); + setDetailsLabelProvider(new DetailsLabelProvider()); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.Dialog#getInitialSize() + */ + protected Point getInitialSize() { + final Point defaultSize = super.getInitialSize(); + if (defaultSize.x == 500 && defaultSize.y == 600) { + // Increase the parents default size + defaultSize.x = (int) Math.round(defaultSize.x * 1.9); // x has room to align with the default spec line-wrapping + defaultSize.y = (int) Math.round(defaultSize.y * 1.75); + } + return defaultSize; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.SelectionDialog#isResizable() + */ + protected boolean isResizable() { + return true; + } + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.tool.tlc.ui.dialog.FilteredItemsSelectionDialog#createContentCoposite(org.eclipse.swt.widgets.Composite) + */ + protected Composite createContentComposite(final Composite parent) { + sashForm = new SashForm(parent, SWT.VERTICAL); + sashForm.setLayoutData(new GridData(GridData.FILL_BOTH)); + return sashForm; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#createExtendedContentArea(org.eclipse.swt.widgets.Composite) + */ + protected Control createExtendedContentArea(final Composite parent) { + + final Composite content = new Composite(parent, SWT.BORDER); + + final GridData layoutData = new GridData(GridData.FILL_HORIZONTAL); + layoutData.heightHint = 200; + content.setLayoutData(layoutData); + + final GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginWidth = 0; + layout.marginHeight = 0; + content.setLayout(layout); + + sourceViewer = new SourceViewer(content, null, SWT.WRAP | SWT.V_SCROLL); + sourceViewer.getTextWidget().setLayoutData(new GridData(GridData.FILL_BOTH)); + sourceViewer.getTextWidget().setWordWrap(true); + sourceViewer.getTextWidget().setEditable(false); + sourceViewer.getTextWidget().setFont(TLCUIActivator.getDefault().getCourierFont()); + + // The weights can only be set *after* its nested controls (content + // above) are added. Thus it's done here instead of createContentControl(..). + final int top = getDialogSettings().get(SASH_RATIO_TOP) == null ? 75 + : getDialogSettings().getInt(SASH_RATIO_TOP); + final int bottom = getDialogSettings().get(SASH_RATIO_BOTTOM) == null ? 25 + : getDialogSettings().getInt(SASH_RATIO_BOTTOM); + sashForm.setWeights(new int[] { top, bottom }); + + return content; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#getDialogSettings() + */ + protected IDialogSettings getDialogSettings() { + final IDialogSettings settings = Activator.getDefault().getDialogSettings(); + if (settings.get(SHOW_CONSTANTS) == null) { + // Show constants by default + settings.put(SHOW_CONSTANTS, true); + } + return settings; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#storeDialog(org.eclipse.jface.dialogs.IDialogSettings) + */ + protected void storeDialog(IDialogSettings settings) { + settings.put(SHOW_CONSTANTS, toggleShowConstantsAction.isChecked()); + settings.put(SHOW_CLOSED_SPECS, toggleShowSpecAction.isChecked()); + settings.put(SASH_RATIO_TOP, sashForm.getWeights()[0]); + settings.put(SASH_RATIO_BOTTOM, sashForm.getWeights()[1]); + super.storeDialog(settings); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#fillViewMenu(org.eclipse.jface.action.IMenuManager) + */ + protected void fillViewMenu(IMenuManager menuManager) { + menuManager.add(toggleShowConstantsAction); + menuManager.add(toggleShowSpecAction); + super.fillViewMenu(menuManager); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#validateItem(java.lang.Object) + */ + protected IStatus validateItem(final Object item) { + // Nothing to validate so far + return Status.OK_STATUS; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#handleSelected(org.eclipse.jface.viewers.StructuredSelection) + */ + protected void handleSelected(StructuredSelection selection) { + if (selection != null && selection.size() == 0) { + // unset the sourceviewer when the filter turned the search results + // empty + sourceViewer.setDocument(new Document(EMPTY_STRING)); + } else if (selection != null && selection.getFirstElement() instanceof Module) { + final Module module = (Module) selection.getFirstElement(); + try { + sourceViewer.setDocument(new Document(new String(Files.readAllBytes(module.getFile().toPath())))); + } catch (IOException e) { + sourceViewer.setDocument(new Document(EMPTY_STRING)); + } + } else if (selection != null && selection.getFirstElement() instanceof ILaunchConfiguration) { + final ILaunchConfiguration config = (ILaunchConfiguration) selection.getFirstElement(); + try { + // By default, show the model's comment/description and fall-back + // to its constants. If there are no constants, the last fall-back + // is the model's name. + final List<String> fallbacksFallback = new ArrayList<String>(); + fallbacksFallback.add(ModelHelper.getModelName(config)); + + final String fallback = ModelHelper.prettyPrintConstants(config, "\n", true); + + final String attribute = config.getAttribute(IModelConfigurationConstants.MODEL_COMMENTS, fallback); + if (!EMPTY_STRING.equals(attribute)) { + sourceViewer.setDocument(new Document(attribute)); + } else { + sourceViewer.setDocument(new Document(fallback)); + } + } catch (final CoreException ignored) { + sourceViewer.setDocument(new Document(EMPTY_STRING)); + } + } else if (selection != null && selection.getFirstElement() instanceof Spec) { + final Spec spec = (Spec) selection.getFirstElement(); + final Path path = spec.getRootFile().getLocation().makeAbsolute().toFile().toPath(); + try { + sourceViewer.setDocument(new Document(new String(Files.readAllBytes(path)))); + } catch (IOException e) { + sourceViewer.setDocument(new Document(EMPTY_STRING)); + } + } else if (selection != null && selection.getFirstElement() instanceof ItemsListSeparator) { + sourceViewer.setDocument(new Document(EMPTY_STRING)); + } + super.handleSelected(selection); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#createFilter() + */ + protected ItemsFilter createFilter() { + return new TLCItemFilter(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#getItemsComparator() + */ + protected Comparator<Object> getItemsComparator() { + return new Comparator<Object>() { + + public int compare(final Object o1, final Object o2) { + if (o1 instanceof ILaunchConfiguration && o2 instanceof ILaunchConfiguration) { + final ILaunchConfiguration c1 = (ILaunchConfiguration) o1; + final ILaunchConfiguration c2 = (ILaunchConfiguration) o2; + return ModelHelper.getModelName(c1.getFile()).compareTo(ModelHelper.getModelName(c2.getFile())); + } else if (o1 instanceof Module && o2 instanceof Module) { + final Module m1 = (Module) o1; + final Module m2 = (Module) o2; + return m1.getModuleName().compareTo(m2.getModuleName()); + } else if (o1 instanceof ItemsListSeparator && o1 == modulesSep && o2 instanceof ItemsListSeparator) { + return -1; + } else if (o1 instanceof ItemsListSeparator && o1 == specsSep && o2 instanceof ItemsListSeparator) { + return 1; + } else if (o1 instanceof Spec && o2 instanceof Spec) { + return ((Spec) o1).getName().compareTo(((Spec) o2).getName()); + } else if (o1 instanceof ILaunchConfiguration && o2 instanceof Module) { + return -1; + } else if (o1 instanceof ILaunchConfiguration && o2 instanceof Spec) { + return -1; + } else if (o1 instanceof ILaunchConfiguration && o2 instanceof ItemsListSeparator) { + return -1; + } else if (o1 instanceof Module && o2 instanceof ILaunchConfiguration) { + return 1; + } else if (o1 instanceof Module && o2 instanceof Spec) { + return -1; + } else if (o1 instanceof Module && o2 instanceof ItemsListSeparator && o2 == modulesSep) { + return 1; + } else if (o1 instanceof Module && o2 instanceof ItemsListSeparator) { + return -1; + } else if (o1 instanceof Spec && o2 instanceof ILaunchConfiguration) { + return 1; + } else if (o1 instanceof Spec && o2 instanceof Module) { + return 1; + } else if (o1 instanceof Spec && o2 instanceof ItemsListSeparator) { + return 1; + } else if (o1 instanceof ItemsListSeparator && o2 instanceof ILaunchConfiguration) { + return 1; + } else if (o1 instanceof ItemsListSeparator && o1 == modulesSep && o2 instanceof Module) { + return -1; + } else if (o1 instanceof ItemsListSeparator && o2 instanceof Module) { + return 1; + } else if (o1 instanceof ItemsListSeparator && o2 instanceof Spec) { + return -1; + } else { + return 1; + } + } + }; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#fillContentProvider(org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.AbstractContentProvider, org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter, org.eclipse.core.runtime.IProgressMonitor) + */ + protected void fillContentProvider(final AbstractContentProvider contentProvider, final ItemsFilter itemsFilter, + final IProgressMonitor progressMonitor) throws CoreException { + final Spec spec = Activator.getSpecManager().getSpecLoaded(); + // On the initial/welcome page, no spec is open. + if (spec != null) { + // Models + final List<ILaunchConfiguration> models = ModelHelper.getModelsBySpec(spec); + for (final ILaunchConfiguration model : models) { + if (itemsFilter.isConsistentItem(model)) { + contentProvider.add(model, itemsFilter); + } + } + + // Modules + final List<Module> modules = spec.getModules(); + if (modules.size() > 0) { + contentProvider.add(modulesSep, itemsFilter); + for (Module module : modules) { + if (itemsFilter.isConsistentItem(module)) { + contentProvider.add(module, itemsFilter); + } + } + } + } + + // All closed specs + if (toggleShowSpecAction.isChecked()) { + final Spec[] specs = Activator.getSpecManager().getRecentlyOpened(); + if (specs.length > 0) { + contentProvider.add(specsSep, itemsFilter); + for (int i = 0; i < specs.length; i++) { + final Spec aSpec = specs[i]; + if (!aSpec.equals(spec) && itemsFilter.isConsistentItem(aSpec)) { + contentProvider.add(aSpec, itemsFilter); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog#getElementName(java.lang.Object) + */ + public String getElementName(final Object item) { + return EMPTY_STRING; + } + + public class TLCItemFilter extends ItemsFilter { + + private final TableLabelProvider labelProvider = new TableLabelProvider(); + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter#isConsistentItem(java.lang.Object) + */ + public boolean isConsistentItem(final Object item) { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.ItemsFilter#matchItem(java.lang.Object) + */ + public boolean matchItem(final Object item) { + if (getPattern() == null || getPattern().length() == 0) { + return true; + } + // Use the text shown by the label provider. The user definitely + // wants to search inside what is actually shown. + return patternMatcher.matches(labelProvider.getText(item)); + } + } + + private class DetailsLabelProvider extends LabelProvider { + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object) + */ + public String getText(final Object element) { + if (element instanceof Module) { + final Module module = (Module) element; + return module.getModuleName(); + } else if (element instanceof ILaunchConfiguration) { + final ILaunchConfiguration config = (ILaunchConfiguration) element; + return ModelHelper.getModelName(config.getFile()); + } else if (element instanceof Spec) { + final Spec spec = (Spec) element; + return spec.getName(); + } else if (element instanceof ItemsListSeparator) { + return EMPTY_STRING; + } + return super.getText(element); + } + } + + private class TableLabelProvider extends LabelProvider implements IDescriptionProvider, IStyledLabelProvider { + + private static final String DELIM = ":"; + + public String getText(final Object element) { + if (element == null) { + return null; + } + if (element instanceof Spec) { + final Spec spec = (Spec) element; + final IFile root = spec.getRootFile(); + if (root == null) { + return null; + } + return spec.getName() + " [ " + root.getName() + " ]"; + } else if (element instanceof Module) { + return ((Module) element).getModuleName(); + } else if (element instanceof ILaunchConfiguration) { + final ILaunchConfiguration config = (ILaunchConfiguration) element; + try { + String attribute = config.getAttribute(IModelConfigurationConstants.MODEL_COMMENTS, EMPTY_STRING); + if (toggleShowConstantsAction.isChecked() && EMPTY_STRING.equals(attribute)) { + attribute = ModelHelper.prettyPrintConstants(config, ", "); + } + if (!EMPTY_STRING.equals(attribute)) { + if (attribute.contains("\n")) { + attribute = attribute.split("\n")[0]; + } + return ModelHelper.getModelName(config) + DELIM + " " + attribute; + } + } catch (CoreException e) { + } + return ModelHelper.getModelName(config); + } else if (element instanceof ItemsListSeparator) { + final ItemsListSeparator ils = (ItemsListSeparator) element; + return ils.getName(); + } + return null; + } + + public String getDescription(final Object element) { + return super.getText(element); + } + + public Image getImage(final Object element) { + if (element == null) { + return null; + } + if (element instanceof Spec) { + if (Activator.getSpecManager().isSpecLoaded((Spec) element)) { + return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); + } + return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT_CLOSED); + } else if (element instanceof Module) { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); + } else if (element instanceof ILaunchConfiguration) { + return ModelImage; + } + return null; + } + + public StyledString getStyledText(Object element) { + final String text = getText(element); + if (text == null || EMPTY_STRING.equals(text)) { + return new StyledString(); + } + + final StyledString string = new StyledString(text); + + if (element instanceof Spec) { + string.setStyle(0, string.length(), StyledString.QUALIFIER_STYLER); + } else if (element instanceof ILaunchConfiguration && text.indexOf(DELIM) != -1) { + final int index = text.indexOf(DELIM); + string.setStyle(index, text.length() - index, StyledString.DECORATIONS_STYLER); + } else if (element instanceof ItemsListSeparator) { + string.setStyle(0, string.length(), StyledString.QUALIFIER_STYLER); + } + return string; + } + } + + private class ToggleShowAction extends Action { + + public ToggleShowAction(final String label, final boolean checked) { + super(label, IAction.AS_CHECK_BOX); + setChecked(checked); + } + + public void run() { + // Doesn't do anything. The dialog has to be re-opened to see its effect. + } + } +} \ No newline at end of file diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ISectionConstants.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ISectionConstants.java index 5fc0b711dd9dd6874d2e0a811ed3d0b3ecfdd428..136dd40daacbb459e1b7e1229cdc1b3793636fa0 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ISectionConstants.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ISectionConstants.java @@ -32,6 +32,8 @@ package org.lamport.tla.toolbox.tool.tlc.ui.editor; public interface ISectionConstants { // sections of the first page + public final static String SEC_COMMENTS = "__what_is_the_description"; + public final static String SEC_WHAT_IS_THE_SPEC = "__what_is_the_spec"; public final static String SEC_WHAT_TO_CHECK = "__what_to_check"; public final static String SEC_WHAT_TO_CHECK_INVARIANTS = "__what_to_check_invariants"; diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ModelEditor.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ModelEditor.java index c82df901d974364b6118e19bbf43d5cf44ce7b9e..e48c398974cf5f1842df842d1a13c6913ff38226 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ModelEditor.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/ModelEditor.java @@ -74,7 +74,7 @@ import tla2sany.semantic.ModuleNode; public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider { - /** + /** * Editor ID */ public static final String ID = "org.lamport.tla.toolbox.tool.tlc.ui.editor.ModelEditor"; @@ -96,7 +96,7 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider * It is used in the workspace root listener and is called once after the input is set and after the pages * are added. */ - private ValidateRunnable validateRunable = new ValidateRunnable(); + private final ValidateRunnable validateRunable = new ValidateRunnable(); private class ValidateRunnable implements Runnable { @@ -454,7 +454,7 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider /** * Instead of committing pages, forms and form-parts, we just commit pages */ - protected void commitPages(IProgressMonitor monitor, boolean onSave) + protected synchronized void commitPages(IProgressMonitor monitor, boolean onSave) { // TLCUIActivator.getDefault().logDebug("entering ModelEditor#commitPages(IProgressMonitor monitor, boolean onSave)"); for (int i = 0; i < getPageCount(); i++) @@ -799,6 +799,15 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider IMarker[] modelProblemMarkers = ModelHelper.getModelProblemMarker(getConfig()); DataBindingManager dm = getDataBindingManager(); + // The loop is going to update the page's messages for potentially + // each marker (nested loop). Thus, turn auto update off during the + // loop for all pages (we don't yet know which marker gets displayed + // on which page). + for (int i = 0; i < this.pagesToAdd.length; i++) { + IMessageManager mm = this.pagesToAdd[i].getManagedForm().getMessageManager(); + mm.setAutoUpdate(false); + } + for (int j = 0; j < getPageCount(); j++) { /* @@ -835,24 +844,27 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider if (ModelHelper.EMPTY_STRING.equals(attributeName)) { - String message = modelProblemMarkers[i].getAttribute(IMarker.MESSAGE, + final String message = modelProblemMarkers[i].getAttribute(IMarker.MESSAGE, IModelConfigurationDefaults.EMPTY_STRING); + final int pageId = modelProblemMarkers[i] + .getAttribute(ModelHelper.TLC_MODEL_ERROR_MARKER_ATTRIBUTE_PAGE, -1); // no attribute, this is a global error, not bound to a particular attribute // install it on the first page // if it is a global TLC error, then we call addGlobalTLCErrorMessage() // to add a hyperlink to the TLC Error view - if (bubbleType == IMessageProvider.WARNING) - { - this.pagesToAdd[0].addGlobalTLCErrorMessage("modelProblem_" + i); - this.pagesToAdd[1].addGlobalTLCErrorMessage("modelProblem_" + i); - } else - { - // else install as with other messages - IMessageManager mm = this.pagesToAdd[0].getManagedForm().getMessageManager(); - mm.setAutoUpdate(false); - mm.addMessage("modelProblem_" + i, message, null, bubbleType); - mm.setAutoUpdate(true); - } + if (pageId != -1 && bubbleType == IMessageProvider.WARNING + && !IModelConfigurationDefaults.EMPTY_STRING.equals(message)) { + // Used by the ResultPage to display an error on + // incomplete state space exploration. + this.pagesToAdd[pageId].addGlobalTLCErrorMessage(ResultPage.RESULT_PAGE_PROBLEM, message); + } else if (bubbleType == IMessageProvider.WARNING) { + this.pagesToAdd[0].addGlobalTLCErrorMessage("modelProblem_" + i); + this.pagesToAdd[1].addGlobalTLCErrorMessage("modelProblem_" + i); + } else { + // else install as with other messages + IMessageManager mm = this.pagesToAdd[0].getManagedForm().getMessageManager(); + mm.addMessage("modelProblem_" + i, message, null, bubbleType); + } } else { // attribute found @@ -871,7 +883,6 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider // the header of every page, so the if statement that is commented // out is no longer relevant IMessageManager mm = page.getManagedForm().getMessageManager(); - mm.setAutoUpdate(false); String message = modelProblemMarkers[i].getAttribute(IMarker.MESSAGE, IModelConfigurationDefaults.EMPTY_STRING); @@ -888,7 +899,6 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider } // expand the section with an error dm.expandSection(sectionId); - mm.setAutoUpdate(true); if (page.getId().equals(pageId) && errorPageIndex < j) { @@ -899,6 +909,13 @@ public class ModelEditor extends FormEditor implements ModelHelper.IFileProvider } } } + + // Once all markers have been processed, re-enable auto update again. + for (int i = 0; i < this.pagesToAdd.length; i++) { + final IMessageManager mm = this.pagesToAdd[i].getManagedForm().getMessageManager(); + mm.setAutoUpdate(true); + } + if (switchToErrorPage && errorPageIndex != -1 && currentPageIndex != errorPageIndex) { // the page has a marker diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/AdvancedModelPage.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/AdvancedModelPage.java index c52b56b9031db2f7f322dc667c43a4d775930865..abd930db322fe156337776e21b857cebc5fd3549 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/AdvancedModelPage.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/AdvancedModelPage.java @@ -115,7 +115,7 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo protected void loadData() throws CoreException { // definition overrides - List definitions = getConfig().getAttribute(MODEL_PARAMETER_DEFINITIONS, new Vector()); + List<String> definitions = getConfig().getAttribute(MODEL_PARAMETER_DEFINITIONS, new Vector<String>()); FormHelper.setSerializedInput(definitionsTable, definitions); // new definitions @@ -245,7 +245,7 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo getConfig().setAttribute(LAUNCH_MAXSETSIZE, maxSetSize.getSelection()); // definitions - List definitions = FormHelper.getSerializedInput(definitionsTable); + List<String> definitions = FormHelper.getSerializedInput(definitionsTable); getConfig().setAttribute(MODEL_PARAMETER_DEFINITIONS, definitions); // new definitions @@ -412,7 +412,7 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo * IMessageProvider.ERROR, modelValuesSource.getControl()); * setComplete(false); } */ - List values = modelValuesSet.getValuesAsList(); + List<String> values = modelValuesSet.getValuesAsList(); // check list of model values if these are already used validateUsage(MODEL_PARAMETER_MODEL_VALUES, values, "modelValues2_", "A model value", "Advanced Model Values", true); @@ -451,7 +451,7 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo { opDefNodes = specObj.getExternalModuleTable().getRootModule().getOpDefs(); } - Hashtable nodeTable = new Hashtable(); + Hashtable<String, OpDefNode> nodeTable = new Hashtable<String, OpDefNode>(); if (opDefNodes != null) { @@ -465,12 +465,13 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo // get widget for definition overrides Control widget = UIHelper.getWidget(dm.getAttributeControl(MODEL_PARAMETER_DEFINITIONS)); // check the definition overrides - List definitions = (List) definitionsTable.getInput(); + @SuppressWarnings("unchecked") + List<Assignment> definitions = (List<Assignment>) definitionsTable.getInput(); for (int i = 0; i < definitions.size(); i++) { - Assignment definition = (Assignment) definitions.get(i); - List values = Arrays.asList(definition.getParams()); + Assignment definition = definitions.get(i); + List<String> values = Arrays.asList(definition.getParams()); // check list of parameters validateUsage(MODEL_PARAMETER_DEFINITIONS, values, "param1_", "A parameter name", "Definition Overrides", false); @@ -657,7 +658,7 @@ public class AdvancedModelPage extends BasicFormPage implements IConfigurationCo int expand = 0; try { - List definitions = getConfig().getAttribute(MODEL_PARAMETER_DEFINITIONS, new Vector()); + List<String> definitions = getConfig().getAttribute(MODEL_PARAMETER_DEFINITIONS, new Vector<String>()); if ((definitions != null) && (definitions.size() != 0)) { expand = Section.EXPANDED; } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/BasicFormPage.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/BasicFormPage.java index b0db9efcd49a4c089b4311c7883615810cf3031a..fff6e10479e559cf36181ffcbd4040c96da9fd19 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/BasicFormPage.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/BasicFormPage.java @@ -820,6 +820,15 @@ public abstract class BasicFormPage extends FormPage implements IModelConfigurat getManagedForm().getMessageManager().setAutoUpdate(applyChange); } + public void resetMessage(final Object key) { + getManagedForm().getMessageManager().setAutoUpdate(false); + getManagedForm().getMessageManager().removeMessage(key);; + // make the run possible + setComplete(true); + // make the change visible + getManagedForm().getMessageManager().setAutoUpdate(true); + } + /** * This method adds the text "TLC Error" next to the title of the page. * The text will appear as a hyperlink. Clicking on the link will give @@ -833,8 +842,13 @@ public abstract class BasicFormPage extends FormPage implements IModelConfigurat */ public void addGlobalTLCErrorMessage(String key) { - IMessageManager mm = getManagedForm().getMessageManager(); - mm.addMessage(key, TLC_ERROR_STRING, null, IMessageProvider.WARNING); + addGlobalTLCErrorMessage(key, TLC_ERROR_STRING); + } + + public void addGlobalTLCErrorMessage(String key, String message) + { + IMessageManager mm = getManagedForm().getMessageManager(); + mm.addMessage(key, message, null, IMessageProvider.WARNING); /*globalTLCErrorHyperLink.setText(TLC_ERROR_STRING); globalTLCErrorHyperLink.setToolTipText(tooltipText); globalTLCErrorHyperLink.setForeground(TLCUIActivator.getColor(SWT.COLOR_DARK_YELLOW)); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/MainModelPage.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/MainModelPage.java index 2b43a9f3633f7a3c56bed00db8f274b0ebbb01fb..d15185c30d5cb0c7b33a4fd09d53f7b51d41198a 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/MainModelPage.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/MainModelPage.java @@ -63,6 +63,7 @@ import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.ImageHyperlink; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; import org.lamport.tla.toolbox.tool.tlc.launch.IConfigurationConstants; import org.lamport.tla.toolbox.tool.tlc.launch.IConfigurationDefaults; import org.lamport.tla.toolbox.tool.tlc.model.Assignment; @@ -107,6 +108,7 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta private Button noSpecRadio; // re-added on 10 Sep 2009 private Button closedFormulaRadio; private Button initNextFairnessRadio; + private SourceViewer commentsSource; private SourceViewer initFormulaSource; private SourceViewer nextFormulaSource; // private SourceViewer fairnessFormulaSource; @@ -323,8 +325,8 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta } } - if (cloud.equalsIgnoreCase("aws-ec2")) { - MainModelPage.this.putOnTopOfStack("aws-ec2", false, false); + if (cloud.equalsIgnoreCase("aws-ec2") || cloud.equalsIgnoreCase("Azure")) { + MainModelPage.this.putOnTopOfStack("jclouds", false, false); String email = getConfig().getAttribute(LAUNCH_DISTRIBUTED_RESULT_MAIL_ADDRESS, LAUNCH_DISTRIBUTED_RESULT_MAIL_ADDRESS_DEFAULT); resultMailAddressText.setText(email); } else if(cloud.equalsIgnoreCase("ad hoc")) { @@ -335,6 +337,10 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta // distribute FPSet count distributedFPSetCountSpinner.setSelection(getConfig().getAttribute(LAUNCH_DISTRIBUTED_FPSET_COUNT, LAUNCH_DISTRIBUTED_FPSET_COUNT_DEFAULT)); + + // comments/description/notes + String commentsStr = getConfig().getAttribute(MODEL_COMMENTS, EMPTY_STRING); + commentsSource.setDocument(new Document(commentsStr)); } public void validatePage(boolean switchToErrorPage) @@ -762,6 +768,9 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta */ public void commit(boolean onSave) { + final String comments = FormHelper.trimTrailingSpaces(commentsSource.getDocument().get()); + getConfig().setAttribute(MODEL_COMMENTS, comments); + // TLCUIActivator.getDefault().logDebug("Main page commit"); // closed formula String closedFormula = FormHelper.trimTrailingSpaces(this.specSource.getDocument().get()); @@ -906,6 +915,39 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta GridData gd; TableWrapData twd; + Section section; + GridLayout layout; + + /* + * Comments/notes section spanning two columns + */ + Composite top = toolkit.createComposite(body); + top.setLayout(FormHelper.createFormTableWrapLayout(false, 2)); + twd = new TableWrapData(TableWrapData.FILL_GRAB); + twd.colspan = 2; + top.setLayoutData(twd); + + section = FormHelper.createSectionComposite(top, "Model description", "", toolkit, sectionFlags + | Section.EXPANDED, getExpansionListener()); + + final ValidateableSectionPart commentsPart = new ValidateableSectionPart(section, this, SEC_COMMENTS); + managedForm.addPart(commentsPart); + final DirtyMarkingListener commentsListener = new DirtyMarkingListener(commentsPart, true); + + final Composite commentsArea = (Composite) section.getClient(); + commentsArea.setLayout(new TableWrapLayout()); + + commentsSource = FormHelper.createFormsSourceViewer(toolkit, commentsArea, SWT.V_SCROLL | SWT.WRAP); + // layout of the source viewer + twd = new TableWrapData(TableWrapData.FILL_GRAB); + twd.heightHint = 60; + commentsSource.addTextListener(commentsListener); + commentsSource.getTextWidget().setLayoutData(twd); + commentsSource.getTextWidget().addFocusListener(focusListener); + toolkit.paintBordersFor(commentsArea); + + dm.bindAttribute(MODEL_COMMENTS, commentsSource, commentsPart); + /* * Because the two Composite objects `left' and `right' are added to the * object `body' in this order, `left' is displayed to the left of `right'. @@ -924,9 +966,6 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta right.setLayoutData(twd); right.setLayout(new GridLayout(1, false)); - Section section; - GridLayout layout; - // ------------------------------------------ // what is the spec section = FormHelper.createSectionComposite(left, "What is the behavior spec?", "", toolkit, sectionFlags @@ -1325,7 +1364,7 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta toolkit.createLabel(distComp, "Run in distributed mode"); distributedCombo = new Combo(distComp, SWT.READ_ONLY); - distributedCombo.setItems(new String[] {"off", "ad hoc", "aws-ec2"}); + distributedCombo.setItems(new String[] {"off", "ad hoc", "aws-ec2", "Azure"}); distributedCombo.select(0); HelpButton.helpButton(distComp, "model/distributed-mode.html") ; distributedCombo.addSelectionListener(howToRunListener); @@ -1518,14 +1557,14 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta resultMailAddressText.addModifyListener(howToRunListener); dm.bindAttribute(LAUNCH_DISTRIBUTED_RESULT_MAIL_ADDRESS, resultMailAddressText, howToRunPart); - distributedOptions.setData("aws-ec2", resultAddress); + distributedOptions.setData("jclouds", resultAddress); distributedCombo.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { int selectionIndex = distributedCombo.getSelectionIndex(); String item = distributedCombo.getItem(selectionIndex); - if (item.equalsIgnoreCase("aws-ec2")) { - MainModelPage.this.putOnTopOfStack("aws-ec2", false, false); + if (item.equalsIgnoreCase("aws-ec2") || item.equalsIgnoreCase("Azure")) { + MainModelPage.this.putOnTopOfStack("jclouds", false, false); } else if(item.equalsIgnoreCase("ad hoc")) { MainModelPage.this.putOnTopOfStack("ad hoc", false, true); } else { @@ -1574,6 +1613,7 @@ public class MainModelPage extends BasicFormPage implements IConfigurationConsta // add listeners propagating the changes of the elements to the changes // of the // parts to the list to be activated after the values has been loaded + dirtyPartListeners.add(commentsListener); dirtyPartListeners.add(whatIsTheSpecListener); dirtyPartListeners.add(whatToCheckListener); dirtyPartListeners.add(howToRunListener); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/ResultPage.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/ResultPage.java index 313a2d1ee9a135aa9f2e40c335e23a2e77afcd4b..bbd1bc782b7664647b48153734e11bb0ca12cede 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/ResultPage.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/page/ResultPage.java @@ -5,12 +5,14 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Hashtable; import java.util.List; import java.util.TimeZone; import java.util.Vector; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.WorkspaceJob; @@ -25,6 +27,8 @@ import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.ITableColorProvider; +import org.eclipse.jface.viewers.ITableFontProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TableViewer; @@ -35,6 +39,7 @@ import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; @@ -81,10 +86,12 @@ import org.lamport.tla.toolbox.util.UIHelper; * A page to display results of model checking (the "third tab" * of the model editor). * @author Simon Zambrovski - * @version $Id$ */ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPresenter { + + public static final String RESULT_PAGE_PROBLEM = "ResultPageProblem"; + public static final String ID = "resultPage"; private static final String TOOLTIP = "Click on a row to go to action."; @@ -133,6 +140,8 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres } }; + private IMarker incompleteStateExploration; + /** * Constructor for the page * @param editor @@ -261,13 +270,48 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres ResultPage.this.errorStatusHyperLink.setText(text); ResultPage.this.errorStatusHyperLink.setForeground(color); - + // update the error view TLCErrorView.updateErrorView(dataProvider.getConfig()); break; default: break; } + + // Set label provider to highlight unexplored states if + // TLC is done but not all states are explored. + final StateSpaceLabelProvider sslp = (StateSpaceLabelProvider) ResultPage.this.stateSpace + .getLabelProvider(); + if (dataProvider.isDone() && dataProvider.getProgressInformation().size() > 0) { + final long statesLeft = dataProvider.getProgressInformation().get(0).getLeftStates(); + if (statesLeft > 0) { + sslp.setHighlightUnexplored(); + // Create a problem marker which gets displayed by + // BasicFormPage/ModelEditor as a warning on the + // result page. + if (incompleteStateExploration == null) { + final Hashtable<String, Object> marker = ModelHelper.createMarkerDescription( + "State space exploration incomplete", IMarker.SEVERITY_WARNING); + marker.put(ModelHelper.TLC_MODEL_ERROR_MARKER_ATTRIBUTE_PAGE, 2); + incompleteStateExploration = ModelHelper.installModelProblemMarker(getConfig().getFile(), + marker, ModelHelper.TLC_MODEL_ERROR_MARKER_TLC); + } + } else { + if (incompleteStateExploration != null) { + try { + incompleteStateExploration.delete(); + ResultPage.this.resetMessage(RESULT_PAGE_PROBLEM); + incompleteStateExploration = null; + } catch (CoreException e) { + TLCUIActivator.getDefault().logError(e.getMessage(), e); + } + } + sslp.unsetHighlightUnexplored(); + } + } + ResultPage.this.stateSpace.refresh(); + + } finally { disposeLock.unlock(); } @@ -289,14 +333,14 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres provider.setPresenter(this); } else { - // no data provider - reinit(); - } + // no data provider + reinit(); + } - // constant expression + // constant expression String expression = getConfig().getAttribute(MODEL_EXPRESSION_EVAL, EMPTY_STRING); - expressionEvalInput.setDocument(new Document(expression)); - } + expressionEvalInput.setDocument(new Document(expression)); + } /** * Reinitialize the fields @@ -333,32 +377,36 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres public void dispose() { disposeLock.lock(); - try { - /* - * Remove graph windows raised for the page. - */ - String suffix = getGraphTitleSuffix(this); - Shell[] shells = UIHelper.getCurrentDisplay().getShells(); - for (int i = 0; i < shells.length; i++) - { - if (shells[i].getText().endsWith(suffix)) - { - shells[i].dispose(); - } - } - - JFaceResources.getFontRegistry().removeListener(fontChangeListener); - - TLCModelLaunchDataProvider provider = TLCOutputSourceRegistry.getModelCheckSourceRegistry().getProvider( - getConfig()); - if (provider != null) - { - provider.setPresenter(null); - } - super.dispose(); - } finally { - disposeLock.unlock(); - } + try { + /* + * Remove graph windows raised for the page. + */ + String suffix = getGraphTitleSuffix(this); + Shell[] shells = UIHelper.getCurrentDisplay().getShells(); + for (int i = 0; i < shells.length; i++) { + if (shells[i].getText().endsWith(suffix)) { + shells[i].dispose(); + } + } + + if (incompleteStateExploration != null) { + incompleteStateExploration.delete(); + incompleteStateExploration = null; + } + + JFaceResources.getFontRegistry().removeListener(fontChangeListener); + + TLCModelLaunchDataProvider provider = TLCOutputSourceRegistry.getModelCheckSourceRegistry() + .getProvider(getConfig()); + if (provider != null) { + provider.setPresenter(null); + } + super.dispose(); + } catch (CoreException e) { + e.printStackTrace(); + } finally { + disposeLock.unlock(); + } } /** @@ -373,8 +421,9 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres */ protected void createBodyContent(IManagedForm managedForm) { - int sectionFlags = Section.TITLE_BAR | Section.DESCRIPTION | Section.TREE_NODE | Section.EXPANDED | SWT.WRAP; - int textFieldFlags = SWT.MULTI | SWT.V_SCROLL | SWT.READ_ONLY | SWT.FULL_SELECTION | SWT.WRAP; + final int sectionFlags = Section.TITLE_BAR | Section.DESCRIPTION | Section.TREE_NODE | Section.EXPANDED | SWT.WRAP; + final int textFieldFlags = SWT.MULTI | SWT.V_SCROLL | SWT.READ_ONLY | SWT.FULL_SELECTION; + final int expressionFieldFlags = textFieldFlags | SWT.WRAP; FormToolkit toolkit = managedForm.getToolkit(); Composite body = managedForm.getForm().getBody(); @@ -474,7 +523,7 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres expressionComposite.setLayout(gLayout); toolkit.createLabel(expressionComposite, "Expression: "); - expressionEvalInput = FormHelper.createFormsSourceViewer(toolkit, expressionComposite, textFieldFlags); + expressionEvalInput = FormHelper.createFormsSourceViewer(toolkit, expressionComposite, expressionFieldFlags); // We want the value section to get larger as the window // gets larger but not the expression section. @@ -482,7 +531,7 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres valueComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); valueComposite.setLayout(gLayout); toolkit.createLabel(valueComposite, "Value: "); - expressionEvalResult = FormHelper.createFormsOutputViewer(toolkit, valueComposite, textFieldFlags); + expressionEvalResult = FormHelper.createFormsOutputViewer(toolkit, valueComposite, expressionFieldFlags); // We dont want these items to fill excess // vertical space because then in some cases @@ -596,6 +645,10 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres // Get the user input (the path to the TLC output file). final FileDialog fileDialog = new FileDialog(new Shell()); final String path = fileDialog.open(); + if (path == null) { + // User cancelled the dialog + return; + } // I/O operations should never run inside the UI thread. final Job j = new WorkspaceJob("Loading output file...") { @@ -786,7 +839,7 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres /** * Provides labels for the state space table */ - static class StateSpaceLabelProvider extends LabelProvider implements ITableLabelProvider + static class StateSpaceLabelProvider extends LabelProvider implements ITableLabelProvider, ITableFontProvider, ITableColorProvider { public final static String[] columnTitles = new String[] { "Time", "Diameter", "States Found", "Distinct States", "Queue Size" }; @@ -800,6 +853,7 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres public final static int COL_LEFT = 4; private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // $NON-NLS-1$ + private boolean doHighlight = false; /* (non-Javadoc) * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int) @@ -809,7 +863,7 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres return null; } - /** + /** * @param stateTable */ public static void createTableColumns(Table stateTable, ResultPage page) @@ -871,7 +925,35 @@ public class ResultPage extends BasicFormPage implements ITLCModelLaunchDataPres } return null; } - } + + public Color getForeground(Object element, int columnIndex) { + return null; // Use default color + } + + public Color getBackground(Object element, int columnIndex) { + final StateSpaceInformationItem ssii = (StateSpaceInformationItem) element; + if (doHighlight && columnIndex == COL_LEFT && ssii.isMostRecent()) { + return TLCUIActivator.getColor(SWT.COLOR_RED); + } + return null; + } + + public Font getFont(Object element, int columnIndex) { + final StateSpaceInformationItem ssii = (StateSpaceInformationItem) element; + if (doHighlight && columnIndex == COL_LEFT && ssii.isMostRecent()) { + return JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT); + } + return null; + } + + public void setHighlightUnexplored() { + doHighlight = true; + } + + public void unsetHighlightUnexplored() { + doHighlight = false; + } + } /** * Provides labels for the coverage table diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/part/ValidateableConstantSectionPart.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/part/ValidateableConstantSectionPart.java index c43520f5e716980e07038cfc6c14e92bb871d098..764a56c1b36170b3e4a1f3042657eaa5e6224ba4 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/part/ValidateableConstantSectionPart.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/part/ValidateableConstantSectionPart.java @@ -22,7 +22,6 @@ import org.lamport.tla.toolbox.tool.tlc.ui.wizard.AssignmentWizardPage; /** * Section part for the constants * @author Simon Zambrovski - * @version $Id$ */ public class ValidateableConstantSectionPart extends ValidateableTableSectionPart { @@ -78,6 +77,10 @@ public class ValidateableConstantSectionPart extends ValidateableTableSectionPar { IStructuredSelection selection = (IStructuredSelection) tableViewer.getSelection(); Assignment formula = (Assignment) selection.getFirstElement(); + if (formula == null) { + // User clicked on an empty line in the formula table + return; + } Assignment editedFormula = doEditFormula(formula); if (editedFormula != null) { diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/provider/FormulaContentProvider.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/provider/FormulaContentProvider.java index 3f18806633d499dccc086c289ff87ed2677f34e7..a841f0a614d0779700a1f6643511a6dbd80739c1 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/provider/FormulaContentProvider.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/editor/provider/FormulaContentProvider.java @@ -9,7 +9,6 @@ import org.lamport.tla.toolbox.tool.tlc.model.Formula; /** * @author Simon Zambrovski - * @version $Id$ */ public class FormulaContentProvider implements IStructuredContentProvider { @@ -42,7 +41,8 @@ public class FormulaContentProvider implements IStructuredContentProvider { if (inputElement != null && inputElement instanceof Vector) { - Vector formulaList = (Vector) inputElement; + @SuppressWarnings("unchecked") + Vector<Formula> formulaList = (Vector<Formula>) inputElement; return formulaList.toArray(new Formula[formulaList.size()]); } return EMPTY; diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelContentProvider.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelContentProvider.java index 8ff36bd5c88f6660ff943fcc4fe7e6d268020ff8..34e890e4b20f2f4f002cc2274146479711306f09 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelContentProvider.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelContentProvider.java @@ -1,5 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.ui.modelexplorer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.Vector; import org.eclipse.core.resources.IProject; @@ -14,90 +42,130 @@ import org.lamport.tla.toolbox.spec.Spec; import org.lamport.tla.toolbox.tool.ToolboxHandle; import org.lamport.tla.toolbox.tool.tlc.launch.TLCModelLaunchDelegate; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; +import org.lamport.tla.toolbox.ui.provider.IGroup; /** - * Provides information about TLC models (launch configurations) - * in the current project - * @author Simon Zambrovski - * @version $Id$ + * Provides information about TLC models (launch configurations) in the current + * project */ -public class ModelContentProvider implements ITreeContentProvider -{ - // content extension for the Toolbox explorer contributed by the TLC - public static final String TLC_NCE = "toolbox.content.ModelContent"; - private static final Object[] EMPTY_ARRAY = new Object[0]; +public class ModelContentProvider implements ITreeContentProvider { + // content extension for the Toolbox explorer contributed by the TLC + public static final String TLC_NCE = "toolbox.content.ModelContent"; + private static final Object[] EMPTY_ARRAY = new Object[0]; + + private final Map<ILaunchConfiguration, Group> reverse = new HashMap<ILaunchConfiguration, Group>(); + + public Object[] getChildren(final Object parentElement) { + if (parentElement instanceof Spec) { + final Spec currentSpec = (Spec) parentElement; + final ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); - public Object[] getChildren(Object parentElement) - { - if (parentElement instanceof Spec) - { - Spec currentSpec = (Spec) parentElement; - ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + final ILaunchConfigurationType configType = launchManager + .getLaunchConfigurationType(TLCModelLaunchDelegate.LAUNCH_CONFIGURATION_TYPE); - ILaunchConfigurationType configType = launchManager - .getLaunchConfigurationType(TLCModelLaunchDelegate.LAUNCH_CONFIGURATION_TYPE); + final Vector<ILaunchConfiguration> models = new Vector<ILaunchConfiguration>(); - Vector models = new Vector(); + final IProject specProject = currentSpec.getProject(); + try { + final ILaunchConfiguration[] configs = launchManager.getLaunchConfigurations(configType); + for (int i = 0; i < configs.length; i++) { + // skip launches from other specs (projects) + if (!specProject.equals(configs[i].getFile().getProject()) || !configs[i].exists()) { + continue; + } + models.add(configs[i]); + } + } catch (final CoreException e) { + TLCUIActivator.getDefault().logError("Error fetching the models", e); + } - IProject specProject = currentSpec.getProject(); - try - { - ILaunchConfiguration[] configs = launchManager.getLaunchConfigurations(configType); - for (int i = 0; i < configs.length; i++) - { - // skip launches from other specs (projects) - if (!specProject.equals(configs[i].getFile().getProject()) || !configs[i].exists()) - { - continue; - } - models.add(configs[i]); - } - } catch (CoreException e) - { - TLCUIActivator.getDefault().logError("Error fetching the models", e); - } + // only get models of the current spec + if (ToolboxHandle.getCurrentSpec() == parentElement) { + return new Group[] {new Group((Spec) parentElement, models.toArray(new ILaunchConfiguration[models.size()]))}; + } + } else if (parentElement instanceof Group) { + return ((Group) parentElement).getModels(); + } + return EMPTY_ARRAY; + } - // only get models of the current spec - if (ToolboxHandle.getCurrentSpec() == parentElement) - { - return models.toArray(new ILaunchConfiguration[models.size()]); - } - } - return EMPTY_ARRAY; - } + public Object getParent(final Object element) { + if (element instanceof ILaunchConfiguration) { + if (((ILaunchConfiguration) element).exists()) { + return ToolboxHandle.getSpecByName(((ILaunchConfiguration) element).getFile().getProject().getName()); + } + } + return null; + } - public Object getParent(Object element) - { - if (element instanceof ILaunchConfiguration) - { - if (((ILaunchConfiguration) element).exists()) - { - return ToolboxHandle.getSpecByName(((ILaunchConfiguration) element).getFile().getProject().getName()); - } - } - return null; - } + public boolean hasChildren(final Object element) { + if (element instanceof Group) { + return ((Group) element).getModels().length > 0; + } + /* + * Models are shown for the current spec only + */ + return (element instanceof Spec && ToolboxHandle.getCurrentSpec() == element); + } - public boolean hasChildren(Object element) - { - /* - * Models are shown for the current spec only - */ - return (element instanceof Spec && ToolboxHandle.getCurrentSpec() == element); - } + public Object[] getElements(final Object inputElement) { + return getChildren(inputElement); + } - public Object[] getElements(Object inputElement) - { - return getChildren(inputElement); - } + public void dispose() { + reverse.clear(); + } - public void dispose() - { - } + public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { + // nothing to do + } - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) - { - // nothing to do - } + public static final class Group implements IGroup { + + private final ILaunchConfiguration[] models; + private final Spec spec; + + public Group(Spec spec, ILaunchConfiguration[] iLaunchConfigurations) { + this.spec = spec; + this.models = iLaunchConfigurations; + } + + public ILaunchConfiguration[] getModels() { + return models; + } + + public Spec getSpec() { + return spec; + } + + public String toString() { + return "models"; + } + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(models); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Group other = (Group) obj; + if (!Arrays.equals(models, other.models)) + return false; + return true; + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelLabelProvider.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelLabelProvider.java index e9c0d6a7940160b4a7e0cafcd977e7fbe28b693d..146e5463de9dac661979a2a9ef4c2943e712d8d8 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelLabelProvider.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/modelexplorer/ModelLabelProvider.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.ui.modelexplorer; import org.eclipse.core.runtime.CoreException; @@ -8,103 +33,91 @@ import org.eclipse.debug.core.ILaunchManager; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.navigator.IDescriptionProvider; +import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; +import org.lamport.tla.toolbox.tool.tlc.ui.modelexplorer.ModelContentProvider.Group; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; /** * Provides labels for the TLC models - * - * @author Simon Zambrovski - * @version $Id$ */ -public class ModelLabelProvider extends LabelProvider implements IDescriptionProvider -{ - private Image image = TLCUIActivator.getImageDescriptor("/icons/full/choice_sc_obj.gif").createImage(); - private ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); - - /** - * Retrieves model's image - */ - public Image getImage(Object element) - { - if (element instanceof ILaunchConfiguration) - { - return image; - } - return super.getImage(element); - } +public class ModelLabelProvider extends LabelProvider implements IDescriptionProvider { + private Image image = TLCUIActivator.getImageDescriptor("/icons/full/choice_sc_obj.gif").createImage(); + private final ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); - /** - * Retrieves model's label - */ - public String getText(Object element) - { - if (element instanceof ILaunchConfiguration) - { - ILaunchConfiguration config = (ILaunchConfiguration) element; - String modelName = ModelHelper.getModelName(config.getFile()); - try - { - if (ModelHelper.isModelStale(config)) - { - return modelName + " [ crashed ]"; - } - if (ModelHelper.isModelRunning(config)) - { - ILaunch[] launches = launchManager.getLaunches(); - boolean found = false; - for (int i = 0; i < launches.length; i++) - { - if (launches[i].getLaunchConfiguration().contentsEqual(config)) - { - found = true; - break; - } - } - if (found) - { - return modelName + " [ modelchecking ]"; - } else - { - // the MC crashed - // mark the error - ModelHelper.staleModel(config); - return modelName + " [ crashed ]"; - } - } - } catch (CoreException e) - { - TLCUIActivator.getDefault().logError("Error creating description for a model", e); - } - return modelName; - } - return null; - } + /** + * Retrieves model's image + */ + public Image getImage(final Object element) { + if (element instanceof ILaunchConfiguration || element instanceof Group) { + return image; + } + return super.getImage(element); + } - /** - * Description to be shown in the status bar - */ - public String getDescription(Object element) - { - if (element instanceof ILaunchConfiguration) - { - return getText(element); - } - return null; + /** + * Retrieves model's label + */ + public String getText(final Object element) { + if (element instanceof ILaunchConfiguration) { + final ILaunchConfiguration config = (ILaunchConfiguration) element; + final String modelName = ModelHelper.getModelName(config.getFile()); + try { + if (ModelHelper.isModelStale(config)) { + return modelName + " [ crashed ]"; + } + if (ModelHelper.isModelRunning(config)) { + final ILaunch[] launches = launchManager.getLaunches(); + boolean found = false; + for (int i = 0; i < launches.length; i++) { + if (launches[i].getLaunchConfiguration().contentsEqual(config)) { + found = true; + break; + } + } + if (found) { + return modelName + " [ modelchecking ]"; + } else { + // the MC crashed + // mark the error + ModelHelper.staleModel(config); + return modelName + " [ crashed ]"; + } + } + } catch (final CoreException e) { + TLCUIActivator.getDefault().logError("Error creating description for a model", e); + } + return modelName; + } else if (element instanceof Group) { + return element.toString(); + } + return null; + } - } + /** + * Description to be shown in the status bar + */ + public String getDescription(final Object element) { + if (element instanceof ILaunchConfiguration) { + try { + final ILaunchConfiguration ilc = (ILaunchConfiguration) element; + return ilc.getAttribute(IModelConfigurationConstants.MODEL_COMMENTS, getText(element)); + } catch (CoreException e) { + return getText(element); + } + } + return null; + } - /** - * Dispose the image - */ - public void dispose() - { - if (image != null) - { - image.dispose(); - } - image = null; - super.dispose(); - } + /** + * Dispose the image + */ + public void dispose() { + if (image != null) { + image.dispose(); + } + image = null; + super.dispose(); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/ITLCPreferenceConstants.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/ITLCPreferenceConstants.java index 5d8bb492961a591f6d9137cb677e38499ef729d2..a9461f270e9b22106a775e9e8e752db92e50de8c 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/ITLCPreferenceConstants.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/ITLCPreferenceConstants.java @@ -1,14 +1,19 @@ package org.lamport.tla.toolbox.tool.tlc.ui.preference; +import org.lamport.tla.toolbox.tool.tlc.ui.view.TLCErrorView; + import tlc2.tool.fp.FPSet; /** * TLC preferences * @author Simon Zambrovski - * @version $Id$ */ public interface ITLCPreferenceConstants { + /** + * Maximum number of states to show in {@link TLCErrorView} + */ + public static final String I_TLC_TRACE_MAX_SHOW_ERRORS = "traceMaxShowErrors"; /** * Popup on TLC errors */ diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferenceInitializer.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferenceInitializer.java index c8f814b756eb362f847a156cabae50c6a97a4bac..c06de09cb71a91aefaa313cd6c642abaab6d2724 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferenceInitializer.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferenceInitializer.java @@ -23,6 +23,7 @@ public class TLCPreferenceInitializer extends AbstractPreferenceInitializer { IPreferenceStore store = TLCUIActivator.getDefault().getPreferenceStore(); + store.setDefault(ITLCPreferenceConstants.I_TLC_TRACE_MAX_SHOW_ERRORS, 10000); store.setDefault(ITLCPreferenceConstants.I_TLC_POPUP_ERRORS, true); store.setDefault(ITLCPreferenceConstants.I_TLC_REVALIDATE_ON_MODIFY, true); store.setDefault(ITLCPreferenceConstants.I_TLC_MAXIMUM_HEAP_SIZE_DEFAULT, MAX_HEAP_SIZE_DEFAULT); diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferencePage.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferencePage.java index 68614e4763b0aa215bfcd50d1278b3bf450d1a0f..b4f34012ebb71bbb6abc062f88fe6de6b6d1424d 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferencePage.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/preference/TLCPreferencePage.java @@ -1,62 +1,64 @@ -package org.lamport.tla.toolbox.tool.tlc.ui.preference; - -import org.eclipse.jface.preference.BooleanFieldEditor; -import org.eclipse.jface.preference.FieldEditorPreferencePage; -import org.eclipse.jface.preference.IntegerFieldEditor; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPreferencePage; -import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; -import org.lamport.tla.toolbox.util.IHelpConstants; -import org.lamport.tla.toolbox.util.UIHelper; - -/** - * Preferences for TLC - * @author Simon Zambrovski - * @version $Id$ - */ -public class TLCPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage -{ - - /** - * Constructor - */ - public TLCPreferencePage() - { - super(GRID); - setPreferenceStore(TLCUIActivator.getDefault().getPreferenceStore()); - setDescription("TLC Model Checker preferences"); - } - - protected Control createContents(Composite parent) - { - Control pageControl = super.createContents(parent); - UIHelper.setHelp(pageControl, IHelpConstants.TLC_PREFERENCE_PAGE); - return pageControl; - } - - /** - * Create field editors - */ - protected void createFieldEditors() - { - addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_POPUP_ERRORS, "&Always pop up TLC errors view", - getFieldEditorParent())); - - addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_REVALIDATE_ON_MODIFY, - "&Re-validate model on save", getFieldEditorParent())); - // addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_DELETE_PREVIOUS_FILES, - // "&Automatically delete unused data from previous model run", getFieldEditorParent())); - addField(new IntegerFieldEditor(ITLCPreferenceConstants.I_TLC_MAXIMUM_HEAP_SIZE_DEFAULT, - "Maximum JVM Heap Size default in % of physical system memory", getFieldEditorParent())); - addField(new IntegerFieldEditor(ITLCPreferenceConstants.I_TLC_AUTO_LOCK_MODEL_TIME, "TLC run auto-lock time", - getFieldEditorParent())); - - } - - public void init(IWorkbench workbench) - { - - } -} +package org.lamport.tla.toolbox.tool.tlc.ui.preference; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IntegerFieldEditor; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; +import org.lamport.tla.toolbox.util.IHelpConstants; +import org.lamport.tla.toolbox.util.UIHelper; + +/** + * Preferences for TLC + * @author Simon Zambrovski + */ +public class TLCPreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage +{ + + /** + * Constructor + */ + public TLCPreferencePage() + { + super(GRID); + setPreferenceStore(TLCUIActivator.getDefault().getPreferenceStore()); + setDescription("TLC Model Checker preferences"); + } + + protected Control createContents(Composite parent) + { + Control pageControl = super.createContents(parent); + UIHelper.setHelp(pageControl, IHelpConstants.TLC_PREFERENCE_PAGE); + return pageControl; + } + + /** + * Create field editors + */ + protected void createFieldEditors() + { + addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_POPUP_ERRORS, "&Always pop up TLC errors view", + getFieldEditorParent())); + + addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_REVALIDATE_ON_MODIFY, + "&Re-validate model on save", getFieldEditorParent())); + // addField(new BooleanFieldEditor(ITLCPreferenceConstants.I_TLC_DELETE_PREVIOUS_FILES, + // "&Automatically delete unused data from previous model run", getFieldEditorParent())); + addField(new IntegerFieldEditor(ITLCPreferenceConstants.I_TLC_MAXIMUM_HEAP_SIZE_DEFAULT, + "Maximum JVM Heap Size default in % of physical system memory", getFieldEditorParent())); + addField(new IntegerFieldEditor(ITLCPreferenceConstants.I_TLC_AUTO_LOCK_MODEL_TIME, "TLC run auto-lock time (in minutes)", + getFieldEditorParent())); + IntegerFieldEditor integerFieldEditor = new IntegerFieldEditor(ITLCPreferenceConstants.I_TLC_TRACE_MAX_SHOW_ERRORS, + "Maximum tail length of Trace explorer states", getFieldEditorParent()); + integerFieldEditor.setValidRange(1, Integer.MAX_VALUE); + addField(integerFieldEditor); + } + + public void init(IWorkbench workbench) + { + + } +} diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/ActionClickListener.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/ActionClickListener.java index 370de8fc072a37fd0bd256cf0edc8d3350034ed0..cd28aa9e0658b83882c53602bad7d43833efc616 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/ActionClickListener.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/ActionClickListener.java @@ -1,11 +1,19 @@ package org.lamport.tla.toolbox.tool.tlc.ui.util; +import org.eclipse.core.runtime.Platform; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Display; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCState; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; import org.lamport.tla.toolbox.util.UIHelper; @@ -27,7 +35,7 @@ import tla2sany.st.Location; * @author Daniel Ricketts * */ -public class ActionClickListener implements MouseListener { +public class ActionClickListener implements MouseListener, KeyListener { private final Viewer viewer; @@ -52,6 +60,20 @@ public class ActionClickListener implements MouseListener { */ public void mouseUp(MouseEvent e) {} + /* (non-Javadoc) + * @see org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.KeyEvent) + */ + public void keyPressed(KeyEvent e) {} + + /* (non-Javadoc) + * @see org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events.KeyEvent) + */ + public void keyReleased(final KeyEvent event) { + if (event.keyCode == SWT.CR) { + goToAction(viewer.getSelection(), (event.stateMask & SWT.CTRL) != 0); + } + } + private void goToAction(final ISelection selection, boolean jumpToPCal) { if (selection != null && !selection.isEmpty()) { if (selection instanceof StructuredSelection) { @@ -60,11 +82,19 @@ public class ActionClickListener implements MouseListener { // so taking the first element of the structured selection // should work final Object firstElement = structuredSelection.getFirstElement(); - if (firstElement instanceof IModuleLocatable) { + if (firstElement instanceof LoaderTLCState) { + final LoaderTLCState loader = (LoaderTLCState) firstElement; + // Loading more states can potentially block for a couple + // seconds. Thus, give feedback to user. + BusyIndicator.showWhile(Display.getCurrent(), new Runnable() { + public void run() { + loader.loadMore(); + } + }); + } else if (firstElement instanceof IModuleLocatable) { final IModuleLocatable moduleLocatable = (IModuleLocatable) firstElement; Location location = moduleLocatable.getModuleLocation(); if (location != null) { - /* * jumpToNested will be true if the location could be * shown in a nested saved module editor. If it is @@ -78,8 +108,48 @@ public class ActionClickListener implements MouseListener { UIHelper.jumpToLocation(location, jumpToPCal); } } + } else if (!Platform.getWS().equals(Platform.WS_WIN32) && viewer instanceof TreeViewer) { + // Windows has built-in expand/collapse on double click + TreeViewer treeViewer = (TreeViewer) viewer; + if (treeViewer.getExpandedState(firstElement)) { + treeViewer.collapseToLevel(firstElement, 1); + } else { + treeViewer.expandToLevel(firstElement, 1); + } } } } } + + public static class LoaderTLCState extends TLCState { + + private final TLCError error; + private final int numberOfStatesToShow; + private final TreeViewer viewer; + + public LoaderTLCState(TreeViewer viewer, int numberOfStatesToShow, TLCError error) { + super(-1, "Load more..."); + this.viewer = viewer; + this.numberOfStatesToShow = numberOfStatesToShow; + this.error = error; + setLabel(String.format("Load %s additional states...", numberOfStatesToShow)); + } + + public void loadMore() { + error.reduceTraceRestrictionBy(numberOfStatesToShow); + viewer.getTree().setItemCount(error.getTraceSize() + (error.isTraceRestricted() ? 1 : 0)); + // Reset the viewer's selection to the empty selection. With empty + // selection, the subsequent setInput call does *not* invalidate the + // viewer content provider's lazy policy. + // Since we know that this loadMore() method is called when the user + // clicks the first tree item (which is the LoaderTLCState), there + // is no point in preserving the selection anyway. + viewer.setSelection(new ISelection() { + public boolean isEmpty() { + return true; + } + }); + viewer.setInput(error); + } + } } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/FormHelper.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/FormHelper.java index 738ed4d7c99612e11bde0395eb503d343071fd4f..7df26d48079c7a66f8f131852a770f5217f4f528 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/FormHelper.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/util/FormHelper.java @@ -25,6 +25,7 @@ import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; import org.eclipse.ui.forms.widgets.TableWrapLayout; +import org.lamport.tla.toolbox.tool.tlc.model.Assignment; import org.lamport.tla.toolbox.tool.tlc.model.Formula; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; @@ -222,12 +223,13 @@ public class FormHelper * @param source - viewer containing the formulas/assignments * @return */ - public static List getSerializedInput(TableViewer table) + public static List<String> getSerializedInput(TableViewer table) { if (table instanceof CheckboxTableViewer) { CheckboxTableViewer source = (CheckboxTableViewer) table; - List formulas = (List) source.getInput(); + @SuppressWarnings("unchecked") + List<Formula> formulas = (List<Formula>) source.getInput(); Object[] checkedArray = source.getCheckedElements(); if (formulas == null) @@ -235,16 +237,16 @@ public class FormHelper return null; } - Vector result = new Vector(formulas.size()); - List checked = Arrays.asList(checkedArray); + Vector<String> result = new Vector<String>(formulas.size()); + List<Object> checked = Arrays.asList(checkedArray); - Iterator formulaIterator = formulas.iterator(); + Iterator<Formula> formulaIterator = formulas.iterator(); Formula formula; String entry; while (formulaIterator.hasNext()) { - formula = (Formula) formulaIterator.next(); + formula = formulaIterator.next(); entry = ((checked.contains(formula)) ? "1" : "0") + formula.toString(); result.add(entry); } @@ -253,7 +255,8 @@ public class FormHelper } else { - List assignments = (List) table.getInput(); + @SuppressWarnings("unchecked") + List<Assignment> assignments = (List<Assignment>) table.getInput(); if (assignments == null) { return null; @@ -268,23 +271,24 @@ public class FormHelper /** * Performs the inverse operation to {@link FormHelper#getSerializedInput(CheckboxTableViewer)} */ - public static void setSerializedInput(TableViewer table, List serializedInput) + public static void setSerializedInput(TableViewer table, List<String> serializedInput) { - Vector input = ((Vector) table.getInput()); + @SuppressWarnings("unchecked") + Vector<Formula> input = ((Vector<Formula>) table.getInput()); if (input == null) { - input = new Vector(); + input = new Vector<Formula>(); } // handling Formulas if (table instanceof CheckboxTableViewer) { - Iterator serializedIterator = serializedInput.iterator(); - Vector checked = new Vector(); + Iterator<String> serializedIterator = serializedInput.iterator(); + Vector<Formula> checked = new Vector<Formula>(); CheckboxTableViewer checkTable = (CheckboxTableViewer) table; while (serializedIterator.hasNext()) { - String entry = (String) serializedIterator.next(); + String entry = serializedIterator.next(); Formula formula = new Formula(entry.substring(1)); input.add(formula); if ("1".equals(entry.substring(0, 1))) @@ -298,7 +302,7 @@ public class FormHelper } else // handling Assignments { - List deserializeAssignmentList = ModelHelper.deserializeAssignmentList(serializedInput); + List<Assignment> deserializeAssignmentList = ModelHelper.deserializeAssignmentList(serializedInput); table.setInput(deserializeAssignmentList); } diff --git a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorView.java b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorView.java index 34fe05c70784cf5d3953d7d233c76a3d02f86834..a1500d2a745b324af76b2216aed437089131ec21 100644 --- a/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorView.java +++ b/org.lamport.tla.toolbox.tool.tlc.ui/src/org/lamport/tla/toolbox/tool/tlc/ui/view/TLCErrorView.java @@ -1,9 +1,7 @@ package org.lamport.tla.toolbox.tool.tlc.ui.view; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Vector; import java.util.regex.Pattern; @@ -20,18 +18,17 @@ import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.ILazyTreeContentProvider; +import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITableColorProvider; -import org.eclipse.jface.viewers.ITableFontProvider; -import org.eclipse.jface.viewers.ITableLabelProvider; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerFilter; -import org.eclipse.jface.viewers.ViewerSorter; +import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyledText; @@ -62,10 +59,12 @@ import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.part.ViewPart; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants; +import org.lamport.tla.toolbox.tool.tlc.model.Formula; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCError; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCFcnElementVariableValue; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCFunctionVariableValue; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCModelLaunchDataProvider; +import org.lamport.tla.toolbox.tool.tlc.output.data.TLCMultiVariableValue; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCNamedVariableValue; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCRecordVariableValue; import org.lamport.tla.toolbox.tool.tlc.output.data.TLCSequenceVariableValue; @@ -79,6 +78,7 @@ import org.lamport.tla.toolbox.tool.tlc.traceexplorer.TraceExplorerComposite; import org.lamport.tla.toolbox.tool.tlc.ui.TLCUIActivator; import org.lamport.tla.toolbox.tool.tlc.ui.preference.ITLCPreferenceConstants; import org.lamport.tla.toolbox.tool.tlc.ui.util.ActionClickListener; +import org.lamport.tla.toolbox.tool.tlc.ui.util.ActionClickListener.LoaderTLCState; import org.lamport.tla.toolbox.tool.tlc.ui.util.FormHelper; import org.lamport.tla.toolbox.tool.tlc.ui.util.TLCUIHelper; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; @@ -93,18 +93,15 @@ import tlc2.output.MP; * explorer. This is the view of the error description. * * @author Simon Zambrovski - * @version $Id$ */ - public class TLCErrorView extends ViewPart { + private static final String INNER_WEIGHTS_KEY = "INNER_WEIGHTS_KEY"; private static final String OUTER_WEIGHTS_KEY = "OUTER_WEIGHTS_KEY"; public static final String ID = "toolbox.tool.tlc.view.TLCErrorView"; - private static final String TOOLTIP = "Click on a row to see in viewer, double-click to go to action in spec."; - /** * This is the pattern of an error message resulting from evaluating the constant * expression entered in the expression field by the user. @@ -122,6 +119,8 @@ public class TLCErrorView extends ViewPart { return new Document("Select line in Error Trace to show its value here."); } + + private int numberOfStatesToShow; private FormToolkit toolkit; private Form form; @@ -139,13 +138,18 @@ public class TLCErrorView extends ViewPart // listener on changes to the tlc output font preference private FontPreferenceChangeListener fontChangeListener; + public TLCErrorView() { + numberOfStatesToShow = TLCUIActivator.getDefault().getPreferenceStore() + .getInt(ITLCPreferenceConstants.I_TLC_TRACE_MAX_SHOW_ERRORS); + } + /** * Clears the view */ public void clear() { errorViewer.setDocument(EMPTY_DOCUMENT()); - setTraceInput(Collections.EMPTY_LIST); + setTraceInput(new TLCError()); traceExplorerComposite.getTableViewer().setInput(new Vector<TLCState>()); traceExplorerComposite.changeExploreEnablement(false); valueViewer.setInput(EMPTY_DOCUMENT()); @@ -161,33 +165,25 @@ public class TLCErrorView extends ViewPart * @param problems * a list of {@link TLCError} objects representing the errors. */ - protected void fill(String modelName, List<TLCError> problems) + protected void fill(String modelName, List<TLCError> problems, final List<String> serializedInput) { - - try - { - /* - * Fill the trace explorer expression table - * with expressions saved in the config. - * - * Setting the input of the trace explorer composite - * table viewer to an empty vector is done to avoid adding duplicates. - * - * FormHelper.setSerializedInput adds the elements from the config - * to the table viewer. - */ - traceExplorerComposite.getTableViewer().setInput(new Vector()); - FormHelper.setSerializedInput(traceExplorerComposite.getTableViewer(), configFileHandle.getAttribute( - IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS, new Vector())); - } catch (CoreException e) - { - TLCUIActivator.getDefault().logError("Error loading trace explorer expressions into table", e); - } + /* + * Fill the trace explorer expression table with expressions saved in + * the config. + * + * Setting the input of the trace explorer composite table viewer to an + * empty vector is done to avoid adding duplicates. + * + * FormHelper.setSerializedInput adds the elements from the config to + * the table viewer. + */ + traceExplorerComposite.getTableViewer().setInput(new Vector<Formula>()); + FormHelper.setSerializedInput(traceExplorerComposite.getTableViewer(), serializedInput); // if there are errors + TLCError trace = null; if (problems != null && !problems.isEmpty()) { - List<TLCState> states = null; StringBuffer buffer = new StringBuffer(); // iterate over the errors for (int i = 0; i < problems.size(); i++) @@ -199,31 +195,13 @@ public class TLCErrorView extends ViewPart // read out the trace if any if (error.hasTrace()) { - Assert.isTrue(states == null, "Two traces are provided. Unexpected. This is a bug"); - states = error.getStates(); + Assert.isTrue(trace == null, "Two traces are provided. Unexpected. This is a bug"); + trace = error; + } } - } - if (states == null) + if (trace == null) { - states = new LinkedList<TLCState>(); - } - - /* - * determine if trace has changed. this is important for really long - * traces because resetting the trace input locks up the toolbox for a few - * seconds in these cases, so it is important to not reset the trace - * if it is not necessary - */ - List<TLCState> oldStates = (List<TLCState>) variableViewer.getInput(); - boolean isNewTrace = states != null && oldStates != null && !(states == oldStates); - - /* - * Set the data structures that cause highlighting of changes in the - * error trace. - */ - if (isNewTrace) - { - setDiffInfo(states); + trace = new TLCError(); } IDocument document = errorViewer.getDocument(); @@ -236,17 +214,20 @@ public class TLCErrorView extends ViewPart TLCUIActivator.getDefault().logError("Error reporting the error " + buffer.toString(), e); } + /* + * determine if trace has changed. this is important for really long + * traces because resetting the trace input locks up the toolbox for a few + * seconds in these cases, so it is important to not reset the trace + * if it is not necessary + */ + TLCError oldTrace = (TLCError) variableViewer.getInput(); + boolean isNewTrace = trace != null && oldTrace != null && !(trace == oldTrace); // update the trace information if (isNewTrace) { - this.setTraceInput(states); + this.setTraceInput(trace); traceExplorerComposite.changeExploreEnablement(true); } - if (states != null && !states.isEmpty()) - { - variableViewer.expandToLevel(2); - } - this.form.setText(modelName); } else @@ -426,29 +407,52 @@ public class TLCErrorView extends ViewPart // gd.grabExcessHorizontalSpace = true ; tree.setLayoutData(gd); - tree.setToolTipText(TOOLTIP); // Initialize the trace display's resizer. TraceDisplayResizer resizer = new TraceDisplayResizer(); resizer.comp = sashForm; resizer.tree = tree; + + tree.addControlListener(resizer); + + variableViewer = new TreeViewer(tree); + final StateContentProvider provider = new StateContentProvider(variableViewer); + variableViewer.setUseHashlookup(true); + variableViewer.setContentProvider(provider); + ColumnViewerToolTipSupport.enableFor(variableViewer); + getSite().setSelectionProvider(variableViewer); - final StateViewerSorter sorter = new StateViewerSorter(); + final StateLabelProvider labelProvider = new StateLabelProvider(); for (int i = 0; i < StateLabelProvider.COLUMN_TEXTS.length; i++) { - TreeColumn column = new TreeColumn(tree, SWT.LEFT); - column.setText(StateLabelProvider.COLUMN_TEXTS[i]); - column.setWidth(StateLabelProvider.COLUMN_WIDTH[i]); - resizer.column[i] = column; // set up the resizer. - column.setToolTipText(TOOLTIP); - column.addSelectionListener(new SelectionAdapter() { - public void widgetSelected(SelectionEvent e) { - sorter.reverseStateSortDirection(); - variableViewer.refresh(); + final TreeViewerColumn column = new TreeViewerColumn(variableViewer, i); + column.getColumn().setText(StateLabelProvider.COLUMN_TEXTS[i]); + column.getColumn().setWidth(StateLabelProvider.COLUMN_WIDTH[i]); + column.setLabelProvider(labelProvider); + resizer.column[i] = column.getColumn(); // set up the resizer. + column.getColumn().addSelectionListener(new SelectionAdapter() { + public void widgetSelected(final SelectionEvent e) { + // reverse the current trace + final TLCError error = (TLCError) variableViewer.getInput(); + error.reverseTrace(); + // Reset the viewer's selection to the empty selection. With empty + // selection, the subsequent refresh call does *not* invalidate the + // StateContentProvider's lazy policy. + // We know that the user clicked on the tree's column header + // and the real selection is of little importance. + variableViewer.setSelection(new ISelection() { + public boolean isEmpty() { + return true; + } + }); + variableViewer.refresh(false); + + // remember the order for next trace shown + final IDialogSettings dialogSettings = Activator.getDefault().getDialogSettings(); + dialogSettings.put(TLCModelLaunchDataProvider.STATESORTORDER, + !dialogSettings.getBoolean(TLCModelLaunchDataProvider.STATESORTORDER)); } }); } - - tree.addControlListener(resizer); // I need to add a listener for size changes to column[0] to // detect when the user has tried to resize the individual columns. @@ -458,14 +462,8 @@ public class TLCErrorView extends ViewPart // be? resizer.column[0].addListener(eventType, resizer); - variableViewer = new TreeViewer(tree); - variableViewer.setContentProvider(new StateContentProvider()); - variableViewer.setFilters(new ViewerFilter[] { new StateFilter() }); - variableViewer.setLabelProvider(new StateLabelProvider()); - variableViewer.setSorter(sorter); - getSite().setSelectionProvider(variableViewer); - variableViewer.getTree().addMouseListener(new ActionClickListener(variableViewer)); + variableViewer.getTree().addKeyListener(new ActionClickListener(variableViewer)); variableViewer.addSelectionChangedListener(new ISelectionChangedListener() { @@ -663,16 +661,23 @@ public class TLCErrorView extends ViewPart { return; } + updateErrorView(provider, config, openErrorView); + } catch (CoreException e) + { + TLCUIActivator.getDefault().logError("Error determining if trace explorer expressions should be shown", e); + } + } + + public static void updateErrorView(final TLCModelLaunchDataProvider provider, final ILaunchConfiguration config, + boolean openErrorView) { + try { TLCErrorView errorView; - if (provider.getErrors().size() > 0 && openErrorView == true) - { + if (provider.getErrors().size() > 0 && openErrorView == true) { errorView = (TLCErrorView) UIHelper.openView(TLCErrorView.ID); - } else - { + } else { errorView = (TLCErrorView) UIHelper.findView(TLCErrorView.ID); } - if (errorView != null) - { + if (errorView != null) { /* * We need a handle on the actual underlying configuration file handle * in order to retrieve the expressions that should be put in the trace @@ -680,24 +685,23 @@ public class TLCErrorView extends ViewPart * all of the expressions that should appear. The filling of the trace * explorer table occurs in the fill() method. */ - if (config.isWorkingCopy()) - { + if (config.isWorkingCopy()) { errorView.configFileHandle = ((ILaunchConfigurationWorkingCopy) config).getOriginal(); - } else - { + } else { errorView.configFileHandle = config; } + final List<String> serializedInput = errorView.configFileHandle + .getAttribute(IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS, new Vector<String>()); // fill the name and the errors - errorView.fill(ModelHelper.getModelName(provider.getConfig().getFile()), provider.getErrors()); + errorView.fill(ModelHelper.getModelName(provider.getConfig().getFile()), provider.getErrors(), + serializedInput); - if (provider.getErrors().size() == 0) - { + if (provider.getErrors().size() == 0) { errorView.hide(); } } - } catch (CoreException e) - { + } catch (CoreException e) { TLCUIActivator.getDefault().logError("Error determining if trace explorer expressions should be shown", e); } @@ -814,159 +818,162 @@ public class TLCErrorView extends ViewPart /** * Content provider for the tree table */ - static class StateContentProvider implements ITreeContentProvider - // - // evtl. for path-based addressing in the tree - // , ITreePathContentProvider - { - - public Object[] getChildren(Object parentElement) - { - if (parentElement instanceof List) - { - return (TLCState[]) ((List) parentElement).toArray(new TLCState[((List) parentElement).size()]); - } else if (parentElement instanceof TLCState) - { - TLCState state = (TLCState) parentElement; - if (!state.isStuttering() && !state.isBackToState()) - { - return state.getVariables(); - } - } else if (parentElement instanceof TLCVariable) - { - TLCVariable variable = (TLCVariable) parentElement; - TLCVariableValue value = variable.getValue(); - if (value instanceof TLCSetVariableValue) - { - return ((TLCSetVariableValue) value).getElements(); - } else if (value instanceof TLCSequenceVariableValue) - { - return ((TLCSequenceVariableValue) value).getElements(); - } else if (value instanceof TLCFunctionVariableValue) - { - return ((TLCFunctionVariableValue) value).getFcnElements(); - } else if (value instanceof TLCRecordVariableValue) - { - return ((TLCRecordVariableValue) value).getPairs(); - } - return null; - } else if (parentElement instanceof TLCVariableValue) - { - TLCVariableValue value = (TLCVariableValue) parentElement; - if (value instanceof TLCSetVariableValue) - { - return ((TLCSetVariableValue) value).getElements(); - } else if (value instanceof TLCSequenceVariableValue) - { - return ((TLCSequenceVariableValue) value).getElements(); - } else if (value instanceof TLCFunctionVariableValue) - { - return ((TLCFunctionVariableValue) value).getFcnElements(); - } else if (value instanceof TLCRecordVariableValue) - { - return ((TLCRecordVariableValue) value).getPairs(); - } else if (value instanceof TLCNamedVariableValue) - { - return getChildren(((TLCNamedVariableValue) value).getValue()); - } else if (value instanceof TLCFcnElementVariableValue) - { - return getChildren(((TLCFcnElementVariableValue) value).getValue()); - } - return null; - } - return null; - } - - public Object getParent(Object element) - { - return null; - } - - public boolean hasChildren(Object element) - { - if (element instanceof List) - return true; - - return (getChildren(element) != null); - } - - public Object[] getElements(Object inputElement) - { - return getChildren(inputElement); - } - - public void dispose() - { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) - { - } - } - - static class StateFilter extends ViewerFilter - { - - public boolean select(Viewer viewer, Object parentElement, Object element) - { - return true; - } - - } - - static class StateViewerSorter extends ViewerSorter { - - private static final String STATESORTORDER = "STATESORTORDER"; - - /** - * Sort order in which states are sorted in the variable viewer - */ - private boolean stateSortDirection; + private class StateContentProvider implements ILazyTreeContentProvider { + + private final TreeViewer viewer; + private List<TLCState> states = new ArrayList<TLCState>(0); + + public StateContentProvider(TreeViewer viewer) { + this.viewer = viewer; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + public void dispose() { + } - private final IDialogSettings dialogSettings; + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object) + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // Eagerly cache the list of states as it can be a sublist of the + // complete trace. Getting the sublist in the updateElement method + // means we obtain it over and over again for each top-level tree + // item. + if (newInput instanceof TLCError) { + this.states = ((TLCError) newInput).getStates(); + } else if (newInput == null) { + this.states = new ArrayList<TLCState>(0); + } else { + throw new IllegalArgumentException(); + } + } - public StateViewerSorter() { - dialogSettings = Activator.getDefault().getDialogSettings(); - stateSortDirection = dialogSettings.getBoolean(STATESORTORDER); + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ILazyTreeContentProvider#updateElement(java.lang.Object, int) + */ + public void updateElement(Object parent, int viewerIndex) { + if (parent instanceof TLCError) { + final TLCError error = (TLCError) parent; + if (error.isTraceRestricted() && viewerIndex == 0) { + // If only a subset of the trace is shown, show a dummy item + // at the top which can be double-clicked to load more. + viewer.replace(parent, viewerIndex, new ActionClickListener.LoaderTLCState(viewer, + Math.min(numberOfStatesToShow, error.getNumberOfRestrictedTraceStates()), error)); + return; + } + // decrease index into states by one if the viewers first element is a dummy item + final int statesIndex = viewerIndex - (error.isTraceRestricted() ? 1 : 0); + final TLCState child = states.get(statesIndex); + // Diffing is supposed to be lazy and thus is done here when + // the state is first used by the viewer. The reason why it + // has to be lazy is to be able to efficiently handle traces + // with hundreds or thousands of states where it would be a + // waste to diff all state pairs even if the user is never + // going to look at all states anyway. + // TODO If ever comes up as a performance problem again, the + // nested TLCVariableValues could also be diffed lazily. + if (statesIndex > 0) { + final TLCState predecessor = states.get(statesIndex - 1); + predecessor.diff(child); + } + viewer.replace(parent, viewerIndex, child); + if (child.getVariablesAsList().size() > 0) { + viewer.setHasChildren(child, true); + } + // Lazily expand the children + viewer.expandToLevel(child, 1); + } else if (parent instanceof TLCState) { + final TLCState state = (TLCState) parent; + if ((state.isStuttering() || state.isBackToState())) { + viewer.setChildCount(state, 0); + } else { + final List<TLCVariable> variablesAsList = state.getVariablesAsList(); + if (variablesAsList.size() > viewerIndex) { + final TLCVariable child = variablesAsList.get(viewerIndex); + viewer.replace(parent, viewerIndex, child); + if (child.getChildCount() > 0) { + viewer.setHasChildren(child, true); + } + } + } + } else if (parent instanceof TLCVariable + && ((TLCVariable) parent).getValue() instanceof TLCMultiVariableValue) { + final TLCMultiVariableValue multiValue = (TLCMultiVariableValue) ((TLCVariable) parent).getValue(); + final TLCVariableValue child = multiValue.asList().get(viewerIndex); + viewer.replace(parent, viewerIndex, child); + if (child.getChildCount() > 0) { + viewer.setHasChildren(child, true); + } + } else if (parent instanceof TLCVariable) { + final TLCVariable variable = (TLCVariable) parent; + final TLCVariableValue child = variable.getValue(); + viewer.replace(parent, viewerIndex, child); + if (child.getChildCount() > 0) { + viewer.setChildCount(child, child.getChildCount()); + } + } else if (parent instanceof TLCMultiVariableValue) { + final TLCMultiVariableValue multiValue = (TLCMultiVariableValue) parent; + final TLCVariableValue child = multiValue.asList().get(viewerIndex); + viewer.replace(parent, viewerIndex, child); + if (child.getChildCount() > 0) { + viewer.setHasChildren(child, true); + } + } else if (parent instanceof TLCVariableValue + && ((TLCVariableValue) parent).getValue() instanceof TLCMultiVariableValue) { + final TLCMultiVariableValue multiValue = (TLCMultiVariableValue) ((TLCVariableValue) parent).getValue(); + final TLCVariableValue child = multiValue.asList().get(viewerIndex); + viewer.replace(parent, viewerIndex, child); + if (child.getChildCount() > 0) { + viewer.setHasChildren(child, true); + } + } else { + throw new IllegalArgumentException(); + } } - - public void reverseStateSortDirection() { - stateSortDirection = !stateSortDirection; - dialogSettings.put(STATESORTORDER, stateSortDirection); - } - - public int compare(final Viewer viewer, final Object e1, final Object e2) { - // The error trace has to be sorted on the number of the state. An - // unordered state sequence is rather incomprehensible. The default - // is ordering the state trace first to last for educational reasons. - // Advanced users are free to click the table column headers to permanently - // change the order. - if (e1 instanceof TLCState && e2 instanceof TLCState) { - final TLCState s1 = (TLCState) e1; - final TLCState s2 = (TLCState) e2; - Integer is1 = s1.getStateNumber(); - Integer is2 = s2.getStateNumber(); - - // If either is a back state, make sure they are larger - // than any regular state. If both are back states, simply - // compare their state number. The latter case is AFAICT not - // possible. - if (s1.isBackToState() && !s2.isBackToState()) { - is1 = Integer.MAX_VALUE; + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ILazyTreeContentProvider#updateChildCount(java.lang.Object, int) + */ + public void updateChildCount(Object element, int currentChildCount) { + if (element instanceof TLCError) { + final TLCError error = (TLCError) element; + int traceSize = error.getTraceSize(); + if (traceSize != currentChildCount) { + if (error.isTraceRestricted()) { + viewer.setChildCount(element, traceSize + 1); + } else { + viewer.setChildCount(element, traceSize); + } } - else if (s2.isBackToState() && !s1.isBackToState()) { - is2 = Integer.MAX_VALUE; + } else if (element instanceof TLCState) { + final TLCState state = (TLCState) element; + if (((state.isStuttering() || state.isBackToState()) && currentChildCount != 0)) { + viewer.setChildCount(element, 0); + } else if (currentChildCount != state.getVariablesAsList().size()) { + viewer.setChildCount(element, state.getVariablesAsList().size()); } - - // Two regular states, delegate to state number - if(!stateSortDirection) { // negated because the default coming from DialogSettings is false - return Integer.valueOf(is1).compareTo(is2); - } else { - return Integer.valueOf(is2).compareTo(is1); + } else if (element instanceof TLCVariable) { + final TLCVariable variable = (TLCVariable) element; + if (currentChildCount != variable.getChildCount()) { + viewer.setChildCount(element, variable.getChildCount()); + } + } else if (element instanceof TLCVariableValue) { + final TLCVariableValue value = (TLCVariableValue) element; + if (currentChildCount != value.getChildCount()) { + viewer.setChildCount(element, value.getChildCount()); } + } else { + throw new IllegalArgumentException(); } - // Sort just on the label provided by the label provider - return super.compare(viewer, e1, e2); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.ILazyTreeContentProvider#getParent(java.lang.Object) + */ + public Object getParent(Object element) { + return null; } } @@ -975,8 +982,7 @@ public class TLCErrorView extends ViewPart * implement ITableColorProvider instead of IColorProvider. This allows * coloring of individual columns, not just of entire rows. */ - static class StateLabelProvider extends LabelProvider implements ITableLabelProvider, ITableColorProvider, - ITableFontProvider // IColorProvider + static class StateLabelProvider extends CellLabelProvider { public static final int NAME = 0; public static final int VALUE = 1; @@ -984,11 +990,12 @@ public class TLCErrorView extends ViewPart public static final int[] COLUMN_WIDTH = { 200, 200 }; public static final String[] COLUMN_TEXTS = { "Name", "Value" }; - private Image stateImage; - private Image varImage; - private Image recordImage; - private Image setImage; - + private final Image stateImage; + private final Image varImage; + private final Image recordImage; + private final Image setImage; + private final Image loadMoreImage; + public StateLabelProvider() { stateImage = TLCUIActivator.getImageDescriptor("/icons/full/default_co.gif").createImage(); @@ -996,12 +1003,16 @@ public class TLCErrorView extends ViewPart recordImage = TLCUIActivator.getImageDescriptor("/icons/full/brkpi_obj.gif").createImage(); // setImage = TLCUIActivator.getImageDescriptor("/icons/full/over_co.gif").createImage(); setImage = TLCUIActivator.getImageDescriptor("/icons/full/compare_method.gif").createImage(); + loadMoreImage = TLCUIActivator.getImageDescriptor("/icons/full/add.gif").createImage(); // other candidate is newstream_wiz.gif, nav_go.gif, debugt_obj.gif } - public Image getColumnImage(Object element, int columnIndex) + private Image getColumnImage(Object element, int columnIndex) { if (columnIndex == NAME) { + if (element instanceof LoaderTLCState) { + return loadMoreImage; + } if (element instanceof TLCState) { return stateImage; @@ -1020,7 +1031,7 @@ public class TLCErrorView extends ViewPart return null; } - public String getColumnText(Object element, int columnIndex) + private String getColumnText(Object element, int columnIndex) { if (element instanceof TLCState) { @@ -1037,7 +1048,11 @@ public class TLCErrorView extends ViewPart } return state.getLabel(); case VALUE: - return "State (num = " + state.getStateNumber() + ")"; + if (state instanceof ActionClickListener.LoaderTLCState) { + return ""; + } else { + return "State (num = " + state.getStateNumber() + ")"; + } // state.toString(); default: break; @@ -1130,74 +1145,33 @@ public class TLCErrorView extends ViewPart * the table. It highlights the entire row for an added or deleted item. * For a changed value, only the value is highlighted. */ - public Color getBackground(Object element, int column) - { - if (changedRows.contains(element)) - { - if (column == VALUE) - { - return TLCUIActivator.getDefault().getChangedColor(); - } - } else if (addedRows.contains(element)) - { - return TLCUIActivator.getDefault().getAddedColor(); - } else if (deletedRows.contains(element)) - { - return TLCUIActivator.getDefault().getDeletedColor(); - } - return null; - } - - /* - * Here are the three HashSet objects that contain the objects - * representing rows in the table displaying the trace that should be - * highlighted. They have the following meanings: - * - * changedRows: Rows indicating values that have changed from the last - * state. Subobjects of the value column of such a row could also be - * highlighted. - * - * addedRows: Rows that have been added to a value since the last state. - * - * deletedRows: Rows that are deleted in the following state. - * - * The same row can appear in both the deletedRows set and the - * changedRows or addedRows set. In that case, it should be displayed as - * a changed or added row--since we can't do multicolored backgrounds to - * show that it is both. - */ - protected HashSet<Object> changedRows = new HashSet<Object>(); - protected HashSet<TLCVariableValue> addedRows = new HashSet<TLCVariableValue>(); - protected HashSet<TLCVariableValue> deletedRows = new HashSet<TLCVariableValue>(); + private Color getBackground(Object element, int column) { + if (element instanceof TLCVariable) { + final TLCVariable var = (TLCVariable) element; + if (var.isChanged() && column == VALUE) { + return TLCUIActivator.getDefault().getChangedColor(); + } + } else if (element instanceof TLCVariableValue) { + final TLCVariableValue value = (TLCVariableValue) element; + if (value.isChanged()) { + if (column == VALUE) { + return TLCUIActivator.getDefault().getChangedColor(); + } + } else if (value.isAdded()) { + return TLCUIActivator.getDefault().getAddedColor(); + } else if (value.isDeleted()) { + return TLCUIActivator.getDefault().getDeletedColor(); + } + } + return null; + } - public Color getForeground(Object element, int i) + private Color getForeground(Object element, int i) { return null; } - public Image getImage(Object element) - { - return getColumnImage(element, 0); - } - - public String getText(Object element) - { - return getColumnText(element, 0); - } - - public void dispose() - { - /* - * Remove images - */ - stateImage.dispose(); - varImage.dispose(); - recordImage.dispose(); - setImage.dispose(); - super.dispose(); - } - - public Font getFont(Object element, int columnIndex) + private Font getFont(Object element, int columnIndex) { if (element instanceof TLCVariable) { @@ -1206,388 +1180,64 @@ public class TLCErrorView extends ViewPart { return JFaceResources.getFontRegistry().getBold(""); } + } else if (element instanceof ActionClickListener.LoaderTLCState) { + return JFaceResources.getFontRegistry().getBold(""); } return null; } - } - - /* - * Sets the HashSet objects of StateLabelProvider object that stores the - * sets of objects to be highlighted to show state changes in the states - * contained in the parameter stateList. - */ - private void setDiffInfo(List<TLCState> stateList) - { - if (stateList.size() < 2) - { - return; - } - - /* - * Set states to the array of TLCState objects in stateList, and set - * changedRows, addedRows, and deletedRows to the HashSet into which all - * the appropriate row objects are put, and initialize each HashSet to - * empty. + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.BaseLabelProvider#dispose() */ - TLCState[] states = new TLCState[stateList.size()]; - for (int i = 0; i < states.length; i++) - { - states[i] = stateList.get(i); - } - StateLabelProvider labelProvider = (StateLabelProvider) variableViewer.getLabelProvider(); - HashSet<Object> changedRows = labelProvider.changedRows; - HashSet<TLCVariableValue> addedRows = labelProvider.addedRows; - HashSet<TLCVariableValue> deletedRows = labelProvider.deletedRows; - changedRows.clear(); - addedRows.clear(); - deletedRows.clear(); - - TLCState firstState = states[0]; - TLCVariable[] firstVariables = firstState.getVariables(); - - for (int i = 1; i < states.length; i++) - { - TLCState secondState = states[i]; - if (secondState.isStuttering() || secondState.isBackToState()) - { - // there are no variables in second state - // because it is a stuttering or a back to state - // step - break; - } - TLCVariable[] secondVariables = secondState.getVariables(); - for (int j = 0; j < firstVariables.length; j++) - { - TLCVariableValue firstValue = firstVariables[j].getValue(); - TLCVariableValue secondValue = secondVariables[j].getValue(); - if (!firstValue.toSimpleString().equals(secondValue.toSimpleString())) - { - changedRows.add(secondVariables[j]); - setInnerDiffInfo(firstValue, secondValue, changedRows, addedRows, deletedRows); - } - } - - firstState = secondState; - firstVariables = secondVariables; - } - - } - - /** - * The recursive method called by setDiffInfo that adds the subobjects of - * the variable value objects to the HashSets that indicate which rows of - * the hierarchical trace table should be highlighted to show the parts of - * the state that have changed. - * - * It is called with the objects in the Value columns of corresponding - * values that have changed. It adds rows of these two objects' table - * representations to the appropriate HashSets to indicate that those rows - * should be appropriately highlighted. - */ - private void setInnerDiffInfo(TLCVariableValue first, TLCVariableValue second, HashSet<Object> changed, HashSet<TLCVariableValue> added, - HashSet<TLCVariableValue> deleted) - { - if (first instanceof TLCSimpleVariableValue) - { - return; - } else if (first instanceof TLCSetVariableValue) - { /* - * SETS For two - * sets, the only - * meaningful - * changes are - * additions and - * deletions. - */ - - if (!(second instanceof TLCSetVariableValue)) - { - return; - } - TLCVariableValue[] firstElts = ((TLCSetVariableValue) first).getElements(); - TLCVariableValue[] secondElts = ((TLCSetVariableValue) second).getElements(); - - for (int i = 0; i < firstElts.length; i++) - { - boolean notfound = true; - int j = 0; - while (notfound && j < secondElts.length) - { - if (firstElts[i].toSimpleString().equals(secondElts[j].toSimpleString())) - { - notfound = false; - } - j++; - } - if (notfound) - { - deleted.add(firstElts[i]); - } - } - - for (int i = 0; i < secondElts.length; i++) - { - boolean notfound = true; - int j = 0; - while (notfound && j < firstElts.length) - { - if (firstElts[j].toSimpleString().equals(secondElts[i].toSimpleString())) - { - notfound = false; - } - j++; - } - if (notfound) - { - added.add(secondElts[i]); - } - } - } else if (first instanceof TLCRecordVariableValue) - { - /* - * RECORDS We mark a record element as added or deleted if its label - * does not appear in one of the elements of the other record. We - * mark the element as changed, and call setInnerDiffInfo on the - * elements' values if elements with same label but different values - * appear in the two records. - */ - if (!(second instanceof TLCRecordVariableValue)) - { - return; - } - TLCVariableValue[] firstElts = ((TLCRecordVariableValue) first).getPairs(); - TLCVariableValue[] secondElts = ((TLCRecordVariableValue) second).getPairs(); - - String[] firstLHStrings = new String[firstElts.length]; - for (int i = 0; i < firstElts.length; i++) - { - firstLHStrings[i] = ((TLCNamedVariableValue) firstElts[i]).getName(); - } - String[] secondLHStrings = new String[secondElts.length]; - for (int i = 0; i < secondElts.length; i++) - { - secondLHStrings[i] = ((TLCNamedVariableValue) secondElts[i]).getName(); - } - - setElementArrayDiffInfo(firstElts, firstLHStrings, secondElts, secondLHStrings, changed, added, deleted); - } else if (first instanceof TLCFunctionVariableValue) - { - /* - * FUNCTIONS We mark a record element as added or deleted if its - * label does not appear in one of the elements of the other record. - * We mark the element as changed, and call setInnerDiffInfo on the - * elements' values if elements with same label but different values - * appear in the two records. - */ - if (!(second instanceof TLCFunctionVariableValue)) - { - return; - } - - setFcnElementArrayDiffInfo(((TLCFunctionVariableValue) first).getFcnElements(), - ((TLCFunctionVariableValue) second).getFcnElements(), changed, added, deleted); - - } - - else if (first instanceof TLCSequenceVariableValue) + public void dispose() { /* - * SEQUENCES In general, it's not clear how differences between two - * sequences should be highlighted. We adopt the following - * preliminary approach: If one sequence is a proper initial prefix - * or suffix of the other, then the difference is interpreted as - * adding or deleting the appropriate sequence elements. Otherwise, - * the sequences are treated as functions. - * - * Note: If one sequence is both an initial prefix and a suffix of - * the other then we give preference to interpreting the operation - * as adding to the end or removing from the front. - */ - if (!(second instanceof TLCSequenceVariableValue)) - { - return; - } - TLCFcnElementVariableValue[] firstElts = ((TLCSequenceVariableValue) first).getElements(); - TLCFcnElementVariableValue[] secondElts = ((TLCSequenceVariableValue) second).getElements(); - if (firstElts.length == secondElts.length) - { - setFcnElementArrayDiffInfo(firstElts, secondElts, changed, added, deleted); - return; - } - - TLCFcnElementVariableValue[] shorter = firstElts; - TLCFcnElementVariableValue[] longer = secondElts; - boolean firstShorter = true; - if (firstElts.length > secondElts.length) - { - longer = firstElts; - shorter = secondElts; - firstShorter = false; - } - boolean isPrefix = true; - for (int i = 0; i < shorter.length; i++) - { - if (!((TLCVariableValue) shorter[i].getValue()).toSimpleString().equals( - ((TLCVariableValue) longer[i].getValue()).toSimpleString())) - { - isPrefix = false; - break; - } - } - boolean isSuffix = true; - for (int i = 0; i < shorter.length; i++) - { - if (!((TLCVariableValue) shorter[i].getValue()).toSimpleString().equals( - ((TLCVariableValue) longer[i + longer.length - shorter.length].getValue()).toSimpleString())) - { - - isSuffix = false; - break; - } - } - /* - * If it's both a prefix and a suffix, we interpret the change as - * either adding to the end or deleting from the front. If it's - * neither, we treat the sequences as functions. - */ - if (isPrefix && isSuffix) - { - if (firstShorter) - { - isSuffix = false; - } else - { - isPrefix = false; - } - } else if (!(isPrefix || isSuffix)) - { - setFcnElementArrayDiffInfo(firstElts, secondElts, changed, added, deleted); - return; - } - /* - * There are four cases: isPrefix and firstShorter : we mark end of - * longer (= second) as added. isPrefix and !firstShorter : we mark - * end of longer (= first) as deleted. isSuffix and firstShorter : - * we mark beginning of longer (=second) as added. isSuffix and - * !firstShorter : we mark beginning of longer (=first) as deleted. + * Remove images */ - int firstEltToMark = (isPrefix) ? shorter.length : 0; - HashSet<TLCVariableValue> howToMark = (firstShorter) ? added : deleted; - - for (int i = 0; i < longer.length - shorter.length; i++) - { - howToMark.add(longer[i + firstEltToMark]); - } - // setFcnElementArrayDiffInfo(firstElts, secondElts, changed, added, - // deleted); - } - - return; - - } - - /** - * A method that sets the diff highlighting information for two arrays of - * either TLCFcnElementVariableValue or TLCNamedVariableValue objects, - * representing the value elements of twos values represented by - * TLCFunctionVariableValue, TLCRecordVariableValue, or - * TLCSequenceVariableValue objects. The parameters firstElts and secondElts - * are the two arrays, and firstLHStrings and secondLHStrings are the - * results of applying the toString or toSimpleString method to their first - * elements. In plain math, this means that we are doing a diff on two - * functions (possibly two records or two sequences) where the ...Strings - * arrays are string representations of the domain elements of each of the - * function elements. - * - * The HashSet arguments are the sets of element objects that are to be - * highlighted in the appropriate fashion. - * - * We mark a function element as added or deleted if its left-hand value - * does not appear in one of the elements of the other function. We mark the - * element as changed, and call setInnerDiffInfo on the elements' values if - * elements with the same left-hand values having different values appear in - * the two records. - */ - private void setElementArrayDiffInfo(TLCVariableValue[] firstElts, String[] firstLHStrings, - TLCVariableValue[] secondElts, String[] secondLHStrings, HashSet<Object> changed, HashSet<TLCVariableValue> added, HashSet<TLCVariableValue> deleted) - { - - for (int i = 0; i < firstElts.length; i++) - { - boolean notfound = true; - int j = 0; - while (notfound && j < secondElts.length) - { - if (firstLHStrings[i].equals(secondLHStrings[j])) - { - notfound = false; - TLCVariableValue first = (TLCVariableValue) firstElts[i].getValue(); - TLCVariableValue second = (TLCVariableValue) secondElts[j].getValue(); - if (!first.toSimpleString().equals(second.toSimpleString())) - { - changed.add(secondElts[j]); - setInnerDiffInfo(first, second, changed, added, deleted); - } - } - j++; - } - if (notfound) - { - deleted.add(firstElts[i]); - } - } - - for (int i = 0; i < secondElts.length; i++) - { - boolean notfound = true; - int j = 0; - while (notfound && j < firstElts.length) - { - if (firstElts[j].toSimpleString().equals(secondElts[i].toSimpleString())) - { - notfound = false; - } - j++; - } - if (notfound) - { - added.add(secondElts[i]); - } + stateImage.dispose(); + varImage.dispose(); + recordImage.dispose(); + setImage.dispose(); + loadMoreImage.dispose(); + super.dispose(); } - } + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.IToolTipProvider#getToolTipText(java.lang.Object) + */ + public String getToolTipText(Object element) { + if (element instanceof LoaderTLCState) { + return "Double-click to load more states.\nIf the number of states is large, this might take a few seconds."; + } + return "Click on a row to see in viewer below, double-click to go to corresponding action in spec."; + } - /** - * A method that sets the diff highlighting information for two arrays of - * TLCFcnElementVariableValue objects. The parameters firstElts and - * secondElts are the two arrays.In plain math, this means that we are doing - * a diff on two functions (possibly two sequences). This method calls - * setElementArrayDiffInfo to do the work. - */ - private void setFcnElementArrayDiffInfo(TLCFcnElementVariableValue[] firstElts, - TLCFcnElementVariableValue[] secondElts, HashSet<Object> changed, HashSet<TLCVariableValue> added, HashSet<TLCVariableValue> deleted) - { - String[] firstLHStrings = new String[firstElts.length]; - for (int i = 0; i < firstElts.length; i++) - { - firstLHStrings[i] = firstElts[i].getFrom().toSimpleString(); - } - String[] secondLHStrings = new String[secondElts.length]; - for (int i = 0; i < secondElts.length; i++) - { - secondLHStrings[i] = secondElts[i].getFrom().toSimpleString(); - } - setElementArrayDiffInfo(firstElts, firstLHStrings, secondElts, secondLHStrings, changed, added, deleted); + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.CellLabelProvider#update(org.eclipse.jface.viewers.ViewerCell) + */ + public void update(ViewerCell cell) { + // labels + cell.setText(getColumnText(cell.getElement(), cell.getColumnIndex())); + + // images + cell.setImage(getColumnImage(cell.getElement(), cell.getColumnIndex())); + + // font + cell.setFont(getFont(cell.getElement(), cell.getColumnIndex())); + + // colors + cell.setForeground(getForeground(cell.getElement(), cell.getColumnIndex())); + cell.setBackground(getBackground(cell.getElement(), cell.getColumnIndex())); + } } - public List getTrace() - { + public TLCError getTrace() + { if (variableViewer == null) { return null; } - return (List) variableViewer.getInput(); + return (TLCError) variableViewer.getInput(); } /** @@ -1623,10 +1273,32 @@ public class TLCErrorView extends ViewPart * * @param states */ - private void setTraceInput(List<TLCState> states) + void setTraceInput(TLCError error) { - variableViewer.setInput(states); - if (!states.isEmpty()) + // If itemCount is large (>10.000 items), the underlying OS window + // toolkit can be slow. As a possible fix, look into + // http://www.eclipse.org/nattable/. For background, read + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=129457#c27 + error.restrictTraceTo(numberOfStatesToShow); + variableViewer.getTree().setItemCount(error.getTraceSize() + (error.isTraceRestricted() ? 1 : 0)); + variableViewer.setInput(error); + // If the number of states in the trace is sufficiently small, eagerly + // expand all root level items (which translates to the states + // variables). This causes the TreeViewer to correctly determine the + // vertical scroll bar's height. For larger number of states, we accept + // an incorrect scroll bar height in return for lazy and thus much + // faster item handling. I pulled the limit out of thin air, but + // tested it on three modern (2015) laptops with Win/Mac/Linux. + // + // There seems to be an implementation inside the Eclipse SDK that + // correctly handles the expanded state of a virtual tree: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=201135 + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=266189 + final int level = 1; + if (error.getTraceSize(level) < 1000) { + variableViewer.expandToLevel(level + 1); // viewer counts root node. + } + if (!error.isTraceEmpty()) { valueViewer.setDocument(NO_VALUE_DOCUMENT()); } else diff --git a/org.lamport.tla.toolbox.tool.tlc/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox.tool.tlc/META-INF/MANIFEST.MF index 486d7820944730805849a946833d689a32a8c1eb..ee1f9989da0baceb08f815b0d7fc78616dfe026a 100644 --- a/org.lamport.tla.toolbox.tool.tlc/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox.tool.tlc/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ Require-Bundle: org.eclipse.ui, org.eclipse.jdt.launching;bundle-version="3.4.1", org.eclipse.core.expressions;bundle-version="3.4.100", org.eclipse.ui.editors;bundle-version="3.5.0", - org.eclipse.jface.text;bundle-version="3.5.0" + org.eclipse.jface.text;bundle-version="3.5.0", + org.lamport.tla.toolbox.product.standalone Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ActivationPolicy: lazy Export-Package: org.lamport.tla.toolbox.tool.tlc.job, diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/TLCLifecycleParticipant.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/TLCLifecycleParticipant.java index 3c0cc37d7e345e0537def5f3d424ebc3f5a4d7c4..03a4dfac8d56a7d4e095d47585405cfd956b5fa0 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/TLCLifecycleParticipant.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/TLCLifecycleParticipant.java @@ -1,8 +1,7 @@ package org.lamport.tla.toolbox.tool.tlc; import org.eclipse.core.runtime.jobs.Job; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleException; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; import org.lamport.tla.toolbox.tool.tlc.job.TLCJob; /** @@ -15,7 +14,7 @@ public class TLCLifecycleParticipant extends ToolboxLifecycleParticipant { /* (non-Javadoc) * @see org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant#terminate() */ - public void terminate() throws ToolboxLifecycleException { + public void terminate() { Job.getJobManager().cancel(TLCJob.AllJobsMatcher); } } diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/IModelConfigurationConstants.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/IModelConfigurationConstants.java index 57a667b06b479d1c37344b485648b0f3f11f2066..56d2e303be8eef74a998e539e71ac75decc5ad25 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/IModelConfigurationConstants.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/IModelConfigurationConstants.java @@ -6,6 +6,10 @@ package org.lamport.tla.toolbox.tool.tlc.launch; */ public interface IModelConfigurationConstants extends IConfigurationConstants { + /** + * Comments + */ + public static final String MODEL_COMMENTS = "modelComments"; /** * number showing if one closed formula is used */ diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TLCModelLaunchDelegate.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TLCModelLaunchDelegate.java index 7cacc389e7670388f810b34a8b3a8a42c562440b..7ece962398f7d8f8b84f15a977d9a479fc4c2cf8 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TLCModelLaunchDelegate.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TLCModelLaunchDelegate.java @@ -533,7 +533,7 @@ public class TLCModelLaunchDelegate extends LaunchConfigurationDelegate implemen // parse the MC file IParseResult parseResult = ToolboxHandle.parseModule(rootModule, new SubProgressMonitor(monitor, 1), false, false); - Vector detectedErrors = parseResult.getDetectedErrors(); + Vector<TLAMarkerInformationHolder> detectedErrors = parseResult.getDetectedErrors(); boolean status = !AdapterFactory.isProblemStatus(parseResult.getStatus()); monitor.worked(1); diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TraceExplorerDelegate.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TraceExplorerDelegate.java index 2ea3733beb50b3edb3cafb1a3936fe2901aa14cd..7da30e408b0f438d73ed5317af5adcc2661fc95a 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TraceExplorerDelegate.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/launch/TraceExplorerDelegate.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Daniel Ricketts - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.launch; import java.util.Hashtable; @@ -32,6 +57,7 @@ import org.lamport.tla.toolbox.tool.tlc.TLCActivator; import org.lamport.tla.toolbox.tool.tlc.job.TLCJob; import org.lamport.tla.toolbox.tool.tlc.job.TLCProcessJob; import org.lamport.tla.toolbox.tool.tlc.job.TraceExplorerJob; +import org.lamport.tla.toolbox.tool.tlc.model.Assignment; import org.lamport.tla.toolbox.tool.tlc.model.TypedSet; import org.lamport.tla.toolbox.tool.tlc.traceexplorer.SimpleTLCState; import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper; @@ -70,11 +96,6 @@ import tla2sany.semantic.OpDefNode; * * The third method, launch(), is called if and only if finalLaunchCheck() returns true. It creates an instance of * {@link TLCProcessJob} which launches TLC. - * - * - * - * @author Daniel Ricketts - * */ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILaunchConfigurationDelegate, IModelConfigurationConstants, IConfigurationConstants @@ -87,7 +108,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa private IFile tlaFile; private IFile cfgFile; private IFile outFile; - private List trace; + private List<SimpleTLCState> trace; private String initId; private String nextId; @@ -258,7 +279,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa cfgFile = project.getFile(targetFolderPath.append(ModelHelper.TE_FILE_CFG)); outFile = project.getFile(targetFolderPath.append(ModelHelper.TE_FILE_OUT)); - TLCActivator.getDefault().logDebug("Writing files to: " + targetFolderPath.toOSString()); + TLCActivator.logDebug("Writing files to: " + targetFolderPath.toOSString()); IFile[] files = new IFile[] { tlaFile, cfgFile, outFile }; @@ -321,7 +342,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa // ignore this fact // FIXME this should be fixed at // some later point in time - TLCActivator.getDefault().logError("Error deleting a file " + members[i].getLocation(), e); + TLCActivator.logError("Error deleting a file " + members[i].getLocation(), e); } } monitor.done(); @@ -410,7 +431,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa * object returned by SANY. */ traceExpressionData = writer.createAndAddTEVariablesAndDefinitions(ModelHelper.deserializeFormulaList(config - .getAttribute(IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS, new Vector())), + .getAttribute(IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS, new Vector<String>())), TRACE_EXPLORE_EXPRESSIONS); // add the initial state predicate and next state action without @@ -465,26 +486,19 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa * dialog. It attempts to replace messages containing references * to locations to module TE with the string from that location. */ - StringBuffer errorMessage = new StringBuffer(); - Iterator it = parseResult.getDetectedErrors().iterator(); + final StringBuffer errorMessage = new StringBuffer(); + Iterator<TLAMarkerInformationHolder> it = parseResult.getDetectedErrors().iterator(); while (it.hasNext()) { - Object next = it.next(); - if (next instanceof TLAMarkerInformationHolder) - { - - TLAMarkerInformationHolder errorInfo = (TLAMarkerInformationHolder) next; - errorMessage.append(errorInfo.getMessage() + "\n"); - - } else - { - TLCActivator - .logDebug("Parse error while running trace explorer not represented by TLAMarkerInformationHolder." - + "This is unexpected."); - } + TLAMarkerInformationHolder errorInfo = it.next(); + errorMessage.append(errorInfo.getMessage() + "\n"); } - MessageDialog.openError(UIHelper.getShellProvider().getShell(), - "Parsing error when running trace explorer", errorMessage.toString()); + UIHelper.runUIAsync(new Runnable() { + public void run() { + MessageDialog.openError(UIHelper.getShellProvider().getShell(), + "Parsing error when running trace explorer", errorMessage.toString()); + } + }); return false; } @@ -507,7 +521,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa */ OpDefNode[] opDefNodes = ((ParseResult) parseResult).getSpecObj().getExternalModuleTable().getRootModule() .getOpDefs(); - Hashtable nodeTable = new Hashtable(opDefNodes.length); + Hashtable<String, OpDefNode> nodeTable = new Hashtable<String, OpDefNode>(opDefNodes.length); Assert.isNotNull(opDefNodes, "OpDefNodes[] from parsing TE.tla is null. This is a bug."); for (int j = 0; j < opDefNodes.length; j++) @@ -522,7 +536,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa * We use the following object to collect level three expressions in order to display * these in a message to the user. */ - Vector levelThreeExpressions = new Vector(); + Vector<TraceExpressionInformationHolder> levelThreeExpressions = new Vector<TraceExpressionInformationHolder>(); for (int i = 0; i < traceExpressionData.length; i++) { OpDefNode opDefNode = (OpDefNode) nodeTable.get(traceExpressionData[i].getIdentifier()); @@ -540,18 +554,22 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa // the launch should not proceed if (!levelThreeExpressions.isEmpty()) { - StringBuffer errorBuffer = new StringBuffer(); + final StringBuffer errorBuffer = new StringBuffer(); errorBuffer .append("The trace explorer cannot evaluate temporal formulas. The following expressions are temporal formulas:\n\n"); - Iterator it = levelThreeExpressions.iterator(); + Iterator<TraceExpressionInformationHolder> it = levelThreeExpressions.iterator(); while (it.hasNext()) { - TraceExpressionInformationHolder expressionInfo = (TraceExpressionInformationHolder) it.next(); + TraceExpressionInformationHolder expressionInfo = it.next(); errorBuffer.append(expressionInfo.getExpression() + "\n\n"); } - MessageDialog.openError(UIHelper.getShellProvider().getShell(), "Temporal formulas found", errorBuffer - .toString()); + UIHelper.runUIAsync(new Runnable() { + public void run() { + MessageDialog.openError(UIHelper.getShellProvider().getShell(), "Temporal formulas found", errorBuffer + .toString()); + } + }); return false; } @@ -637,7 +655,7 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException { - TLCActivator.getDefault().logDebug("launch called"); + TLCActivator.logDebug("launch called"); // check the modes if (!MODE_TRACE_EXPLORE.equals(mode)) { @@ -676,8 +694,8 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa private void writeModelInfo(ILaunchConfiguration config, ModelWriter writer) throws CoreException { // constants list - List constants = ModelHelper.deserializeAssignmentList(config.getAttribute(MODEL_PARAMETER_CONSTANTS, - new Vector())); + final List<Assignment> constants = ModelHelper.deserializeAssignmentList(config.getAttribute(MODEL_PARAMETER_CONSTANTS, + new Vector<String>()), true); // the advanced model values TypedSet modelValues = TypedSet.parseSet(config.getAttribute(MODEL_PARAMETER_MODEL_VALUES, EMPTY_STRING)); @@ -692,10 +710,9 @@ public class TraceExplorerDelegate extends TLCModelLaunchDelegate implements ILa // add definitions for CONSTANT parameters instantiated by ordinary values. writer.addConstantsBis(constants, MODEL_PARAMETER_CONSTANTS); // definition overrides list - List overrides = ModelHelper.deserializeAssignmentList(config.getAttribute(MODEL_PARAMETER_DEFINITIONS, - new Vector())); + List<Assignment> overrides = ModelHelper.deserializeAssignmentList(config.getAttribute(MODEL_PARAMETER_DEFINITIONS, + new Vector<String>())); writer.addFormulaList(ModelWriter.createOverridesContent(overrides, ModelWriter.DEFOV_SCHEME), "CONSTANT", MODEL_PARAMETER_DEFINITIONS); } - } diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/model/Assignment.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/model/Assignment.java index c1c8504b5c0aa9e609e38e53cebebaaa11707420..4583567da78e1c0dba5669fcc6374e52f4c9848a 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/model/Assignment.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/model/Assignment.java @@ -57,9 +57,14 @@ public class Assignment extends Formula } } - public String getFormula() + public String getFormula() { + return getFormula(""); + } + + public String getFormula(String tab) { StringBuffer buffer = new StringBuffer(getLeft()); + buffer.append(tab); buffer.append(ASSIGNMENT_SIGN); if (this.modelValue) @@ -217,6 +222,13 @@ public class Assignment extends Formula return modelValue; } + /** + * @return true iff model value but not a set of values + */ + public boolean isSimpleModelValue() { + return isModelValue() && !isSetOfModelValues(); + } + /** * Returns true, iff the assignment is a set of model values */ @@ -290,4 +302,28 @@ public class Assignment extends Formula return (params.length == obj.params.length); } + public String prettyPrint() { + return prettyPrint(""); + } + + public String prettyPrint(final String delim) { + final StringBuffer buf = new StringBuffer(); + if (!isModelValue()) { + return getFormula(delim); + } else if (isSetOfModelValues()) { + buf.append(getLeft()); + buf.append(delim); + buf.append(ASSIGNMENT_SIGN); + if (isSymmetricalSet()) { + buf.append("s"); + } + buf.append(getFormattedRight()); + } else { + // Ordinary model value, just skip the value (no point showing "X <- + // X"). + buf.append(getLeft()); + } + return buf.toString(); + } + } diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/output/internal/BroadcastStreamListener.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/output/internal/BroadcastStreamListener.java index 496ab188a4d65bf19e2b3a32b2d883316db3f0af..54645b9bf9ae31365d6875aa72d4f573624d1a07 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/output/internal/BroadcastStreamListener.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/output/internal/BroadcastStreamListener.java @@ -13,7 +13,6 @@ import org.lamport.tla.toolbox.tool.tlc.output.IProcessOutputSink; /** * A listener broadcasting the stream appending to extensions * @author Simon Zambrovski - * @version $Id$ */ public class BroadcastStreamListener implements IStreamListener { @@ -44,7 +43,7 @@ public class BroadcastStreamListener implements IStreamListener listeners[i].appendText(text); } catch (Exception e) { - TLCActivator.getDefault().logError("Error broadcasting the message", e); + TLCActivator.logError("Error broadcasting the message", e); } } } @@ -67,7 +66,7 @@ public class BroadcastStreamListener implements IStreamListener } } catch (Exception e) { - TLCActivator.getDefault().logError("Error broadcasting the stream closed event", e); + TLCActivator.logError("Error broadcasting the stream closed event", e); } } } @@ -81,7 +80,7 @@ public class BroadcastStreamListener implements IStreamListener IConfigurationElement[] decls = Platform.getExtensionRegistry().getConfigurationElementsFor( IProcessOutputSink.EXTENSION_ID); - Vector validExtensions = new Vector(); + Vector<IProcessOutputSink> validExtensions = new Vector<IProcessOutputSink>(); for (int i = 0; i < decls.length; i++) { try @@ -91,7 +90,7 @@ public class BroadcastStreamListener implements IStreamListener validExtensions.add(extension); } catch (CoreException e) { - TLCActivator.getDefault().logError("Error instatiating the IProcessSink extension", e); + TLCActivator.logError("Error instatiating the IProcessSink extension", e); } } return (IProcessOutputSink[]) validExtensions.toArray(new IProcessOutputSink[validExtensions.size()]); diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelper.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelper.java index 50ca3324f5be2487acb53dc0464a0ae3da64a651..807b2b5efc58958f3afa65c388fa721efe99fadd 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelper.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelHelper.java @@ -1,9 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.util; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Hashtable; import java.util.Iterator; @@ -97,6 +123,11 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur public static final String TLC_MODEL_ERROR_MARKER_ATTRIBUTE_NAME = "attributeName"; public static final String TLC_MODEL_ERROR_MARKER_ATTRIBUTE_IDX = "attributeIndex"; + /** + * The zero-based id of the BasicFormPage to show the error on. + */ + public static final String TLC_MODEL_ERROR_MARKER_ATTRIBUTE_PAGE = "basicFormPageId"; + /** * marker on .launch file with boolean attribute modelIsRunning */ @@ -186,6 +217,9 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur return proposition; } + public static String getModelName(ILaunchConfiguration config) { + return getModelName(config.getFile()); + } /** * Transforms a model name to the name visible to the user @@ -268,7 +302,7 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur final ILaunchConfiguration[] launchConfigurations = getAllLaunchConfigurations(); for (int i = 0; i < launchConfigurations.length; i++) { final ILaunchConfiguration iLaunchConfiguration = launchConfigurations[i]; - if (iLaunchConfiguration.getName().startsWith(aSpec.getName())) { + if (getSpecPrefix(iLaunchConfiguration).equals(aSpec.getName())) { res.add(iLaunchConfiguration); } } @@ -421,7 +455,18 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur * De-serialize assignment list. * @see ModelHelper#serializeAssignmentList(List) */ - public static List<Assignment> deserializeAssignmentList(List<String> serializedList) + public static List<Assignment> deserializeAssignmentList(final List<String> serializedList) { + return deserializeAssignmentList(serializedList, false); + } + + /** + * De-serialize assignment list. + * @param serializedList + * @param stripSymmetry Strips any symmetry definitions from assignments iff true + * @return The list of all {@link Assignment} + * @see ModelHelper#serializeAssignmentList(List) + */ + public static List<Assignment> deserializeAssignmentList(final List<String> serializedList, final boolean stripSymmetry) { Vector<Assignment> result = new Vector<Assignment>(serializedList.size()); Iterator<String> iter = serializedList.iterator(); @@ -448,7 +493,7 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur assign.setModelValue(true); // is symmetrical - if (fields.length > 4 && fields[4].equals("1")) + if (!stripSymmetry && fields.length > 4 && fields[4].equals("1")) { assign.setSymmetric(true); } @@ -1220,7 +1265,7 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur * (key type : <code>String</code> value type : <code>String</code>, * <code>Integer</code>, or <code>Boolean</code>) or <code>null</code> */ - public static IMarker installModelProblemMarker(IResource resource, @SuppressWarnings("rawtypes") Map properties, String markerType) + public static IMarker installModelProblemMarker(IResource resource, Map<String, Object> properties, String markerType) { Assert.isNotNull(resource); Assert.isTrue(resource.exists()); @@ -2141,4 +2186,78 @@ public class ModelHelper implements IModelConfigurationConstants, IModelConfigur } return null; } + + public static String prettyPrintConstants(final ILaunchConfiguration config, String delim) throws CoreException { + return prettyPrintConstants(config, delim, false); + } + + public static String prettyPrintConstants(final ILaunchConfiguration config, String delim, boolean align) throws CoreException { + final List<Assignment> assignments = deserializeAssignmentList( + config.getAttribute(IModelConfigurationConstants.MODEL_PARAMETER_CONSTANTS, new ArrayList<String>())); + + // Sort the assignments: Basic assignments alphabetically first, Set + // model values including symmetric ones (alphabetically), Basic model + // values. + Collections.sort(assignments, new Comparator<Assignment>() { + public int compare(Assignment a1, Assignment a2) { + if (a1.isSimpleModelValue() && a2.isSimpleModelValue()) { + return a1.getLeft().compareTo(a2.getLeft()); + } else if (a1.isSetOfModelValues() && a2.isSetOfModelValues()) { + return a1.getLeft().compareTo(a2.getLeft()); + } else if (a1.isSimpleModelValue() && !a2.isModelValue()) { + return 1; + } else if (a1.isSimpleModelValue() && a2.isSetOfModelValues()) { + return 1; + } else if (a1.isSetOfModelValues() && !a2.isModelValue()) { + return 1; + } else if (a1.isSetOfModelValues() && a2.isSimpleModelValue()) { + return -1; + } else if (!a1.isModelValue() && a2.isModelValue()) { + return -1; + } else { + // Basic assignments + return a1.getLeft().compareTo(a2.getLeft()); + } + } + }); + + // Determine the longest label of the assignment's left hand side. + int longestLeft = 0; + for (int i = 0; i < assignments.size() && align; i++) { + final Assignment assignment = assignments.get(i); + if (!assignment.isSimpleModelValue()) { + longestLeft = Math.max(longestLeft, assignment.getLeft().length()); + } + } + + final StringBuffer buf = new StringBuffer(); + for (int i = 0; i < assignments.size(); i++) { + final Assignment assignment = assignments.get(i); + if (assignment.isSimpleModelValue()) { + buf.append("Model values: "); + for (; i < assignments.size(); i++) { + buf.append(assignments.get(i).prettyPrint()); + if (i < assignments.size() - 1) { + buf.append(", "); + } + } + } else if (align) { + final int length = longestLeft - assignment.getLeft().length(); + final StringBuffer whitespaces = new StringBuffer(length); + for (int j = 0; j < length; j++) { + whitespaces.append(" "); + } + buf.append(assignment.prettyPrint(whitespaces.toString())); + if (i < assignments.size() - 1) { + buf.append(delim); + } + } else { + buf.append(assignment.prettyPrint()); + if (i < assignments.size() - 1) { + buf.append(delim); + } + } + } + return buf.toString(); + } } diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelNameValidator.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelNameValidator.java index 071533567cf55996c02839aeccd083f47c806c05..009dc1747d0588e96cf44d4864a6abd7f409fbff 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelNameValidator.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelNameValidator.java @@ -39,6 +39,9 @@ public class ModelNameValidator implements IInputValidator { return "Model name cannot begin with \"" + project.getName() + "___\"."; } + if (newText.contains(":")) { + return "Model name cannot contain ':' characters."; + } IStatus fileStatus = ResourcesPlugin.getWorkspace().validateName(newText, IResource.FILE); if (! fileStatus.isOK()) { return fileStatus.getMessage(); diff --git a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelWriter.java b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelWriter.java index 5c5815e73f4c73eb01274df5158136f7f7a7822c..4374083409d974b74b4e208073a4c746d7996e05 100644 --- a/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelWriter.java +++ b/org.lamport.tla.toolbox.tool.tlc/src/org/lamport/tla/toolbox/tool/tlc/util/ModelWriter.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool.tlc.util; import java.util.Hashtable; @@ -8,7 +33,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.debug.core.ILaunchConfiguration; @@ -29,8 +53,6 @@ import tla2sany.semantic.OpDefNode; /** * Encapsulates two buffers and provides semantic methods to add content to the _MC file and the CFG file of the model - * @author Simon Zambrovski - * @version $Id$ */ public class ModelWriter { @@ -148,13 +170,13 @@ public class ModelWriter * @param constants * @param modelValues */ - public void addConstants(List constants, TypedSet modelValues, String attributeConstants, String attributeMVs) + public void addConstants(List<Assignment> constants, TypedSet modelValues, String attributeConstants, String attributeMVs) { // add declarations for model values introduced on Advanced Model page. addMVTypedSet(modelValues, "MV CONSTANT declarations ", attributeMVs); Assignment constant; - Vector symmetrySets = new Vector(); + Vector<String> symmetrySets = new Vector<String>(); // first run for all the declarations for (int i = 0; i < constants.size(); i++) @@ -222,7 +244,7 @@ public class ModelWriter // symmetric model value sets added for (int i = 0; i < symmetrySets.size(); i++) { - tlaBuffer.append("Permutations(").append((String) symmetrySets.get(i)).append(")"); + tlaBuffer.append("Permutations(").append(symmetrySets.get(i)).append(")"); if (i != symmetrySets.size() - 1) { tlaBuffer.append(" \\union "); @@ -235,7 +257,7 @@ public class ModelWriter } - public void addConstantsBis(List constants, /* TypedSet modelValues, */ String attributeConstants /* , String attributeMVs */) + public void addConstantsBis(List<Assignment> constants, /* TypedSet modelValues, */ String attributeConstants /* , String attributeMVs */) { // // add declarations for model values introduced on Advanced Model page. // addMVTypedSet(modelValues, "MV CONSTANT declarations ", attributeMVs); @@ -368,18 +390,16 @@ public class ModelWriter * @return array of {@link TraceExpressionInformationHolder} where each element * contains the expression, the identifier, and the variable name */ - public TraceExpressionInformationHolder[] createAndAddTEVariablesAndDefinitions(List expressions, + public TraceExpressionInformationHolder[] createAndAddTEVariablesAndDefinitions(List<Formula> expressions, String attributeName) { TraceExpressionInformationHolder[] expressionData = new TraceExpressionInformationHolder[expressions.size()]; - Iterator it = expressions.iterator(); + Iterator<Formula> it = expressions.iterator(); int position = 0; while (it.hasNext()) { - Object next = it.next(); - Assert.isTrue(next instanceof Formula); - String expression = ((Formula) next).getFormula(); + String expression = it.next().getFormula(); if (expression != null && expression.length() > 0) { @@ -568,7 +588,7 @@ public class ModelWriter * @return String[], first element is the identifier for the initial state predicate, * second element is the identifier for the next-state action */ - public String[] addInitNextForTE(List trace, TraceExpressionInformationHolder[] expressionData) + public String[] addInitNextForTE(List<SimpleTLCState> trace, TraceExpressionInformationHolder[] expressionData) { String initId = getValidIdentifier(INIT_SCHEME); String nextId = getValidIdentifier(NEXT_SCHEME); @@ -676,14 +696,14 @@ public class ModelWriter * @param initId the identifier to be used for the initial state predicate, cannot be null * @param nextId the identifier to be used for the next-state action, cannot be null */ - public void addInitNextForTE(List trace, TraceExpressionInformationHolder[] expressionData, String initId, + public void addInitNextForTE(List<SimpleTLCState> trace, TraceExpressionInformationHolder[] expressionData, String initId, String nextId) { if (trace.size() > 0) { - Iterator it = trace.iterator(); - SimpleTLCState currentState = (SimpleTLCState) it.next(); + Iterator<SimpleTLCState> it = trace.iterator(); + SimpleTLCState currentState = it.next(); /******************************************************* * Add the init definition. * @@ -1166,7 +1186,7 @@ public class ModelWriter * @param keyword the keyword to be used in the CFG file * @param attributeName the name of the attribute in the model file */ - public void addFormulaList(List elements, String keyword, String attributeName) + public void addFormulaList(List<String[]> elements, String keyword, String attributeName) { if (elements.isEmpty()) { @@ -1177,7 +1197,7 @@ public class ModelWriter for (int i = 0; i < elements.size(); i++) { - String[] element = (String[]) elements.get(i); + String[] element = elements.get(i); cfgBuffer.append(element[0]).append(CR); // when a definition in the root module is overriden as a model value // there is nothing to add to the MC.tla file so, we do not do the following @@ -1230,10 +1250,10 @@ public class ModelWriter * @return a list with at most one String[] element * @throws CoreException */ - public static List createSourceContent(String propertyName, String labelingScheme, ILaunchConfiguration config) + public static List<String[]> createSourceContent(String propertyName, String labelingScheme, ILaunchConfiguration config) throws CoreException { - Vector result = new Vector(); + Vector<String[]> result = new Vector<String[]>(); String value = config.getAttribute(propertyName, EMPTY_STRING); if (value.trim().length() == 0) { @@ -1250,17 +1270,17 @@ public class ModelWriter return result; } - public static List createFalseInit(String var) + public static List<String[]> createFalseInit(String var) { - List list = new Vector(); + List<String[]> list = new Vector<String[]>(); String identifier = getValidIdentifier(INIT_SCHEME); list.add(new String[] { identifier, identifier + DEFINES_CR + "FALSE/\\" + var + EQ + "0" }); return list; } - public static List createFalseNext(String var) + public static List<String[]> createFalseNext(String var) { - List list = new Vector(); + List<String[]> list = new Vector<String[]>(); String identifier = getValidIdentifier(NEXT_SCHEME); list.add(new String[] { identifier, identifier + DEFINES_CR + "FALSE/\\" + var + PRIME + EQ + var }); return list; @@ -1272,9 +1292,9 @@ public class ModelWriter * @param labelingScheme * @return */ - public static List createFormulaListContent(List serializedFormulaList, String labelingScheme) + public static List<String[]> createFormulaListContent(List<String> serializedFormulaList, String labelingScheme) { - List formulaList = ModelHelper.deserializeFormulaList(serializedFormulaList); + List<Formula> formulaList = ModelHelper.deserializeFormulaList(serializedFormulaList); return (createListContent(formulaList, labelingScheme)); } @@ -1334,9 +1354,9 @@ public class ModelWriter * Was throwing null-pointer exception when called with spec unparsed. * Hacked a fix to handle this case. LL 20 Sep 2009 */ - public static List createOverridesContent(List overrides, String labelingScheme) + public static List<String[]> createOverridesContent(List<Assignment> overrides, String labelingScheme) { - Vector resultContent = new Vector(overrides.size()); + Vector<String[]> resultContent = new Vector<String[]>(overrides.size()); String[] content; String id; Assignment formula; @@ -1349,7 +1369,7 @@ public class ModelWriter return resultContent; } OpDefNode[] opDefNodes = specObj.getExternalModuleTable().getRootModule().getOpDefs(); - Hashtable nodeTable = new Hashtable(opDefNodes.length); + Hashtable<String, OpDefNode> nodeTable = new Hashtable<String, OpDefNode>(opDefNodes.length); for (int j = 0; j < opDefNodes.length; j++) { @@ -1363,9 +1383,9 @@ public class ModelWriter // formulas // to .cfg : <id> // to _MC.tla : <id> == <expression> - formula = ((Assignment) overrides.get(i)); + formula = overrides.get(i); - OpDefNode defNode = (OpDefNode) nodeTable.get(formula.getLabel()); + OpDefNode defNode = nodeTable.get(formula.getLabel()); if (defNode == null) { @@ -1423,9 +1443,9 @@ public class ModelWriter * @param labelingScheme * @return */ - public static List createListContent(List formulaList, String labelingScheme) + public static List<String[]> createListContent(List<Formula> formulaList, String labelingScheme) { - Vector resultContent = new Vector(formulaList.size()); + Vector<String[]> resultContent = new Vector<String[]>(formulaList.size()); String[] content; String label; for (int i = 0; i < formulaList.size(); i++) @@ -1434,7 +1454,7 @@ public class ModelWriter // formulas // to .cfg : <id> // to _MC.tla : <id> == <expression> - content = new String[] { label, label + DEFINES_CR + ((Formula) formulaList.get(i)).getFormula() }; + content = new String[] { label, label + DEFINES_CR + formulaList.get(i).getFormula() }; resultContent.add(content); } return resultContent; @@ -1527,7 +1547,7 @@ public class ModelWriter * @param traceExpressionData * @return */ - public static List createTraceInitContent(String traceInit, TraceExpressionInformationHolder[] traceExpressionData) + public static List<String[]> createTraceInitContent(String traceInit, TraceExpressionInformationHolder[] traceExpressionData) { String id = getValidIdentifier(INIT_SCHEME); StringBuffer initPredicate = new StringBuffer(); @@ -1545,21 +1565,21 @@ public class ModelWriter initPredicate.append(TRACE_NA); } } - Vector toReturn = new Vector(); + Vector<String[]> toReturn = new Vector<String[]>(); toReturn.add(new String[] { id, initPredicate.toString() }); return toReturn; } - public static List createTraceNextContent(List traceNextActions, + public static List<String[]> createTraceNextContent(List<String> traceNextActions, TraceExpressionInformationHolder[] traceExpressionData) { String id = getValidIdentifier(NEXT_SCHEME); StringBuffer nextActionDisj = new StringBuffer(); nextActionDisj.append(id).append(DEFINES_CR); - Iterator it = traceNextActions.iterator(); + Iterator<String> it = traceNextActions.iterator(); while (it.hasNext()) { - String actionConj = (String) it.next(); + String actionConj = it.next(); nextActionDisj.append(TLA_OR).append(L_PAREN).append(actionConj); for (int i = 0; i < traceExpressionData.length; i++) { @@ -1576,7 +1596,7 @@ public class ModelWriter nextActionDisj.append(R_PAREN).append(CR); } - Vector toReturn = new Vector(); + Vector<String[]> toReturn = new Vector<String[]>(); toReturn.add(new String[] { id, nextActionDisj.toString() }); return toReturn; } @@ -1603,12 +1623,12 @@ public class ModelWriter } Matcher matcher = ModelWriter.ID_MATCHER.matcher(text); - Vector regions = new Vector(); + Vector<Region> regions = new Vector<Region>(); while (matcher.find()) { regions.add(new Region(matcher.start(), matcher.end() - matcher.start())); } - return (IRegion[]) regions.toArray(new IRegion[regions.size()]); + return regions.toArray(new IRegion[regions.size()]); } /** diff --git a/org.lamport.tla.toolbox/META-INF/MANIFEST.MF b/org.lamport.tla.toolbox/META-INF/MANIFEST.MF index 237bc4953cd662c714058fa578bb73f69eb1293b..0e2ce0338024cbfa869aec5d08e5db318df6239e 100644 --- a/org.lamport.tla.toolbox/META-INF/MANIFEST.MF +++ b/org.lamport.tla.toolbox/META-INF/MANIFEST.MF @@ -15,7 +15,10 @@ Require-Bundle: org.eclipse.ui, org.eclipse.jface.text, org.eclipse.ui.navigator;bundle-version="3.3.101";visibility:=reexport, org.eclipse.core.expressions;bundle-version="3.4.100", - org.lamport.tlatools;bundle-version="1.0.0";visibility:=reexport + org.lamport.tlatools;bundle-version="1.0.0";visibility:=reexport, + org.eclipse.team.ui, + org.eclipse.core.filesystem;bundle-version="1.5.0", + org.lamport.tla.toolbox.product.standalone Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ActivationPolicy: lazy Bundle-ClassPath: ., diff --git a/org.lamport.tla.toolbox/plugin.xml b/org.lamport.tla.toolbox/plugin.xml index f194a00d52016fc4481e79bf0869faee8aeea460..1ec66e70c268c16786f24ef42eb51daac4262b38 100644 --- a/org.lamport.tla.toolbox/plugin.xml +++ b/org.lamport.tla.toolbox/plugin.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.2"?> <plugin> - <extension-point id="org.lamport.tla.toolbox.tool" name="Toolbox Life Cycle Participant" schema="schema/org.lamport.tla.toolbox.tool.exsd"/> <extension-point id="org.lamport.tla.toolbox.spec" name="Specification Life Cycle Participant" schema="schema/org.lamport.tla.toolbox.spec.exsd"/> <!-- --> <!-- Perspectives --> @@ -208,9 +207,14 @@ point="org.eclipse.ui.commands"> <!-- spec commands --> <category - description="Speicification Commands" + description="Dummy Command" + id="toolbox.command.category.dummy" + name="Dummy Command"> + </category> + <category + description="Specification Commands" id="toolbox.command.category.spec" - name="Speicification Commands"> + name="Specification Commands"> </category> <command categoryId="toolbox.command.category.spec" @@ -346,6 +350,10 @@ id="org.lamport.tla.toolbox.refresh" name="Refresh Specification"> </command> + <command + id="org.lamport.tla.toolbox.history" + name="Module History"> + </command> </extension> <!-- Key bindings @@ -752,6 +760,11 @@ label="Refresh" style="push"> </command> + <command + commandId="org.lamport.tla.toolbox.history" + label="Show in History" + style="push"> + </command> </menuContribution> <menuContribution @@ -772,6 +785,16 @@ locationURI="toolbar:org.eclipse.ui.trim.status"> <toolbar id="toolbox.trim"> + <!-- Dummy command needed to make the contributions height --> + <!-- be larger than too small. --> + <!-- https://bugs.eclipse.org/bugs/show_bug.cgi?id=471313 --> + <command + id="org.lamport.tla.toolbox.ui.contribution.dummy" + commandId="toolbox.command.category.dummyj" + label=" "> + <!--   is the "No break space" unicode character. --> + <!-- It is not printable and thus make the dummy invisible. --> + </command> <control class="org.lamport.tla.toolbox.ui.contribution.SizeControlContribution" id="toolbox.contributions.spec.size"> @@ -1020,6 +1043,27 @@ </test> </enabledWhen> </handler> + <handler + class="org.lamport.tla.toolbox.ui.handler.ShowHistoryHandler" + commandId="org.lamport.tla.toolbox.history"> + <enabledWhen> + <and> + <count + value="1"> + </count> + <with + variable="selection"> + <iterate + ifEmpty="false" + operator="or"> + <instanceof + value="org.lamport.tla.toolbox.spec.Module"> + </instanceof> + </iterate> + </with> + </and> + </enabledWhen> + </handler> </extension> <extension point="org.eclipse.core.expressions.propertyTesters"> @@ -1304,6 +1348,18 @@ class="org.lamport.tla.toolbox.util.pref.UnwantedPreferenceManager"> </participant> </extension> + <extension + point="org.lamport.tla.toolbox.tool"> + <participant + class="org.lamport.tla.toolbox.ui.navigator.ToolboxExplorerResourceListener"> + </participant> + </extension> + <extension + point="org.lamport.tla.toolbox.tool"> + <participant + class="org.lamport.tla.toolbox.ui.view.ProblemViewResourceListener"> + </participant> + </extension> <!-- --> <!-- Specification content --> diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/Activator.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/Activator.java index b9e1c1c7256ac837e595318ec3d9f20f737db7ec..59bd2e61b56d6a23334a06ea403a73cdd2cbbc27 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/Activator.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/Activator.java @@ -48,7 +48,7 @@ public class Activator extends AbstractTLCActivator { super.start(context); plugin = this; - + // register the listeners IWorkspace workspace = ResourcesPlugin.getWorkspace(); @@ -88,7 +88,7 @@ public class Activator extends AbstractTLCActivator getThread().wait(100); } } catch (InterruptedException e) { - e.printStackTrace(); + logError(e.getMessage(), e); } state = context.getBundle().getState(); } @@ -100,6 +100,12 @@ public class Activator extends AbstractTLCActivator }; initializerJob.setRule(workspace.getRuleFactory().buildRule()); initializerJob.schedule(); + + // Running plug-in tests with Eclipse 4.5, the foundation suspends the + // JobManager at startup causing our jobs to never be executed. In turn, + // this causes the Toolbox to hang at startup. Thus, resume the JobManager + // which is a no-op outside the test mode. + Job.getJobManager().resume(); // activate handler to show the radio buttons in perspective selection // UIJob job = new UIJob("InitCommandsWorkaround") { diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/job/DeleteOutOfSyncJob.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/job/DeleteOutOfSyncJob.java index c2dbbe18fb5979342bd824e849e7058ecda05b42..bc875b889b769d9f8fe7024478138a0fb08ce64d 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/job/DeleteOutOfSyncJob.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/job/DeleteOutOfSyncJob.java @@ -1,41 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.job; -import java.util.List; - -import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.WorkspaceJob; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; +import org.lamport.tla.toolbox.util.ResourceHelper; /** * Delete files out of sync - * @author Simon Zambrovski - * @version $Id$ */ -public class DeleteOutOfSyncJob extends WorkspaceJob -{ - private final List files; +public class DeleteOutOfSyncJob extends WorkspaceJob { + private final IResource[] files; - /** - * @param name - */ - public DeleteOutOfSyncJob(List files) - { - super("deleteOutOfSyncFiles"); - this.files = files; - } + /** + * @param name + */ + public DeleteOutOfSyncJob(final IResource[] files) { + super("deleteOutOfSyncFiles"); + this.files = files; + this.setRule(ResourceHelper.getDeleteRule(files)); + } - /** - * @see org.eclipse.core.resources.WorkspaceJob#runInWorkspace(org.eclipse.core.runtime.IProgressMonitor) - */ - public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException - { - for (int i = 0; i < files.size(); i++) - { - ((IFile)files.get(i)).delete(true, monitor); - } - return Status.OK_STATUS; - } + /** + * @see org.eclipse.core.resources.WorkspaceJob#runInWorkspace(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus runInWorkspace(final IProgressMonitor monitor) throws CoreException { + final IWorkspace ws = ResourcesPlugin.getWorkspace(); + ws.delete(files, isBlocking(), monitor); + return Status.OK_STATUS; + } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Module.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Module.java index 90e8fc60e668e21c653a9ea790f80784c94369ef..e2ac47c45b3122b1a4b39acfc0800301143821d4 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Module.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Module.java @@ -1,38 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrowski - initial API and implementation + ******************************************************************************/ + package org.lamport.tla.toolbox.spec; import java.io.File; +import org.eclipse.core.resources.IResource; import org.lamport.tla.toolbox.util.RCPNameToFileIStream; import tla2sany.semantic.ModuleNode; /** * Representation of a module - * @author Simon Zambrovski - * @version $Id$ */ public class Module { - private File file; + private final File file; + private ModuleNode node; private boolean isRoot = false; + private IResource resource; public Module(String absoluteFilename) { file = new File(absoluteFilename); } - /** + public Module(IResource resource) { + this(resource.getLocation().toOSString()); + this.resource = resource; + } + + /** * Retrieves absolute path of the module file * * @return path of the module file */ public String getAbsolutePath() { - if (file == null) - { - return null; - } return file.getAbsolutePath(); } @@ -46,16 +74,27 @@ public class Module return file; } + /** + * @return null or the Eclipse specific resource handle for this module. A + * IResource is the pendant to the non-Eclipse specific + * java.io.File. + * + * @see {@link Module#getFile()} + */ + public IResource getResource() { + return resource; + } + + public void setResource(IResource aResource) { + this.resource = aResource; + } + /** * Retrieves the module name * @return the name of the module */ public String getModuleName() { - if (file == null) - { - return null; - } String filename = file.getName(); if (filename.toLowerCase().indexOf(".tla") != -1) { @@ -106,11 +145,4 @@ public class Module { this.isRoot = isRoot; } - - public void destroy() - { - this.file = null; - this.node = null; - this.isRoot = false; - } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Spec.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Spec.java index ec473167bd65506bbf4d8c4ae3f5f26439666e7e..61a5c9cf3cd386685bc59fa6b8a471693039d979 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Spec.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/Spec.java @@ -297,7 +297,8 @@ public class Spec implements IAdaptable { /** * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ - public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { + @SuppressWarnings("unchecked") + public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { // lookup the IAdapterManager service IAdapterManager manager = Platform.getAdapterManager(); // forward the request to IAdapterManager service @@ -591,4 +592,30 @@ public class Spec implements IAdaptable { @SuppressWarnings("serial") private static class NullTLAtoPCalMapping extends TLAtoPCalMapping { } + + /** + * @return true iff this spec is the currently active spec. + * + * @see Activator#getSpecManager() + * + */ + public boolean isCurrentSpec() { + + return Activator.getSpecManager().getSpecLoaded() == this; + } + + public List<Module> getModules() { + final List<Module> modules = new ArrayList<Module>(); + final IResource[] moduleResources = getModuleResources(); + for (int i = 0; i < moduleResources.length; i++) { + // skip non-modules + if (!ResourceHelper.isModule(moduleResources[i])) { + continue; + } + final Module module = new Module(moduleResources[i]); + module.setRoot(ResourceHelper.isRoot((IFile) moduleResources[i])); + modules.add(module); + } + return modules; + } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ModuleParserLauncher.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ModuleParserLauncher.java index 27a2691812f9fcb46daaae1260f98f01cb63d37c..289452a8f568a4b3022de615acbcd52551fa01d3 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ModuleParserLauncher.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ModuleParserLauncher.java @@ -13,7 +13,6 @@ import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.spec.Module; @@ -240,21 +239,21 @@ public class ModuleParserLauncher // Set the moduleNode components only if there were no parsing or // semantic errors. - Vector userModules = new Vector(); + Vector<Module> userModules = new Vector<Module>(); // Vector standardModules = new Vector(); boolean rootModuleFound = false; - final Vector resourcesToTimeStamp = new Vector(); + final Vector<IResource> resourcesToTimeStamp = new Vector<IResource>(); // iterate over parse units - Enumeration enumerate = moduleSpec.parseUnitContext.keys(); + Enumeration<String> enumerate = moduleSpec.parseUnitContext.keys(); while (enumerate.hasMoreElements()) { // should cancel? checkCancel(monitor); // This enumeration finds all non-inner modules in the spec. - String moduleName = (String) enumerate.nextElement(); + String moduleName = enumerate.nextElement(); ParseUnit parseUnit = (ParseUnit) moduleSpec.parseUnitContext.get(moduleName); String absoluteFileName = null; @@ -277,6 +276,7 @@ public class ModuleParserLauncher if (moduleResource != null && moduleResource.exists()) { resourcesToTimeStamp.add(moduleResource); + module.setResource(moduleResource); } } @@ -328,10 +328,10 @@ public class ModuleParserLauncher public void run(IProgressMonitor monitor) throws CoreException { - Iterator iterator = resourcesToTimeStamp.iterator(); + Iterator<IResource> iterator = resourcesToTimeStamp.iterator(); while (iterator.hasNext()) { - IResource resource = (IResource) iterator.next(); + IResource resource = iterator.next(); resource.setPersistentProperty(TLAParsingBuilderConstants.LAST_BUILT, String.valueOf(System .currentTimeMillis())); } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ParseResult.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ParseResult.java index d06fbf4d609190c8b8e5fe47f62fceb2f44c69e3..89a2da76ca5bd8a9ed29b0009eaad1454bd17c7a 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ParseResult.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/spec/parser/ParseResult.java @@ -21,7 +21,7 @@ public class ParseResult implements IParseResult private IResource parsedResource; private Errors parseErrors; private Errors semanticErrors; - private Vector detectedErrors; + private Vector<TLAMarkerInformationHolder> detectedErrors; /** * Time stamp for when the parser * was called that eventually produced this result @@ -42,7 +42,7 @@ public class ParseResult implements IParseResult this.parsedResource = parsedResource; this.parseErrors = parseErrors; this.semanticErrors = semanticErrors; - this.detectedErrors = new Vector(); + this.detectedErrors = new Vector<TLAMarkerInformationHolder>(); this.parserCalled = parserCalled; } @@ -100,7 +100,7 @@ public class ParseResult implements IParseResult return semanticErrors; } - public Vector getDetectedErrors() + public Vector<TLAMarkerInformationHolder> getDetectedErrors() { return detectedErrors; } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/IParseResult.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/IParseResult.java index 1add91089cef310c9606af758e0fd153183c2da6..cc4d0ca1fa112d04affb99e3cff4ac4ce0f6651d 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/IParseResult.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/IParseResult.java @@ -20,5 +20,5 @@ public interface IParseResult /** * Retrieves the list of {@link TLAMarkerInformationHolder} or an empty list */ - public Vector getDetectedErrors(); + public Vector<TLAMarkerInformationHolder> getDetectedErrors(); } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxHandle.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxHandle.java index a2e1a1d3725a3b06b130047647e97997264a8fba..991a7b3c484d72ae4f4871e0ce5cb332dd999bf4 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxHandle.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxHandle.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.tool; import java.io.File; @@ -26,8 +51,6 @@ import tla2sany.modanalyzer.SpecObj; /** * Provides shortcuts to the internal toolbox methods, that should be made accessible to the other tools - * @author Simon Zambrovski - * @version $Id$ */ public class ToolboxHandle { @@ -122,7 +145,8 @@ public class ToolboxHandle * @param rootModule, name of the module * @return list of modules it depends (EXTEND) on */ - public static List getExtendedModules(String moduleName) + @SuppressWarnings("unchecked") + public static List<String> getExtendedModules(String moduleName) { return Activator.getModuleDependencyStorage().getListOfExtendedModules(moduleName); } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleException.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleException.java deleted file mode 100644 index aeeb727f6cf2532518ff65df76413270d3babae2..0000000000000000000000000000000000000000 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/tool/ToolboxLifecycleException.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.lamport.tla.toolbox.tool; - - -/** - * Exception thrown during toolbox initialization or termination - * @author Simon Zambrovski - * @version $Id$ - */ -public class ToolboxLifecycleException extends Exception -{ - private static final long serialVersionUID = 6237014663324928734L; - - public ToolboxLifecycleException(String message) - { - super(message); - } - - public ToolboxLifecycleException(String message, Throwable cause) - { - super(message, cause); - } -} diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/contribution/ModuleListContributionItem.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/contribution/ModuleListContributionItem.java index 712a036fd1f57d732a9bebd5c8b04c58fee87e75..99ccf42fe3d8a69b436d257da829479c2ddc8bde 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/contribution/ModuleListContributionItem.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/contribution/ModuleListContributionItem.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.ui.contribution; import java.util.HashMap; @@ -20,8 +45,6 @@ import org.lamport.tla.toolbox.util.UIHelper; /** * Contribution item for opening the modules - * @author Simon Zambrovski - * @version $Id$ */ public class ModuleListContributionItem extends CompoundContributionItem { @@ -35,12 +58,11 @@ public class ModuleListContributionItem extends CompoundContributionItem */ protected IContributionItem[] getContributionItems() { - - Spec spec = Activator.getSpecManager().getSpecLoaded(); - Vector moduleContributions = new Vector(); - HashMap parameters = new HashMap(); + final Spec spec = Activator.getSpecManager().getSpecLoaded(); + final Vector<IContributionItem> moduleContributions = new Vector<IContributionItem>(); + HashMap<String, String> parameters = new HashMap<String, String>(); - Vector outOfSyncResourcesToDelete = new Vector(); + final Vector<IResource> outOfSyncResourcesToDelete = new Vector<IResource>(); // create the contribution item for add module CommandContributionItemParameter param = new CommandContributionItemParameter(UIHelper.getActiveWindow(), @@ -54,8 +76,8 @@ public class ModuleListContributionItem extends CompoundContributionItem if (spec != null) { - IResource[] modules = spec.getModuleResources(); - IResource rootModule = spec.getRootFile(); + final IResource[] modules = spec.getModuleResources(); + final IResource rootModule = spec.getRootFile(); boolean isRoot; for (int i = 0; i < modules.length; i++) { @@ -73,7 +95,7 @@ public class ModuleListContributionItem extends CompoundContributionItem isRoot = rootModule.equals(modules[i]); - parameters = new HashMap(); + parameters = new HashMap<String, String>(); // fill the module name for the handler parameters.put(OpenModuleHandler.PARAM_MODULE, ResourceHelper.getModuleNameChecked( modules[i].getName(), false)); @@ -89,9 +111,11 @@ public class ModuleListContributionItem extends CompoundContributionItem } } - DeleteOutOfSyncJob job = new DeleteOutOfSyncJob(outOfSyncResourcesToDelete); - job.setRule(ResourceHelper.getDeleteRule((IResource[]) outOfSyncResourcesToDelete.toArray(new IResource[outOfSyncResourcesToDelete.size()]))); - job.schedule(); + if (outOfSyncResourcesToDelete.size() > 0) { + final DeleteOutOfSyncJob job = new DeleteOutOfSyncJob( + (IResource[]) outOfSyncResourcesToDelete.toArray(new IResource[outOfSyncResourcesToDelete.size()])); + job.schedule(); + } return (IContributionItem[]) moduleContributions.toArray(new IContributionItem[moduleContributions.size()]); } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/AddModuleHandler.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/AddModuleHandler.java index c93785dfcce8166d0891ff8828413efe99369e79..4543b885654dd7529fcd8087de7c0f4d5c3b6656 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/AddModuleHandler.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/AddModuleHandler.java @@ -7,6 +7,8 @@ import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IHandler; +import org.eclipse.core.filesystem.EFS; +import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; @@ -16,6 +18,7 @@ import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.handlers.HandlerUtil; +import org.eclipse.ui.ide.FileStoreEditorInput; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.spec.Spec; import org.lamport.tla.toolbox.util.ResourceHelper; @@ -83,15 +86,28 @@ public class AddModuleHandler extends AbstractHandler implements IHandler // check the folder we are in if (!ResourceHelper.isProjectParent(modulePath.removeLastSegments(1), spec.getProject())) { - // the selected resource is not in the same directory as the root file - MessageDialog - .openInformation( - window.getShell(), - "Wrong TLA+ Module is part of the spec", - "The provided module " - + module.getName() - + " is not located in the same directory as the root file. \nPlease select the module in " - + spec.getRootFile().getFullPath().removeLastSegments(1).toOSString()); + // the selected resource is not in the same directory as + // the root file + MessageDialog.openInformation(window.getShell(), "TLA+ Module is not part of the current spec.", + "The provided module " + module.getName() + + " is not part of the spec which is currently open. It will therefore be opened in read-only mode.\n" + + "If you want to make changes to this file, you will have to open the corresponding spec first."); + + // Open TLA's read-only editor on a .tla file that does + // *not* belong to the current spec. It is opened + // read-only, because we want to allow any changes, + // because we couldn't parse the spec anyway. The reason + // why this functionality is offered, is to allow users + // to look at .tla files of closed spec. + // http://wiki.eclipse.org/FAQ_How_do_I_open_an_editor_on_a_file_outside_the_workspace%3F + final IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(moduleFileName)); + if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { + UIHelper.openEditor("org.lamport.tla.toolbox.editor.basic.TLAEditorReadOnly", + new FileStoreEditorInput(fileStore)); + } else { + throw new ExecutionException(moduleFileName + + " cannot be opened in read-only mode because its file content could not be obtained."); + } return null; } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandler.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandler.java index ee4231d47b74ba82d919b47bdbca9d4bc3982d18..51bff5f682291821bcbee8830d6dea852957f363 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandler.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandler.java @@ -8,11 +8,13 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.progress.UIJob; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.spec.Spec; import org.lamport.tla.toolbox.spec.nature.ParserHelper; +import org.lamport.tla.toolbox.ui.navigator.ToolboxExplorer; import org.lamport.tla.toolbox.ui.perspective.InitialPerspective; import org.lamport.tla.toolbox.ui.perspective.SpecLoadedPerspective; import org.lamport.tla.toolbox.util.ToolboxJob; @@ -75,6 +77,12 @@ public class OpenSpecHandler extends AbstractHandler implements IHandler @Override public IStatus runInUIThread(IProgressMonitor monitor) { UIHelper.openEditor(TLA_EDITOR, new FileEditorInput(spec.getRootFile())); + + // Expand the spec's subitems (modules & models group and their subitems). + // getViewer() cannot return null here. After all, this listener + // is handling its double-click event. + ToolboxExplorer.getViewer().expandToLevel(spec, AbstractTreeViewer.ALL_LEVELS); + return Status.OK_STATUS; } }; diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandlerDelegate.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandlerDelegate.java index 1c79f2bfde0aa966e7493055267406f7dacd6345..cc978811f2e9d3407ce69dd5a08b9893acfb1a33 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandlerDelegate.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/OpenSpecHandlerDelegate.java @@ -1,6 +1,7 @@ package org.lamport.tla.toolbox.ui.handler; import java.util.HashMap; +import java.util.Map; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; @@ -40,7 +41,7 @@ public class OpenSpecHandlerDelegate extends AbstractHandler implements IHandler Object selected = ((IStructuredSelection) selection).getFirstElement(); if (selected instanceof Spec) { - HashMap parameters = new HashMap(); + final Map<String, String> parameters = new HashMap<String, String>(); // fill the spec name for the handler parameters.put(OpenSpecHandler.PARAM_SPEC, ((Spec) selected).getName()); diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/PCalTranslateModuleHandler.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/PCalTranslateModuleHandler.java index 4c15fa2bc20b26423d60a83e63de491a6bc3684e..58f9bc8db1be184fdebf3622b505905bdd5121fb 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/PCalTranslateModuleHandler.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/PCalTranslateModuleHandler.java @@ -1,100 +1,121 @@ -package org.lamport.tla.toolbox.ui.handler; - -import java.lang.reflect.InvocationTargetException; - -import org.eclipse.core.commands.ExecutionEvent; -import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.commands.IHandler; -import org.eclipse.core.commands.IHandler2; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.jface.dialogs.ProgressMonitorDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IWorkbenchWindow; -import org.lamport.tla.toolbox.Activator; -import org.lamport.tla.toolbox.job.TranslatorJob; -import org.lamport.tla.toolbox.util.UIHelper; - -/** - * Triggers the PCal translation of the module - * @author Simon Zambrovski - * @version $Id$ - */ -public class PCalTranslateModuleHandler extends SaveDirtyEditorAbstractHandler implements IHandler, IHandler2 -{ - public final static String COMMAND_ID = "toolbox.command.module.translate.active"; - - /* (non-Javadoc) - * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent) - */ - public Object execute(ExecutionEvent event) throws ExecutionException - { - if (!saveDirtyEditor(event)) { - return null; - } - - IEditorInput editorInput = activeEditor.getEditorInput(); - if (editorInput instanceof IFileEditorInput) - { - final IResource fileToBuild = ((IFileEditorInput) editorInput).getFile(); - - IRunnableWithProgress translatorOperation = TranslatorJob.getAsRunnableWithProgress(fileToBuild); - try - { - // Getting progress monitor - final IWorkbenchWindow window = UIHelper.getActiveWindow(); - final Shell shell = (window != null) ? window.getShell() : null; - final ProgressMonitorDialog progressDialog = new ProgressMonitorDialog(shell); - final IProgressMonitor monitor = progressDialog.getProgressMonitor(); - // Changing the first argument of the following call to false - // will make the translator run in the UI thread, making the UI - // unresponsive while the translator is running. However, it - // might fix the Heisenbug that causes the translator to produce - // a bogus "missing `begin'" error when run after fixing - // a real error. Or again, it might not. - // - Note added by LL on 2 Apr 2013 - progressDialog.run(true, false, translatorOperation); - fileToBuild.refreshLocal(IResource.DEPTH_ONE, monitor); - - } catch (InvocationTargetException e) - { - Activator.getDefault().logError("Error during PlusCal Trnaslation", e); - } catch (InterruptedException e) - { - Activator.getDefault().logError("Error during PlusCal Trnaslation", e); - } catch (CoreException e) - { - Activator.getDefault().logError("Error during PlusCal Trnaslation", e); - } - - /* - TranslatorJob job = new TranslatorJob(fileToBuild); - job.setUser(true); - // TODO config file is also changed - job.setRule(getModifyRule(new IResource[]{fileToBuild})); - job.addJobChangeListener(new JobChangeAdapter(){ - public void done(IJobChangeEvent event) - { - if (Status.OK_STATUS.equals(event.getResult())) - { - try - { - fileToBuild.refreshLocal(IResource.DEPTH_ONE, null); - } catch (CoreException e) - { - e.printStackTrace(); - } - } - } - }); - job.schedule(); - */ - - } - return null; - } -} +package org.lamport.tla.toolbox.ui.handler; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.commands.IHandler2; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchWindow; +import org.lamport.tla.toolbox.Activator; +import org.lamport.tla.toolbox.job.TranslatorJob; +import org.lamport.tla.toolbox.util.UIHelper; + +/** + * Triggers the PCal translation of the module + * @author Simon Zambrovski + * @version $Id$ + */ +public class PCalTranslateModuleHandler extends SaveDirtyEditorAbstractHandler implements IHandler, IHandler2 +{ + public final static String COMMAND_ID = "toolbox.command.module.translate.active"; + + /* (non-Javadoc) + * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent) + */ + public Object execute(ExecutionEvent event) throws ExecutionException + { + if (!saveDirtyEditor(event)) { + return null; + } + + IEditorInput editorInput = activeEditor.getEditorInput(); + if (editorInput instanceof IFileEditorInput) + { + final IResource fileToBuild = ((IFileEditorInput) editorInput).getFile(); + + IRunnableWithProgress translatorOperation = TranslatorJob.getAsRunnableWithProgress(fileToBuild); + try + { + // Getting progress monitor + final IWorkbenchWindow window = UIHelper.getActiveWindow(); + final Shell shell = (window != null) ? window.getShell() : null; + final ProgressMonitorDialog progressDialog = new ProgressMonitorDialog(shell); + final IProgressMonitor monitor = progressDialog.getProgressMonitor(); + // Changing the first argument of the following call to false + // will make the translator run in the UI thread, making the UI + // unresponsive while the translator is running. However, it + // might fix the Heisenbug that causes the translator to produce + // a bogus "missing `begin'" error when run after fixing + // a real error. Or again, it might not. + // - Note added by LL on 2 Apr 2013 + // Note added by LL on 17 Aug 2015: + // Changing the first argument apparently does not fix that bug. + // However, running the translator on the following algorithm + // reliably produces the bug: + // + // (* --algorithm alok_test { + // { + // print "hello" + // print "world" + // } + // } *) + // + // Running the translator repeatedly on it switches between the + // "missing ;" and the "missing `begin'" error. After correcting + // the "missing ;" error, it reliably produces the "missing begin" + // error. The translator behaves correctly when run from the + // command line, so this is only a problem when the translator is + // run from the Toolbox. + // + // This bug was fixed on 17 Aug 2015 by a change to ParseAlgorithm.java. + // + progressDialog.run(true, false, translatorOperation); + fileToBuild.refreshLocal(IResource.DEPTH_ONE, monitor); + + } catch (InvocationTargetException e) + { + Activator.getDefault().logError("Error during PlusCal Trnaslation", e); + } catch (InterruptedException e) + { + Activator.getDefault().logError("Error during PlusCal Trnaslation", e); + } catch (CoreException e) + { + Activator.getDefault().logError("Error during PlusCal Trnaslation", e); + } + + /* + TranslatorJob job = new TranslatorJob(fileToBuild); + job.setUser(true); + // TODO config file is also changed + job.setRule(getModifyRule(new IResource[]{fileToBuild})); + job.addJobChangeListener(new JobChangeAdapter(){ + public void done(IJobChangeEvent event) + { + if (Status.OK_STATUS.equals(event.getResult())) + { + try + { + fileToBuild.refreshLocal(IResource.DEPTH_ONE, null); + } catch (CoreException e) + { + e.printStackTrace(); + } + } + } + }); + job.schedule(); + */ + + } + return null; + } +} diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/ShowHistoryHandler.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/ShowHistoryHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..8b3df977ca5da9eeda07725023e5716bd08ee05d --- /dev/null +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/handler/ShowHistoryHandler.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.ui.handler; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.team.internal.ui.history.LocalHistoryPage; +import org.eclipse.team.ui.TeamUI; +import org.eclipse.team.ui.history.IHistoryPage; +import org.eclipse.team.ui.history.IHistoryView; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.HandlerUtil; +import org.lamport.tla.toolbox.spec.Module; + +@SuppressWarnings("restriction") +public class ShowHistoryHandler extends AbstractHandler implements IHandler { + + /* (non-Javadoc) + * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent) + */ + public Object execute(final ExecutionEvent event) throws ExecutionException { + final IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (activeWorkbenchWindow == null) { + return null; + } + + final ISelection selection = HandlerUtil.getCurrentSelection(event); + if (selection instanceof IStructuredSelection) { + final IStructuredSelection iss = (IStructuredSelection) selection; + final Object input = iss.getFirstElement(); + if (input instanceof Module) { + final IHistoryView view = TeamUI.showHistoryFor(activeWorkbenchWindow.getActivePage(), + ((Module) input).getResource(), null); + final IHistoryPage page = view.getHistoryPage(); + if (page instanceof LocalHistoryPage) { + final LocalHistoryPage historyPage = (LocalHistoryPage) page; + historyPage.setClickAction(true); + } + } + } + return null; + } +} diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorer.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorer.java index 6c7be8fa9094a3eebe6a92d6b131638beee8f19e..73c1ece03bf7c5b7cc49f84f37a87a21fa4e8149 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorer.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorer.java @@ -1,23 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.ui.navigator; import java.util.HashMap; +import java.util.Map; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.navigator.CommonNavigator; import org.eclipse.ui.navigator.CommonViewer; import org.lamport.tla.toolbox.Activator; +import org.lamport.tla.toolbox.spec.Module; +import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.ui.handler.OpenModuleHandler; +import org.lamport.tla.toolbox.ui.provider.IGroup; import org.lamport.tla.toolbox.util.UIHelper; /** * Specification Explorer - * @author Simon Zambrovski - * @version $Id$ */ public class ToolboxExplorer extends CommonNavigator { @@ -39,7 +65,21 @@ public class ToolboxExplorer extends CommonNavigator { super.handleDoubleClick(anEvent); // open the model - UIHelper.runCommand(ToolboxExplorer.COMMAND_ID, new HashMap<String, String>()); + if (anEvent.getSelection() instanceof IStructuredSelection) { + IStructuredSelection iss = (IStructuredSelection) anEvent.getSelection(); + Object firstElement = iss.getFirstElement(); + if (firstElement instanceof Module) { + final Map<String, String> parameters = new HashMap<String, String>(); + parameters.put(OpenModuleHandler.PARAM_MODULE, ((Module) firstElement).getModuleName()); + UIHelper.runCommand(OpenModuleHandler.COMMAND_ID, parameters); + } else if (firstElement instanceof IGroup) { + // No-Op + } else if (firstElement instanceof Spec && ((Spec) firstElement).isCurrentSpec()) { + // No-op, do not re-open an open spec again. + } else { + UIHelper.runCommand(ToolboxExplorer.COMMAND_ID, new HashMap<String, String>()); + } + } } /** @@ -65,12 +105,14 @@ public class ToolboxExplorer extends CommonNavigator * Retrieves the current viewer if any * @return the instance of common viewer or <code>null</code> */ - private static CommonViewer getViewer() + public static CommonViewer getViewer() { CommonNavigator navigator = findCommonNavigator(ToolboxExplorer.VIEW_ID); if (navigator != null) { - return navigator.getCommonViewer(); + final CommonViewer commonViewer = navigator.getCommonViewer(); + commonViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS); + return commonViewer; } return null; @@ -79,7 +121,7 @@ public class ToolboxExplorer extends CommonNavigator /** * Refreshes the instance of the viewer if any */ - private static void refresh() + static void refresh() { CommonViewer instance = getViewer(); if (instance != null) @@ -113,37 +155,4 @@ public class ToolboxExplorer extends CommonNavigator // contentService.update(); // } // } - - /* - * Use an inner class because instantiation of ProblemView itself should be - * left to the Eclipse foundation and not be triggered directly via new. - */ - public static class ResourceListener implements IResourceChangeListener { - - private static ResourceListener INSTANCE; - - public synchronized static void init() { - if (INSTANCE == null) { - INSTANCE = new ResourceListener(); - } - } - - private ResourceListener() { - // We might have missed events during Toolbox startup when there was - // a workspace but no UI yet. - resourceChanged(null); - - // update CNF viewers - final IWorkspace workspace = ResourcesPlugin.getWorkspace(); - workspace.addResourceChangeListener(this); - } - - public void resourceChanged(IResourceChangeEvent event) { - UIHelper.runUIAsync(new Runnable() { - public void run() { - ToolboxExplorer.refresh(); - } - }); - } - } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorerResourceListener.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorerResourceListener.java new file mode 100644 index 0000000000000000000000000000000000000000..cb5322012d60418a881fc4f60f688e13f53f9df5 --- /dev/null +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/navigator/ToolboxExplorerResourceListener.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ +package org.lamport.tla.toolbox.ui.navigator; + +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.ui.navigator.CommonViewer; +import org.lamport.tla.toolbox.Activator; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.util.UIHelper; + +public class ToolboxExplorerResourceListener extends ToolboxLifecycleParticipant implements IResourceChangeListener { + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant#postWorkbenchWindowOpen() + */ + public void postWorkbenchWindowOpen() { + // We might have missed events during Toolbox startup when there was + // a workspace but no UI yet. + resourceChanged(null); + + // update CNF viewers + final IWorkspace workspace = ResourcesPlugin.getWorkspace(); + workspace.addResourceChangeListener(this); + } + + public void resourceChanged(final IResourceChangeEvent event) { + UIHelper.runUIAsync(new Runnable() { + public void run() { + ToolboxExplorer.refresh(); + // Expand the current spec and all its children + final CommonViewer viewer = ToolboxExplorer.getViewer(); + // Event is only null when this Ctor calls us causing the + // initial expanded state of a spec to be fully expanded. + // Afterwards, the users expanded states is preserved. + if (event == null && viewer != null) { // viewer might already be disposed which happens when the Toolbox shuts down. + final Spec specLoaded = Activator.getSpecManager().getSpecLoaded(); + viewer.expandToLevel(specLoaded, + AbstractTreeViewer.ALL_LEVELS); + } + } + }); + } +} \ No newline at end of file diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/IGroup.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/IGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..a2d93320fd85d1e34556d028e9d05d919ad3d450 --- /dev/null +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/IGroup.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package org.lamport.tla.toolbox.ui.provider; + +/** + * Just a marker interface. Each extender is ignored on double click events in the spec explorer. + */ +public interface IGroup { + +} diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/SpecContentProvider.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/SpecContentProvider.java index 46a34146b3769770c2181c4200f2b554fdd531b8..abdacbba28df70c23931898f41e46db02e1e888a 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/SpecContentProvider.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/SpecContentProvider.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.ui.provider; import java.util.Hashtable; @@ -13,120 +38,163 @@ import org.lamport.tla.toolbox.spec.Spec; import org.lamport.tla.toolbox.spec.manager.WorkspaceSpecManager; import org.lamport.tla.toolbox.util.ResourceHelper; - /** * Content provider listing the specifications - * @author Simon Zambrovski - * @version $Id$ */ -public class SpecContentProvider implements ITreeContentProvider - { - private static final Object[] EMPTY_ARRAY = new Object[0]; - private Hashtable reverseLookup; +public class SpecContentProvider implements ITreeContentProvider { + private static final Object[] EMPTY_ARRAY = new Object[0]; + + private final Hashtable<Module, Group> reverseLookup; + + public SpecContentProvider() { + reverseLookup = new Hashtable<Module, Group>(); + } - - public SpecContentProvider() - { - reverseLookup = new Hashtable(31); - } + /** + * Retrieves the children + */ + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof WorkspaceSpecManager) { + return ((WorkspaceSpecManager) parentElement).getRecentlyOpened(); + } else if (parentElement instanceof Spec) { + final Spec spec = (Spec) parentElement; + if (Activator.getSpecManager().isSpecLoaded(spec)) { + final Module[] constructModules = constructModules(spec); + final Group group = new Group(spec, constructModules); + for (int i = 0; i < constructModules.length; i++) { + final Module module = constructModules[i]; + reverseLookup.put(module, group); + } + return new Group[] {group}; + } + return EMPTY_ARRAY; + } else if (parentElement instanceof Group) { + return ((Group) parentElement).getModules(); + } else { + return EMPTY_ARRAY; + } + } - /** - * Retrieves the children - */ - public Object[] getChildren(Object parentElement) - { - if (parentElement instanceof WorkspaceSpecManager) - { - return ((WorkspaceSpecManager)parentElement).getRecentlyOpened(); - } else if (parentElement instanceof Spec) - { - return EMPTY_ARRAY; - /* - Module[] modules = constructModules((Spec) parentElement); - return modules; - */ - } else - { - return EMPTY_ARRAY; - } - } + public Object getParent(Object element) { + if (element instanceof Spec) { + return Activator.getSpecManager(); // ResourcesPlugin.getWorkspace().getRoot(); + } else if (element instanceof Group) { + return Activator.getSpecManager().isSpecLoaded(((Group) element).getSpec()); + } else if (element instanceof Module) { + return reverseLookup.get(element); + } + return null; + } + public boolean hasChildren(Object element) { + // A Spec only has children (visually indicated by a triangle in the + // Spec Explorer) if it is open. + if (element instanceof Spec) { + return Activator.getSpecManager().isSpecLoaded((Spec) element); + } else if (element instanceof Group) { + Group group = (Group) element; + if (Activator.getSpecManager().isSpecLoaded(group.getSpec())) { + return group.getModules().length > 0; + } + } + return (element instanceof WorkspaceSpecManager + /* || element instanceof Spec */); + } + + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } - public Object getParent(Object element) - { - if (element instanceof Spec) - { - return Activator.getSpecManager(); //ResourcesPlugin.getWorkspace().getRoot(); - } else if (element instanceof Module) - { - /* - Spec spec = (Spec) reverseLookup.get(element); - // cheaty hack - if (spec == null) - { - spec = Activator.getSpecManager().getSpecLoaded(); - } - return spec; - */ - } - return null; - } + public void dispose() { + reverseLookup.clear(); + } - public boolean hasChildren(Object element) { - // A Spec only has children (visually indicated by a triangle in the - // Spec Explorer) if it is open. - if (element instanceof Spec) { - return Activator.getSpecManager().isSpecLoaded((Spec) element); + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // no-op + } + + protected Module[] constructModules(Spec spec) { + final Vector<IResource> outOfSyncResourcesToDelete = new Vector<IResource>(); + final Vector<Module> modules = new Vector<Module>(); + final IResource[] moduleResources = spec.getModuleResources(); + for (int i = 0; i < moduleResources.length; i++) { + // skip non-modules + if (!ResourceHelper.isModule(moduleResources[i])) { + continue; + } + // skip non-existing files + if (!moduleResources[i].isSynchronized(IResource.DEPTH_ZERO)) { + outOfSyncResourcesToDelete.add(moduleResources[i]); + continue; } - return (element instanceof WorkspaceSpecManager - /* || element instanceof Spec */); + final Module module = new Module(moduleResources[i]); + modules.add(module); } - public Object[] getElements(Object inputElement) - { - return getChildren(inputElement); - } - - public void dispose() - { - reverseLookup = null; - } + if (outOfSyncResourcesToDelete.size() > 0) { + final DeleteOutOfSyncJob job = new DeleteOutOfSyncJob( + (IResource[]) outOfSyncResourcesToDelete.toArray(new IResource[outOfSyncResourcesToDelete.size()])); + job.schedule(500); + } - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) - { - - } - /** - * @deprecated not used - */ - protected Module[] constructModules(Spec spec) - { - - Vector outOfSyncResourcesToDelete = new Vector(); - Vector modules = new Vector(); - IResource[] moduleResources = spec.getModuleResources(); - for (int i = 0; i < moduleResources.length; i++) - { - // skip non-modules - if (!ResourceHelper.isModule(moduleResources[i])) - { - continue; - } - // skip non-existing files - if (!moduleResources[i].isSynchronized(IResource.DEPTH_ZERO)) - { - outOfSyncResourcesToDelete.add(moduleResources[i]); - continue; - } - Module module = new Module(moduleResources[i].getLocation().toOSString()); - modules.add(module); - reverseLookup.put(module, spec); - } + return (Module[]) modules.toArray(new Module[modules.size()]); + } + + public static class Group implements IGroup { + private final Module[] modules; + private final Spec spec; - DeleteOutOfSyncJob job = new DeleteOutOfSyncJob(outOfSyncResourcesToDelete); - job.setRule(ResourceHelper.getDeleteRule((IResource[]) outOfSyncResourcesToDelete.toArray(new IResource[outOfSyncResourcesToDelete.size()]))); - job.schedule(500); + public Group(Spec spec, Module[] modules) { + this.spec = spec; + this.modules = modules; + } + /** + * @return the spec + */ + public Spec getSpec() { + return spec; + } + /** + * @return the modules + */ + public Module[] getModules() { + return modules; + } - return (Module[]) modules.toArray(new Module[modules.size()]); - } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + return "modules"; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((spec == null) ? 0 : spec.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Group other = (Group) obj; + if (spec == null) { + if (other.spec != null) + return false; + } else if (!spec.equals(other.spec)) + return false; + return true; + } + } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/ToolboxLabelProvider.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/ToolboxLabelProvider.java index c9fd6cb65a77c5b1743a0dd4715079ecee8dde6d..1dd68140c18fdf64d43f30f0209623dd87faf070 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/ToolboxLabelProvider.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/provider/ToolboxLabelProvider.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ package org.lamport.tla.toolbox.ui.provider; import org.eclipse.core.resources.IFile; @@ -11,60 +36,50 @@ import org.eclipse.ui.navigator.IDescriptionProvider; import org.lamport.tla.toolbox.Activator; import org.lamport.tla.toolbox.spec.Module; import org.lamport.tla.toolbox.spec.Spec; +import org.lamport.tla.toolbox.ui.provider.SpecContentProvider.Group; /** * Label provider for all toolbox internal elements - * @author Simon Zambrovski - * @version $Id$ */ -public class ToolboxLabelProvider extends LabelProvider implements ILabelProvider, IDescriptionProvider -{ - public String getText(Object element) - { - if (element == null) - { - return null; - } - if (element instanceof Spec) - { - Spec spec = (Spec) element; - IFile root = spec.getRootFile(); - if (root == null) - { - return null; - } - return spec.getName() + " [ " + root.getName() + " ]"; - } else if (element instanceof Module) - { - return ((Module) element).getModuleName(); - } - return null; - } +public class ToolboxLabelProvider extends LabelProvider implements ILabelProvider, IDescriptionProvider { + + public String getText(final Object element) { + if (element == null) { + return null; + } + if (element instanceof Spec) { + final Spec spec = (Spec) element; + final IFile root = spec.getRootFile(); + if (root == null) { + return null; + } + return spec.getName() + " [ " + root.getName() + " ]"; + } else if (element instanceof Module) { + return ((Module) element).getModuleName(); + } else if (element instanceof Group) { + return element.toString(); + } + return null; + } - public String getDescription(Object element) - { - String text = getText(element); - return "This is a description of " + text; - } + public String getDescription(final Object element) { + return super.getText(element); + } - public Image getImage(Object element) - { - if (element == null) - { - return null; - } - if (element instanceof Spec) - { - if (((Spec) element) == Activator.getSpecManager().getSpecLoaded()) - { - return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); - } - return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT_CLOSED); - } else if (element instanceof Module) - { - - return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); - } - return null; - } + public Image getImage(final Object element) { + if (element == null) { + return null; + } + if (element instanceof Spec) { + if (Activator.getSpecManager().isSpecLoaded((Spec) element)) { + return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); + } + return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT_CLOSED); + } else if (element instanceof Module) { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); + } else if (element instanceof Group) { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY); + } + return null; + } } \ No newline at end of file diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemView.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemView.java index 4b6320a483295536d1429d3e535c08fec4962384..034642a8c2aae4aaa9cb2c5bdc067e0619992219 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemView.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemView.java @@ -6,11 +6,6 @@ import java.util.Collections; import java.util.List; import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.custom.StyledText; @@ -27,18 +22,12 @@ import org.lamport.tla.toolbox.util.AdapterFactory; import org.lamport.tla.toolbox.util.TLAMarkerHelper; import org.lamport.tla.toolbox.util.UIHelper; import org.lamport.tla.toolbox.util.compare.MarkerComparator; -import org.lamport.tla.toolbox.util.pref.IPreferenceConstants; -import org.lamport.tla.toolbox.util.pref.PreferenceStoreHelper; /** * Shows parse problems * @version $Id$ * @author Simon Zambrovski */ -/** - * @author makuppe - * - */ public class ProblemView extends ViewPart { public static final String ID = "toolbox.view.ProblemView"; @@ -166,76 +155,4 @@ public class ProblemView extends ViewPart { bar.setFocus(); } - - /* - * Use an inner class because instantiation of ProblemView itself should be - * left to the Eclipse foundation and not be triggered directly via new. - */ - public static class ResourceListener implements IResourceChangeListener { - - private static ResourceListener INSTANCE; - - public synchronized static void init() { - if (INSTANCE == null) { - INSTANCE = new ResourceListener(); - } - } - - private ResourceListener() { - // Check if there have been any markers set already - showOrHideProblemView(); - - // ...and listen for new markers - final IWorkspace workspace = ResourcesPlugin.getWorkspace(); - workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_BUILD); - } - - private void showOrHideProblemView() { - boolean showProblems = PreferenceStoreHelper.getInstancePreferenceStore().getBoolean( - IPreferenceConstants.I_PARSER_POPUP_ERRORS); - if (showProblems) { - if (TLAMarkerHelper.currentSpecHasProblems()) { - // This used to be in Activator. However, - // at startup there might not be an - // activePage which results in a - // NullPointerException. Thus, have the - // ProblemView check the parse status when - // UI startup complete. - ProblemView view = (ProblemView) UIHelper.getActivePage().findView(ProblemView.ID); - // show - if (view != null) { - // already shown, hide - UIHelper.hideView(ProblemView.ID); - } - - // not shown, show - UIHelper.openViewNoFocus(ProblemView.ID); - } else { - // hide - UIHelper.hideView(ProblemView.ID); - } - } - } - - private boolean hasMarkerDelta(IResourceChangeEvent event) { - IMarkerDelta[] deltas = event.findMarkerDeltas(TLAMarkerHelper.TOOLBOX_MARKERS_ALL_MARKER_ID, true); - if (deltas.length > 0) { - return true; - } - return false; - } - - public void resourceChanged(IResourceChangeEvent event) { - // no marker update - if (!hasMarkerDelta(event)) { - return; - } else { - UIHelper.runUIAsync(new Runnable() { - public void run() { - showOrHideProblemView(); - } - }); - } - } - } } diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemViewResourceListener.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemViewResourceListener.java new file mode 100644 index 0000000000000000000000000000000000000000..e49491915589205024b62e8ff9fb4c850a86fb6d --- /dev/null +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ProblemViewResourceListener.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Simon Zambrovski - initial API and implementation + ******************************************************************************/ +package org.lamport.tla.toolbox.ui.view; + +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.util.TLAMarkerHelper; +import org.lamport.tla.toolbox.util.UIHelper; +import org.lamport.tla.toolbox.util.pref.IPreferenceConstants; +import org.lamport.tla.toolbox.util.pref.PreferenceStoreHelper; + +/* + * Use an inner class because instantiation of ProblemView itself should be + * left to the Eclipse foundation and not be triggered directly via new. + */ +public class ProblemViewResourceListener extends ToolboxLifecycleParticipant implements IResourceChangeListener { + + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant#postWorkbenchWindowOpen() + */ + public void postWorkbenchWindowOpen() { + // Check if there have been any markers set already + showOrHideProblemView(); + + // ...and listen for new markers + final IWorkspace workspace = ResourcesPlugin.getWorkspace(); + workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_BUILD); + } + + private void showOrHideProblemView() { + boolean showProblems = PreferenceStoreHelper.getInstancePreferenceStore().getBoolean( + IPreferenceConstants.I_PARSER_POPUP_ERRORS); + if (showProblems) { + if (TLAMarkerHelper.currentSpecHasProblems()) { + // This used to be in Activator. However, + // at startup there might not be an + // activePage which results in a + // NullPointerException. Thus, have the + // ProblemView check the parse status when + // UI startup complete. + ProblemView view = (ProblemView) UIHelper.getActivePage().findView(ProblemView.ID); + // show + if (view != null) { + // already shown, hide + UIHelper.hideView(ProblemView.ID); + } + + // not shown, show + UIHelper.openViewNoFocus(ProblemView.ID); + } else { + // hide + UIHelper.hideView(ProblemView.ID); + } + } + } + + private boolean hasMarkerDelta(IResourceChangeEvent event) { + IMarkerDelta[] deltas = event.findMarkerDeltas(TLAMarkerHelper.TOOLBOX_MARKERS_ALL_MARKER_ID, true); + if (deltas.length > 0) { + return true; + } + return false; + } + + public void resourceChanged(IResourceChangeEvent event) { + // no marker update + if (!hasMarkerDelta(event)) { + return; + } else { + UIHelper.runUIAsync(new Runnable() { + public void run() { + showOrHideProblemView(); + } + }); + } + } +} \ No newline at end of file diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ToolboxWelcomeView.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ToolboxWelcomeView.java index 18fe08ff3511e98c134652887f0bd0060a51eba0..5637ae342ac0f9387720824d0deb25feaf012cec 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ToolboxWelcomeView.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/view/ToolboxWelcomeView.java @@ -25,43 +25,17 @@ ******************************************************************************/ package org.lamport.tla.toolbox.ui.view; -import java.net.URL; - -import org.eclipse.core.runtime.FileLocator; -import org.eclipse.core.runtime.Path; -import org.eclipse.jface.resource.ColorDescriptor; -import org.eclipse.jface.resource.FontDescriptor; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StyleRange; -import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.graphics.RGB; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.help.IWorkbenchHelpSystem; import org.eclipse.ui.part.ViewPart; +import org.lamport.tla.toolbox.ui.intro.ToolboxIntroPart; import org.lamport.tla.toolbox.util.UIHelper; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; /** * The toolbox view that is shown when no spec is loaded. * @author Daniel Ricketts - * @version $Id$ */ public class ToolboxWelcomeView extends ViewPart { - public ToolboxWelcomeView() { - } private Composite parentComposite; @@ -71,139 +45,10 @@ public class ToolboxWelcomeView extends ViewPart { parentComposite = parent; - createControl(parent); + ToolboxIntroPart.createControl(parent); UIHelper.setHelp(parent, "WelcomeView"); } - - public static void createControl(Composite container) { - Composite outerContainer = new Composite(container, SWT.NONE); - - // The local resource manager takes care of disposing images, fonts and colors when - // the outerContainer Composite is disposed. - final LocalResourceManager localResourceManager = new LocalResourceManager(JFaceResources.getResources(), - outerContainer); - - final GridLayout gridLayout = new GridLayout(); - gridLayout.numColumns = 2; - outerContainer.setLayout(gridLayout); - final Color backgroundColor = localResourceManager.createColor(ColorDescriptor.createFrom(new RGB(255, 255, 228))); - outerContainer.setBackground(backgroundColor); - - /* Logo */ - final Label lblImage = new Label(outerContainer, SWT.NONE); - lblImage.setText("Invisible text"); - final Bundle bundle = FrameworkUtil.getBundle(ToolboxWelcomeView.class); - final URL url = FileLocator.find(bundle, new Path("images/splash_small.bmp"), null); - final ImageDescriptor logoImage = ImageDescriptor.createFromURL(url); - lblImage.setImage(localResourceManager.createImage(logoImage)); - - /* Welcome header */ - final Label lblHeader = new Label(outerContainer, SWT.WRAP); - - // Double its font size - final FontDescriptor headerFontDescriptor = JFaceResources.getHeaderFontDescriptor(); - final FontData fontData = headerFontDescriptor.getFontData()[0]; - lblHeader.setFont(localResourceManager.createFont(headerFontDescriptor.setHeight(fontData.getHeight() * 2))); - - // Color value (taken from old style.css) - lblHeader.setForeground(localResourceManager.createColor(new RGB(0, 0, 192))); - - lblHeader.setLayoutData(new GridData(SWT.LEFT, SWT.BOTTOM, true, false, 1, 1)); - lblHeader.setText("Welcome to the TLA\u207A Toolbox"); - lblHeader.setBackground(backgroundColor); - - /* What is next section */ - - Label lblSeparator = new Label(outerContainer, SWT.NONE); - lblSeparator.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); - - final StyledText styledWhatIsNext = new StyledText(outerContainer, SWT.WRAP | SWT.CENTER); - styledWhatIsNext.setBackground(backgroundColor); - final String whatIsnext = "There is no specification open. Click on Help if you're not sure what you should do next."; - styledWhatIsNext.setText(whatIsnext); - GridData gd_styledWhatIsNext = new GridData( GridData.FILL_HORIZONTAL); - gd_styledWhatIsNext.horizontalAlignment = SWT.LEFT; - gd_styledWhatIsNext.horizontalSpan = 2; - styledWhatIsNext.setLayoutData(gd_styledWhatIsNext); - - StyleRange winStyle = new StyleRange(); - winStyle.underline = true; - winStyle.underlineStyle = SWT.UNDERLINE_LINK; - - int[] winRange = {whatIsnext.indexOf("Help"), "Help".length()}; - StyleRange[] winStyles = {winStyle}; - styledWhatIsNext.setStyleRanges(winRange, winStyles); - - // link styled text to getting started guide - styledWhatIsNext.addListener(SWT.MouseDown, new Listener() { - public void handleEvent(Event event) { - IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem(); - helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/contents.html"); - } - }); - -// Composite composite = new Composite(outerContainer, SWT.NONE); -// composite.setLayout(new GridLayout(2, false)); -// composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true, 2, 1)); //SWT.FILL, SWT.CENTER, false, false, 2, 1)); - - /* Getting started text */ - - final StyledText styledGettingStarted = new StyledText(outerContainer, SWT.WRAP | SWT.CENTER); - styledGettingStarted.setBackground(backgroundColor); - final String lblString = "If this is the first time you have used the Toolbox, please read the Getting Started guide."; - styledGettingStarted.setText(lblString); - GridData gd_styledGettingStarted = new GridData( GridData.FILL_HORIZONTAL); - gd_styledGettingStarted.horizontalAlignment = SWT.LEFT; - gd_styledGettingStarted.horizontalSpan = 2; - styledGettingStarted.setLayoutData(gd_styledGettingStarted); - new Label(outerContainer, SWT.NONE); - new Label(outerContainer, SWT.NONE); - - StyleRange style = new StyleRange(); - style.underline = true; - style.underlineStyle = SWT.UNDERLINE_LINK; - - int[] range = {lblString.indexOf("Getting Started"), "Getting Started".length()}; - StyleRange[] styles = {style}; - styledGettingStarted.setStyleRanges(range, styles); - - // link styled text to getting started guide - styledGettingStarted.addListener(SWT.MouseDown, new Listener() { - public void handleEvent(Event event) { - IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem(); - helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/gettingstarted/gettingstarted.html"); - } - }); - -// This shows a help button next to the styled text -// final Button helpButton = new Button(composite, SWT.PUSH); -// helpButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1)); -// helpButton.setSize(36, 36); -// helpButton.setText (""); -// helpButton.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_LCL_LINKTO_HELP)); -// helpButton.addListener (SWT.Selection, new Listener () { -// public void handleEvent (Event event) { -// IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem(); -//// helpSystem.displayHelp("org.lamport.tla.toolbox.GettingStarted"); -//// helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/contents.html"); -// helpSystem.displayHelpResource("/org.lamport.tla.toolbox.doc/html/gettingstarted/gettingstarted.html"); -// } -// }); - - /* Toolbox version */ - final Label verticalFillUp = new Label(outerContainer, SWT.NONE); - verticalFillUp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true, 2, 1)); - verticalFillUp.setBackground(backgroundColor); - - final Label horizontalLine = new Label(outerContainer, SWT.SEPARATOR | SWT.HORIZONTAL); - horizontalLine.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); - - final Label lblVersion = new Label(outerContainer, SWT.WRAP); - lblVersion.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); - lblVersion.setText("Version 1.5.1 of 1 June 2015"); - lblVersion.setBackground(backgroundColor); - } public void setFocus() { diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/wizard/NewSpecWizardPage.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/wizard/NewSpecWizardPage.java index 191c6367aeebaa23d7377ac51164fbd53ccb99de..3535353bf47d308b938d2d3757385654166923b5 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/wizard/NewSpecWizardPage.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/ui/wizard/NewSpecWizardPage.java @@ -28,7 +28,6 @@ import org.lamport.tla.toolbox.util.UIHelper; * A wizard page input of the specification name and the location of the root file * * @author Simon Zambrovski - * @version $Id$ */ public class NewSpecWizardPage extends WizardPage { @@ -88,7 +87,7 @@ public class NewSpecWizardPage extends WizardPage // brows button Button button = new Button(container, SWT.PUSH); - button.setText("Browse..."); + button.setText("&Browse..."); button.addSelectionListener(new SelectionAdapter() { public synchronized void widgetSelected(SelectionEvent e) { @@ -297,7 +296,7 @@ public class NewSpecWizardPage extends WizardPage Spec existingSpec = Activator.getSpecManager().getSpecByName(specName); if (existingSpec != null) { - reportError("The specification with provided name is already exists \nand uses " + reportError("The specification with provided name already exists \nand uses " + existingSpec.getRootFilename() + " as root module."); return; } @@ -327,7 +326,7 @@ public class NewSpecWizardPage extends WizardPage if (!importExisting.getSelection()) { reportError("The " + getSpecName() + ".toolbox directory already exists at the provided location." - + "\nPlease select a different specification name or root-module file."); + + "\nPlease select a different specification name or root-module file."); return; } importExisting.setEnabled(true); diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/UIHelper.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/UIHelper.java index c517c382ab8cb93040c4d5ac827695d1bfef2395..b9cfaa354c373afe0f49a081310298bbd9ab4aa6 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/UIHelper.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/UIHelper.java @@ -1150,7 +1150,12 @@ public class UIHelper { * @return The currently active editor */ public static IEditorPart getActiveEditor() { - return getActivePage().getActiveEditor(); + final IWorkbenchPage activePage = getActivePage(); + if (activePage != null) { + // At Toolbox startup, activePage can be null. + return activePage.getActiveEditor(); + } + return null; } /** diff --git a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/pref/UnwantedPreferenceManager.java b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/pref/UnwantedPreferenceManager.java index e297e578eec400498e942a1c4f4e449b6c16a548..3ed0b3c98f1c8cc755e917fce5fbb97b120b51c1 100644 --- a/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/pref/UnwantedPreferenceManager.java +++ b/org.lamport.tla.toolbox/src/org/lamport/tla/toolbox/util/pref/UnwantedPreferenceManager.java @@ -3,8 +3,10 @@ package org.lamport.tla.toolbox.util.pref; import org.eclipse.jface.preference.IPreferenceNode; import org.eclipse.jface.preference.PreferenceManager; import org.lamport.tla.toolbox.Activator; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleException; -import org.lamport.tla.toolbox.tool.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant; +import org.lamport.tla.toolbox.tool.ToolboxHandle; +import org.lamport.tla.toolbox.ui.intro.ToolboxIntroPart; +import org.lamport.tla.toolbox.util.UIHelper; /** * This class removes unwanted preference pages that are declared @@ -25,7 +27,7 @@ public class UnwantedPreferenceManager extends ToolboxLifecycleParticipant // TODO Auto-generated constructor stub } - public void initialize() throws ToolboxLifecycleException + public void initialize() { if (Activator.getDefault().getWorkbench() == null) { @@ -95,4 +97,13 @@ public class UnwantedPreferenceManager extends ToolboxLifecycleParticipant } } + /* (non-Javadoc) + * @see org.lamport.tla.toolbox.lifecycle.ToolboxLifecycleParticipant#terminate() + */ + public void terminate() { + if (!ToolboxHandle.getInstanceStore().getBoolean(ToolboxHandle.I_RESTORE_LAST_SPEC)) { + UIHelper.getActivePage().closeAllEditors(true); + UIHelper.switchPerspective(ToolboxIntroPart.PERSPECTIVE_ID); + } + } } diff --git a/pom.xml b/pom.xml index c3cf44027cff1053ba90b1259cce9f9806b95f3c..93a6bce55179225a2500cafec5e763ae9917ba5a 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ <!-- http://maven.apache.org/general.html#encoding-warning --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <tycho-version>0.21.0</tycho-version> + <tycho-version>0.24.0</tycho-version> <!-- no default here --> <tycho.test.vm.argline>-Xmx500m -Xdebug -Xrunjdwp:transport=dt_socket,address=1044,server=y,suspend=n</tycho.test.vm.argline> @@ -176,11 +176,6 @@ </environment> <!-- Mac --> - <environment> - <os>macosx</os> - <ws>cocoa</ws> - <arch>x86</arch> - </environment> <environment> <os>macosx</os> <ws>cocoa</ws> diff --git a/tlatools/build.properties b/tlatools/build.properties index 40edd6e44e2d13cee0b67d4d03ec4eadef80c7d4..ce318758b302beeea273f6a96e2008eee549884a 100644 --- a/tlatools/build.properties +++ b/tlatools/build.properties @@ -1,6 +1,8 @@ source.. = src/,\ src-aj/,\ - test/ + test/,\ + test-concurrent/,\ + test-long/ bin.includes = META-INF/,\ . output.. = bin/ diff --git a/tlatools/customBuild.xml b/tlatools/customBuild.xml index f062665e9d0c452b5a1d712264ce952e6f3f93f1..25ac4df0d82be8b0bce9109f7b409434394b5eb0 100644 --- a/tlatools/customBuild.xml +++ b/tlatools/customBuild.xml @@ -43,6 +43,7 @@ <target name="default" depends="info" description="Default"> <antcall target="compile" inheritall="true" inheritrefs="true" /> <antcall target="compile-aj" inheritall="true" inheritrefs="true" /> + <antcall target="compile-test" inheritall="true" inheritrefs="true" /> <antcall target="test" inheritall="true" inheritrefs="true" /> <antcall target="dist" inheritall="true" inheritrefs="true" /> <antcall target="test-dist" inheritall="true" inheritrefs="true" /> @@ -53,16 +54,39 @@ <antcall target="dist-mixed-zip" inheritall="true" inheritrefs="true" /> </target> + <!-- Compiles and runs the short-running tests. --> + <target name="shorttests" depends="info" description="Run tests"> + <antcall target="default" inheritall="true" inheritrefs="true" /> + <antcall target="test-verify" inheritall="true" inheritrefs="true" /> + </target> - - <target name="clean" description="Cleans the compilation output directory" unless="noclean"> + <!-- Compiles and runs *only* the long-running tests. Not the ones. --> + <target name="longtests" depends="info" description="Run long-running tests"> + <antcall target="compile" inheritall="true" inheritrefs="true" /> + <antcall target="compile-aj" inheritall="true" inheritrefs="true"> + <!-- long tests require AspjectJ --> + <param name="withaj" value="true"/> + </antcall> + <antcall target="compile-test" inheritall="true" inheritrefs="true" /> + <antcall target="dist" inheritall="true" inheritrefs="true" /> + <antcall target="test-dist-long" inheritall="true" inheritrefs="true" /> + </target> + + <!-- ===========================================================--> + <!-- Don't call any of the targets below, use the umbrella --> + <!-- targets above which correctly call a target's dependencies --> + <!-- ===========================================================--> + + <!-- Cleans the compilation output directory --> + <target name="clean" unless="noclean"> <delete includeemptydirs="true" failonerror="false"> <fileset dir="${class.dir}" includes="*/**" /> <fileset dir="${test.class.dir}" includes="*/**" /> </delete> </target> - <target name="compile" depends="clean" description="Compile"> + <!-- Compiles the TLA+ tools code --> + <target name="compile" depends="clean"> <echo> ================================================================ = The following warnings about sun.misc.Unsafe can be ignored. = @@ -92,6 +116,7 @@ </copy> </target> + <!-- Compiles AspectJ auxiliary code --> <target name="compile-aj" if="withaj"> <echo> ==================================================================== @@ -101,7 +126,7 @@ ==================================================================== </echo> <!-- compile aspectj related class files --> - <iajc destdir="${class.dir}" sourceRoots="${src-aj.dir}" debug="true"> + <iajc destdir="${class.dir}" sourceRoots="${src-aj.dir}" debug="true" source="1.5" target="1.5"> <classpath refid="project.classpath" /> <classpath> <pathelement location="lib/aspectjrt-1.8.5.jar" /> @@ -128,7 +153,8 @@ </copy> </target> - <target name="dist" description="Build a distribution"> + <!-- Creates a distribution out of compiled code --> + <target name="dist"> <!-- Extract javax.mail jar into class directory to be packaged into tla2tools.jar --> <!-- javax.mail is used by the MailSender functionality, that mails the output --> <!-- of TLC to a given email address. --> @@ -159,12 +185,14 @@ <!-- Depending on security level, the user will see a warning otherwise. --> <!-- http://docs.oracle.com/javase/7/docs/technotes/guides/jweb/security/manifest.html --> <attribute name="Application-Name" value="TLC" /> - <attribute name="permissions" value="all-permissions" /> <!-- won't work with 'sandbox' as it writes files --> + <attribute name="permissions" value="all-permissions" /> + <!-- won't work with 'sandbox' as it writes files --> </manifest> </jar> </target> - <target name="test" description="Executes accompining unit tests" unless="test.skip"> + <!-- Compiles the TLA+ tools *test* code --> + <target name="compile-test" unless="test.skip"> <!-- compile unit tests --> <mkdir dir="${test.class.dir}" /> <javac includeantruntime="false" srcdir="${test.dir}" destdir="${test.class.dir}" debug="true" verbose="false"> @@ -182,10 +210,16 @@ <copy todir="${ws.class.dir}"> <fileset dir="${class.dir}" /> </copy> + </target> + + <!-- Executes accompining unit tests --> + <target name="test" unless="test.skip"> <!-- run junit tests --> <mkdir dir="${test.reports}" /> <jacoco:coverage destfile="target/code-coverage.exec"> <junit printsummary="yes" haltonfailure="no" haltonerror="no" forkmode="perTest" fork="yes"> + <!-- enable all assertions --> + <jvmarg value="-ea"/> <!-- Uncomment to open a debug port in each forked VM to remote attach your Eclipse at port 1044. <jvmarg value="-Xdebug" /> <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1044" /> @@ -196,7 +230,7 @@ <pathelement location="lib/cglib-nodep-3.1.jar" /> <pathelement location="lib/objenesis-2.1.jar" /> <pathelement location="lib/easymock-3.3.1.jar" /> - <pathelement path="${ws.class.dir}" /> + <pathelement path="${class.dir}" /> <pathelement path="${test.class.dir}" /> </classpath> <formatter type="xml" /> @@ -207,6 +241,7 @@ <fileset dir="${test.dir}"> <include name="**/*Test*.java" /> <exclude name="**/ModelCheckerTestCase.java" /> + <exclude name="**/TLCServerTestCase.java" /> <exclude name="**/SuccessfulSimulationTestCase.java" /> <exclude name="**/AbstractExampleTestCase.java" /> <exclude name="**/TestMPRecorder.java" /> @@ -223,27 +258,13 @@ <delete dir="${ws.class.dir}" deleteonexit="true"/> </target> - <target name="test-dist" description="Executes accompining unit tests on jar file" unless="test.skip"> - <!-- compile unit tests --> - <mkdir dir="${test.class.dir}" /> - <javac includeantruntime="false" srcdir="${test.dir}" destdir="${test.class.dir}" debug="true" verbose="false"> - <classpath refid="project.classpath" /> - <classpath> - <pathelement location="lib/junit-4.8.2.jar" /> - <pathelement location="lib/easymock-3.3.1.jar" /> - <pathelement path="${class.dir}" /> - </classpath> - </javac> - <!-- copy class.dir to path with whitespace --> - <!-- this is required by some tests to make sense --> - <!-- even throw a "+" and whitespace into the mix --> - <property name="ws.class.dir" value="TLA+ Tools" /> - <copy todir="${ws.class.dir}"> - <fileset dir="${class.dir}" /> - </copy> + <!-- Executes accompining unit tests on jar file --> + <target name="test-dist" unless="test.skip"> <!-- run junit tests on tlatools.jar --> <mkdir dir="${test.reports}/onJar" /> <junit printsummary="yes" haltonfailure="no" haltonerror="no" forkmode="perTest" fork="yes"> + <!-- enable all assertions --> + <jvmarg value="-ea"/> <classpath refid="project.classpath" /> <classpath> <pathelement location="lib/junit-4.8.2.jar" /> @@ -261,6 +282,7 @@ <fileset dir="${test.dir}"> <include name="**/*Test*.java" /> <exclude name="**/ModelCheckerTestCase.java" /> + <exclude name="**/TLCServerTestCase.java" /> <exclude name="**/SuccessfulSimulationTestCase.java" /> <exclude name="**/AbstractExampleTestCase.java" /> <exclude name="**/TestMPRecorder.java" /> @@ -276,7 +298,8 @@ <delete dir="${ws.class.dir}" deleteonexit="true"/> </target> - <target name="test-dist-long" description="Executes accompining long-running unit tests on jar file" unless="test.skip"> + <!-- Executes accompining long-running unit tests on jar file --> + <target name="test-dist-long" unless="test.skip"> <!-- compile unit tests --> <mkdir dir="${test.class.dir}" /> <javac includeantruntime="false" srcdir="${test.dir}-long" destdir="${test.class.dir}" debug="true" verbose="false"> @@ -296,6 +319,8 @@ <!-- run junit tests on tlatools.jar --> <mkdir dir="${test.reports}/onJarLong" /> <junit printsummary="yes" haltonfailure="no" haltonerror="no" maxmemory="4096m" forkmode="perTest" fork="yes"> + <!-- enable all assertions --> + <jvmarg value="-ea"/> <jvmarg value="-javaagent:lib/aspectjweaver-1.8.5.jar" /> <sysproperty key="org.aspectj.weaver.showWeaveInfo" value="false"/> <sysproperty key="aj.weaving.verbose" value="false"/> @@ -304,6 +329,8 @@ <pathelement location="lib/junit-4.8.2.jar" /> <pathelement location="${dist.file}" /> <pathelement path="${test.class.dir}" /> + <!-- Need class.dir on path to find AspectJ related classes which don't get packaged into dist --> + <pathelement path="${class.dir}" /> </classpath> <formatter type="xml" /> @@ -329,8 +356,8 @@ <delete dir="${ws.class.dir}" deleteonexit="true"/> </target> - <!-- --> - <target name="test-verify" description="Verifies TLC parts with NASA's pathfinder model checker"> + <!-- Verifies TLC parts with NASA's pathfinder model checker --> + <target name="test-verify"> <!-- Compile our boilerplate code needed to verify TLC. E.g. the StateQueueVerify creates --> <!-- examplary produces & consumers and a dummy implementation of StateQueue. --> <javac includeantruntime="false" srcdir="${test.dir}-verify" destdir="${test.class.dir}" debug="true" verbose="false"> @@ -363,7 +390,8 @@ </junit> </target> - <target name="dist-mixed-jar" description="Build a distribution" depends="default"> + <!-- Build a distribution --> + <target name="dist-mixed-jar" depends="default"> <!-- create a JAR file for the users --> <jar destfile="${dist-mixed.file.jar}"> <fileset dir="${class.dir}" includes="**/*" /> @@ -389,7 +417,8 @@ </target> - <target name="dist-mixed-zip" description="Build a distribution" depends="default"> + <!-- Build a distribution --> + <target name="dist-mixed-zip" depends="default"> <!-- create a zip file for the users and developers--> <zip destfile="${dist-mixed.file}"> <zipfileset dir="${src.dir}" includes="**/*.java" prefix="tla" /> diff --git a/tlatools/pom.xml b/tlatools/pom.xml index 147cd9c783d0dc531cd1cb83915fc5965992da1a..3ea4f1c5146947a39fee51d69816d783b3b1b9d4 100644 --- a/tlatools/pom.xml +++ b/tlatools/pom.xml @@ -81,9 +81,9 @@ <dependencies> <dependency> - <groupId>ant</groupId> + <groupId>org.apache.ant</groupId> <artifactId>ant-junit</artifactId> - <version>1.6.2</version> + <version>1.7.1</version> <scope>compile</scope> </dependency> <dependency> diff --git a/tlatools/src/pcal/ParseAlgorithm.java b/tlatools/src/pcal/ParseAlgorithm.java index 753f5d5be77df98a8f05b8b430e93443e93a5efe..7b878cbbe3f104c0cab3864d2f05ab75696624d6 100644 --- a/tlatools/src/pcal/ParseAlgorithm.java +++ b/tlatools/src/pcal/ParseAlgorithm.java @@ -292,6 +292,12 @@ public class ParseAlgorithm * Performs the initialization method of the AST to create default * * objects for each subclass. * *******************************************************************/ + + /* + * Following added by LL on 17 Aug 2015 to fix bug apparently caused by + * incomplete initialization + */ + LATsize = 0 ; } diff --git a/tlatools/src/pcal/PcalFixIDs.java b/tlatools/src/pcal/PcalFixIDs.java index 282c77cdc81921944749280c5ec865155137d016..ac08502054e8dec858a3f092487d8ad58e480af7 100644 --- a/tlatools/src/pcal/PcalFixIDs.java +++ b/tlatools/src/pcal/PcalFixIDs.java @@ -181,10 +181,25 @@ public class PcalFixIDs { // For some reason, this originally called PcalDebug.ReportBug. // Since it can occur just because there is no procedure by that // name in the code, it occurs on an error. Fixed 31 May 2013 by LL. + // This fix caused the for loop that follows to throw an ArrayIndexOutOfBounds + // exception. When run from the Toolbox, this caused the error message + // to be lost and the Translate command to fail with no message. + // It appears that it's not necessary to report the error here, because + // it will be caught later. Moreover, when caught later, the location + // of the bad procedure name is indicated. That location doesn't seem to + // available here. However, I'm hesitant to remove the error report in case + // the error isn't caught later in all cases. So I have added the return + // to avoid the exception. Hopefully, no further exceptions can be caused + // by the error. However, I don't know how to make sure that's the + // case without figuring out how the code works. + // I also removed an unhelpful part of the message. LL 30 Aug 2015 PcalDebug.reportError( - "Could not find procedure name `" + pName + - "' in method FixMultiprocess"); - } + "Could not find procedure name `" + pName +"'" + // + "' in method FixMultiprocess" + ) ; + return ; // Added 30 Aug 2015 + + } // For each k such that path[pNum][k] is true // (meaning that procedure number pNum calls // procedure number k), add diff --git a/tlatools/src/pcal/PcalParams.java b/tlatools/src/pcal/PcalParams.java index c7315d531780237fcaa76c6237110856c3213658..35c68b56889aa6aeadaba0201aa499d459291027 100644 --- a/tlatools/src/pcal/PcalParams.java +++ b/tlatools/src/pcal/PcalParams.java @@ -1,318 +1,318 @@ -/*************************************************************************** -* CLASS PcalParams * -* * -* The fields of this class consist of all the parameters used by * -* pcal.trans. Some of them are set by program options. * -***************************************************************************/ -package pcal ; -import java.util.Vector; - -/** - * Holds parameters for the PCal Translator - * @author Keith Marzullo - * @author Simon Zambrovski - * @version $Id$ - */ -public final class PcalParams -{ - /** - * Parameters to be updated on each new release. - */ - public static final String modDate = "2 Apr 2013"; - public static final String version = "1.8"; - /** - * SZ Mar 9, 2009: - * Added re-initialization method. Since PcalParams class - * is used instead of PcalParams instance, it is required to - * take care of parameter initialization and de-initialization - * by explicit method. This is required in order to make PCal - * instance reentrant. - * - * Maybe at some point in time this should be converted to an ordinary - * configuration object, from the collection of public static variables. - */ - public static void resetParams() - { - Debug = false; - SpecOption = false; - MyspecOption = false; - Spec2Option = false; - Myspec2Option = false; - SpecFile = ""; - WriteASTFlag = false; - LabelFlag = false; - ReportLabelsFlag = false; - LabelRoot = "Lbl_"; - FairnessOption = ""; - FairAlgorithm = false; - CheckTermination = false; - Nocfg = false; - NoDoneDisjunct = false; - optionsInFile = false; - versionOption = null; - inputVersionNumber = VersionToNumber(PcalParams.version); - PcalTLAGen.wrapColumn = 78; - PcalTLAGen.ssWrapColumn = 45; - tlaPcalMapping = null ; - - } - - - /************************************************************************* - * Parameters related to non-file Options * - *************************************************************************/ - public static boolean Debug = false ; - /*********************************************************************** - * True if the -debug option is chosen. * - ***********************************************************************/ - - public static boolean SpecOption = false ; - /*********************************************************************** - * True if the -spec option is chosen. * - ***********************************************************************/ - - public static boolean MyspecOption = false ; - /*********************************************************************** - * True if the -myspec option is chosen. * - ***********************************************************************/ - - public static boolean Spec2Option = false ; - /*********************************************************************** - * True if the -spec2 option is chosen. * - ***********************************************************************/ - - public static boolean Myspec2Option = false ; - /*********************************************************************** - * True if the -myspec2 option is chosen. * - ***********************************************************************/ - - public static String SpecFile = "" ; - /*********************************************************************** - * The file name if the -spec option is chosen. * - ***********************************************************************/ - - public static boolean WriteASTFlag = false ; - /*********************************************************************** - * True if the -writeAST option is chosen. * - ***********************************************************************/ - - public static boolean LabelFlag = false ; - /********************************************************************* - * True iff the -label option is chosen. * - *********************************************************************/ - - public static boolean ReportLabelsFlag = false ; - /********************************************************************* - * True iff the -reportLabels option is chosen. * - *********************************************************************/ - - public static String LabelRoot = "Lbl_" ; - /********************************************************************* - * The root of translator-generated labels, set to its non-default * - * value by the -labelRoot option. * - *********************************************************************/ - - /************************************************************************* - * Parameters for Spec and .cfg file options. * - *************************************************************************/ - public static String FairnessOption = "" ; - /********************************************************************* - * Should be "", "wf", "sf", "wfNext", or "sfNext". * - *********************************************************************/ - - public static boolean CheckTermination = false ; - /********************************************************************* - * True iff there is a -termination option. * - *********************************************************************/ - - public static boolean Nocfg = false ; - /********************************************************************* - * True iff there is a -nocfg option. * - *********************************************************************/ - - public static boolean NoDoneDisjunct = false ; - /********************************************************************* - * True iff there is a -noDoneDisjunct option. * - *********************************************************************/ - - /********************************************************************** - * The following parameter is set true if --fair algorithm is used. * - *********************************************************************/ - public static boolean FairAlgorithm = false ; - - /************************************************************************* - * Parameters related to language definition. * - *************************************************************************/ - public static TLAExpr DefaultVarInit() - /********************************************************************* - * Returns the default initial value of a variable declared with no * - * value specified, which is herein defined to be "{}". * - * * - * Default initial value changed to "defaultInitValue" * - * by LL on 22 Aug 2007 * - *********************************************************************/ - { Vector line = new Vector() ; -// line.addElement(new TLAToken("{", 0, 0)) ; -// line.addElement(new TLAToken("}", 0, 0)) ; - line.addElement(new TLAToken("defaultInitValue", 0, 0)); - Vector vec = new Vector() ; - vec.addElement(line) ; - TLAExpr exp = new TLAExpr(vec) ; - exp.normalize() ; - return exp ; - } - - /*********************************************************************** - * The strings identifying the beginning of the algorithm in the .tla * - * file. * - * The strings "--fair" and "algorithm" added by LL on 6 July 2011. * - ***********************************************************************/ - public static final String BeginAlg = "--algorithm" ; - public static final String BeginFairAlg = "--fair" ; - public static final String BeginFairAlg2 = "algorithm" ; - - - - /*********************************************************************** - * The strings marking the beginning and end of the translated * - * algorithm in a .tla input file. The translation is put immediately * - * after any line containing * - * * - * BeginXLation1 [one or more spaces] BeginXlation2 * - * * - * It is followed by a line containing * - * * - * EndXLation1 [one or more spaces] EndXlation2. * - ***********************************************************************/ - public static final String BeginXlation1 = "BEGIN" ; - public static final String BeginXlation2 = "TRANSLATION" ; - - public static final String EndXlation1 = "END" ; - public static final String EndXlation2 = "TRANSLATION" ; - - /************************************************************************* - * The string identifying the end of the automatically generated part of * - * the .cfg file and the beginning of the user-added part. * - *************************************************************************/ - public static String CfgFileDelimiter = - "\\* Add statements after this line." ; - -/************ Stuff for .pcal file ***************************************/ - -// /*********************************************************************** -// * The string identifying the beginning of the algorithm in the .pcal * -// * file. * -// ***********************************************************************/ -// public static final String PcalBeginAlg = "algorithm"; -// -// /** -// * The <row, col> (in Java coordinates) of the beginning of the -// * "algorithm" token in the .pcal file. -// */ -// public static IntPair endOfPreamble = null; -// -// /** -// * The <row, col> (in Java coordinates) of the beginning of the TLA+ -// * "code" that follows the algorithm in the input (.pcal) and output -// * (.tla) files. For outputSuffixLoc, the column always equals 0. -// */ -// public static IntPair inputSuffixLoc = null; -// public static IntPair outputSuffixLoc = null; -// - /************************************************************************* - * File parameters. * - *************************************************************************/ - public static String TLAInputFile = "" ; - /*********************************************************************** - * The name of the input file, with no extension. It is set to equal * - * the argument with which the program is called, minus the extension. * - * With the introduction of pcal files, the name no longer makes sense. * - ***********************************************************************/ - - /** - * Pcal-File Parameters - * The following parameters were introduced when .pcal files were - * (briefly) added. However, most of them still seem to be used. - */ - - public static boolean optionsInFile = false ; - // Set true when an options statement has been found in the - // module. It is a kludgy way to pass an argument to - // trans.parseAndProcessStringArguments; things are done this - // way because of the way the code evolved, and no intelligent - // design has stepped in to fix it. - public static String versionOption = null; - public static int inputVersionNumber = VersionToNumber(PcalParams.version); - // The input file's version number * 1000 -// public static boolean readOnly = false; - // True iff this is a .pcal input file and the .tla file should - // not be writable. This is obsolete and is not used. - - /** - * The following parameter is used to hold the TLAtoPCalMapping object - * that is computed by the translation. Some of that object's fields are - * used in creating the actual mapping. It's easier to have the methods - * that need to use those fields access them via this parameter than to do - * the more politically correct thing and pass the fields or the object - * as a parameter in the method calls. - */ - public static TLAtoPCalMapping tlaPcalMapping ; - - /** - * If str is a version number like 3.17, then this returns 1000 times - * its numeric value--e.g., 3170. Otherwise, it returns -1. - * - */ - public static int VersionToNumber(String str) { - boolean error = false ; - int i = 0; - int result = 0; - int digitsAfterDecimal = 0; - boolean afterDecimal = false; - while ((!error) && i < str.length()) { - char c = str.charAt(i); - if (('0' <= c) && (c <= '9')) { - result = 10 * result + (c - '0'); - if (afterDecimal) { - digitsAfterDecimal++; - if (digitsAfterDecimal > 3) { - error = true; - } - } - } else if (c == '.') { - afterDecimal = true; - } else { - error = true; - } - i++; - } - if (error) { - return -1; - } - for (int j = 0; j < 3 - digitsAfterDecimal; j++) { - result = 10 * result; - } - return result; - } - /** - * Processes the version argument ver. It sets versionNumber - * and returns true if it is a legal version number; otherwise, - * it reports the error with PcalDebug.reportError and returns false. - */ - static boolean ProcessVersion(String ver) { - int vnum = VersionToNumber(ver); - if (vnum < 0) { - PcalDebug.reportError("Illegal version " + ver + " specified."); - return false; - } - if (vnum > VersionToNumber(PcalParams.version)) { - PcalDebug.reportError("Specified version " + ver + - " later than current version " + PcalParams.version); - return false; - } - inputVersionNumber = vnum; - return true ; - } - } - -/* last modified on Thu 23 Aug 2007 at 10:40:25 PST by lamport */ +/*************************************************************************** +* CLASS PcalParams * +* * +* The fields of this class consist of all the parameters used by * +* pcal.trans. Some of them are set by program options. * +***************************************************************************/ +package pcal ; +import java.util.Vector; + +/** + * Holds parameters for the PCal Translator + * @author Keith Marzullo + * @author Simon Zambrovski + * @version $Id$ + */ +public final class PcalParams +{ + /** + * Parameters to be updated on each new release. + */ + public static final String modDate = "18 Aug 2015"; + public static final String version = "1.8"; + /** + * SZ Mar 9, 2009: + * Added re-initialization method. Since PcalParams class + * is used instead of PcalParams instance, it is required to + * take care of parameter initialization and de-initialization + * by explicit method. This is required in order to make PCal + * instance reentrant. + * + * Maybe at some point in time this should be converted to an ordinary + * configuration object, from the collection of public static variables. + */ + public static void resetParams() + { + Debug = false; + SpecOption = false; + MyspecOption = false; + Spec2Option = false; + Myspec2Option = false; + SpecFile = ""; + WriteASTFlag = false; + LabelFlag = false; + ReportLabelsFlag = false; + LabelRoot = "Lbl_"; + FairnessOption = ""; + FairAlgorithm = false; + CheckTermination = false; + Nocfg = false; + NoDoneDisjunct = false; + optionsInFile = false; + versionOption = null; + inputVersionNumber = VersionToNumber(PcalParams.version); + PcalTLAGen.wrapColumn = 78; + PcalTLAGen.ssWrapColumn = 45; + tlaPcalMapping = null ; + + } + + + /************************************************************************* + * Parameters related to non-file Options * + *************************************************************************/ + public static boolean Debug = false ; + /*********************************************************************** + * True if the -debug option is chosen. * + ***********************************************************************/ + + public static boolean SpecOption = false ; + /*********************************************************************** + * True if the -spec option is chosen. * + ***********************************************************************/ + + public static boolean MyspecOption = false ; + /*********************************************************************** + * True if the -myspec option is chosen. * + ***********************************************************************/ + + public static boolean Spec2Option = false ; + /*********************************************************************** + * True if the -spec2 option is chosen. * + ***********************************************************************/ + + public static boolean Myspec2Option = false ; + /*********************************************************************** + * True if the -myspec2 option is chosen. * + ***********************************************************************/ + + public static String SpecFile = "" ; + /*********************************************************************** + * The file name if the -spec option is chosen. * + ***********************************************************************/ + + public static boolean WriteASTFlag = false ; + /*********************************************************************** + * True if the -writeAST option is chosen. * + ***********************************************************************/ + + public static boolean LabelFlag = false ; + /********************************************************************* + * True iff the -label option is chosen. * + *********************************************************************/ + + public static boolean ReportLabelsFlag = false ; + /********************************************************************* + * True iff the -reportLabels option is chosen. * + *********************************************************************/ + + public static String LabelRoot = "Lbl_" ; + /********************************************************************* + * The root of translator-generated labels, set to its non-default * + * value by the -labelRoot option. * + *********************************************************************/ + + /************************************************************************* + * Parameters for Spec and .cfg file options. * + *************************************************************************/ + public static String FairnessOption = "" ; + /********************************************************************* + * Should be "", "wf", "sf", "wfNext", or "sfNext". * + *********************************************************************/ + + public static boolean CheckTermination = false ; + /********************************************************************* + * True iff there is a -termination option. * + *********************************************************************/ + + public static boolean Nocfg = false ; + /********************************************************************* + * True iff there is a -nocfg option. * + *********************************************************************/ + + public static boolean NoDoneDisjunct = false ; + /********************************************************************* + * True iff there is a -noDoneDisjunct option. * + *********************************************************************/ + + /********************************************************************** + * The following parameter is set true if --fair algorithm is used. * + *********************************************************************/ + public static boolean FairAlgorithm = false ; + + /************************************************************************* + * Parameters related to language definition. * + *************************************************************************/ + public static TLAExpr DefaultVarInit() + /********************************************************************* + * Returns the default initial value of a variable declared with no * + * value specified, which is herein defined to be "{}". * + * * + * Default initial value changed to "defaultInitValue" * + * by LL on 22 Aug 2007 * + *********************************************************************/ + { Vector line = new Vector() ; +// line.addElement(new TLAToken("{", 0, 0)) ; +// line.addElement(new TLAToken("}", 0, 0)) ; + line.addElement(new TLAToken("defaultInitValue", 0, 0)); + Vector vec = new Vector() ; + vec.addElement(line) ; + TLAExpr exp = new TLAExpr(vec) ; + exp.normalize() ; + return exp ; + } + + /*********************************************************************** + * The strings identifying the beginning of the algorithm in the .tla * + * file. * + * The strings "--fair" and "algorithm" added by LL on 6 July 2011. * + ***********************************************************************/ + public static final String BeginAlg = "--algorithm" ; + public static final String BeginFairAlg = "--fair" ; + public static final String BeginFairAlg2 = "algorithm" ; + + + + /*********************************************************************** + * The strings marking the beginning and end of the translated * + * algorithm in a .tla input file. The translation is put immediately * + * after any line containing * + * * + * BeginXLation1 [one or more spaces] BeginXlation2 * + * * + * It is followed by a line containing * + * * + * EndXLation1 [one or more spaces] EndXlation2. * + ***********************************************************************/ + public static final String BeginXlation1 = "BEGIN" ; + public static final String BeginXlation2 = "TRANSLATION" ; + + public static final String EndXlation1 = "END" ; + public static final String EndXlation2 = "TRANSLATION" ; + + /************************************************************************* + * The string identifying the end of the automatically generated part of * + * the .cfg file and the beginning of the user-added part. * + *************************************************************************/ + public static String CfgFileDelimiter = + "\\* Add statements after this line." ; + +/************ Stuff for .pcal file ***************************************/ + +// /*********************************************************************** +// * The string identifying the beginning of the algorithm in the .pcal * +// * file. * +// ***********************************************************************/ +// public static final String PcalBeginAlg = "algorithm"; +// +// /** +// * The <row, col> (in Java coordinates) of the beginning of the +// * "algorithm" token in the .pcal file. +// */ +// public static IntPair endOfPreamble = null; +// +// /** +// * The <row, col> (in Java coordinates) of the beginning of the TLA+ +// * "code" that follows the algorithm in the input (.pcal) and output +// * (.tla) files. For outputSuffixLoc, the column always equals 0. +// */ +// public static IntPair inputSuffixLoc = null; +// public static IntPair outputSuffixLoc = null; +// + /************************************************************************* + * File parameters. * + *************************************************************************/ + public static String TLAInputFile = "" ; + /*********************************************************************** + * The name of the input file, with no extension. It is set to equal * + * the argument with which the program is called, minus the extension. * + * With the introduction of pcal files, the name no longer makes sense. * + ***********************************************************************/ + + /** + * Pcal-File Parameters + * The following parameters were introduced when .pcal files were + * (briefly) added. However, most of them still seem to be used. + */ + + public static boolean optionsInFile = false ; + // Set true when an options statement has been found in the + // module. It is a kludgy way to pass an argument to + // trans.parseAndProcessStringArguments; things are done this + // way because of the way the code evolved, and no intelligent + // design has stepped in to fix it. + public static String versionOption = null; + public static int inputVersionNumber = VersionToNumber(PcalParams.version); + // The input file's version number * 1000 +// public static boolean readOnly = false; + // True iff this is a .pcal input file and the .tla file should + // not be writable. This is obsolete and is not used. + + /** + * The following parameter is used to hold the TLAtoPCalMapping object + * that is computed by the translation. Some of that object's fields are + * used in creating the actual mapping. It's easier to have the methods + * that need to use those fields access them via this parameter than to do + * the more politically correct thing and pass the fields or the object + * as a parameter in the method calls. + */ + public static TLAtoPCalMapping tlaPcalMapping ; + + /** + * If str is a version number like 3.17, then this returns 1000 times + * its numeric value--e.g., 3170. Otherwise, it returns -1. + * + */ + public static int VersionToNumber(String str) { + boolean error = false ; + int i = 0; + int result = 0; + int digitsAfterDecimal = 0; + boolean afterDecimal = false; + while ((!error) && i < str.length()) { + char c = str.charAt(i); + if (('0' <= c) && (c <= '9')) { + result = 10 * result + (c - '0'); + if (afterDecimal) { + digitsAfterDecimal++; + if (digitsAfterDecimal > 3) { + error = true; + } + } + } else if (c == '.') { + afterDecimal = true; + } else { + error = true; + } + i++; + } + if (error) { + return -1; + } + for (int j = 0; j < 3 - digitsAfterDecimal; j++) { + result = 10 * result; + } + return result; + } + /** + * Processes the version argument ver. It sets versionNumber + * and returns true if it is a legal version number; otherwise, + * it reports the error with PcalDebug.reportError and returns false. + */ + static boolean ProcessVersion(String ver) { + int vnum = VersionToNumber(ver); + if (vnum < 0) { + PcalDebug.reportError("Illegal version " + ver + " specified."); + return false; + } + if (vnum > VersionToNumber(PcalParams.version)) { + PcalDebug.reportError("Specified version " + ver + + " later than current version " + PcalParams.version); + return false; + } + inputVersionNumber = vnum; + return true ; + } + } + +/* last modified on Thu 23 Aug 2007 at 10:40:25 PST by lamport */ diff --git a/tlatools/src/pcal/trans.java b/tlatools/src/pcal/trans.java index 37f7237776d3bf62301115a8e7fb531cc89700fd..5f872f71bd0ba0e1e612de128207b3e0d4e496ef 100644 --- a/tlatools/src/pcal/trans.java +++ b/tlatools/src/pcal/trans.java @@ -1,1970 +1,1999 @@ -package pcal; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Vector; - -import pcal.MappingObject.Break; -import pcal.exception.FileToStringVectorException; -import pcal.exception.ParseAlgorithmException; -import pcal.exception.PcalResourceFileReaderException; -import pcal.exception.RemoveNameConflictsException; -import pcal.exception.StringVectorToFileException; -import pcal.exception.TLCTranslationException; -import pcal.exception.UnrecoverableException; -import tla2tex.Debug; -import util.ToolIO; - -/*************************************************************************** -* <pre> -* CLASS trans * -* * -* BUGS: * -* Accepts if (...) {...} ; else {...} * -* Generates code as if the ";" were not there. * -* ----------------------------------------------------------------- * -* History: * -* Version 1.0: Original release. * -* * -* Version 1.1: (March 2006) * -* Introduced ability to have the translator * -* add missing labels. * -* * -* Version 1.2: (August 2007) * -* Introduced defaultInitValue so liveness can be * -* checked even with "uninitialized" variables. * -* * -* Version 1.3: (February 2008) * -* Added "await" as a synonym for "when" * -* * -* Version 1.4: (June 2010) * -* Added the options statement and the -lineWidth option. * -* * -* Version 1.5: (Jan 2011) * -* 1. Added the -noDoneDisjunct option. * -* 2. Added the new way of specifying fairness, with * -* the "fair" keyword with "+" modifier, and the "+" * -* and "-" label modifiers. * -* 3. Automatically removes the stuttering-on-termination * -* disjunction if all processes are "while (TRUE)" * -* statements with no gotos to "Done". * -* 4. In addition, if the "while (TRUE)"s have no * -* internal labels, it removes the pc variable. * -* 5. Changed the representation of Process and Procedure * -* nodes in the abstract syntax tree produced by * -* the -writeAST option and used when TLC is producing * -* the translation. * -* The changes 3-5 are not implemented when the -version * -* option specifies an earlier version. * -* * -* Version 1.6: (July 2011) * -* - Added the "--fair algorithm" syntax for specifying * -* weak fairness of the next-state action. (This * -* changes the way fairness of a uniprocess algorithm was * -* specified in Version 1.5. All legal Version 1.5 * -* algorithms should work with the "version 1.5" option.) * -* - Permitted previously defined macros to be called * -* inside a macro definition. * -* * -* Version 1.7: (19 January 2012) * -* - Translator adds "BEGIN/END TRANSLATION" if needed. * -* - Added support for Toolbox Goto PCal Source command. * -* * -* Version 1.8: (30 Mar 2012) * -* - Changed translation to remove stuttering-on- * -* termination disjunction if some process is * -* "while (TRUE)". * -* - Omitted the Termination definition if the stuttering- * -* on-termination disjunction is removed, since that * -* implies Termination is always FALSE. * -* (4 May 2012) * -* - Removed the unnecessary CASE in the pc = ... clause of * -* the Init predicate when there is only a single process * -* statement. * -* ----------------------------------------------------------------- * -* * -* This is the main method of the +CAL to TLA+ translation program. * -* the program has the following command-line options. Only the ones * -* marked with ** besides them can be specified in the file's * -* options statement. The "-" can be omitted when the option is in * -* the options statement. * -* * -* -help : Type a help file instead of running the program. * -* * -* -spec name : Uses TLC and the TLA+ specification name.tla to do * -* the translation. The files name.tla and name.cfg * -* are copied from the java/ directory to the current * -* directory; the file AST.tla that defines `fairness' * -* to equal the fairness option and `ast' to equal * -* the the AST data structure representing the algorithm * -* is written to the current directory; and TLC is run * -* on name.tla to produce the translation. * -* * -* -myspec name : Like -spec, except it finds the files names.tla * -* and names.cfg in the current directory, instead * -* of writing them there. * -* * -* -spec2 name * -* -myspec2 name : Like -spec and -myspec, except they use TLC2 * -* instead of TLC (aka TLC1). * -* * -* -writeAST : Writes the AST file as in the -spec option, but does * -* not perform the translation. * -* * -* -debug : Run in debugging mode, whatever that means. For the * -* parser, it just means that the toString() methods * -* output the line and column number along with the * -* node. * -* * -* -unixEOL : Forces the use of Unix end-of-line convention, regardless * -* of the system's default. Without this, when run on * -* Windows, the files it writes seem to have a "^M" at the * -* end of every line when viewed with Emacs. * -* * -*** -wf : Conjoin to formula Spec weak fairness of each process's * -* next-state action * -* * -*** -sf : Conjoin to formula Spec strong fairness of each process's * -* next-state action * -* * -*** -wfNext : Conjoin to formula Spec weak fairness of the entire * -* next-state action * -* * -*** -nof : Conjoin no fairness formula to Spec. This is the default, * -* except when the -termination option is chosen. * -* * -*** -termination : Add to the .cfg file the command * -* PROPERTY Termination * -* With this option, the default fairness option * -* becomes -wf. * -* * -* -nocfg : Suppress writing of the .cfg file. * -* * -*** -noDoneDisjunct : Suppress the disjunct of the next-state * -* relation that describes stuttering steps taken * -* when the algorithm has halted. * -* * -*** -label : Tells the translator to add missing labels. This is * -* the default only for a uniprocess algorithm in which * -* the user has typed no labels. * -* * -* -reportLabels : True if the translator should print the names * -* and locations of all labels it adds. Like * -* -label, it tells the translator to add missing * -* labels. * -* * -*** -labelRoot name : If the translator adds missing labels, it * -* names them name1, name2, etc. Default value * -* is "Lbl_". * -* * -* THE FOLLOWING OPTION ADDED IN VERSION 1.31 * -* * -*** -lineWidth : The translation tries to perform the translation so * -* lines have this maximum width. (It will often * -* fail.) Default is 78, minimum value is 60. * -* * -* THE FOLLOWING OPTIONS ADDED IN VERSION 1.4 * -* * -*** -lineWidth : The translation tries to perform the translation so * -* lines have this maximum width. (It will often * -* fail.) Default is 78, minimum value is 60. * -* * -*** -version : The version of PlusCal for which this algorithm is * -* written. If the language is ever changed to make * -* algorithms written for earlier versions no longer * -* legal, then the translator should do the appropriate * -* thing when the earlier version number is specified. * -* ------------------------------------------------------------------------ * -* * -* The program uses vector objects from the Vector class to implement * -* sequences (lists). This generates a compiler warning. * -* * -* In Java data structures like arrays and Vectors, numbering starts with * -* 0. Unlike programmers, human beings count from 1. I use the term "Java * -* ordinal" to refer a number that denotes a position that represents the * -* first item as 0, and the term "human ordinal" to refer to an ordinary * -* ordinal that counts the first item as 1. * -* * -* * -* NOTE: * -* * -* One process should be able to read the pc or stack of another. There * -* is no logical reason to forbid this. However, the definition of * -* Translation in PlusCal.tla does not distinguish between instances of pc * -* in the original algorithm and ones inserted by the translation. The * -* latter instances must be subscripted--that is replaced by something * -* like pc[self]. Therefore, the Translation operator subscripts the * -* instances of pc from the original algorithm. The Java Translate method * -* must not do this, but must subscript (and prime) only the instances of * -* pc and stack introduced during the translation process. * -* * -* * -* The following bugs should all have been fixed by the addition of * -* ParseAlgorithm.Uncommment by LL on 18 Feb 2006. * -* * -* - There cannot be a comment between a label and the * -* following ":". * -* * -* - There cannot be a comment immediately before the ")" that ends * -* the list of arguments in a call statement. * -* * -* - The code for reporting the location of an error has the * -* following problem. If the token where the error occurs is * -* preceded by a comment, then the position reported is that of the * -* beginning of the comment rather than that of the token. * -* * -* TENTATIVE CHANGE MADE * -* * -* The following change was made along with a modification to the parser * -* to allow semi-colons to be omitted when they're obviously unnecessary. * -* * -* The parser does not parse expressions in the +CAL algorithm; it just * -* scans ahead to the first token that it can determine is not part of the * -* expression. To make this work, the following tokens that are legal in * -* a TLA+ expression are illegal in a +CAL expression: * -* * -* variable variables begin do then := || * -* * -* Making additional tokens illegal might help the parser find errors * -* sooner. For example, example, if one forgets the ";" and writes * -* * -* x := x + 1 * -* if x > y + 17 then ... * -* * -* the parser takes everything up to the "then" to be the right-hand side * -* of the "x :=" assignment. Making "if" illegal in an expression would * -* allow the parser to catch the error at the "if". * -* </pre> -***************************************************************************/ -class trans -{ - /** Status indicating no errors and successful process */ - static final int STATUS_OK = 1; - /** Status of no errors, but abort of the translation */ - private static final int STATUS_EXIT_WITHOUT_ERROR = 0; - /** Status of present errors and abort of the translation */ - static final int STATUS_EXIT_WITH_ERRORS = -1; - - /** - * Main function called from the command line - * @param args, command line arguments - */ - public static void main(String[] args) - { - runMe(args); - } - - /** - * The main translator method - * @return one of {@link trans#STATUS_OK}, {@link trans#STATUS_EXIT_WITH_ERRORS}, - * {@link trans#STATUS_EXIT_WITH_ERRORS} - * indicating the status - * - * Modified by LL on 16 Dec 2011. Changed the return value to the - * TLAtoPCalMapping object for the translation. (The status return - * value was not being used.) If the translation fails, it returns - * null. - */ - /** - * @param args - * @return - */ - /** - * @param args - * @return - */ -// public static int runMe(String[] args) - public static TLAtoPCalMapping runMe(String[] args) // added for testing - { - /********************************************************************* - * Get and print version number. * - *********************************************************************/ - // String lastModified = - // "last modified on Wed 11 March 2009 at 14:52:58 PST by lamport"; - /******************************************************************* - * This string is inserted by an Emacs macro when a new version is * - * saved. Unfortunately, Eclipse isn't Emacs, so the modification * - * date must be entered manually in the PcalParams module. * - *******************************************************************/ - - if (ToolIO.getMode() == ToolIO.SYSTEM) - { - PcalDebug.reportInfo("pcal.trans Version " + PcalParams.version + " of " + PcalParams.modDate); - } - - // SZ Mar 9, 2009: - /* - * This method is called in order to make sure, that the - * parameters are not sticky because these are could have been initialized - * by the previous run - */ - PcalParams.resetParams(); - /********************************************************************* - * Get and process arguments. - *********************************************************************/ - - /** - * Create the new TLAtoPCalMapping object, call it mapping - * here and set PcalParams.tlaPcalMapping to point to it. - */ - TLAtoPCalMapping mapping = new TLAtoPCalMapping() ; - PcalParams.tlaPcalMapping = mapping; - - int status = parseAndProcessArguments(args); - - if (status != STATUS_OK) - { -// return exitWithStatus(status); - return new TLAtoPCalMapping() ; // added for testing - } - - /********************************************************************* - * Read the input file, and set the Vector inputVec equal to its * - * contents, where inputVec[i] is the string containing the contents * - * of line i+1 of the input file. * - *********************************************************************/ - Vector inputVec = null; - try - { - inputVec = fileToStringVector(PcalParams.TLAInputFile + /* (PcalParams.fromPcalFile ? ".pcal" : */".tla" /*)*/); - } catch (FileToStringVectorException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - - /********************************************************************* - * outputVec is an alias for inputVec if the input is a .tla file, * - * which was not always the case in the aborted version 1.31. * - *********************************************************************/ - // Vector outputVec = PcalParams.fromPcalFile ? new Vector() : inputVec; - Vector outputVec = inputVec; - - /********************************************************************* - * Set untabInputVec to be the vector of strings obtained from * - * inputVec by replacing tabs with spaces. * - * * - * Tabs are date from the days when memory cost $1 per bit and are a * - * stupid anachronism. They should be banned. Although the various * - * methods taken from TLATeX should deal with tabs, there are * - * undoubtedly corner cases that don't work right. In particular, I * - * think there's one case where PcalCharReader.backspace() might be * - * called to backspace over a tab. It's easier to simply get rid of * - * the tabs than to try to make it work. * - * * - * Since the user might be evil enough to prefer tabs, with tla-file * - * input, the parts of the output file that are not produced by the * - * translator are copied from inputVec, so any tabs the user wants * - * are kept. * - *********************************************************************/ - Vector untabInputVec = removeTabs(inputVec); - - /** - * Look through the file for PlusCal options. They are put anywhere - * in the file (either before or after the module or in a comment) - * with the following sequence - * PlusCal <optional white space> options <optional white space> - * ( <options> ) - * - * where <options> has the same format as options on the command - * line. - */ - IntPair searchLoc = new IntPair(0, 0); - boolean notDone = true; - while (notDone) - { - try - { - ParseAlgorithm.FindToken("PlusCal", untabInputVec, searchLoc, ""); - String line = ParseAlgorithm.GotoNextNonSpace(untabInputVec, searchLoc); - String restOfLine = line.substring(searchLoc.two); - if (restOfLine.startsWith("options")) - { - // The first string after "PlusCal" not starting with a - // space character is "options" - if (ParseAlgorithm.NextNonIdChar(restOfLine, 0) == 7) - { - // The "options" should begin an options line - PcalParams.optionsInFile = true; - ParseAlgorithm.ProcessOptions(untabInputVec, searchLoc); - notDone = false; - } - } - } catch (ParseAlgorithmException e) - { - // The token "PlusCal" not found. - notDone = false; - } - } - - /** - * translationLine is set to the line of the output file at which - * the \* BEGIN TRANSLATION appears--whether it is inserted into the - * tla-file input by the user, or inserted into the output by the - * translator for pcal-file input. - */ - int translationLine = 0; - - /********************************************************************* - * Set algLine, algCol to the line and column just after the string * - * [--]algorithm that begins the algorithm. (These are Java * - * ordinals, in which counting begins at 0.) * - * * - * Modified by LL on 18 Feb 2006 to use untabInputVec instead of * - * inputVec, to correct bug that occurred when tabs preceded the * - * "--algorithm". * - * * - * For the code to handle pcal-input, I introduced the use of * - * IntPair objects to hold <line, column> Java coordinates (counting * - * from zero) in a file (or an image of a file in a String Vector). * - * For methods that advance through the file, the IntPair object is * - * passed as an argument and is advanced by the method. This is * - * what I should have been doing from the start, but I wasn't smart * - * enough The IntPair curLoc is the main one used in the part of the * - * following code that handles pcal-file input. * - *********************************************************************/ - int algLine = 0; - int algCol = -1; - /******************************************************************* - * If the BEGIN/END TRANSLATION region exists, then set * - * translationLine to the number of the line after which the * - * translation is to be inserted and delete the previous version * - * of the translation (if it exists) from inputVec. (Line * - * numbering is by Java ordinals.) If the region doesn't exist, * - * set translationLine to -1. * - * * - * Note: we remove the previous version from inputVec, because * - * that's where the translated output is going to go, and also * - * from untabInputVec, because we will then detect if the begin * - * and end translation lines contain part of the algorithm within * - * them. * - *******************************************************************/ - translationLine = findTokenPair(untabInputVec, 0, PcalParams.BeginXlation1, PcalParams.BeginXlation2); - if (translationLine != -1) - { - - - int endTranslationLine = findTokenPair(untabInputVec, translationLine + 1, PcalParams.EndXlation1, - PcalParams.EndXlation2); - if (endTranslationLine == -1) - { - PcalDebug.reportError("No line containing `" + PcalParams.EndXlation1 + " " + PcalParams.EndXlation2); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null; - } - - endTranslationLine = endTranslationLine - 1; - while (translationLine < endTranslationLine) - { - inputVec.remove(endTranslationLine); - untabInputVec.remove(endTranslationLine); - endTranslationLine = endTranslationLine - 1; - } - } - - // Search for "--algorithm" or "--fair". - // If found set algLine and algCol right after the last char, - // set foundBegin true, and set foundFairBegin true iff it - // was "--fair". Otherwise, set foundBegin false. - boolean foundBegin = false; - boolean foundFairBegin = false; - while ((algLine < untabInputVec.size()) && !foundBegin) - { - String line = (String) untabInputVec.elementAt(algLine); - algCol = line.indexOf(PcalParams.BeginAlg); - if (algCol != -1) - { - algCol = algCol + PcalParams.BeginAlg.length(); - foundBegin = true; - } else - { - algCol = line.indexOf(PcalParams.BeginFairAlg); - if (algCol != -1) { - // Found the "--fair". The more structurally nice thing to - // do here would be to move past the following "algorithm". - // However, it's easier to pass a parameter to the ParseAlgorithm - // class's GetAlgorithm method that tells it to go past the - // "algorithm" token. - algCol = algCol + PcalParams.BeginFairAlg.length(); - foundBegin = true; - foundFairBegin = true; - - } else { - algLine = algLine + 1; - } - } - ; - } - ; - if (!foundBegin) - { - PcalDebug.reportError("Beginning of algorithm string " + PcalParams.BeginAlg + " not found."); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - ; - - /* - * Set the algColumn and algLine fields of the mapping object. - */ - mapping.algColumn = algCol; - mapping.algLine = algLine; - - if (translationLine == -1) - { - /**************************************************************** - * Insert BEGIN/END TRANSLATION comments immediately after the * - * end of the comment that contains the beginning of the * - * algorithm. Set translationLine to the (Java) line number of * - * the BEGIN TRANSLATION. * - ****************************************************************/ - - // Set ecLine, ecCol to the position immediately after the - // *) that closes the current comment. - int depth = 1 ; - int ecLine = algLine ; - int ecCol = algCol ; - boolean notFound = true ; - while (notFound && ecLine < untabInputVec.size()) { - char[] line = ((String) untabInputVec.elementAt(ecLine)).toCharArray(); - - // check current line - while (notFound && ecCol < line.length-1) { - char ch = line[ecCol] ; - char ch2 = line[ecCol+1] ; - -// The following code isn't needed because the algorithm is inside a comment, and -// quotes and \* have no effect in determining where the comment ends. -// -// if (ch == '"') { -// // gobble string -// ch = ch2 ; -// ecCol++ ; -// while (ch != '"') { -// if (ch == '\\') { -// ecCol = ecCol + 2; -// } -// else { -// ecCol++ ; -// } ; -// if (ecCol < line.length - 1) { -// ch = line[ecCol] ; -// } -// else { -// ch = '"' ; -// } -// } ; -// ecCol++ ; -// } -// -// if (ch == '\\' && ch2 == '*' ) { -// // end of line comment, skip to end of line -// ecCol = 214748364; // a very large int -// } - if (ch == '(' && ch2 == '*') { - // left comment delimiter - depth++; - ecCol = ecCol + 2; - } - else if (ch == '*' && ch2 == ')') { - // right comment delimiter - depth--; - ecCol = ecCol + 2; - if (depth == 0) { - notFound = false ; - } - } - else { - // not an interesting character - ecCol++ ; - } - } - - // if not found, go to next line - if (notFound) { - ecLine++ ; - ecCol = 0; - } - } - - if (notFound) { - PcalDebug.reportError("Algorithm not in properly terminated comment"); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - - // Report an error if there's something else on the line that doesn't begin with "\*". This is probably - - String endStuff = ((String) untabInputVec.elementAt(ecLine)).substring(ecCol).trim() ; - - if (!endStuff.equals("") && !endStuff.startsWith("\\*")) { - PcalDebug.reportError("Text on same line following `*)' that ends the \n comment containing the algorithm."); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } ; - - inputVec.insertElementAt("\\* BEGIN TRANSLATION", ecLine+1) ; - untabInputVec.insertElementAt("\\* BEGIN TRANSLATION", ecLine+1) ; - inputVec.insertElementAt("\\* END TRANSLATION", ecLine+2) ; - untabInputVec.insertElementAt("\\* END TRANSLATION", ecLine+2) ; - - translationLine = ecLine + 1; -//System.out.println(ecLine + ", " + ecCol); -//Debug.printVector(inputVec, "foo"); - } - - /* - * Set the mappings start line. - */ - mapping.tlaStartLine = translationLine + 1; - - /********************************************************************* - * Added by LL on 18 Feb 2006 to fix bugs related to handling of * - * comments. * - * * - * Remove all comments from the algorithm in untabInputVec, * - * replacing (* *) comments by spaces to keep the algorithm tokens * - * in the same positions for error reporting. * - *********************************************************************/ - try - { - ParseAlgorithm.uncomment(untabInputVec, algLine, algCol); - } catch (ParseAlgorithmException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - // } // end else of if (PcalParams.fromPcalFile) -- i.e., end processing - // of .tla input file. - - /********************************************************************* - * Set reader to a PcalCharReader for the input file (with tabs and * - * the previous translation removed), starting right after the * - * PcalParams.BeginAlg string. * - *********************************************************************/ - PcalCharReader reader = new PcalCharReader(untabInputVec, algLine, algCol, inputVec.size(), 0); - - /********************************************************************* - * Set ast to the AST node representing the entire algorithm. * - *********************************************************************/ - AST ast = null; - try - { - ast = ParseAlgorithm.getAlgorithm(reader, foundFairBegin); -// System.out.println(ast.toString()); -// For testing, we print out when the new code for eliminating the -// suttering-on-done and pc is used. -// if (ParseAlgorithm.omitPC || ParseAlgorithm.omitStutteringWhenDone) { -// System.out.println("omit pc = " + ParseAlgorithm.omitPC + -// ", omitStutteringWhenDone = " + ParseAlgorithm.omitStutteringWhenDone); -// } - - } catch (ParseAlgorithmException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - PcalDebug.reportInfo("Parsing completed."); -// tla-pcal debugging -//System.out.println("Translation Output:"); -//System.out.println(ast.toString()); - /********************************************************************* - * For -writeAST option, just write the file AST.tla and halt. * - *********************************************************************/ - if (PcalParams.WriteASTFlag) - { - WriteAST(ast); -// return exitWithStatus(STATUS_EXIT_WITHOUT_ERROR); - return null ; // added for testing - } - ; - - /********************************************************************* - * Rename algorithm variables to eliminate name conflicts--for * - * example, if the same variable is declared inside different * - * procedures, if a variable name and a label are the same, or if * - * the same label is used in to different procedures. This should * - * also report an error and terminate if it discovers a conflict * - * between the variable of a `with' statement and some other * - * identifier in the algorithm. It should also detect other * - * conflicts--for example, if there is a variable named "Init" or * - * "TRUE". However, there are conflicts that the translator can't * - * spot--for example, if a variable name is the same as the name of * - * some operator defined elsewhere in the TLA+ module. So it's not * - * worth going overboard in this checking. * - *********************************************************************/ - - // SZ February.15 2009: made non-static to make PCal stateless for tool runs - PCalTLAGenerator pcalTLAGenerator = new PCalTLAGenerator(ast); - try - { - pcalTLAGenerator.removeNameConflicts(); - } catch (RemoveNameConflictsException e1) - { - PcalDebug.reportError(e1); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - - /********************************************************************* - * Set the vector `translation' to the translation of the algorithm * - * represented by the AST ast. If called with the -spec option, * - * do the translation by calling TLC. Otherwise, call the ordinary * - * Translate method. * - *********************************************************************/ - Vector translation = null; - boolean tlcTranslation = PcalParams.SpecOption || PcalParams.MyspecOption || PcalParams.Spec2Option - || PcalParams.Myspec2Option; - - if (tlcTranslation) - { - try - { - translation = TLCTranslate(ast); - } catch (TLCTranslationException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - } else - { - try - { - translation = pcalTLAGenerator.translate(); - } catch (RemoveNameConflictsException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - } - ; - - PcalDebug.reportInfo("Translation completed."); -// tla-pcal Debugging -//System.exit(0); - /********************************************************************* - * For .tla input: * - * Rename the old file by changing its extension from "tla" to "old". * - *********************************************************************/ - // if (!PcalParams.fromPcalFile) - // { - File file; - try - { - file = new File(PcalParams.TLAInputFile + ".old"); - if (file.exists()) - { - file.delete(); - } - ; - file = new File(PcalParams.TLAInputFile + ".tla"); - file.renameTo(new File(PcalParams.TLAInputFile + ".old")); - } catch (Exception e) - { - PcalDebug.reportError("Could not rename input file " + PcalParams.TLAInputFile + ".tla" + " to " - + PcalParams.TLAInputFile + ".old"); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - ; - // } - - /********************************************************************* - * Add the translation to outputVec. * - *********************************************************************/ - int i = 0; - while (i < translation.size()) - { - outputVec.insertElementAt(translation.elementAt(i), i + translationLine + 1); - i = i + 1; - } - - /********************************************************************* - * Code from aborted version 1.31. * - * For .pcal input, set outputSuffixLoc and add the rest of the * - * input file to the output. * - *********************************************************************/ - // if (PcalParams.fromPcalFile) - // { - // PcalParams.outputSuffixLoc = new IntPair(outputVec.size(), 0); - // // if there's stuff in the suffix on the same line with the - // // end of the algorithm, write it on a separate line. - // IntPair curLoc = new IntPair(PcalParams.inputSuffixLoc.one, PcalParams.inputSuffixLoc.two); - // if (curLoc.one < untabInputVec.size()) - // { - // String lastLine = (String) untabInputVec.elementAt(curLoc.one); - // if (curLoc.two < lastLine.length()) - // { - // outputVec.addElement(lastLine.substring(curLoc.two)); - // } - // curLoc.one++; - // } - // // Copy the rest of the input file into the output file. - // for (int ii = curLoc.one; ii < untabInputVec.size(); ii++) - // { - // outputVec.addElement((String) untabInputVec.elementAt(ii)); - // } - // } - /********************************************************************* - * Write the output file. * - *********************************************************************/ - try - { - WriteStringVectorToFile(outputVec, PcalParams.TLAInputFile + ".tla"); - } catch (StringVectorToFileException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - - PcalDebug.reportInfo("New file " + PcalParams.TLAInputFile + ".tla" + " written."); - - /********************************************************************* - * Write the cfg file, unless the -nocfg option is used. * - *********************************************************************/ - File cfgFile = new File(PcalParams.TLAInputFile + ".cfg"); - Vector cfg = null; - boolean writeCfg = !PcalParams.Nocfg; - if (writeCfg && cfgFile.exists()) - { - if (cfgFile.canRead()) - { - try - { - cfg = fileToStringVector(PcalParams.TLAInputFile + ".cfg"); - } catch (FileToStringVectorException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - } else - { - /************************************************************* - * cfg file is read-only. * - *************************************************************/ - writeCfg = false; - PcalDebug.reportInfo("File " + PcalParams.TLAInputFile + ".cfg is read only, new version not written."); - } - } else - { - cfg = new Vector(); - cfg.addElement(PcalParams.CfgFileDelimiter); - } - ; - - /********************************************************************* - * Delete previously written part of cfg file. * - *********************************************************************/ - if (writeCfg) - { - i = 0; - boolean done = false; - while ((!done) && (cfg.size() > i)) - { - if (((String) cfg.elementAt(i)).indexOf(PcalParams.CfgFileDelimiter) == -1) - { - i = i + 1; - } else - { - done = true; - } - } - if (done) - { - /************************************************************* - * Delete all lines before the delimiting comment string. * - *************************************************************/ - while (i > 0) - { - cfg.removeElementAt(0); - i = i - 1; - } - } else - { - /************************************************************* - * The delimiting comment string written by the translator * - * not found in the cfg file, so presumably the user created * - * the cfg file before running the translator on the input * - * file. We insert the delimiter. * - *************************************************************/ - cfg.add(0, PcalParams.CfgFileDelimiter); - } - ; - - /****************************************************************** - * If defaultInitValue is used, add a CONSTANT statement setting * - * it to a model value of the same name. * - * (Added 22 Aug 2007 by LL.) * - ******************************************************************/ - if (tlcTranslation || ParseAlgorithm.hasDefaultInitialization) - { - cfg.add(0, "CONSTANT defaultInitValue = defaultInitValue"); - } - ; - /****************************************************************** - * Insert the `PROPERTY Termination' line if requested. * - ******************************************************************/ - if (PcalParams.CheckTermination) - { - cfg.add(0, "PROPERTY Termination"); - } - ; - - /****************************************************************** - * Insert the SPECIFICATION line if there isn't already one. * - ******************************************************************/ - i = 0; - boolean hasSpec = false; - while (i < cfg.size()) - { - String thisLine = (String) cfg.elementAt(i); - if ((thisLine.indexOf("SPECIFICATION") != -1) - && ((thisLine.indexOf("\\*") == -1) || (thisLine.indexOf("\\*") > thisLine - .indexOf("SPECIFICATION")))) - { - hasSpec = true; - } - ; - i = i + 1; - } - ; - if (hasSpec) - { - PcalDebug.reportInfo("File " + PcalParams.TLAInputFile - + ".cfg already contains SPECIFICATION statement," + "\n so new one not written."); - } else - { - cfg.add(0, "SPECIFICATION Spec"); - } - ; - try - { - WriteStringVectorToFile(cfg, PcalParams.TLAInputFile + ".cfg"); - } catch (StringVectorToFileException e) - { - PcalDebug.reportError(e); -// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); - return null ; // added for testing - } - PcalDebug.reportInfo("New file " + PcalParams.TLAInputFile + ".cfg" + " written."); - } - ; - -// return exitWithStatus(STATUS_EXIT_WITHOUT_ERROR); - return PcalParams.tlaPcalMapping ; // added for testing - } // END main - - /** - * If run in the system mode, exits the program, in tool mode returns the status - * @param status - */ - private static int exitWithStatus(int status) - { - if (ToolIO.getMode() == ToolIO.SYSTEM) - { - // return exit status in system mode - System.exit(status); - } - - // just exit the function in tool mode - return status; - } - - /********************** Writing the AST ************************************/ - private static boolean WriteAST(AST ast) - { - Vector astFile = new Vector(); - astFile.addElement("------ MODULE AST -------"); - astFile.addElement("EXTENDS TLC"); - astFile.addElement("fairness == \"" + PcalParams.FairnessOption + "\""); - astFile.addElement(" "); - astFile.addElement("ast == "); - astFile.addElement(ast.toString()); - astFile.addElement("=========================="); - try - { - WriteStringVectorToFile(astFile, "AST.tla"); - } catch (StringVectorToFileException e) - { - PcalDebug.reportError(e); - return false; - } - PcalDebug.reportInfo("Wrote file AST.tla."); - return true; - } - - /************************* THE TLC TRANSLATION *****************************/ - - private static Vector TLCTranslate(AST ast) throws TLCTranslationException - /*********************************************************************** - * The result is a translation of the algorithm represented by ast * - * obtained by using TLC to execute the definition of Translation(ast) * - * in the TLA+ module PlusCal. It equals a vector with a single * - * element, which is the entire translation as a single string. * - * * - * This method relies on a bug in TLC's pretty-print routine that * - * causes it not to work properly on the output produced by the TLA * - * spec. Instead of prettyprinting the output as * - * * - * << "VARIABLES ...", * - * "vars == ... ", * - * ... * - * >> * - * * - * it prints the entire translation on a single line as * - * * - * << "VARIABLES ...", "vars == ... ", ... >> * - * * - * This allows the method to find the entire translation as the single * - * line that begins with "<<". If this TLC bug is fixed, then the * - * TLCTranslate method will have to be modified to read the spec as a * - * sequence of lines. This will probably require the TLA module that * - * translates the spec to print a special marker line to indicate the * - * end of the translation. * - ***********************************************************************/ - { - /********************************************************************* - * Write the file AST.tla that contains * - *********************************************************************/ - WriteAST(ast); - - /********************************************************************* - * For the -spec (rather than -myspec) option, copy the * - * specification's .tla and .cfg files PlusCal.tla to current * - * directory. * - *********************************************************************/ - if (PcalParams.SpecOption || PcalParams.Spec2Option) - { - try - { - Vector parseFile = PcalResourceFileReader.ResourceFileToStringVector(PcalParams.SpecFile + ".tla"); - - WriteStringVectorToFile(parseFile, PcalParams.SpecFile + ".tla"); - parseFile = PcalResourceFileReader.ResourceFileToStringVector(PcalParams.SpecFile + ".cfg"); - WriteStringVectorToFile(parseFile, PcalParams.SpecFile + ".cfg"); - - PcalDebug - .reportInfo("Wrote files " + PcalParams.SpecFile + ".tla and " + PcalParams.SpecFile + ".cfg."); - - } catch (UnrecoverableException e) - { - throw new TLCTranslationException(e.getMessage()); - } - - } - ; - /********************************************************************* - * Run TLC on the specification file and set tlcOut to TLC's output. * - *********************************************************************/ - String javaInvocation; - if (PcalParams.SpecOption || PcalParams.MyspecOption) - { - // Modified on 29 May 2010 by LL so tlc2 is run in - // all cases. - PcalDebug.reportInfo("Running TLC2."); - javaInvocation = "java -Xss1m tlc2.TLC "; - } else - { - PcalDebug.reportInfo("Running TLC2."); - javaInvocation = "java -Xss1m tlc2.TLC "; - } - ; - String tlcOut = " "; - Runtime rt = Runtime.getRuntime(); - try - { - // Modified on 29 May 2010 by LL to replace getErrorStream() with - // getInputStream(), which by Java logic gets standard out. (And no, - // getErrorStream() did not get standard non-error.) Apparently, - // TLC has been changed to put its output on stdout. - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rt.exec( - javaInvocation + PcalParams.SpecFile).getInputStream())); - while (tlcOut.indexOf("<<") == -1) - { - tlcOut = bufferedReader.readLine(); - } - ; - bufferedReader.close(); - } catch (Exception e) - { - throw new TLCTranslationException("Error reading output of TLC"); - } - ; - - /********************************************************************* - * Test if the translation failed and reported an error message, * - * bracketed by "@Error@" and "@EndError@" strings. If it did, * - * report the error and halt. If not, set tlcOut to the value of * - * Translation(ast) with the outermost "<<" and ">>" removed. * - *********************************************************************/ - if (tlcOut.indexOf("@Error@") != -1) - { - throw new TLCTranslationException("TLC's translation of the parsed algorithm failed with\n Error: " - + tlcOut.substring(tlcOut.indexOf("@Error@") + 7, tlcOut.indexOf("@EndError@"))); - } - ; - tlcOut = tlcOut.substring(2, tlcOut.lastIndexOf(">>")) + " "; - PcalDebug.reportInfo("Read TLC output."); - - /********************************************************************* - * Set transl to the string obtained by converting tlcOut, which is * - * a comma-separated sequence of strings, to the single string that * - * they represent. See PlusCal.tla for an explanation of the * - * encoding of TLA+ statements as sequences of strings. * - *********************************************************************/ - int i = 0; - String transl = ""; - while (i < tlcOut.length()) - { - if (tlcOut.charAt(i) == '"') - { - i = i + 1; - if ((tlcOut.charAt(i) == '\\') && (tlcOut.charAt(i + 1) == '"')) - /******************************************************* - * This is a quoted string. * - *******************************************************/ - { - if (tlcOut.charAt(i + 2) != '"') - { - throw new TLCTranslationException("I'm confused"); - - } - ; - i = i + 3; - while (tlcOut.charAt(i) != '"') - { - i = i + 1; - } - i = i + 1; - transl = transl + "\""; - while (tlcOut.charAt(i) != '"') // " - { - if (tlcOut.charAt(i) == '\\') - { - /*********************************************** - * Get special character. * - ***********************************************/ - transl = transl + tlcOut.substring(i, i + 2); - i = i + 2; - } else - { - transl = transl + tlcOut.substring(i, i + 1); - i = i + 1; - } - ; - } - ; - i = i + 8; - transl = transl + "\""; - } else - { - while (tlcOut.charAt(i) != '"') - { - if ((tlcOut.charAt(i) == '\\') && (tlcOut.charAt(i + 1) == '\\')) - { - i = i + 1; - } - ; - transl = transl + tlcOut.substring(i, i + 1); - i = i + 1; - } - ; - i = i + 1; - } - ; - } // END if (tlcOut.charAt(i) == '"') - else if (tlcOut.charAt(i) == ',') - { - i = i + 1; - } else - { - if (tlcOut.charAt(i) != ' ') - { - throw new TLCTranslationException("Expected space but found `" + tlcOut.charAt(i) + "'"); - } - ; - transl = transl + tlcOut.substring(i, i + 1); - i = i + 1; - } - ; - } - ; // END while - /* ****************************************************************** - * Wrap the translated string into approximately 80 character lines * - *******************************************************************/ - transl = WrapString(transl, 78); - Vector result = new Vector(); - result.addElement(transl); - return result; - } - - /***************** METHODS FOR READING AND WRITING FILES *****************/ - - private static void WriteStringVectorToFile(Vector inputVec, String fileName) throws StringVectorToFileException - /*********************************************************************** - * Writes the Vector of strings inputVec to file named fileName, with * - * each element of inputVec written on a new line. * - ***********************************************************************/ - { - try - { - // I have no idea what Java does if you try to write a new version - // of a read-only file. On Windows, it's happy to write it. Who - // the hell knows what it does on other operating systems? So, something - // like the following code could be necessary. However, the setWritable() - // method was introduced in Java 1.6, and in December 2009, that version - // isn't available on the Mac. And I can't find out how to set a file - // to be writable in any earlier version of Java. On the web, the advice - // is to copy the file, delete the old version, and rename the copy. - // But the File method's documentation actually says that delete may or - // may not delete the read-only file, depending on the OS. - // - // File file = new File(fileName); - // if (! file.canWrite()) { - // file.setWritable(true); - // } - BufferedWriter fileW = new BufferedWriter(new FileWriter(fileName)); - int lineNum = 0; - while (lineNum < inputVec.size()) - { - fileW.write((String) inputVec.elementAt(lineNum)); - fileW.newLine(); - lineNum = lineNum + 1; - } - ; - fileW.close(); - } catch (Exception e) - { - throw new StringVectorToFileException("Could not write file " + fileName); - } - ; - - } - - private static Vector fileToStringVector(String fileName) throws FileToStringVectorException - /*********************************************************************** - * Reads file fileName into a StringVector, a vector in which each * - * element is a line of the file. * - ***********************************************************************/ - { - Vector inputVec = new Vector(100); - try - { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName))); - try - { - String nextLine = bufferedReader.readLine(); - while (nextLine != null) - { - inputVec.addElement(nextLine); - nextLine = bufferedReader.readLine(); - } - ; - bufferedReader.close(); - } catch (IOException e) - { - /********************************************************* - * Error while reading input file. * - *********************************************************/ - throw new FileToStringVectorException("Error reading file " + fileName + "."); - } - } - - catch (FileNotFoundException e) - { - /************************************************************** - * Input file could not be found. * - **************************************************************/ - throw new FileToStringVectorException("Input file " + fileName + " not found."); - } - ; - return inputVec; - } - - /********************* PROCESSING THE COMMAND LINE ***********************/ - - /** - * Processes the command line arguments - * - * This method changes values of public static variables of the {@link PcalParams} - * - * SZ: This will cause problems, due to the fact that the PCalParams are static. - * Any initialization should overwrite the previous, which is currently NOT the case - * Should be re-factored to non-static access to the properties - * - * @return status of processing. - * the status {@link trans#STATUS_OK} indicates that no errors has been detected. - * the status {@link trans#STATUS_EXIT_WITHOUT_ERROR} indicates that no errors has been found but translation - * should not be started (e.G -help call) - * the status {@link trans#STATUS_EXIT_WITH_ERRORS} indicates errors - * - * Change made on 9 December 2009 for pcal-file input. This procedure is - * called a second time if there is pcal-file input with an options statement. - * It will be the second call iff {@link PcalParams#optionsInFile} equals true. - * The second call should have a dummy extra argument in place of the - * command-line's file-name argument. When pcal files were eliminated, this - * kludgy mechanism was kept and used to indicate if the method is being called - * for options specified inside the module. - */ - static int parseAndProcessArguments(String[] args) - { - - /** ******************************************************************* - *<pre> - * Get the command-line arguments and set the appropriate parameters. * - * The following command line arguments are handled. Only the ones * - * marked with ** besides them can be specified in the module file's * - * options statement. The "-" can be omitted when the option is in * - * the module file's options statement. * - * * - * -help : Type a help file instead of running the program. * - * * - *** -spec name : Uses TLC and the TLA+ specification name.tla to do * - * the translation. The files name.tla and name.cfg * - * are copied from the java/ directory to the current * - * directory; the file AST.tla that defines `fairness' * - * to equal the fairness option and `ast' to equal * - * the the AST data structure representing the * - * algorithm is written to the current directory; and * - * TLC is run on name.tla to produce the translation. * - * * - *** -myspec name : Like -spec, except it finds the files names.tla * - * and names.cfg in the current directory, instead * - * of writing them there. * - * * - * -spec2 name * - * -myspec2 name : Like -spec and -myspec, except they use TLC2 * - * instead of TLC (aka TLC1). * - * * - * -writeAST : Writes the AST file as in the -spec option, but does * - * not perform the translation. * - * * - * -debug : Run in debugging mode, whatever that means. For the * - * parser, it just means that the toString() methods * - * output the line and column number along with the * - * node. * - * * - * -unixEOL : Forces the use of Unix end-of-line convention, * - * regardless of the system's default. Without this, * - * when run on Windows, the files it writes seem to have * - * a "^M" at the end of every line when viewed with * - * Emacs. * - * * - *** -wf : Conjoin to formula Spec weak fairness of each process's * - * next-state action * - * * - *** -sf : Conjoin to formula Spec strong fairness of each process's * - * next-state action * - * * - *** -wfNext : Conjoin to formula Spec weak fairness of the entire * - * next-state action * - * * - *** -nof : Conjoin no fairness formula to Spec. This is the default, * - * except when the -termination option is chosen. * - * * - *** -termination : Add to the .cfg file the command * - * PROPERTY Termination * - * * - * -nocfg : Suppress writing of the .cfg file. * - * * - * * - *** -noDoneDisjunct : Suppress the disjunct of the next-state * - * relation that describes stuttering steps taken * - * when the algorithm has halted. * - * * - *** -label : Tells the translator to add missing labels. This is * - * the default only for a uniprocess algorithm in which * - * the user has typed no labels. * - * * - * -reportLabels : True if the translator should print the names * - * and locations of all labels it adds. Like * - * -label, it tells the translator to add missing * - * labels. * - * * - *** -labelRoot name : If the translator adds missing labels, it * - * names them name1, name2, etc. Default value * - * is "Lbl_". * - * * - * THE FOLLOWING OPTIONS ADDED IN VERSION 1.4 * - * * - *** -lineWidth : The translation tries to perform the translation so * - * lines have this maximum width. (It will often * - * fail.) Default is 78, minimum value is 60. * - * * - *** -version : The version of PlusCal for which this algorithm is * - * written. If the language is ever changed to make * - * algorithms written for earlier versions no longer * - * legal, then the translator should do the appropriate * - * thing when the earlier version number is specified. * - *</pre> - ********************************************************************* */ - boolean inFile = PcalParams.optionsInFile; - boolean notInFile = !inFile; - // Just convenient abbreviations - boolean firstFairness = inFile; - // Used to allow a fairness property specified by a command-line - // option to be overridden by one in the pcal-file's options statement. - // It is set false when the first fairness property is set from - // the options statement. - boolean explicitNof = false; - // Set true when the "nof" fairness option is set by an explicit - // user request, rather than by default. It was added to fix - // a bug in -termination introduced in version 1.4 by having - // the options statement in the file. I think option processing - // can be simplified to eliminate this, but it's easier to add - // this kludge. - int nextArg = 0; - /****************************************************************** - * The number of the argument being processed. * - ******************************************************************/ - int maxArg = args.length - 1; - /****************************************************************** - * The number of option arguments. (For processing command-line * - * arguments, the last element of args is the input-file name.) * - ******************************************************************/ - if (maxArg < 0) - { - return CommandLineError("No arguments specified"); - } - - if (notInFile && (args[maxArg].length() != 0) && (args[maxArg].charAt(0) == '-')) - /****************************************************************** - * If the last argument begins with "-", then no file has been * - * specified. This should mean that the user has typed "-help", * - * but it could be a mistake. But let's just assume she typed * - * "-help", since she either wants or needs help. * - ******************************************************************/ - { - if (OutputHelpMessage()) - { - return STATUS_EXIT_WITHOUT_ERROR; - - } else - { - return STATUS_EXIT_WITH_ERRORS; - } - } - - while (nextArg < maxArg) - /******************************************************************* - * Process all the arguments, except for the input-file name. * - *******************************************************************/ - { - String option = args[nextArg]; - if (notInFile && option.equals("-help")) - { - if (OutputHelpMessage()) - { - return STATUS_EXIT_WITHOUT_ERROR; - } else - { - return STATUS_EXIT_WITH_ERRORS; - } - } else if (notInFile && option.equals("-writeAST")) - { - PcalParams.WriteASTFlag = true; - if (CheckForConflictingSpecOptions()) - { - return STATUS_EXIT_WITH_ERRORS; - } - } else if (option.equals("-spec") || - (inFile && option.equals("spec"))) - { - PcalParams.SpecOption = true; - if (CheckForConflictingSpecOptions()) - { - return STATUS_EXIT_WITH_ERRORS; - } - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Specification name must follow `-spec' option"); - } - PcalParams.SpecFile = args[nextArg]; - } else if (option.equals("-myspec") || - (inFile && option.equals("myspec"))) - { - PcalParams.MyspecOption = true; - if (CheckForConflictingSpecOptions()) - { - return STATUS_EXIT_WITH_ERRORS; - } - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Specification name must follow `-myspec' option"); - } - PcalParams.SpecFile = args[nextArg]; - } else if (notInFile && option.equals("-spec2")) - { - PcalParams.Spec2Option = true; - if (CheckForConflictingSpecOptions()) - { - return STATUS_EXIT_WITH_ERRORS; - } - ; - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Specification name must follow `-spec' option"); - } - PcalParams.SpecFile = args[nextArg]; - } else if (notInFile && option.equals("-myspec2")) - { - PcalParams.Myspec2Option = true; - if (CheckForConflictingSpecOptions()) - { - return STATUS_EXIT_WITH_ERRORS; - } - ; - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Specification name must follow `-myspec' option"); - } - PcalParams.SpecFile = args[nextArg]; - } else if (notInFile && option.equals("-debug")) - { - PcalParams.Debug = true; - } else if (notInFile && option.equals("-unixEOL")) - { - System.setProperty("line.separator", "\n"); - } else if (option.equals("-termination") || (inFile && option.equals("termination"))) - { - PcalParams.CheckTermination = true; - } else if (option.equals("-nocfg")) - { - PcalParams.Nocfg = true; - } else if (option.equals("-noDoneDisjunct") || (inFile && option.equals("noDoneDisjunct"))) - { - PcalParams.NoDoneDisjunct = true; - } else if (option.equals("-wf") || (inFile && option.equals("wf"))) - { - if (firstFairness) - { - PcalParams.FairnessOption = ""; - firstFairness = false; - } - if (!PcalParams.FairnessOption.equals("")) - { - return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); - } - PcalParams.FairnessOption = "wf"; - } else if (option.equals("-sf") || (inFile && option.equals("sf"))) - { - if (firstFairness) - { - PcalParams.FairnessOption = ""; - firstFairness = false; - } - if (!PcalParams.FairnessOption.equals("")) - { - return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); - } - PcalParams.FairnessOption = "sf"; - } else if (option.equals("-wfNext") || (inFile && option.equals("wfNext"))) - { - if (firstFairness) - { - PcalParams.FairnessOption = ""; - firstFairness = false; - } - if (!PcalParams.FairnessOption.equals("")) - { - return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); - } - PcalParams.FairnessOption = "wfNext"; - } else if (option.equals("-nof") || (inFile && option.equals("nof"))) - { - if (firstFairness) - { - PcalParams.FairnessOption = ""; - firstFairness = false; - } - if (!PcalParams.FairnessOption.equals("")) - { - return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); - } - PcalParams.FairnessOption = "nof"; - explicitNof = true; - } else if (option.equals("-label") || (inFile && option.equals("label"))) - { - PcalParams.LabelFlag = true; - } else if (notInFile && option.equals("-reportLabels")) - { - PcalParams.ReportLabelsFlag = true; - PcalParams.LabelFlag = true; - } else if (option.equals("-labelRoot") || (inFile && option.equals("labelRoot"))) - { - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Label root must follow `-labelRoot' option"); - } - PcalParams.LabelRoot = args[nextArg]; - } - // else if (option.equals("-readOnly") || (pcal && option.equals("readOnly"))) { - // PcalParams.readOnly = true; - // } - // else if (option.equals("-writable") || (pcal && option.equals("writable"))) { - // PcalParams.readOnly = false; - // } - else if (option.equals("-version") || (inFile && option.equals("version"))) - { - nextArg = nextArg + 1; - if (nextArg == maxArg) - { - return CommandLineError("Version number must follow `-version' option"); - } - if (!PcalParams.ProcessVersion(args[nextArg])) - { - return CommandLineError("Bad version number"); - } - - } else if (option.equals("-lineWidth")) - { - nextArg = nextArg + 1; - try - { - if (nextArg == maxArg) - { - throw new NumberFormatException(); - } - int a = new Integer(args[nextArg]).intValue(); - if (a < 60) - { - throw new NumberFormatException(); - } - PcalTLAGen.wrapColumn = a; - PcalTLAGen.ssWrapColumn = a - 33; - } catch (Exception e) - { - return CommandLineError("Integer value at least 60 must follow `-lineWidth' option"); - } - - } else - { - if (notInFile) - { - return CommandLineError("Unknown command-line option: " + option); - } else - { - return CommandLineError("Unknown or illegal option in options statement: " + option); - } - } - ; - nextArg = nextArg + 1; - } // END while (nextArg < maxArg) - - if (nextArg > maxArg) - /****************************************************************** - * The last option took an argument that was the last * - * command-line argument. * - ******************************************************************/ - { - return CommandLineError("No input file specified"); - } - - // SZ 02.16.2009: since this is a modification of the parameters, moved - // to the parameter handling method - if (PcalParams.FairnessOption.equals("-nof")) - { - PcalParams.FairnessOption = ""; - } - if (PcalParams.CheckTermination && PcalParams.FairnessOption.equals("") && !explicitNof) - { - PcalParams.FairnessOption = "wf"; - - } - - /******************************************************************** - * If we are processing the command-line arguments, we need to get * - * the input-file name. Otherwise, we're done. * - *******************************************************************/ - if (inFile) - { - return STATUS_OK; - } - - /******************************************************************** - * Set PcalParams.TLAInputFile to the last argument, removing a * - * "tla" extension if it has one. * - ********************************************************************/ - /* - int dotIndex = args[maxArg].lastIndexOf(".") ; - if (dotIndex == -1) - { - PcalParams.TLAInputFile = args[maxArg]; - } - else if (args[maxArg].substring(dotIndex).equals(".tla")) - { - PcalParams.TLAInputFile = args[maxArg].substring(0, dotIndex); - } - else - { - return CommandLineError("Input file has extension other than tla"); - } - */ - - // SZ 02.16.2009: check for correct file extension (ignoring case) - // and file existence. also handles dots in the pathname - File file = new File(args[maxArg]); - boolean hasExtension = false; - if (file.getName().lastIndexOf(".") == -1) - { - // no extension - PcalParams.TLAInputFile = file.getPath(); - } else - { - // extension present - if (file.getName().toLowerCase().endsWith(".tla")) - { - hasExtension = true; - } - // Aborted version 1.31 code - // else if (file.getName().toLowerCase().endsWith(".pcal")){ - // hasExtension = true; - // PcalParams.fromPcalFile = true; - // } - else - { - return CommandLineError("Input file has extension other than " /* pcal or */+ "tla"); - } - } - if (hasExtension) - { - // cut the extension - PcalParams.TLAInputFile = file.getPath().substring(0, file.getPath().lastIndexOf(".")); - if (!file.exists()) - { - return CommandLineError("Input file " + file.getPath() + " does not exist."); - } - } else - { - // aborted version 1.31 code - // file = new File(PcalParams.TLAInputFile + ".pcal"); - // if (file.exists()) - // { - // PcalParams.fromPcalFile = true; - // } else - // { - file = new File(PcalParams.TLAInputFile + ".tla"); - if (!file.exists()) - { - return CommandLineError("Input file " + PcalParams.TLAInputFile + ".pcal and " + file.getPath() - + ".tla not found"); - } - // } - } - // file = new File(PcalParams.TLAInputFile + (PcalParams.fromPcalFile?".pcal":".tla")); - // if (!file.exists()) - // { - // return CommandLineError("Input file " + file.getPath() + " not found"); - // } - - return STATUS_OK; - } - - /** - * Prints out the help message - * @return status if it has been successfully printed - */ - private static boolean OutputHelpMessage() - { - Vector helpVec = null; - try - { - helpVec = PcalResourceFileReader.ResourceFileToStringVector("help.txt"); - } catch (PcalResourceFileReaderException e) - { - PcalDebug.reportError(e); - return false; - } - int i = 0; - while (i < helpVec.size()) - { - ToolIO.out.println((String) helpVec.elementAt(i)); - i = i + 1; - } - - return true; - } - - /** - * Returns if the options are conflicting - * @return true if the provided options are conflicting, false otherwise - */ - private static boolean CheckForConflictingSpecOptions() - { - if ((PcalParams.SpecOption ? 1 : 0) + (PcalParams.MyspecOption ? 1 : 0) + (PcalParams.Spec2Option ? 1 : 0) - + (PcalParams.Myspec2Option ? 1 : 0) + (PcalParams.WriteASTFlag ? 1 : 0) > 1) - { - CommandLineError("\nCan have at most one of the options " + "-spec, -myspec, -spec2, -myspec2, writeAST"); - return true; - } - ; - return false; - } - - private static int CommandLineError(String msg) - /********************************************************************* - * Announce a command line error with the string indicating the * - * explanation and halt. * - *********************************************************************/ - { - PcalDebug.reportError("Command-line error: " + msg + "."); -// ToolIO.out.println("Command-line error: " + msg + "."); -// ToolIO.out.println("Use -help option for more information."); - return STATUS_EXIT_WITH_ERRORS; - } - - private static int findTokenPair(Vector vec, int lineNum, String tok1, String tok2) - /********************************************************************* - * Returns the number of the first line at or after lineNum in the * - * vector of strings vec containing tok1 followed by 1 or more * - * spaces followed by tok2. Returns -1 if such a line is not found. * - *********************************************************************/ - { - int i = lineNum; - while (i < vec.size()) - { - String line = (String) vec.elementAt(i); - int col = line.indexOf(tok1); - int nextcol = col + tok1.length(); - if (col != -1) - { - while ((nextcol < line.length()) && (line.charAt(nextcol) == ' ')) - { - nextcol = nextcol + 1; - } - ; - if ((nextcol < line.length()) && (nextcol == line.indexOf(tok2))) - { - return i; - } - } - ; - i = i + 1; - } - ; - return -1; - } - - /************************** RemoveTabs *********************************/ - - public static Vector removeTabs(Vector vec) - { - /******************************************************************** - * Returns a string vector obtained from the string vector vec by * - * replacing any evil tabs with the appropriate number of spaces, * - * where "appropriate" means adding from 1 to 8 spaces in order to * - * make the next character fall on a column with Java column * - * number (counting from 0) congruent to 0 mod 8. This is what * - * Emacs does when told to remove tabs, which makes it good enough * - * for me. * - ********************************************************************/ - Vector newVec = new Vector(); - int i = 0; - while (i < vec.size()) - { - String oldline = (String) vec.elementAt(i); - String newline = ""; - int next = 0; - while (next < oldline.length()) - { - if (oldline.charAt(next) == '\t') - { - int toAdd = 8 - (newline.length() % 8); - while (toAdd > 0) - { - newline = newline + " "; - toAdd = toAdd - 1; - } - } else - { - newline = newline + oldline.substring(next, next + 1); - } - ; - next = next + 1; - } - newVec.addElement(newline); - i = i + 1; - } - ; - return newVec; - } - - /********************* STRING UTILITY FUNCTIONS ***********************/ - - private static int NextSpace(String s, int cur) - /******************************************************************** - * Returns the first space in s at or after col. If there is none, * - * return the index of the last character in s. Spaces in strings * - * are not treated as spaces. Assumes s[cur] is not in a string. * - ********************************************************************/ - { - int i = cur; - boolean inString = false; - while ((i < s.length()) && ((s.charAt(i) != ' ') || inString)) - { - if ((s.charAt(i) == '"') && ((i == 0) || (s.charAt(i - 1) != '\\'))) - inString = !inString; - i = i + 1; - } - if (i == s.length()) - return i - 1; - else - return i; - } - - private static String WrapString(String inString, int col) - /********************************************************************* - * Returns the string inString with lines wrapped approximately at * - * col, taking care not to wrap in a string. * - *********************************************************************/ - { - int i = 0; - int ccol = 1; - StringBuffer sb = new StringBuffer(); - while (i < inString.length()) - { - if (inString.charAt(i) == ' ') // An initial space or a space - { - sb.append(' '); // that follows a space. It - i = i + 1; // can always be appended to a line. - ccol = ccol + 1; - } else - // Find next word, which starts at i. - { - int j = NextSpace(inString, i); - if (ccol + (j - i + 1) > col) - { - sb.append('\n'); - ccol = 1; - } - while (i <= j) // If this overflows col, then the word - { - sb.append(inString.charAt(i)); - // is longer than col. - i = i + 1; - ccol = ccol + 1; - } - } - } - return sb.toString(); - } - -} +package pcal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Vector; + +import pcal.MappingObject.Break; +import pcal.exception.FileToStringVectorException; +import pcal.exception.ParseAlgorithmException; +import pcal.exception.PcalResourceFileReaderException; +import pcal.exception.RemoveNameConflictsException; +import pcal.exception.StringVectorToFileException; +import pcal.exception.TLCTranslationException; +import pcal.exception.UnrecoverableException; +import tla2tex.Debug; +import util.ToolIO; + +/*************************************************************************** +* <pre> +* CLASS trans * +* * +* BUGS: * +* - Interprets * +* * +* if (...) call f(...) ; return * +* * +* as if it were * +* * +* if (...) {call f(...) ; return } * +* * +* See 2 Dec 2015 Tlaplus Google group post by Jaak Ristioja * +* * +* - Accepts if (...) {...} ; else {...} * +* Generates code as if the ";" were not there. * +* +* - When the body of a macro contains a statement with(v \in ...) +* and v is a macro parameter, the argument is not being substituted +* for v. +* +* - I came across a "TLAExpr.renormalize() found anchor has moved to left" +* error, apparently caused by a substitution of an expression longer +* than the macro parameter it is instantiating in some weird case. +* * +* POSSIBLE FEATURE: * +* Adds the pc variable if a label has a + or - modifier. It's * +* not needed for a + modifier (and all the other conditions * +* for eliminating pc hold). The solution is not to try to handle * +* this case, but to add a "strongly fair process" construct, since * +* the pc can be eliminated only when there's just a single label. * +* This would be easy to implement if + modifiers are ignored or * +* cause an error and - modifiers mean no fairness. Then "strongly * +* fair" would act like "fair" except that + modifiers do nothing * +* and an SF instead of a WF is produced. * +* ----------------------------------------------------------------- * +* History: * +* Version 1.0: Original release. * +* * +* Version 1.1: (March 2006) * +* Introduced ability to have the translator * +* add missing labels. * +* * +* Version 1.2: (August 2007) * +* Introduced defaultInitValue so liveness can be * +* checked even with "uninitialized" variables. * +* * +* Version 1.3: (February 2008) * +* Added "await" as a synonym for "when" * +* * +* Version 1.4: (June 2010) * +* Added the options statement and the -lineWidth option. * +* * +* Version 1.5: (Jan 2011) * +* 1. Added the -noDoneDisjunct option. * +* 2. Added the new way of specifying fairness, with * +* the "fair" keyword with "+" modifier, and the "+" * +* and "-" label modifiers. * +* 3. Automatically removes the stuttering-on-termination * +* disjunction if all processes are "while (TRUE)" * +* statements with no gotos to "Done". * +* 4. In addition, if the "while (TRUE)"s have no * +* internal labels, it removes the pc variable. * +* 5. Changed the representation of Process and Procedure * +* nodes in the abstract syntax tree produced by * +* the -writeAST option and used when TLC is producing * +* the translation. * +* The changes 3-5 are not implemented when the -version * +* option specifies an earlier version. * +* * +* Version 1.6: (July 2011) * +* - Added the "--fair algorithm" syntax for specifying * +* weak fairness of the next-state action. (This * +* changes the way fairness of a uniprocess algorithm was * +* specified in Version 1.5. All legal Version 1.5 * +* algorithms should work with the "version 1.5" option.) * +* - Permitted previously defined macros to be called * +* inside a macro definition. * +* * +* Version 1.7: (19 January 2012) * +* - Translator adds "BEGIN/END TRANSLATION" if needed. * +* - Added support for Toolbox Goto PCal Source command. * +* * +* Version 1.8: (30 Mar 2012) * +* - Changed translation to remove stuttering-on- * +* termination disjunction if some process is * +* "while (TRUE)". * +* - Omitted the Termination definition if the stuttering- * +* on-termination disjunction is removed, since that * +* implies Termination is always FALSE. * +* (4 May 2012) * +* - Removed the unnecessary CASE in the pc = ... clause of * +* the Init predicate when there is only a single process * +* statement. * +* ----------------------------------------------------------------- * +* * +* This is the main method of the +CAL to TLA+ translation program. * +* the program has the following command-line options. Only the ones * +* marked with ** besides them can be specified in the file's * +* options statement. The "-" can be omitted when the option is in * +* the options statement. * +* * +* -help : Type a help file instead of running the program. * +* * +* -spec name : Uses TLC and the TLA+ specification name.tla to do * +* the translation. The files name.tla and name.cfg * +* are copied from the java/ directory to the current * +* directory; the file AST.tla that defines `fairness' * +* to equal the fairness option and `ast' to equal * +* the the AST data structure representing the algorithm * +* is written to the current directory; and TLC is run * +* on name.tla to produce the translation. * +* * +* -myspec name : Like -spec, except it finds the files names.tla * +* and names.cfg in the current directory, instead * +* of writing them there. * +* * +* -spec2 name * +* -myspec2 name : Like -spec and -myspec, except they use TLC2 * +* instead of TLC (aka TLC1). * +* * +* -writeAST : Writes the AST file as in the -spec option, but does * +* not perform the translation. * +* * +* -debug : Run in debugging mode, whatever that means. For the * +* parser, it just means that the toString() methods * +* output the line and column number along with the * +* node. * +* * +* -unixEOL : Forces the use of Unix end-of-line convention, regardless * +* of the system's default. Without this, when run on * +* Windows, the files it writes seem to have a "^M" at the * +* end of every line when viewed with Emacs. * +* * +*** -wf : Conjoin to formula Spec weak fairness of each process's * +* next-state action * +* * +*** -sf : Conjoin to formula Spec strong fairness of each process's * +* next-state action * +* * +*** -wfNext : Conjoin to formula Spec weak fairness of the entire * +* next-state action * +* * +*** -nof : Conjoin no fairness formula to Spec. This is the default, * +* except when the -termination option is chosen. * +* * +*** -termination : Add to the .cfg file the command * +* PROPERTY Termination * +* With this option, the default fairness option * +* becomes -wf. * +* * +* -nocfg : Suppress writing of the .cfg file. * +* * +*** -noDoneDisjunct : Suppress the disjunct of the next-state * +* relation that describes stuttering steps taken * +* when the algorithm has halted. * +* * +*** -label : Tells the translator to add missing labels. This is * +* the default only for a uniprocess algorithm in which * +* the user has typed no labels. * +* * +* -reportLabels : True if the translator should print the names * +* and locations of all labels it adds. Like * +* -label, it tells the translator to add missing * +* labels. * +* * +*** -labelRoot name : If the translator adds missing labels, it * +* names them name1, name2, etc. Default value * +* is "Lbl_". * +* * +* THE FOLLOWING OPTION ADDED IN VERSION 1.31 * +* * +*** -lineWidth : The translation tries to perform the translation so * +* lines have this maximum width. (It will often * +* fail.) Default is 78, minimum value is 60. * +* * +* THE FOLLOWING OPTIONS ADDED IN VERSION 1.4 * +* * +*** -lineWidth : The translation tries to perform the translation so * +* lines have this maximum width. (It will often * +* fail.) Default is 78, minimum value is 60. * +* * +*** -version : The version of PlusCal for which this algorithm is * +* written. If the language is ever changed to make * +* algorithms written for earlier versions no longer * +* legal, then the translator should do the appropriate * +* thing when the earlier version number is specified. * +* ------------------------------------------------------------------------ * +* * +* The program uses vector objects from the Vector class to implement * +* sequences (lists). This generates a compiler warning. * +* * +* In Java data structures like arrays and Vectors, numbering starts with * +* 0. Unlike programmers, human beings count from 1. I use the term "Java * +* ordinal" to refer a number that denotes a position that represents the * +* first item as 0, and the term "human ordinal" to refer to an ordinary * +* ordinal that counts the first item as 1. * +* * +* * +* NOTE: * +* * +* One process should be able to read the pc or stack of another. There * +* is no logical reason to forbid this. However, the definition of * +* Translation in PlusCal.tla does not distinguish between instances of pc * +* in the original algorithm and ones inserted by the translation. The * +* latter instances must be subscripted--that is replaced by something * +* like pc[self]. Therefore, the Translation operator subscripts the * +* instances of pc from the original algorithm. The Java Translate method * +* must not do this, but must subscript (and prime) only the instances of * +* pc and stack introduced during the translation process. * +* * +* * +* The following bugs should all have been fixed by the addition of * +* ParseAlgorithm.Uncommment by LL on 18 Feb 2006. * +* * +* - There cannot be a comment between a label and the * +* following ":". * +* * +* - There cannot be a comment immediately before the ")" that ends * +* the list of arguments in a call statement. * +* * +* - The code for reporting the location of an error has the * +* following problem. If the token where the error occurs is * +* preceded by a comment, then the position reported is that of the * +* beginning of the comment rather than that of the token. * +* * +* TENTATIVE CHANGE MADE * +* * +* The following change was made along with a modification to the parser * +* to allow semi-colons to be omitted when they're obviously unnecessary. * +* * +* The parser does not parse expressions in the +CAL algorithm; it just * +* scans ahead to the first token that it can determine is not part of the * +* expression. To make this work, the following tokens that are legal in * +* a TLA+ expression are illegal in a +CAL expression: * +* * +* variable variables begin do then := || * +* * +* Making additional tokens illegal might help the parser find errors * +* sooner. For example, example, if one forgets the ";" and writes * +* * +* x := x + 1 * +* if x > y + 17 then ... * +* * +* the parser takes everything up to the "then" to be the right-hand side * +* of the "x :=" assignment. Making "if" illegal in an expression would * +* allow the parser to catch the error at the "if". * +* </pre> +***************************************************************************/ +class trans +{ + /** Status indicating no errors and successful process */ + static final int STATUS_OK = 1; + /** Status of no errors, but abort of the translation */ + private static final int STATUS_EXIT_WITHOUT_ERROR = 0; + /** Status of present errors and abort of the translation */ + static final int STATUS_EXIT_WITH_ERRORS = -1; + + /** + * Main function called from the command line + * @param args, command line arguments + */ + public static void main(String[] args) + { + runMe(args); + } + + /** + * The main translator method + * @return one of {@link trans#STATUS_OK}, {@link trans#STATUS_EXIT_WITH_ERRORS}, + * {@link trans#STATUS_EXIT_WITH_ERRORS} + * indicating the status + * + * Modified by LL on 16 Dec 2011. Changed the return value to the + * TLAtoPCalMapping object for the translation. (The status return + * value was not being used.) If the translation fails, it returns + * null. + */ + /** + * @param args + * @return + */ + /** + * @param args + * @return + */ +// public static int runMe(String[] args) + public static TLAtoPCalMapping runMe(String[] args) // added for testing + { + /********************************************************************* + * Get and print version number. * + *********************************************************************/ + // String lastModified = + // "last modified on Wed 11 March 2009 at 14:52:58 PST by lamport"; + /******************************************************************* + * This string is inserted by an Emacs macro when a new version is * + * saved. Unfortunately, Eclipse isn't Emacs, so the modification * + * date must be entered manually in the PcalParams module. * + *******************************************************************/ + + if (ToolIO.getMode() == ToolIO.SYSTEM) + { + PcalDebug.reportInfo("pcal.trans Version " + PcalParams.version + " of " + PcalParams.modDate); + } + + // SZ Mar 9, 2009: + /* + * This method is called in order to make sure, that the + * parameters are not sticky because these are could have been initialized + * by the previous run + */ + PcalParams.resetParams(); + /********************************************************************* + * Get and process arguments. + *********************************************************************/ + + /** + * Create the new TLAtoPCalMapping object, call it mapping + * here and set PcalParams.tlaPcalMapping to point to it. + */ + TLAtoPCalMapping mapping = new TLAtoPCalMapping() ; + PcalParams.tlaPcalMapping = mapping; + + int status = parseAndProcessArguments(args); + + if (status != STATUS_OK) + { +// return exitWithStatus(status); + return new TLAtoPCalMapping() ; // added for testing + } + + /********************************************************************* + * Read the input file, and set the Vector inputVec equal to its * + * contents, where inputVec[i] is the string containing the contents * + * of line i+1 of the input file. * + *********************************************************************/ + Vector inputVec = null; + try + { + inputVec = fileToStringVector(PcalParams.TLAInputFile + /* (PcalParams.fromPcalFile ? ".pcal" : */".tla" /*)*/); + } catch (FileToStringVectorException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + + /********************************************************************* + * outputVec is an alias for inputVec if the input is a .tla file, * + * which was not always the case in the aborted version 1.31. * + *********************************************************************/ + // Vector outputVec = PcalParams.fromPcalFile ? new Vector() : inputVec; + Vector outputVec = inputVec; + + /********************************************************************* + * Set untabInputVec to be the vector of strings obtained from * + * inputVec by replacing tabs with spaces. * + * * + * Tabs are date from the days when memory cost $1 per bit and are a * + * stupid anachronism. They should be banned. Although the various * + * methods taken from TLATeX should deal with tabs, there are * + * undoubtedly corner cases that don't work right. In particular, I * + * think there's one case where PcalCharReader.backspace() might be * + * called to backspace over a tab. It's easier to simply get rid of * + * the tabs than to try to make it work. * + * * + * Since the user might be evil enough to prefer tabs, with tla-file * + * input, the parts of the output file that are not produced by the * + * translator are copied from inputVec, so any tabs the user wants * + * are kept. * + *********************************************************************/ + Vector untabInputVec = removeTabs(inputVec); + + /** + * Look through the file for PlusCal options. They are put anywhere + * in the file (either before or after the module or in a comment) + * with the following sequence + * PlusCal <optional white space> options <optional white space> + * ( <options> ) + * + * where <options> has the same format as options on the command + * line. + */ + IntPair searchLoc = new IntPair(0, 0); + boolean notDone = true; + while (notDone) + { + try + { + ParseAlgorithm.FindToken("PlusCal", untabInputVec, searchLoc, ""); + String line = ParseAlgorithm.GotoNextNonSpace(untabInputVec, searchLoc); + String restOfLine = line.substring(searchLoc.two); + if (restOfLine.startsWith("options")) + { + // The first string after "PlusCal" not starting with a + // space character is "options" + if (ParseAlgorithm.NextNonIdChar(restOfLine, 0) == 7) + { + // The "options" should begin an options line + PcalParams.optionsInFile = true; + ParseAlgorithm.ProcessOptions(untabInputVec, searchLoc); + notDone = false; + } + } + } catch (ParseAlgorithmException e) + { + // The token "PlusCal" not found. + notDone = false; + } + } + + /** + * translationLine is set to the line of the output file at which + * the \* BEGIN TRANSLATION appears--whether it is inserted into the + * tla-file input by the user, or inserted into the output by the + * translator for pcal-file input. + */ + int translationLine = 0; + + /********************************************************************* + * Set algLine, algCol to the line and column just after the string * + * [--]algorithm that begins the algorithm. (These are Java * + * ordinals, in which counting begins at 0.) * + * * + * Modified by LL on 18 Feb 2006 to use untabInputVec instead of * + * inputVec, to correct bug that occurred when tabs preceded the * + * "--algorithm". * + * * + * For the code to handle pcal-input, I introduced the use of * + * IntPair objects to hold <line, column> Java coordinates (counting * + * from zero) in a file (or an image of a file in a String Vector). * + * For methods that advance through the file, the IntPair object is * + * passed as an argument and is advanced by the method. This is * + * what I should have been doing from the start, but I wasn't smart * + * enough The IntPair curLoc is the main one used in the part of the * + * following code that handles pcal-file input. * + *********************************************************************/ + int algLine = 0; + int algCol = -1; + /******************************************************************* + * If the BEGIN/END TRANSLATION region exists, then set * + * translationLine to the number of the line after which the * + * translation is to be inserted and delete the previous version * + * of the translation (if it exists) from inputVec. (Line * + * numbering is by Java ordinals.) If the region doesn't exist, * + * set translationLine to -1. * + * * + * Note: we remove the previous version from inputVec, because * + * that's where the translated output is going to go, and also * + * from untabInputVec, because we will then detect if the begin * + * and end translation lines contain part of the algorithm within * + * them. * + *******************************************************************/ + translationLine = findTokenPair(untabInputVec, 0, PcalParams.BeginXlation1, PcalParams.BeginXlation2); + if (translationLine != -1) + { + + + int endTranslationLine = findTokenPair(untabInputVec, translationLine + 1, PcalParams.EndXlation1, + PcalParams.EndXlation2); + if (endTranslationLine == -1) + { + PcalDebug.reportError("No line containing `" + PcalParams.EndXlation1 + " " + PcalParams.EndXlation2); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null; + } + + endTranslationLine = endTranslationLine - 1; + while (translationLine < endTranslationLine) + { + inputVec.remove(endTranslationLine); + untabInputVec.remove(endTranslationLine); + endTranslationLine = endTranslationLine - 1; + } + } + + // Search for "--algorithm" or "--fair". + // If found set algLine and algCol right after the last char, + // set foundBegin true, and set foundFairBegin true iff it + // was "--fair". Otherwise, set foundBegin false. + boolean foundBegin = false; + boolean foundFairBegin = false; + while ((algLine < untabInputVec.size()) && !foundBegin) + { + String line = (String) untabInputVec.elementAt(algLine); + algCol = line.indexOf(PcalParams.BeginAlg); + if (algCol != -1) + { + algCol = algCol + PcalParams.BeginAlg.length(); + foundBegin = true; + } else + { + algCol = line.indexOf(PcalParams.BeginFairAlg); + if (algCol != -1) { + // Found the "--fair". The more structurally nice thing to + // do here would be to move past the following "algorithm". + // However, it's easier to pass a parameter to the ParseAlgorithm + // class's GetAlgorithm method that tells it to go past the + // "algorithm" token. + algCol = algCol + PcalParams.BeginFairAlg.length(); + foundBegin = true; + foundFairBegin = true; + + } else { + algLine = algLine + 1; + } + } + ; + } + ; + if (!foundBegin) + { + PcalDebug.reportError("Beginning of algorithm string " + PcalParams.BeginAlg + " not found."); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + ; + + /* + * Set the algColumn and algLine fields of the mapping object. + */ + mapping.algColumn = algCol; + mapping.algLine = algLine; + + if (translationLine == -1) + { + /**************************************************************** + * Insert BEGIN/END TRANSLATION comments immediately after the * + * end of the comment that contains the beginning of the * + * algorithm. Set translationLine to the (Java) line number of * + * the BEGIN TRANSLATION. * + ****************************************************************/ + + // Set ecLine, ecCol to the position immediately after the + // *) that closes the current comment. + int depth = 1 ; + int ecLine = algLine ; + int ecCol = algCol ; + boolean notFound = true ; + while (notFound && ecLine < untabInputVec.size()) { + char[] line = ((String) untabInputVec.elementAt(ecLine)).toCharArray(); + + // check current line + while (notFound && ecCol < line.length-1) { + char ch = line[ecCol] ; + char ch2 = line[ecCol+1] ; + +// The following code isn't needed because the algorithm is inside a comment, and +// quotes and \* have no effect in determining where the comment ends. +// +// if (ch == '"') { +// // gobble string +// ch = ch2 ; +// ecCol++ ; +// while (ch != '"') { +// if (ch == '\\') { +// ecCol = ecCol + 2; +// } +// else { +// ecCol++ ; +// } ; +// if (ecCol < line.length - 1) { +// ch = line[ecCol] ; +// } +// else { +// ch = '"' ; +// } +// } ; +// ecCol++ ; +// } +// +// if (ch == '\\' && ch2 == '*' ) { +// // end of line comment, skip to end of line +// ecCol = 214748364; // a very large int +// } + if (ch == '(' && ch2 == '*') { + // left comment delimiter + depth++; + ecCol = ecCol + 2; + } + else if (ch == '*' && ch2 == ')') { + // right comment delimiter + depth--; + ecCol = ecCol + 2; + if (depth == 0) { + notFound = false ; + } + } + else { + // not an interesting character + ecCol++ ; + } + } + + // if not found, go to next line + if (notFound) { + ecLine++ ; + ecCol = 0; + } + } + + if (notFound) { + PcalDebug.reportError("Algorithm not in properly terminated comment"); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + + // Report an error if there's something else on the line that doesn't begin with "\*". This is probably + + String endStuff = ((String) untabInputVec.elementAt(ecLine)).substring(ecCol).trim() ; + + if (!endStuff.equals("") && !endStuff.startsWith("\\*")) { + PcalDebug.reportError("Text on same line following `*)' that ends the \n comment containing the algorithm."); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } ; + + inputVec.insertElementAt("\\* BEGIN TRANSLATION", ecLine+1) ; + untabInputVec.insertElementAt("\\* BEGIN TRANSLATION", ecLine+1) ; + inputVec.insertElementAt("\\* END TRANSLATION", ecLine+2) ; + untabInputVec.insertElementAt("\\* END TRANSLATION", ecLine+2) ; + + translationLine = ecLine + 1; +//System.out.println(ecLine + ", " + ecCol); +//Debug.printVector(inputVec, "foo"); + } + + /* + * Set the mappings start line. + */ + mapping.tlaStartLine = translationLine + 1; + + /********************************************************************* + * Added by LL on 18 Feb 2006 to fix bugs related to handling of * + * comments. * + * * + * Remove all comments from the algorithm in untabInputVec, * + * replacing (* *) comments by spaces to keep the algorithm tokens * + * in the same positions for error reporting. * + *********************************************************************/ + try + { + ParseAlgorithm.uncomment(untabInputVec, algLine, algCol); + } catch (ParseAlgorithmException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + // } // end else of if (PcalParams.fromPcalFile) -- i.e., end processing + // of .tla input file. + + /********************************************************************* + * Set reader to a PcalCharReader for the input file (with tabs and * + * the previous translation removed), starting right after the * + * PcalParams.BeginAlg string. * + *********************************************************************/ + PcalCharReader reader = new PcalCharReader(untabInputVec, algLine, algCol, inputVec.size(), 0); + + /********************************************************************* + * Set ast to the AST node representing the entire algorithm. * + *********************************************************************/ + AST ast = null; + try + { + ast = ParseAlgorithm.getAlgorithm(reader, foundFairBegin); +// System.out.println(ast.toString()); +// For testing, we print out when the new code for eliminating the +// suttering-on-done and pc is used. +// if (ParseAlgorithm.omitPC || ParseAlgorithm.omitStutteringWhenDone) { +// System.out.println("omit pc = " + ParseAlgorithm.omitPC + +// ", omitStutteringWhenDone = " + ParseAlgorithm.omitStutteringWhenDone); +// } + + } catch (ParseAlgorithmException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + PcalDebug.reportInfo("Parsing completed."); +// tla-pcal debugging +//System.out.println("Translation Output:"); +//System.out.println(ast.toString()); + /********************************************************************* + * For -writeAST option, just write the file AST.tla and halt. * + *********************************************************************/ + if (PcalParams.WriteASTFlag) + { + WriteAST(ast); +// return exitWithStatus(STATUS_EXIT_WITHOUT_ERROR); + return null ; // added for testing + } + ; + + /********************************************************************* + * Rename algorithm variables to eliminate name conflicts--for * + * example, if the same variable is declared inside different * + * procedures, if a variable name and a label are the same, or if * + * the same label is used in to different procedures. This should * + * also report an error and terminate if it discovers a conflict * + * between the variable of a `with' statement and some other * + * identifier in the algorithm. It should also detect other * + * conflicts--for example, if there is a variable named "Init" or * + * "TRUE". However, there are conflicts that the translator can't * + * spot--for example, if a variable name is the same as the name of * + * some operator defined elsewhere in the TLA+ module. So it's not * + * worth going overboard in this checking. * + *********************************************************************/ + + // SZ February.15 2009: made non-static to make PCal stateless for tool runs + PCalTLAGenerator pcalTLAGenerator = new PCalTLAGenerator(ast); + try + { + pcalTLAGenerator.removeNameConflicts(); + } catch (RemoveNameConflictsException e1) + { + PcalDebug.reportError(e1); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + + /********************************************************************* + * Set the vector `translation' to the translation of the algorithm * + * represented by the AST ast. If called with the -spec option, * + * do the translation by calling TLC. Otherwise, call the ordinary * + * Translate method. * + *********************************************************************/ + Vector translation = null; + boolean tlcTranslation = PcalParams.SpecOption || PcalParams.MyspecOption || PcalParams.Spec2Option + || PcalParams.Myspec2Option; + + if (tlcTranslation) + { + try + { + translation = TLCTranslate(ast); + } catch (TLCTranslationException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + } else + { + try + { + translation = pcalTLAGenerator.translate(); + } catch (RemoveNameConflictsException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + } + ; + + PcalDebug.reportInfo("Translation completed."); +// tla-pcal Debugging +//System.exit(0); + /********************************************************************* + * For .tla input: * + * Rename the old file by changing its extension from "tla" to "old". * + *********************************************************************/ + // if (!PcalParams.fromPcalFile) + // { + File file; + try + { + file = new File(PcalParams.TLAInputFile + ".old"); + if (file.exists()) + { + file.delete(); + } + ; + file = new File(PcalParams.TLAInputFile + ".tla"); + file.renameTo(new File(PcalParams.TLAInputFile + ".old")); + } catch (Exception e) + { + PcalDebug.reportError("Could not rename input file " + PcalParams.TLAInputFile + ".tla" + " to " + + PcalParams.TLAInputFile + ".old"); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + ; + // } + + /********************************************************************* + * Add the translation to outputVec. * + *********************************************************************/ + int i = 0; + while (i < translation.size()) + { + outputVec.insertElementAt(translation.elementAt(i), i + translationLine + 1); + i = i + 1; + } + + /********************************************************************* + * Code from aborted version 1.31. * + * For .pcal input, set outputSuffixLoc and add the rest of the * + * input file to the output. * + *********************************************************************/ + // if (PcalParams.fromPcalFile) + // { + // PcalParams.outputSuffixLoc = new IntPair(outputVec.size(), 0); + // // if there's stuff in the suffix on the same line with the + // // end of the algorithm, write it on a separate line. + // IntPair curLoc = new IntPair(PcalParams.inputSuffixLoc.one, PcalParams.inputSuffixLoc.two); + // if (curLoc.one < untabInputVec.size()) + // { + // String lastLine = (String) untabInputVec.elementAt(curLoc.one); + // if (curLoc.two < lastLine.length()) + // { + // outputVec.addElement(lastLine.substring(curLoc.two)); + // } + // curLoc.one++; + // } + // // Copy the rest of the input file into the output file. + // for (int ii = curLoc.one; ii < untabInputVec.size(); ii++) + // { + // outputVec.addElement((String) untabInputVec.elementAt(ii)); + // } + // } + /********************************************************************* + * Write the output file. * + *********************************************************************/ + try + { + WriteStringVectorToFile(outputVec, PcalParams.TLAInputFile + ".tla"); + } catch (StringVectorToFileException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + + PcalDebug.reportInfo("New file " + PcalParams.TLAInputFile + ".tla" + " written."); + + /********************************************************************* + * Write the cfg file, unless the -nocfg option is used. * + *********************************************************************/ + File cfgFile = new File(PcalParams.TLAInputFile + ".cfg"); + Vector cfg = null; + boolean writeCfg = !PcalParams.Nocfg; + if (writeCfg && cfgFile.exists()) + { + if (cfgFile.canRead()) + { + try + { + cfg = fileToStringVector(PcalParams.TLAInputFile + ".cfg"); + } catch (FileToStringVectorException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + } else + { + /************************************************************* + * cfg file is read-only. * + *************************************************************/ + writeCfg = false; + PcalDebug.reportInfo("File " + PcalParams.TLAInputFile + ".cfg is read only, new version not written."); + } + } else + { + cfg = new Vector(); + cfg.addElement(PcalParams.CfgFileDelimiter); + } + ; + + /********************************************************************* + * Delete previously written part of cfg file. * + *********************************************************************/ + if (writeCfg) + { + i = 0; + boolean done = false; + while ((!done) && (cfg.size() > i)) + { + if (((String) cfg.elementAt(i)).indexOf(PcalParams.CfgFileDelimiter) == -1) + { + i = i + 1; + } else + { + done = true; + } + } + if (done) + { + /************************************************************* + * Delete all lines before the delimiting comment string. * + *************************************************************/ + while (i > 0) + { + cfg.removeElementAt(0); + i = i - 1; + } + } else + { + /************************************************************* + * The delimiting comment string written by the translator * + * not found in the cfg file, so presumably the user created * + * the cfg file before running the translator on the input * + * file. We insert the delimiter. * + *************************************************************/ + cfg.add(0, PcalParams.CfgFileDelimiter); + } + ; + + /****************************************************************** + * If defaultInitValue is used, add a CONSTANT statement setting * + * it to a model value of the same name. * + * (Added 22 Aug 2007 by LL.) * + ******************************************************************/ + if (tlcTranslation || ParseAlgorithm.hasDefaultInitialization) + { + cfg.add(0, "CONSTANT defaultInitValue = defaultInitValue"); + } + ; + /****************************************************************** + * Insert the `PROPERTY Termination' line if requested. * + ******************************************************************/ + if (PcalParams.CheckTermination) + { + cfg.add(0, "PROPERTY Termination"); + } + ; + + /****************************************************************** + * Insert the SPECIFICATION line if there isn't already one. * + ******************************************************************/ + i = 0; + boolean hasSpec = false; + while (i < cfg.size()) + { + String thisLine = (String) cfg.elementAt(i); + if ((thisLine.indexOf("SPECIFICATION") != -1) + && ((thisLine.indexOf("\\*") == -1) || (thisLine.indexOf("\\*") > thisLine + .indexOf("SPECIFICATION")))) + { + hasSpec = true; + } + ; + i = i + 1; + } + ; + if (hasSpec) + { + PcalDebug.reportInfo("File " + PcalParams.TLAInputFile + + ".cfg already contains SPECIFICATION statement," + "\n so new one not written."); + } else + { + cfg.add(0, "SPECIFICATION Spec"); + } + ; + try + { + WriteStringVectorToFile(cfg, PcalParams.TLAInputFile + ".cfg"); + } catch (StringVectorToFileException e) + { + PcalDebug.reportError(e); +// return exitWithStatus(STATUS_EXIT_WITH_ERRORS); + return null ; // added for testing + } + PcalDebug.reportInfo("New file " + PcalParams.TLAInputFile + ".cfg" + " written."); + } + ; + +// return exitWithStatus(STATUS_EXIT_WITHOUT_ERROR); + return PcalParams.tlaPcalMapping ; // added for testing + } // END main + + /** + * If run in the system mode, exits the program, in tool mode returns the status + * @param status + */ + private static int exitWithStatus(int status) + { + if (ToolIO.getMode() == ToolIO.SYSTEM) + { + // return exit status in system mode + System.exit(status); + } + + // just exit the function in tool mode + return status; + } + + /********************** Writing the AST ************************************/ + private static boolean WriteAST(AST ast) + { + Vector astFile = new Vector(); + astFile.addElement("------ MODULE AST -------"); + astFile.addElement("EXTENDS TLC"); + astFile.addElement("fairness == \"" + PcalParams.FairnessOption + "\""); + astFile.addElement(" "); + astFile.addElement("ast == "); + astFile.addElement(ast.toString()); + astFile.addElement("=========================="); + try + { + WriteStringVectorToFile(astFile, "AST.tla"); + } catch (StringVectorToFileException e) + { + PcalDebug.reportError(e); + return false; + } + PcalDebug.reportInfo("Wrote file AST.tla."); + return true; + } + + /************************* THE TLC TRANSLATION *****************************/ + + private static Vector TLCTranslate(AST ast) throws TLCTranslationException + /*********************************************************************** + * The result is a translation of the algorithm represented by ast * + * obtained by using TLC to execute the definition of Translation(ast) * + * in the TLA+ module PlusCal. It equals a vector with a single * + * element, which is the entire translation as a single string. * + * * + * This method relies on a bug in TLC's pretty-print routine that * + * causes it not to work properly on the output produced by the TLA * + * spec. Instead of prettyprinting the output as * + * * + * << "VARIABLES ...", * + * "vars == ... ", * + * ... * + * >> * + * * + * it prints the entire translation on a single line as * + * * + * << "VARIABLES ...", "vars == ... ", ... >> * + * * + * This allows the method to find the entire translation as the single * + * line that begins with "<<". If this TLC bug is fixed, then the * + * TLCTranslate method will have to be modified to read the spec as a * + * sequence of lines. This will probably require the TLA module that * + * translates the spec to print a special marker line to indicate the * + * end of the translation. * + ***********************************************************************/ + { + /********************************************************************* + * Write the file AST.tla that contains * + *********************************************************************/ + WriteAST(ast); + + /********************************************************************* + * For the -spec (rather than -myspec) option, copy the * + * specification's .tla and .cfg files PlusCal.tla to current * + * directory. * + *********************************************************************/ + if (PcalParams.SpecOption || PcalParams.Spec2Option) + { + try + { + Vector parseFile = PcalResourceFileReader.ResourceFileToStringVector(PcalParams.SpecFile + ".tla"); + + WriteStringVectorToFile(parseFile, PcalParams.SpecFile + ".tla"); + parseFile = PcalResourceFileReader.ResourceFileToStringVector(PcalParams.SpecFile + ".cfg"); + WriteStringVectorToFile(parseFile, PcalParams.SpecFile + ".cfg"); + + PcalDebug + .reportInfo("Wrote files " + PcalParams.SpecFile + ".tla and " + PcalParams.SpecFile + ".cfg."); + + } catch (UnrecoverableException e) + { + throw new TLCTranslationException(e.getMessage()); + } + + } + ; + /********************************************************************* + * Run TLC on the specification file and set tlcOut to TLC's output. * + *********************************************************************/ + String javaInvocation; + if (PcalParams.SpecOption || PcalParams.MyspecOption) + { + // Modified on 29 May 2010 by LL so tlc2 is run in + // all cases. + PcalDebug.reportInfo("Running TLC2."); + javaInvocation = "java -Xss1m tlc2.TLC "; + } else + { + PcalDebug.reportInfo("Running TLC2."); + javaInvocation = "java -Xss1m tlc2.TLC "; + } + ; + String tlcOut = " "; + Runtime rt = Runtime.getRuntime(); + try + { + // Modified on 29 May 2010 by LL to replace getErrorStream() with + // getInputStream(), which by Java logic gets standard out. (And no, + // getErrorStream() did not get standard non-error.) Apparently, + // TLC has been changed to put its output on stdout. + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rt.exec( + javaInvocation + PcalParams.SpecFile).getInputStream())); + while (tlcOut.indexOf("<<") == -1) + { + tlcOut = bufferedReader.readLine(); + } + ; + bufferedReader.close(); + } catch (Exception e) + { + throw new TLCTranslationException("Error reading output of TLC"); + } + ; + + /********************************************************************* + * Test if the translation failed and reported an error message, * + * bracketed by "@Error@" and "@EndError@" strings. If it did, * + * report the error and halt. If not, set tlcOut to the value of * + * Translation(ast) with the outermost "<<" and ">>" removed. * + *********************************************************************/ + if (tlcOut.indexOf("@Error@") != -1) + { + throw new TLCTranslationException("TLC's translation of the parsed algorithm failed with\n Error: " + + tlcOut.substring(tlcOut.indexOf("@Error@") + 7, tlcOut.indexOf("@EndError@"))); + } + ; + tlcOut = tlcOut.substring(2, tlcOut.lastIndexOf(">>")) + " "; + PcalDebug.reportInfo("Read TLC output."); + + /********************************************************************* + * Set transl to the string obtained by converting tlcOut, which is * + * a comma-separated sequence of strings, to the single string that * + * they represent. See PlusCal.tla for an explanation of the * + * encoding of TLA+ statements as sequences of strings. * + *********************************************************************/ + int i = 0; + String transl = ""; + while (i < tlcOut.length()) + { + if (tlcOut.charAt(i) == '"') + { + i = i + 1; + if ((tlcOut.charAt(i) == '\\') && (tlcOut.charAt(i + 1) == '"')) + /******************************************************* + * This is a quoted string. * + *******************************************************/ + { + if (tlcOut.charAt(i + 2) != '"') + { + throw new TLCTranslationException("I'm confused"); + + } + ; + i = i + 3; + while (tlcOut.charAt(i) != '"') + { + i = i + 1; + } + i = i + 1; + transl = transl + "\""; + while (tlcOut.charAt(i) != '"') // " + { + if (tlcOut.charAt(i) == '\\') + { + /*********************************************** + * Get special character. * + ***********************************************/ + transl = transl + tlcOut.substring(i, i + 2); + i = i + 2; + } else + { + transl = transl + tlcOut.substring(i, i + 1); + i = i + 1; + } + ; + } + ; + i = i + 8; + transl = transl + "\""; + } else + { + while (tlcOut.charAt(i) != '"') + { + if ((tlcOut.charAt(i) == '\\') && (tlcOut.charAt(i + 1) == '\\')) + { + i = i + 1; + } + ; + transl = transl + tlcOut.substring(i, i + 1); + i = i + 1; + } + ; + i = i + 1; + } + ; + } // END if (tlcOut.charAt(i) == '"') + else if (tlcOut.charAt(i) == ',') + { + i = i + 1; + } else + { + if (tlcOut.charAt(i) != ' ') + { + throw new TLCTranslationException("Expected space but found `" + tlcOut.charAt(i) + "'"); + } + ; + transl = transl + tlcOut.substring(i, i + 1); + i = i + 1; + } + ; + } + ; // END while + /* ****************************************************************** + * Wrap the translated string into approximately 80 character lines * + *******************************************************************/ + transl = WrapString(transl, 78); + Vector result = new Vector(); + result.addElement(transl); + return result; + } + + /***************** METHODS FOR READING AND WRITING FILES *****************/ + + private static void WriteStringVectorToFile(Vector inputVec, String fileName) throws StringVectorToFileException + /*********************************************************************** + * Writes the Vector of strings inputVec to file named fileName, with * + * each element of inputVec written on a new line. * + ***********************************************************************/ + { + try + { + // I have no idea what Java does if you try to write a new version + // of a read-only file. On Windows, it's happy to write it. Who + // the hell knows what it does on other operating systems? So, something + // like the following code could be necessary. However, the setWritable() + // method was introduced in Java 1.6, and in December 2009, that version + // isn't available on the Mac. And I can't find out how to set a file + // to be writable in any earlier version of Java. On the web, the advice + // is to copy the file, delete the old version, and rename the copy. + // But the File method's documentation actually says that delete may or + // may not delete the read-only file, depending on the OS. + // + // File file = new File(fileName); + // if (! file.canWrite()) { + // file.setWritable(true); + // } + BufferedWriter fileW = new BufferedWriter(new FileWriter(fileName)); + int lineNum = 0; + while (lineNum < inputVec.size()) + { + fileW.write((String) inputVec.elementAt(lineNum)); + fileW.newLine(); + lineNum = lineNum + 1; + } + ; + fileW.close(); + } catch (Exception e) + { + throw new StringVectorToFileException("Could not write file " + fileName); + } + ; + + } + + private static Vector fileToStringVector(String fileName) throws FileToStringVectorException + /*********************************************************************** + * Reads file fileName into a StringVector, a vector in which each * + * element is a line of the file. * + ***********************************************************************/ + { + Vector inputVec = new Vector(100); + try + { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName))); + try + { + String nextLine = bufferedReader.readLine(); + while (nextLine != null) + { + inputVec.addElement(nextLine); + nextLine = bufferedReader.readLine(); + } + ; + bufferedReader.close(); + } catch (IOException e) + { + /********************************************************* + * Error while reading input file. * + *********************************************************/ + throw new FileToStringVectorException("Error reading file " + fileName + "."); + } + } + + catch (FileNotFoundException e) + { + /************************************************************** + * Input file could not be found. * + **************************************************************/ + throw new FileToStringVectorException("Input file " + fileName + " not found."); + } + ; + return inputVec; + } + + /********************* PROCESSING THE COMMAND LINE ***********************/ + + /** + * Processes the command line arguments + * + * This method changes values of public static variables of the {@link PcalParams} + * + * SZ: This will cause problems, due to the fact that the PCalParams are static. + * Any initialization should overwrite the previous, which is currently NOT the case + * Should be re-factored to non-static access to the properties + * + * @return status of processing. + * the status {@link trans#STATUS_OK} indicates that no errors has been detected. + * the status {@link trans#STATUS_EXIT_WITHOUT_ERROR} indicates that no errors has been found but translation + * should not be started (e.G -help call) + * the status {@link trans#STATUS_EXIT_WITH_ERRORS} indicates errors + * + * Change made on 9 December 2009 for pcal-file input. This procedure is + * called a second time if there is pcal-file input with an options statement. + * It will be the second call iff {@link PcalParams#optionsInFile} equals true. + * The second call should have a dummy extra argument in place of the + * command-line's file-name argument. When pcal files were eliminated, this + * kludgy mechanism was kept and used to indicate if the method is being called + * for options specified inside the module. + */ + static int parseAndProcessArguments(String[] args) + { + + /** ******************************************************************* + *<pre> + * Get the command-line arguments and set the appropriate parameters. * + * The following command line arguments are handled. Only the ones * + * marked with ** besides them can be specified in the module file's * + * options statement. The "-" can be omitted when the option is in * + * the module file's options statement. * + * * + * -help : Type a help file instead of running the program. * + * * + *** -spec name : Uses TLC and the TLA+ specification name.tla to do * + * the translation. The files name.tla and name.cfg * + * are copied from the java/ directory to the current * + * directory; the file AST.tla that defines `fairness' * + * to equal the fairness option and `ast' to equal * + * the the AST data structure representing the * + * algorithm is written to the current directory; and * + * TLC is run on name.tla to produce the translation. * + * * + *** -myspec name : Like -spec, except it finds the files names.tla * + * and names.cfg in the current directory, instead * + * of writing them there. * + * * + * -spec2 name * + * -myspec2 name : Like -spec and -myspec, except they use TLC2 * + * instead of TLC (aka TLC1). * + * * + * -writeAST : Writes the AST file as in the -spec option, but does * + * not perform the translation. * + * * + * -debug : Run in debugging mode, whatever that means. For the * + * parser, it just means that the toString() methods * + * output the line and column number along with the * + * node. * + * * + * -unixEOL : Forces the use of Unix end-of-line convention, * + * regardless of the system's default. Without this, * + * when run on Windows, the files it writes seem to have * + * a "^M" at the end of every line when viewed with * + * Emacs. * + * * + *** -wf : Conjoin to formula Spec weak fairness of each process's * + * next-state action * + * * + *** -sf : Conjoin to formula Spec strong fairness of each process's * + * next-state action * + * * + *** -wfNext : Conjoin to formula Spec weak fairness of the entire * + * next-state action * + * * + *** -nof : Conjoin no fairness formula to Spec. This is the default, * + * except when the -termination option is chosen. * + * * + *** -termination : Add to the .cfg file the command * + * PROPERTY Termination * + * * + * -nocfg : Suppress writing of the .cfg file. * + * * + * * + *** -noDoneDisjunct : Suppress the disjunct of the next-state * + * relation that describes stuttering steps taken * + * when the algorithm has halted. * + * * + *** -label : Tells the translator to add missing labels. This is * + * the default only for a uniprocess algorithm in which * + * the user has typed no labels. * + * * + * -reportLabels : True if the translator should print the names * + * and locations of all labels it adds. Like * + * -label, it tells the translator to add missing * + * labels. * + * * + *** -labelRoot name : If the translator adds missing labels, it * + * names them name1, name2, etc. Default value * + * is "Lbl_". * + * * + * THE FOLLOWING OPTIONS ADDED IN VERSION 1.4 * + * * + *** -lineWidth : The translation tries to perform the translation so * + * lines have this maximum width. (It will often * + * fail.) Default is 78, minimum value is 60. * + * * + *** -version : The version of PlusCal for which this algorithm is * + * written. If the language is ever changed to make * + * algorithms written for earlier versions no longer * + * legal, then the translator should do the appropriate * + * thing when the earlier version number is specified. * + *</pre> + ********************************************************************* */ + boolean inFile = PcalParams.optionsInFile; + boolean notInFile = !inFile; + // Just convenient abbreviations + boolean firstFairness = inFile; + // Used to allow a fairness property specified by a command-line + // option to be overridden by one in the pcal-file's options statement. + // It is set false when the first fairness property is set from + // the options statement. + boolean explicitNof = false; + // Set true when the "nof" fairness option is set by an explicit + // user request, rather than by default. It was added to fix + // a bug in -termination introduced in version 1.4 by having + // the options statement in the file. I think option processing + // can be simplified to eliminate this, but it's easier to add + // this kludge. + int nextArg = 0; + /****************************************************************** + * The number of the argument being processed. * + ******************************************************************/ + int maxArg = args.length - 1; + /****************************************************************** + * The number of option arguments. (For processing command-line * + * arguments, the last element of args is the input-file name.) * + ******************************************************************/ + if (maxArg < 0) + { + return CommandLineError("No arguments specified"); + } + + if (notInFile && (args[maxArg].length() != 0) && (args[maxArg].charAt(0) == '-')) + /****************************************************************** + * If the last argument begins with "-", then no file has been * + * specified. This should mean that the user has typed "-help", * + * but it could be a mistake. But let's just assume she typed * + * "-help", since she either wants or needs help. * + ******************************************************************/ + { + if (OutputHelpMessage()) + { + return STATUS_EXIT_WITHOUT_ERROR; + + } else + { + return STATUS_EXIT_WITH_ERRORS; + } + } + + while (nextArg < maxArg) + /******************************************************************* + * Process all the arguments, except for the input-file name. * + *******************************************************************/ + { + String option = args[nextArg]; + if (notInFile && option.equals("-help")) + { + if (OutputHelpMessage()) + { + return STATUS_EXIT_WITHOUT_ERROR; + } else + { + return STATUS_EXIT_WITH_ERRORS; + } + } else if (notInFile && option.equals("-writeAST")) + { + PcalParams.WriteASTFlag = true; + if (CheckForConflictingSpecOptions()) + { + return STATUS_EXIT_WITH_ERRORS; + } + } else if (option.equals("-spec") || + (inFile && option.equals("spec"))) + { + PcalParams.SpecOption = true; + if (CheckForConflictingSpecOptions()) + { + return STATUS_EXIT_WITH_ERRORS; + } + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Specification name must follow `-spec' option"); + } + PcalParams.SpecFile = args[nextArg]; + } else if (option.equals("-myspec") || + (inFile && option.equals("myspec"))) + { + PcalParams.MyspecOption = true; + if (CheckForConflictingSpecOptions()) + { + return STATUS_EXIT_WITH_ERRORS; + } + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Specification name must follow `-myspec' option"); + } + PcalParams.SpecFile = args[nextArg]; + } else if (notInFile && option.equals("-spec2")) + { + PcalParams.Spec2Option = true; + if (CheckForConflictingSpecOptions()) + { + return STATUS_EXIT_WITH_ERRORS; + } + ; + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Specification name must follow `-spec' option"); + } + PcalParams.SpecFile = args[nextArg]; + } else if (notInFile && option.equals("-myspec2")) + { + PcalParams.Myspec2Option = true; + if (CheckForConflictingSpecOptions()) + { + return STATUS_EXIT_WITH_ERRORS; + } + ; + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Specification name must follow `-myspec' option"); + } + PcalParams.SpecFile = args[nextArg]; + } else if (notInFile && option.equals("-debug")) + { + PcalParams.Debug = true; + } else if (notInFile && option.equals("-unixEOL")) + { + System.setProperty("line.separator", "\n"); + } else if (option.equals("-termination") || (inFile && option.equals("termination"))) + { + PcalParams.CheckTermination = true; + } else if (option.equals("-nocfg")) + { + PcalParams.Nocfg = true; + } else if (option.equals("-noDoneDisjunct") || (inFile && option.equals("noDoneDisjunct"))) + { + PcalParams.NoDoneDisjunct = true; + } else if (option.equals("-wf") || (inFile && option.equals("wf"))) + { + if (firstFairness) + { + PcalParams.FairnessOption = ""; + firstFairness = false; + } + if (!PcalParams.FairnessOption.equals("")) + { + return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); + } + PcalParams.FairnessOption = "wf"; + } else if (option.equals("-sf") || (inFile && option.equals("sf"))) + { + if (firstFairness) + { + PcalParams.FairnessOption = ""; + firstFairness = false; + } + if (!PcalParams.FairnessOption.equals("")) + { + return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); + } + PcalParams.FairnessOption = "sf"; + } else if (option.equals("-wfNext") || (inFile && option.equals("wfNext"))) + { + if (firstFairness) + { + PcalParams.FairnessOption = ""; + firstFairness = false; + } + if (!PcalParams.FairnessOption.equals("")) + { + return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); + } + PcalParams.FairnessOption = "wfNext"; + } else if (option.equals("-nof") || (inFile && option.equals("nof"))) + { + if (firstFairness) + { + PcalParams.FairnessOption = ""; + firstFairness = false; + } + if (!PcalParams.FairnessOption.equals("")) + { + return CommandLineError("Can only have one of -wf, -sf, -wfNext, " + "and -nof options"); + } + PcalParams.FairnessOption = "nof"; + explicitNof = true; + } else if (option.equals("-label") || (inFile && option.equals("label"))) + { + PcalParams.LabelFlag = true; + } else if (notInFile && option.equals("-reportLabels")) + { + PcalParams.ReportLabelsFlag = true; + PcalParams.LabelFlag = true; + } else if (option.equals("-labelRoot") || (inFile && option.equals("labelRoot"))) + { + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Label root must follow `-labelRoot' option"); + } + PcalParams.LabelRoot = args[nextArg]; + } + // else if (option.equals("-readOnly") || (pcal && option.equals("readOnly"))) { + // PcalParams.readOnly = true; + // } + // else if (option.equals("-writable") || (pcal && option.equals("writable"))) { + // PcalParams.readOnly = false; + // } + else if (option.equals("-version") || (inFile && option.equals("version"))) + { + nextArg = nextArg + 1; + if (nextArg == maxArg) + { + return CommandLineError("Version number must follow `-version' option"); + } + if (!PcalParams.ProcessVersion(args[nextArg])) + { + return CommandLineError("Bad version number"); + } + + } else if (option.equals("-lineWidth")) + { + nextArg = nextArg + 1; + try + { + if (nextArg == maxArg) + { + throw new NumberFormatException(); + } + int a = new Integer(args[nextArg]).intValue(); + if (a < 60) + { + throw new NumberFormatException(); + } + PcalTLAGen.wrapColumn = a; + PcalTLAGen.ssWrapColumn = a - 33; + } catch (Exception e) + { + return CommandLineError("Integer value at least 60 must follow `-lineWidth' option"); + } + + } else + { + if (notInFile) + { + return CommandLineError("Unknown command-line option: " + option); + } else + { + return CommandLineError("Unknown or illegal option in options statement: " + option); + } + } + ; + nextArg = nextArg + 1; + } // END while (nextArg < maxArg) + + if (nextArg > maxArg) + /****************************************************************** + * The last option took an argument that was the last * + * command-line argument. * + ******************************************************************/ + { + return CommandLineError("No input file specified"); + } + + // SZ 02.16.2009: since this is a modification of the parameters, moved + // to the parameter handling method + if (PcalParams.FairnessOption.equals("-nof")) + { + PcalParams.FairnessOption = ""; + } + if (PcalParams.CheckTermination && PcalParams.FairnessOption.equals("") && !explicitNof) + { + PcalParams.FairnessOption = "wf"; + + } + + /******************************************************************** + * If we are processing the command-line arguments, we need to get * + * the input-file name. Otherwise, we're done. * + *******************************************************************/ + if (inFile) + { + return STATUS_OK; + } + + /******************************************************************** + * Set PcalParams.TLAInputFile to the last argument, removing a * + * "tla" extension if it has one. * + ********************************************************************/ + /* + int dotIndex = args[maxArg].lastIndexOf(".") ; + if (dotIndex == -1) + { + PcalParams.TLAInputFile = args[maxArg]; + } + else if (args[maxArg].substring(dotIndex).equals(".tla")) + { + PcalParams.TLAInputFile = args[maxArg].substring(0, dotIndex); + } + else + { + return CommandLineError("Input file has extension other than tla"); + } + */ + + // SZ 02.16.2009: check for correct file extension (ignoring case) + // and file existence. also handles dots in the pathname + File file = new File(args[maxArg]); + boolean hasExtension = false; + if (file.getName().lastIndexOf(".") == -1) + { + // no extension + PcalParams.TLAInputFile = file.getPath(); + } else + { + // extension present + if (file.getName().toLowerCase().endsWith(".tla")) + { + hasExtension = true; + } + // Aborted version 1.31 code + // else if (file.getName().toLowerCase().endsWith(".pcal")){ + // hasExtension = true; + // PcalParams.fromPcalFile = true; + // } + else + { + return CommandLineError("Input file has extension other than " /* pcal or */+ "tla"); + } + } + if (hasExtension) + { + // cut the extension + PcalParams.TLAInputFile = file.getPath().substring(0, file.getPath().lastIndexOf(".")); + if (!file.exists()) + { + return CommandLineError("Input file " + file.getPath() + " does not exist."); + } + } else + { + // aborted version 1.31 code + // file = new File(PcalParams.TLAInputFile + ".pcal"); + // if (file.exists()) + // { + // PcalParams.fromPcalFile = true; + // } else + // { + file = new File(PcalParams.TLAInputFile + ".tla"); + if (!file.exists()) + { + return CommandLineError("Input file " + PcalParams.TLAInputFile + ".pcal and " + file.getPath() + + ".tla not found"); + } + // } + } + // file = new File(PcalParams.TLAInputFile + (PcalParams.fromPcalFile?".pcal":".tla")); + // if (!file.exists()) + // { + // return CommandLineError("Input file " + file.getPath() + " not found"); + // } + + return STATUS_OK; + } + + /** + * Prints out the help message + * @return status if it has been successfully printed + */ + private static boolean OutputHelpMessage() + { + Vector helpVec = null; + try + { + helpVec = PcalResourceFileReader.ResourceFileToStringVector("help.txt"); + } catch (PcalResourceFileReaderException e) + { + PcalDebug.reportError(e); + return false; + } + int i = 0; + while (i < helpVec.size()) + { + ToolIO.out.println((String) helpVec.elementAt(i)); + i = i + 1; + } + + return true; + } + + /** + * Returns if the options are conflicting + * @return true if the provided options are conflicting, false otherwise + */ + private static boolean CheckForConflictingSpecOptions() + { + if ((PcalParams.SpecOption ? 1 : 0) + (PcalParams.MyspecOption ? 1 : 0) + (PcalParams.Spec2Option ? 1 : 0) + + (PcalParams.Myspec2Option ? 1 : 0) + (PcalParams.WriteASTFlag ? 1 : 0) > 1) + { + CommandLineError("\nCan have at most one of the options " + "-spec, -myspec, -spec2, -myspec2, writeAST"); + return true; + } + ; + return false; + } + + private static int CommandLineError(String msg) + /********************************************************************* + * Announce a command line error with the string indicating the * + * explanation and halt. * + *********************************************************************/ + { + PcalDebug.reportError("Command-line error: " + msg + "."); +// ToolIO.out.println("Command-line error: " + msg + "."); +// ToolIO.out.println("Use -help option for more information."); + return STATUS_EXIT_WITH_ERRORS; + } + + private static int findTokenPair(Vector vec, int lineNum, String tok1, String tok2) + /********************************************************************* + * Returns the number of the first line at or after lineNum in the * + * vector of strings vec containing tok1 followed by 1 or more * + * spaces followed by tok2. Returns -1 if such a line is not found. * + *********************************************************************/ + { + int i = lineNum; + while (i < vec.size()) + { + String line = (String) vec.elementAt(i); + int col = line.indexOf(tok1); + int nextcol = col + tok1.length(); + if (col != -1) + { + while ((nextcol < line.length()) && (line.charAt(nextcol) == ' ')) + { + nextcol = nextcol + 1; + } + ; + if ((nextcol < line.length()) && (nextcol == line.indexOf(tok2))) + { + return i; + } + } + ; + i = i + 1; + } + ; + return -1; + } + + /************************** RemoveTabs *********************************/ + + public static Vector removeTabs(Vector vec) + { + /******************************************************************** + * Returns a string vector obtained from the string vector vec by * + * replacing any evil tabs with the appropriate number of spaces, * + * where "appropriate" means adding from 1 to 8 spaces in order to * + * make the next character fall on a column with Java column * + * number (counting from 0) congruent to 0 mod 8. This is what * + * Emacs does when told to remove tabs, which makes it good enough * + * for me. * + ********************************************************************/ + Vector newVec = new Vector(); + int i = 0; + while (i < vec.size()) + { + String oldline = (String) vec.elementAt(i); + String newline = ""; + int next = 0; + while (next < oldline.length()) + { + if (oldline.charAt(next) == '\t') + { + int toAdd = 8 - (newline.length() % 8); + while (toAdd > 0) + { + newline = newline + " "; + toAdd = toAdd - 1; + } + } else + { + newline = newline + oldline.substring(next, next + 1); + } + ; + next = next + 1; + } + newVec.addElement(newline); + i = i + 1; + } + ; + return newVec; + } + + /********************* STRING UTILITY FUNCTIONS ***********************/ + + private static int NextSpace(String s, int cur) + /******************************************************************** + * Returns the first space in s at or after col. If there is none, * + * return the index of the last character in s. Spaces in strings * + * are not treated as spaces. Assumes s[cur] is not in a string. * + ********************************************************************/ + { + int i = cur; + boolean inString = false; + while ((i < s.length()) && ((s.charAt(i) != ' ') || inString)) + { + if ((s.charAt(i) == '"') && ((i == 0) || (s.charAt(i - 1) != '\\'))) + inString = !inString; + i = i + 1; + } + if (i == s.length()) + return i - 1; + else + return i; + } + + private static String WrapString(String inString, int col) + /********************************************************************* + * Returns the string inString with lines wrapped approximately at * + * col, taking care not to wrap in a string. * + *********************************************************************/ + { + int i = 0; + int ccol = 1; + StringBuffer sb = new StringBuffer(); + while (i < inString.length()) + { + if (inString.charAt(i) == ' ') // An initial space or a space + { + sb.append(' '); // that follows a space. It + i = i + 1; // can always be appended to a line. + ccol = ccol + 1; + } else + // Find next word, which starts at i. + { + int j = NextSpace(inString, i); + if (ccol + (j - i + 1) > col) + { + sb.append('\n'); + ccol = 1; + } + while (i <= j) // If this overflows col, then the word + { + sb.append(inString.charAt(i)); + // is longer than col. + i = i + 1; + ccol = ccol + 1; + } + } + } + return sb.toString(); + } + +} diff --git a/tlatools/src/tla2sany/modanalyzer/ModuleRelationships.java b/tlatools/src/tla2sany/modanalyzer/ModuleRelationships.java index 5dea4c75f4de4d7c2739d3a40f17111496bdd2fb..bc4f81e70caf47f2420b6bf8b2cc9e6bcd1f8381 100644 --- a/tlatools/src/tla2sany/modanalyzer/ModuleRelationships.java +++ b/tlatools/src/tla2sany/modanalyzer/ModuleRelationships.java @@ -33,7 +33,7 @@ class ModuleRelationships { } // end getContext() - Enumeration getKeys() { return modRelHashtable.keys(); } + Enumeration<String> getKeys() { return modRelHashtable.keys(); } // Add the entries from otherMR into THIS; they are assumed not to overlap. diff --git a/tlatools/src/tla2sany/modanalyzer/SpecObj.java b/tlatools/src/tla2sany/modanalyzer/SpecObj.java index fb9a0da5c5c924164f86e370d042efdd13a0b5cb..a9aaa0ab1a9df9ce0e32dc596646c06f01cfa1b3 100644 --- a/tlatools/src/tla2sany/modanalyzer/SpecObj.java +++ b/tlatools/src/tla2sany/modanalyzer/SpecObj.java @@ -5,6 +5,7 @@ package tla2sany.modanalyzer; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; +import java.util.Set; import tla2sany.semantic.AbortException; import tla2sany.semantic.Errors; @@ -38,7 +39,7 @@ public class SpecObj // must be done, i.e. if MODULE A references B, A is lower // on the stack. The same module name can occur multiple times. - public Hashtable parseUnitContext = new Hashtable(); + public Hashtable<String, ParseUnit> parseUnitContext = new Hashtable<String, ParseUnit>(); // Holds all known ParseUnit objects, i.e external, top-level // modules that have so far been encountered, keyed by module // (parseUnit) string name @@ -218,7 +219,7 @@ public class SpecObj // Returns enumeration of the modules so far included in the spec. // As whoever wrote this documentation didn't think was worth mentioning, // it appears that the "modules" being returned are ModulePointer objects. - public final Enumeration getModules() + public final Enumeration<String> getModules() { return moduleRelationshipsSpec.getKeys(); } @@ -232,12 +233,12 @@ public class SpecObj // Prints the context of one ParseUnit public final void printParseUnitContext() { - Enumeration enumerate = parseUnitContext.keys(); + Enumeration<String> enumerate = parseUnitContext.keys(); ToolIO.out.println("parseUnitContext ="); while (enumerate.hasMoreElements()) { - String key = (String) enumerate.nextElement(); + String key = enumerate.nextElement(); ToolIO.out.println(" " + key + "-->" + ((ParseUnit) parseUnitContext.get(key)).getName()); } } @@ -348,7 +349,7 @@ public class SpecObj // parseUnitName. If there is, cause an abort; otherwise return. private void nonCircularityTest(ParseUnit parseUnit, Errors errors) throws AbortException { - HashSet alreadyVisited = new HashSet(); + Set<ParseUnit> alreadyVisited = new HashSet<ParseUnit>(); Vector circularPath = new Vector(); circularPath.addElement(parseUnit); @@ -361,7 +362,7 @@ public class SpecObj // errors, and the method aborts. The set alreadyVisited is // used to prevent searching paths through the same candidate // multiple times. - private void nonCircularityBody(ParseUnit parseUnit, ParseUnit candidate, Errors errors, HashSet alreadyVisited, + private void nonCircularityBody(ParseUnit parseUnit, ParseUnit candidate, Errors errors, Set<ParseUnit> alreadyVisited, Vector circularPath) throws AbortException { // If we have already checked for circularities through this diff --git a/tlatools/src/tla2sany/semantic/SemanticNode.java b/tlatools/src/tla2sany/semantic/SemanticNode.java index 78b1334ca4511e79c085247bb3d8fecc1813c630..e7cd4d39c1c4e45768ad24683dda0c782fc47e85 100644 --- a/tlatools/src/tla2sany/semantic/SemanticNode.java +++ b/tlatools/src/tla2sany/semantic/SemanticNode.java @@ -4,19 +4,18 @@ // last modified on Fri 16 Mar 2007 at 17:22:54 PST by lamport package tla2sany.semantic; -import java.util.Comparator; import java.util.Hashtable; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + import tla2sany.explorer.ExploreNode; import tla2sany.parser.SyntaxTreeNode; import tla2sany.st.Location; import tla2sany.st.TreeNode; -import util.ToolIO; - import tla2sany.xml.XMLExportable; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; +import util.ToolIO; /** * SemanticNode is the (abstract) superclass of all nodes in the @@ -233,10 +232,23 @@ public abstract class SemanticNode * to the following two toString methods. * ***************************************************************************/ public final void toString(StringBuffer sb, String padding) { - sb.append(this.getLocation()); + TreeNode treeNode = getTreeNode(); + if (treeNode instanceof SyntaxTreeNode + && System.getProperty(SemanticNode.class.getName() + ".showPlainFormulae") != null) { + SyntaxTreeNode stn = (SyntaxTreeNode) treeNode; + sb.append(stn.getHumanReadableImage()); + } else { + sb.append(this.getLocation()); + } } public String toString() { + TreeNode treeNode = getTreeNode(); + if (treeNode instanceof SyntaxTreeNode + && System.getProperty(SemanticNode.class.getName() + ".showPlainFormulae") != null) { + SyntaxTreeNode stn = (SyntaxTreeNode) treeNode; + return stn.getHumanReadableImage(); + } return this.getLocation().toString(); } diff --git a/tlatools/src/tla2tex/FindAlignments.java b/tlatools/src/tla2tex/FindAlignments.java index c540837b07ae55bc4f563cd3bf1ec5d85a78c2fe..19ceb885f8fad9d0253dfc7a1156891cc8abedeb 100644 --- a/tlatools/src/tla2tex/FindAlignments.java +++ b/tlatools/src/tla2tex/FindAlignments.java @@ -1,1100 +1,1104 @@ -// Copyright (c) 2003 Compaq Corporation. All rights reserved. -// -// This is the version of FindAlignments modified to be consistent -// with the versions of TokenizeSpec, BuiltInSymbols, etc. that have -// been modified to handle PlusCal, but with no special PlusCal alignments -// added. It is also being modified to add a special rule for -// aligning labels -- or more precisely, aligning tokens that -// follow labels with other tokens. - -/*************************************************************************** -* CLASS FindAlignments * -* * -* Contains the one public method * -* * -* public static void FindAlignments(Token[][] spec) * -* * -* that sets the aboveAlign, belowAlign, and isAlignmentPoint fields for * -* all tokens in the tokenized spec spec--except for NULL and MULTI * -* comment tokens, for which the aboveAlign field is set by * -* CommentToken.processComments. (This method should be called after * -* CommentToken.processComments.) * -* * -* There are six kinds of alignments, illustrated by the following * -* example: * -* * -* Alignment Type Tokens so aligned * -* -------------- ----------------- * -* FirstNonLeftComment foo, x, + , /\ , y/comment r, u/comment k * -* ^^^^^^^^^^^ * -* I think this should be +/comment r * -* LeftComment comments j * -* CommentInner comments p * -* InfixInner ==, => , > * -* AfterInfixInner m, c, * -* InfixArg a, z/z, y/z * -* * -* foo == LET x == /\ a => m * n > c * -* | | | | | | | | | | * -* (* j *) | | | | /\ a => m / q > c (* p *) * -* | | | | | | * -* (* j *) | | x == z | * -* | | | | * -* | | + z \* p * -* | | | | | * -* | | | | (* p *) * -* | | | | | * -* | | + y (* p *) * -* | | | * -* | | \* r * -* | | * -* foo == * -* | * -* (* k *) * -* | * -* u + v * -* * -* For InfixInner and CommentInner alignment, a token's belowAlign * -* field points to the token directly below it with which it is * -* aligned. * -* * -* For all alignment types, if a token t1 is aligned with a token * -* t2 above it, then t1.aboveAlign points to: * -* * -* IF t2 has no aboveAlign pointer THEN t2 * -* ELSE t2.abovealign. * -* * -* Thus, in the example above, the aboveAlign pointer for all the p * -* comments point to the first p comment. * -* * -* To define the types of alignment, we use the following definitions: * -* * -* - A LEFT-COMMENT is a comment that is the first token on its line but * -* not the last token on its line. (The comments (* j *) in the * -* example are left-comments.) For all the types of alignment except * -* CommentInner, left-comments are treated as if they didn't exist. * -* * -* - A RIGHT-COMMENT is a comment that is the last token on its line. * -* * -* Any comment other than a left-comment or a right-comment is treated * -* like any other non-built-in token. We also define: * -* * -* - The COVERING TOKEN of a token t is the right-most token ct that * -* covers t on the last line (one with biggest line number) containing * -* a token that covers t, where a token t1 COVERS a token t2 if t1 * -* lies on an earlier line than t2 and t1.column \leq t2.column. * -* However, if there is a DASH between ct and t, or * -* on the same line as ct, then t has no covering token. * -* (This definition has two versions, depending on whether or not * -* left-comments are ignored.) * -* * -* - The BLOCKING TOKEN of a token t is the left-most token s with * -* s.column \geq t.column on the last line before t's line containing * -* such a non-left-comment token s. * -* * -* Here are the descriptions of the different types of alignment. * -* * -* * -* CommentInner: * -* Token t at position pos is CommentInner above-aligned with its * -* blocking token bt at position bpos iff: * -* /\ t is a right-comment * -* /\ bt is a right-comment * -* /\ t.column = bt.column * -* /\ \/ bt is not above-aligned with anything * -* \/ bt is CommentInner aligned with a token above it * -* * -* FirstNonLeftComment * -* If t is the first token on a line that is not a left-comment, and is * -* not a right-comment that is CommentInner aligned with a token above * -* it, then t is FirstNonLeftComment aligned with its covering token. * -* * -* LeftComment: * -* If t is a left-comment token, then it is LeftComment aligned * -* with its covering token (where left-comments are visible). * -* * -* InfixInner: * -* Token t at position pos is InfixInner aligned with its covering * -* token ct at position cpos iff t is not FirstNonLeftComment aligned, * -* and both t and ct are built-in symbols with the same nonzero alignment * -* class. (The name InfixInner is misleading because non-infix symbols * -* like ":" get aligned by this mechanism.) * -* * -* AfterInfixInner: * -* Token t is After InfixInner aligned with the token above it iff * -* * -* LET lt == the token to the left of t * -* alt == the token at position lt.aboveAlign * -* at == the token to the right of alt * -* IN /\ t is not the first token on the line * -* /\ lt is InnerAligned with token alt above it * -* /\ alt is not the last token on its line. * -* /\ at is the covering token of t * -* /\ t.column = at.column * -* /\ at is not a comment * -* (The name AfterInfixInner means "after InfixInner alignment". * -* Remember that some non-infix symbols get InfixInner aligned.) * -* * -* InfixArg: * -* Token t is InfixArg aligned with its covering token ct iff: * -* LET lt == the token to the left of t * -* alt == the token at position lt.aboveAlign * -* IN /\ t is not the first token on the line * -* /\ lt is the first non-left-comment token * -* /\ lt is an infix operator * -* /\ lt is not InfixInner aligned with any token above it. * -* /\ t.column = ct.column * -* /\ alt is the token to the left of ct * -* /\ lt is either aligned with or lies entirely to the * -* right of alt. * -* * -* Note: The large number of conditions are an attempt to rule * -* out spurious alignments. * -* ---------------------------------------------------------------------- * -* * -* PlusCal Alignment * -* ----------------- * -* * -* The following rule is added for PlusCal. * -* * -* Define a token t to be a FIRST NON-LABEL if either * -* (a) it is the first token on the line, or * -* (b) it is the 2nd token on its line and the first token on * -* the line is a label. * -* * -* AfterLabel: * -* A token t is AfterLabel aligned with a token ta iff: * -* /\ t is a first non-label that is the 2nd token on its line * -* /\ t and ta are left-aligned * -* /\ there is no line between t and ta containing any token * -* to the left of t other than a label that is the line's * -* first token. * -* /\ \/ ta is a first non-label * -* \/ /\ ta is above t * -* /\ \/ ta is the first token on the line * -* \/ the token to the left of ta is a label or * -* a PlusCal symbol * -* * -* Here's an example of AfterLabel alignment. Token b and all the x * -* tokens are AfterLabel aligned with token A. Tokens A and B and all the * -* other x tokens are also AfterLabel aligned with the first and last x * -* tokens. * -* * -* { B * -* l: x * -* m: z * -* x * -* l: A * -* z * -* x * -* l: x * -* } z * -* * -* Note that B is the only token whose alignment is due to the second * -* disjunction of the last conjunction. That disjunct is designed to try * -* to rule out bogus alignments such as the alignment of the y's with the * -* 0 in * -* * -* if (x = 0) { * -* lab: y := 1; * -* lab: y := 2 } * -* * -* while aligning the the y's in * -* * -* if (x = 0) { y := 0; * -* lab: y := 1; * -* lab: y := 2 } * -* * -* and in * -* * -* if (x = 0) { y := 0; * -* lab: y := 1; * -* lab: y := 2 } * -* * -* ---------------------------------------------------------------------- * -* * -* Note: A possible addition is a special case to recognize alignments * -* after a comma, as in * -* * -* <<abcdef, ghi + 7, jklmn>> * -* | | * -* <<a, bc, def>> * -* * -* However, that should be done only if there's a clear intention to align * -* them--as evidenced here by the extra spaces between the "a,", the "cb," * -* and the "def". Otherwise, there would be too many chance alignments. * -***************************************************************************/ -package tla2tex; - -public class FindAlignments -{ public static void FindAlignments(Token[][] spec) - { setSubscriptField(spec) ; - /******************************************************************* - * Set the subscript fields of the tokens. * - *******************************************************************/ - int line = 0 ; - - /********************************************************************* - * Skip all lines beginning with a prolog token. This means that if * - * the module begins on the same line as a prolog token, then the * - * first line of the spec is ignored for alignment. Tant pis! * - *********************************************************************/ - boolean inProlog = true ; - while (inProlog && (line < spec.length)) - { if ( (spec[line].length > 0) - && (spec[line][0].type != Token.PROLOG)) - { inProlog = false;} - else - { line = line + 1;} - }; - - /********************************************************************* - * Set aboveAlign and belowAlign "pointers". * - *********************************************************************/ - while (line < spec.length) - { int item = 0 ; - boolean prevInfixInner = false ; - /**************************************************************** - * True iff the previous item on the line was InfixInner * - * aligned. * - ****************************************************************/ - while (item < spec[line].length) - { - /*************************************************************** - * Set token, pos to the current token and its position. * - ***************************************************************/ - Position pos = new Position(line, item) ; - Token token = spec[line][item] ; - - if (! token.subscript) - {/************************************************************* - * Do not align subscript tokens. * - *************************************************************/ - if (isRightComment(spec, pos)) - { /*********************************************************** - * This is a RightComment. First check if it should be * - * CommentInner aligned. If not, check if it should be * - * FirstNonLeftComment aligned. * - ***********************************************************/ - - /*********************************************************** - * Set btoken, bpos to the blocking token of `token' and * - * its position. * - ***********************************************************/ - Position bpos = blockingPosition(spec, pos); - Token btoken = null ; - if (bpos.line != -1) {btoken = bpos.toToken(spec);}; - - /*********************************************************** - * Set ctok to be a CommentToken alias for token. * - ***********************************************************/ - CommentToken ctok = (CommentToken) token ; - if ( (ctok.subtype == CommentToken.MULTI) - || (ctok.subtype == CommentToken.NULL)) - { /******************************************************** - * This is the continuation of a multi-column comment. * - ********************************************************/ - Debug.Assert( (btoken != null) - && (btoken.type == Token.COMMENT), - "Bad blocking token for a MULTI or NULL token"); - - /******************************************************** - * Set token's aboveAlign to point to the BEGIN_MULTI * - * token beginning the commnet. * - ********************************************************/ - if ( ((CommentToken) btoken).subtype - == CommentToken.BEGIN_MULTI) - { token.aboveAlign = bpos ; } - else - { token.aboveAlign = btoken.aboveAlign ; }; - - /******************************************************** - * Make the blocking token's belowAlign pointer point * - * to token. * - ********************************************************/ - btoken.belowAlign = pos ; - } // END then OF if if ((ctok.subtype == .. )) - else - { /******************************************************** - * This is not the continuation of a multi-line * - * comment. Try to CommentInner align it. * - ********************************************************/ - - if ( (bpos.line != -1) - && isRightComment(spec, bpos) - && (btoken.column == token.column) - && ( ( (btoken.aboveAlign.line == -1) - && (bpos.item > 0)) - || ( (btoken.aboveAlign.line != -1) - && (btoken.aboveAlign. - toToken(spec).belowAlign.line != -1)) - /********************************************** - * Asserts that bpos is aboveAligned with a * - * token that is belowAligned with something, * - * which is possible only if bpos is * - * CommentInner aligned. * - **********************************************/ - ) - ) - { /**************************************************** - * CommentInner align. * - ****************************************************/ - btoken.belowAlign = pos ; - if (btoken.aboveAlign.line == -1) - { token.aboveAlign = bpos; } - else - { token.aboveAlign = btoken.aboveAlign ;}; - } // END then OF if ((bpos.line != -1)...) - else - { /******************************************************* - * FirstNonLeftComment align iff it is the first * - * non-left comment. * - *******************************************************/ - if ( (item == 0) - || ( (item == 1) - && (spec[line][0].type == Token.COMMENT))) - { pos.toToken(spec).aboveAlign = - coveringPosition(spec, pos, true) ; - } ; - }; // END else OF if ((bpos.line != -1)...) - - - } // END else OF if if ((ctok.subtype == .. )) - prevInfixInner = false ; - } // END then OF if (isRightComment(spec, pos)) - - { /*********************************************************** - * This is not a right-comment. Check for every kind of * - * alignment except CommentInner. * - ***********************************************************/ - if (prevInfixInner) - { /******************************************************** - * Check for AfterInfixInner alignment. * - * * - * In the following, the positions lPos, alPos, and * - * aPos are defined by * - * alPos --- aPos * - * | * - * | * - * lPos --- pos * - ********************************************************/ - Debug.Assert(pos.item > 0, - "prevInfixInner true for first item on line"); - Position lPos = new Position(pos.line, pos.item - 1); - Debug.Assert(lPos.toToken(spec).aboveAlign.line != -1, - "prevInfixInner true, but token to left not aligned"); - Position alPos = lPos.toToken(spec).aboveAlign ; - Token alToken = alPos.toToken(spec); - if (alPos.item + 1 < spec[alPos.line].length) - { Position aPos = - new Position(alPos.line, alPos.item + 1) ; - Token atoken = aPos.toToken(spec); - Position cPos = coveringPosition(spec, pos, true); - if ( (cPos.line == aPos.line) - && (cPos.item == aPos.item) - && (token.column == atoken.column) - && (atoken.type != Token.COMMENT) ) - { /************************************************* - * AfterInfixInner aligned. * - *************************************************/ - token.aboveAlign = aPos; - } ; - } // END if (alPos.item + 1 < spec[alPos.line].length) - prevInfixInner = false; - } // END then OF if (prevInfixInner) - else - { /******************************************************** - * Check for every kind of alignment except * - * AfterInfixInner and CommentInner. * - * * - * First, check if FirstNonLeftComment aligned. * - ********************************************************/ - if ( ((item == 0) && (token.type != Token.COMMENT)) - || ((item == 1) && (spec[line][0].type - == Token.COMMENT))) - { /***************************************************** - * FirstNonLeftCommentAligned. * - *****************************************************/ - token.aboveAlign = - coveringPosition(spec, pos, true); - } - else - { /***************************************************** - * Not FirstNonLeftCommentAligned. * - * * - * Next, check if LeftComment aligned. * - *****************************************************/ - if (isLeftComment(spec, pos)) - { token.aboveAlign = - coveringPosition(spec, pos, false); - } - else - { /************************************************* - * Next, check if InfixInner aligned. * - *************************************************/ - Position cpos = coveringPosition(spec, pos, true) ; - Token ctoken = null ; - /*********************************************** - * cpos and ctoken are the covering position * - * and covering token of the current token. * - ***********************************************/ - if (cpos.line != -1) - { ctoken = cpos.toToken(spec);}; - int alignClass = 0 ; // The alignment classes - int calignClass = 0; // of pos and cpos. - if (token.type == Token.BUILTIN) - { alignClass = - BuiltInSymbols.GetBuiltInSymbol( - token.string, true).alignmentType ; } ; - if ( (ctoken != null) - && (ctoken.type == Token.BUILTIN)) - { calignClass = - BuiltInSymbols.GetBuiltInSymbol( - ctoken.string, true).alignmentType ; } ; - if ( (ctoken != null) - && (token.column == ctoken.column) - && (alignClass != 0) - && (alignClass == calignClass)) - { /********************************************** - * InfixInner alignment. * - **********************************************/ - ctoken.belowAlign = pos ; - if (ctoken.aboveAlign.line == -1) - {token.aboveAlign = cpos ; } - else - { token.aboveAlign = ctoken.aboveAlign ; }; - prevInfixInner = true; - } // END then OF if ((token.column == ...)) - else - { /********************************************** - * Not InfixInner alignment. Check last * - * possibility, which is InfixArg alignment. * - **********************************************/ - if ( ( (item == 1) - || ( (item == 2) - && (spec[line][0].type == - Token.COMMENT))) - && (spec[line][item-1].type == Token.BUILTIN) - && (BuiltInSymbols.GetBuiltInSymbol( - spec[line][item-1].string, true).symbolType - == Symbol.INFIX) - /****************************************** - * Correction made 7 Nov 2001. * - * The conjunct above replaced the * - * following. * - * * - * && (BuiltInSymbols.GetBuiltInSymbol( * - * spec[line][item-1].string * - * ).alignmentType != 0) * - * * - * It seems reasonable that this * - * alignment should be performed only * - * when the token to the left is an infix * - * operator. * - ******************************************/ - && (ctoken != null) - && (token.column == ctoken.column) - && (spec[line][item-1].aboveAlign.line - != -1) - ) - { /******************************************* - * Possible InfixArg alignment. * - *******************************************/ - // This can happen and seems harmless. - // Debug.Assert(ctoken.belowAlign.line == -1, - // "Trying to InfixArg align with token " - // + "that is not aligned with token below it"); - - Token lTok = spec[line][item-1] ; - /**************************************** - * The token to the left of the current * - * token. * - ****************************************/ - Position alPos = lTok.aboveAlign ; - Token alTok = alPos.toToken(spec); - /***************************************** - * The token with which lTok is aligned * - * above. * - *****************************************/ - if ( (alPos.line == cpos.line) - && (alPos.item == cpos.item - 1) - ) - { /**************************************** - * InfixArg alignment with a token * - * having a token to its left, as in * - * * - * x == a * - * | * - * + b * - ****************************************/ - token.aboveAlign = cpos; - // following fixes the mis-alignment that occurs if - // the "x == " occupies less space than the "+". - // However, for safety, I'm only fixing it for the - // case of labels, which should be more common. - // However, I'm not doing it until I find - // some example of the bug for fear that it - // might break something else. - // - // ctoken.belowAlign = pos ; - } - else - { if ( (cpos.item == 0) - || ( (cpos.item == 1) - && (spec[cpos.line][0].type - == Token.COMMENT))) - { /***************************************** - * InfixArg alignment with a token * - * having no token to its left. In * - * this case, we can have a * - * situation like * - * * - * x == * - * a * - * + b * - * * - * where a is both above-aligned * - * with x and below-aligned with b. * - * I hope this doesn't cause * - * problems. * - *****************************************/ - token.aboveAlign = cpos; - ctoken.belowAlign = pos ; - } ; - } - }; - };// END else OF if ((token.column == ...)) - }; // END else of if (isLeftComment(spec, pos)) - }; // END else OF if ( ((item == 0) && ... )) - }; // END else OF if (prevInfixInner) - }; // END else OF (isRightComment(spec, pos)) - } // END then OF if (! token.subscript) - else - { prevInfixInner = false ; - /********************************************************** - * Need to reset prevInfixInner for the tokens following * - * a ^ or _. * - **********************************************************/ - } ; - item = item + 1 ; - } ; // END while (item < spec[line].length) - - line = line + 1; - /****************************************************************** - * Skip over epilog lines. * - ******************************************************************/ - if ( (line < spec.length) - && (spec[line].length > 0) - && (spec[line][0].type == Token.EPILOG)) - { line = spec.length ;} ; - }; // END while (line < spec.length) - - // Add the AfterLabel alignments. - FindLabelAlignments(spec) ; - - /********************************************************************* - * Set isAlignmentPoint flags. For simplicity, it is set true for * - * any token that is not the first on the line and is either the * - * source or target of a belowAlign pointer * - *********************************************************************/ - line = 0 ; - while (line < spec.length) - { int item = 0 ; - while (item < spec[line].length) - { Token token = spec[line][item] ; - if (token.aboveAlign.line != -1) - { if (item > 0) - {token.isAlignmentPoint = true ; - }; - if (token.aboveAlign.item != 0) - /******************************************************** - * Corrected apparent bug on 16 Jan 00: This if * - * condition was: token.aboveAlign.line != 0. * - ********************************************************/ - { token.aboveAlign.toToken(spec).isAlignmentPoint - = true; - }; - } ; - - if (token.belowAlign.line != -1) - { if (item > 0) - {token.isAlignmentPoint = true ; - }; - if (token.belowAlign.line != 0) - { token.belowAlign.toToken(spec).isAlignmentPoint - = true; - }; - } ; - - item = item + 1 ; - } ; // END while (item < spec[line].length) - - line = line + 1; - }; // END while (line < spec.length) - } ; - - /** - * Adds AfterLabel alignments to spec. More precisely, for something like - * - * if (x) { stmt - * label: stmt - * stmt - * label: stmt - * stmt - * stmt - * } - * - * it makes the belowAlign field of all stmts but the last one point to the - * stmt below it, and the aboveAlign field of all but the first stmt point to - * the one above it. In this case - * - * while x - * stmt - * label: stmt - * - * the first stmt's aboveAlign field should point to x by the FirstNonLeftComment - * alignment rule. For - * - * while x - * label: stmt - * label: stmt - * - * the method sets the first stmt's aboveAlign field - * - * @param spec - */ - public static void FindLabelAlignments(Token[][] spec) { - /* - * Do nothing if there is no PlusCal algorithm. - */ - if (!TokenizeSpec.hasPcal) { - return ; - } - - /* - * We get the first and last line that may begin with a label or a - * PlusCal statement. Since the algorithm begins with --algorithm - * or --fair, we skip the algorithm's first line. - */ - int pcalStartLine = TokenizeSpec.pcalStart.line + 1 ; - int pcalEndLine = TokenizeSpec.pcalEnd.line ; - - /* - * The algorithm works by repeatedly searching for the next line beginning - * with a label followed by a token tok. It then performs the following - * two steps - * - * 1. Searche downward for tokens AfterLabel aligned with tok, setting - * aboveAlign/belowAlign fields as it finds them. It stops when it reaches a - * line in which tok is not not aligned with the first token on the - * line. If there's a token etok on that line that is AfterLabel - * aligned with tok, so it must follow a label, then the aboveAlign - * field of etok is set to point to the AfterLabel aligned token above - * it, whose belowAlign field points to etok. - * - * 2. IF tok's aboveAlign field points to a token whose belowAlign field - * points to a field whose belowAlign field points to tok - * THEN do nothing. I think this is possible only if those fields were set - * by step 1 for a previous label. If I'm wrong and there's some weird - * situation that caused this alignment (which I think can only be - * the case if tok is a comment), then not stopping will probably - * do more harm than good. - * ELSE search upwards to set aboveAlign/belowAlign fields of tokens - * AfterLabel aligned with tok. As indicated above, if there - * are no tokens above tok that are AfterLabel aligned with it, - * then tok's aboveAlign field must be set to point to its - * covering token. - */ - int curLabelLine = pcalStartLine ; - - while ( (curLabelLine <= pcalEndLine) - && (curLabelLine < spec.length) ) { - /* - * set curLabelLine to the first line at or below its current - * position that begins with a label. - */ - if ( (spec[curLabelLine].length > 1) - && (spec[curLabelLine][0].type == Token.PCAL_LABEL) ) { - - Token tok = spec[curLabelLine][1] ; - int alignCol = tok.column ; - - // Perform step 1 for token tok - int curLine = curLabelLine + 1 ; - - // spec[alignLine][alignItem] is the token to which the - // next token to be aligned is aligned with. - int alignLine = curLabelLine ; - int alignItem = 1 ; - boolean notDone = true ; - while (notDone) { - int curItem = 0 ; - // If spec[alignLine][alignItem] is to be aligned with a token - // on this line, then the token is spec[curLine][nextItem] - // - boolean shouldSkip = false ; - if (spec[curLine].length != 0) { - if (spec[curLine][0].type == Token.PCAL_LABEL) { - // line begins with label - if (spec[curLine].length > 1) { - curItem = 1 ; - } - else { - // The label is the only token on the line. Stop - // iff the label is to the left of the alignment - // column. - notDone = (spec[curLine][0].column >= alignCol) ; - shouldSkip = true ; - } - } - else { - // line doesn't begin with label - } - if (!shouldSkip) { - if (spec[curLine][curItem].column < alignCol) { - notDone = false ; - } - else if (spec[curLine][curItem].column == alignCol) { - spec[alignLine][alignItem].belowAlign = - new Position(curLine, curItem) ; - spec[curLine][curItem].aboveAlign = - new Position(alignLine, alignItem) ; - alignLine = curLine ; - alignItem = curItem ; - } - } - } - curLine++ ; - if ( (curLine > pcalEndLine) - || (curLine >= spec.length)) { - notDone = false ; - } - } - - // Perform step 2 for token tok - - if ( (tok.aboveAlign.line != -1) - && (tok.aboveAlign.toToken(spec).belowAlign.equals(new Position(curLabelLine, 1)))) { - // already aligned with above tokens so do nothing - } - else { - curLine = curLabelLine - 1 ; - alignLine = curLabelLine ; - alignItem = 1 ; - notDone = true ; - while (notDone) { - if ( (spec[curLine].length > 0 ) - && (spec[curLine][0].column <= alignCol) - && ( (spec[curLine][0].type != Token.PCAL_LABEL) - || ( (spec[curLine].length > 1) - && (spec[curLine][1].column <= alignCol) ) ) - // the conjunct above causes the line to be skipped - // if the only token to the left of tok on this - // line is an initial label followed by a token - // to the right of tok. - ) { - if (spec[curLine][0].column == alignCol) { - // spec[curLine][0] is to be aligned with - // spec[alignLine][alignItem] - spec[alignLine][alignItem].aboveAlign = - new Position(curLine, 0) ; - spec[curLine][0].belowAlign = - new Position(alignLine, alignItem) ; - alignLine = curLine ; - alignItem = 0 ; - } - else if (spec[alignLine][alignItem].aboveAlign.line == -1) { - // we need to align spec[alignLine][alignItem] with the - // right-most token on line curLine with column \leq alignCol - int item = 0 ; - while ( (item < spec[curLine].length) - && (spec[curLine][item].column <= alignCol)) { - item++; - } - // item is now either off the line or pointing to a token - // to the right of the alignment token ; - item-- ; - - // We set the aboveAlign pointer of - // to point to spec[curLine][item] if the latter token is either - // the first one on its line, or else the token to its left is - // either a label or a PlusCal token. - // If that's the case, we also set the belowAlign pointer of - // spec[curLine][item] if that token is in the same column - // as spec[alignLine][alignItem]. - // - Token altok = null ; - if (item > 0) { - altok = spec[curLine][item-1] ; - } - if ( (altok != null) - && ( (altok.type == Token.PCAL_LABEL) - || ( (altok.type == Token.BUILTIN) - && BuiltInSymbols.IsBuiltInSymbol(altok.string, true) - && ! BuiltInSymbols.IsBuiltInSymbol(altok.string, false) - ) - ) - ) { - spec[alignLine][alignItem].aboveAlign = - new Position(curLine, item) ; - - if (spec[curLine][item].column == alignCol) { - spec[curLine][item].belowAlign = - new Position(alignLine, alignItem) ; - } - } - // This ends step 2 - notDone = false ; - } - else { - notDone = false ; - } - } - curLine-- ; - if (curLine < pcalStartLine) { - notDone = false ; - } - } - } - } - curLabelLine++ ; - } - } - /************************************************************************* - * The following are functions used in FindAlignments. * - *************************************************************************/ - private static boolean isLeftComment(Token[][] spec, Position p) - /*********************************************************************** - * A left-comment is a comment token that is the first token on a line * - * and has another token to its right. This method returns true iff * - * the token at position p of spec is a left-comment. * - ***********************************************************************/ - { return (p.item == 0) - && (spec[p.line][p.item].type == Token.COMMENT) - && (spec[p.line].length > 1) ; - } ; - - private static boolean isRightComment(Token[][] spec, Position p) - /*********************************************************************** - * A right-comment is a comment token that is the last token on its * - * line. This method returns true iff the token at position p in spec * - * is a right-comment. * - ***********************************************************************/ - { return (p.item == spec[p.line].length - 1) - && (spec[p.line][p.item].type == Token.COMMENT) ; - } ; - - private static Position coveringPosition( - Token[][] spec, Position p, boolean ignore) - /*********************************************************************** - * A token t1 COVERS a token t2 if t1 lies on an earlier line than t2 * - * and t1.column \leq t2.column. This method searches upwards to find * - * the first line with a token that covers the token at position p, * - * and then returns the position of the right-most token on that line * - * that covers the token at p. When searching for the covering token, * - * left-comments are ignored iff the ignore parameter is true. * - ***********************************************************************/ - { /********************************************************************* - * Find covering line. * - *********************************************************************/ - int line = p.line - 1 ; - Token tok = p.toToken(spec) ; - boolean notDone = true ; - while ((line >= 0) && notDone) - { if (spec[line].length > 0) - { if (spec[line][0].type == Token.PROLOG) - { line = -1 ; - notDone = false; - } - else - { int item = 0 ; - if (ignore && isLeftComment(spec, new Position(line, 0))) - { item = 1 ; } - if (spec[line][item].column <= tok.column) - { notDone = false ;} ; - } - }; // END if (spec[line].length > 0) - if (notDone) {line = line - 1 ;}; - } // END while ((line >= 0) && notDone) - - /********************************************************************** - * If no covering line, return (-1, 0). * - **********************************************************************/ - if (line == -1) {return new Position(-1, 0);} ; - - /********************************************************************** - * Find covering item. * - **********************************************************************/ - int item = 0; - int nsItem = 0; - /******************************************************************** - * item is the current item. * - * nsItem is the last non-subscript item found that covers tok. * - ********************************************************************/ - boolean dashFound = false ; - if (spec[line][0].type == Token.DASHES) - { dashFound = true;} ; - while ( (! dashFound) - && (item + 1 < spec[line].length) - && (spec[line][item + 1].column <= tok.column)) - { if (spec[line][item+1].type == Token.DASHES) - {dashFound = true;} ; - item = item + 1; - if (!spec[line][item].subscript) - {nsItem = item;}; - }; - if (dashFound) {return new Position(-1, 0);} ; - - /********************************************************************** - * Return (line, nsItem). * - **********************************************************************/ - return new Position(line, nsItem); - } ; - - private static Position blockingPosition(Token[][] spec, Position p) - /*********************************************************************** - * Searches upwards from position p to find the first token at the * - * same column or to the right of the token at p that is not * - * a subscript token. * - ***********************************************************************/ - { int line = p.line - 1 ; - int item = 0 ; - Token tok = p.toToken(spec) ; - boolean notDone = true ; - while ((line >= 0) && notDone) - { if (spec[line].length > 0) - { if (spec[line][0].type == Token.PROLOG) - { line = -1 ; - notDone = false; - } - else - { item = 0 ; - if (isLeftComment(spec, new Position(line, 0))) - { item = 1 ; } ; - while (notDone && (item < spec[line].length)) - { if ( (spec[line][item].column >= tok.column) - && (! spec[line][item].subscript)) - { notDone = false ; } - else - { item = item+1; }; - } ; - } ; - }; // END if (spec[line].length > 0) - if (notDone) {line = line - 1 ;} ; - } // END while ((line >= 0) && notDone) - - /********************************************************************** - * If no token found, return (-1, 0). * - **********************************************************************/ - if (line == -1) {return new Position(-1, 0);} ; - - /********************************************************************** - * Return (line, item). * - **********************************************************************/ - return new Position(line, item); - } ; - - private static void setSubscriptField(Token[][] spec) - /*********************************************************************** - * Sets the subscript field of the tokens. (This field is true iff * - * the token is part of a sub- or superscript.) Upon encountering a * - * "^" or a token that is a built-in symbol with symbolType - * - * Symbol.SUBSCRIPTED token, the next token or all tokens in the * - * properly parenthesized expression that follows it are * - * subscripted--iff all those tokens lie on the same line. * - ***********************************************************************/ - { int line = 0 ; - while (line < spec.length) - { int item = 0 ; - int startSub = -1 ; - /**************************************************************** - * If a subscript has begun, then this is its first item; * - * otherwise, it equals -1. * - ****************************************************************/ - int nestingDepth = 0 ; - /**************************************************************** - * The current parenthesis nesting level inside a subscript, or * - * 0 if not in a subscript. * - ****************************************************************/ - - - while (item < spec[line].length) - { Token tok = spec[line][item] ; - /************************************************************* - * tok is the current token. * - *************************************************************/ - if (startSub == -1) - { /************************************************************ - * A subscript has not yet begun. * - ************************************************************/ - if ( (tok.type == Token.BUILTIN) - && ( (BuiltInSymbols.GetBuiltInSymbol( - tok.string, true).symbolType - == Symbol.SUBSCRIPTED) - || (tok.string.equals("^")))) - { - startSub = item + 1 ; - } // END then OF if ((tok.type == Token.BUILTIN) ...) - else - { /********************************************************* - * Do nothing. * - *********************************************************/ - }; // END else OF if ((tok.type = Token.BUILTIN) ...) - - } // END then OF if (startSub == -1) - else - { /************************************************************ - * A subscript has begun. * - * * - * Set symType to the symbol type of the token, or * - * NOT_A_SYMBOL if it isn't a symbol. * - ************************************************************/ - int symType = Symbol.NOT_A_SYMBOL ; - if (tok.type == Token.BUILTIN) - { symType = - BuiltInSymbols.GetBuiltInSymbol( - tok.string, true).symbolType ; - }; - - if ( ( (nestingDepth == 0) - && (symType != Symbol.LEFT_PAREN)) - || ( (nestingDepth == 1) - && (symType == Symbol.RIGHT_PAREN))) - { /********************************************************* - * This ends the subscript. Set the subscript field of * - * all tokens from startSub to item and reset startSub. * - *********************************************************/ - nestingDepth = 0 ; - while (startSub <= item) - { spec[line][startSub].subscript = true ; - startSub = startSub + 1; - } - startSub = -1 ; - } // END then OF if (((nestingDepth == 0)... )) - else - { /********************************************************* - * The subscript continues. * - *********************************************************/ - if (symType == Symbol.LEFT_PAREN) - { nestingDepth = nestingDepth + 1; } - else - { if (symType == Symbol.RIGHT_PAREN) - { nestingDepth = nestingDepth - 1; }; - }; - }; // END else OF if (((nestingDepth == 0)... )) - - }; // END else OF if (startSub == -1) - - item = item + 1 ; - } ; // END while (item < spec[line].length) - - - line = line + 1; - } // END while (line < spec.length) - - } ; - - -} - -/* last modified on Sun 5 August 2012 at 17:07:48 PST by lamport */ +// Copyright (c) 2003 Compaq Corporation. All rights reserved. +// +// This is the version of FindAlignments modified to be consistent +// with the versions of TokenizeSpec, BuiltInSymbols, etc. that have +// been modified to handle PlusCal, but with no special PlusCal alignments +// added. It is also being modified to add a special rule for +// aligning labels -- or more precisely, aligning tokens that +// follow labels with other tokens. + +/*************************************************************************** +* CLASS FindAlignments * +* * +* Contains the one public method * +* * +* public static void FindAlignments(Token[][] spec) * +* * +* that sets the aboveAlign, belowAlign, and isAlignmentPoint fields for * +* all tokens in the tokenized spec spec--except for NULL and MULTI * +* comment tokens, for which the aboveAlign field is set by * +* CommentToken.processComments. (This method should be called after * +* CommentToken.processComments.) * +* * +* There are six kinds of alignments, illustrated by the following * +* example: * +* * +* Alignment Type Tokens so aligned * +* -------------- ----------------- * +* FirstNonLeftComment foo, x, + , /\ , y/comment r, u/comment k * +* ^^^^^^^^^^^ * +* I think this should be +/comment r * +* LeftComment comments j * +* CommentInner comments p * +* InfixInner ==, => , > * +* AfterInfixInner m, c, * +* InfixArg a, z/z, y/z * +* * +* foo == LET x == /\ a => m * n > c * +* | | | | | | | | | | * +* (* j *) | | | | /\ a => m / q > c (* p *) * +* | | | | | | * +* (* j *) | | x == z | * +* | | | | * +* | | + z \* p * +* | | | | | * +* | | | | (* p *) * +* | | | | | * +* | | + y (* p *) * +* | | | * +* | | \* r * +* | | * +* foo == * +* | * +* (* k *) * +* | * +* u + v * +* * +* For InfixInner and CommentInner alignment, a token's belowAlign * +* field points to the token directly below it with which it is * +* aligned. * +* * +* For all alignment types, if a token t1 is aligned with a token * +* t2 above it, then t1.aboveAlign points to: * +* * +* IF t2 has no aboveAlign pointer THEN t2 * +* ELSE t2.abovealign. * +* * +* Thus, in the example above, the aboveAlign pointer for all the p * +* comments point to the first p comment. * +* * +* To define the types of alignment, we use the following definitions: * +* * +* - A LEFT-COMMENT is a comment that is the first token on its line but * +* not the last token on its line. (The comments (* j *) in the * +* example are left-comments.) For all the types of alignment except * +* CommentInner, left-comments are treated as if they didn't exist. * +* * +* - A RIGHT-COMMENT is a comment that is the last token on its line. * +* * +* Any comment other than a left-comment or a right-comment is treated * +* like any other non-built-in token. We also define: * +* * +* - The COVERING TOKEN of a token t is the right-most token ct that * +* covers t on the last line (one with biggest line number) containing * +* a token that covers t, where a token t1 COVERS a token t2 if t1 * +* lies on an earlier line than t2 and t1.column \leq t2.column. * +* However, if there is a DASH between ct and t, or * +* on the same line as ct, then t has no covering token. * +* (This definition has two versions, depending on whether or not * +* left-comments are ignored.) * +* * +* - The BLOCKING TOKEN of a token t is the left-most token s with * +* s.column \geq t.column on the last line before t's line containing * +* such a non-left-comment token s. * +* * +* Here are the descriptions of the different types of alignment. * +* * +* * +* CommentInner: * +* Token t at position pos is CommentInner above-aligned with its * +* blocking token bt at position bpos iff: * +* /\ t is a right-comment * +* /\ bt is a right-comment * +* /\ t.column = bt.column * +* /\ \/ bt is not above-aligned with anything * +* \/ bt is CommentInner aligned with a token above it * +* * +* FirstNonLeftComment * +* If t is the first token on a line that is not a left-comment, and is * +* not a right-comment that is CommentInner aligned with a token above * +* it, then t is FirstNonLeftComment aligned with its covering token. * +* * +* LeftComment: * +* If t is a left-comment token, then it is LeftComment aligned * +* with its covering token (where left-comments are visible). * +* * +* InfixInner: * +* Token t at position pos is InfixInner aligned with its covering * +* token ct at position cpos iff t is not FirstNonLeftComment aligned, * +* and both t and ct are built-in symbols with the same nonzero alignment * +* class. (The name InfixInner is misleading because non-infix symbols * +* like ":" get aligned by this mechanism.) * +* * +* AfterInfixInner: * +* Token t is After InfixInner aligned with the token above it iff * +* * +* LET lt == the token to the left of t * +* alt == the token at position lt.aboveAlign * +* at == the token to the right of alt * +* IN /\ t is not the first token on the line * +* /\ lt is InnerAligned with token alt above it * +* /\ alt is not the last token on its line. * +* /\ at is the covering token of t * +* /\ t.column = at.column * +* /\ at is not a comment * +* (The name AfterInfixInner means "after InfixInner alignment". * +* Remember that some non-infix symbols get InfixInner aligned.) * +* * +* InfixArg: * +* Token t is InfixArg aligned with its covering token ct iff: * +* LET lt == the token to the left of t * +* alt == the token at position lt.aboveAlign * +* IN /\ t is not the first token on the line * +* /\ lt is the first non-left-comment token * +* /\ lt is an infix operator * +* /\ lt is not InfixInner aligned with any token above it. * +* /\ t.column = ct.column * +* /\ alt is the token to the left of ct * +* /\ lt is either aligned with or lies entirely to the * +* right of alt. * +* * +* Note: The large number of conditions are an attempt to rule * +* out spurious alignments. * +* ---------------------------------------------------------------------- * +* * +* PlusCal Alignment * +* ----------------- * +* * +* The following rule is added for PlusCal. * +* * +* Define a token t to be a FIRST NON-LABEL if either * +* (a) it is the first token on the line, or * +* (b) it is the 2nd token on its line and the first token on * +* the line is a label. * +* * +* AfterLabel: * +* A token t is AfterLabel aligned with a token ta iff: * +* /\ t is a first non-label that is the 2nd token on its line * +* /\ t and ta are left-aligned * +* /\ there is no line between t and ta containing any token * +* to the left of t other than a label that is the line's * +* first token. * +* /\ \/ ta is a first non-label * +* \/ /\ ta is above t * +* /\ \/ ta is the first token on the line * +* \/ the token to the left of ta is a label or * +* a PlusCal symbol * +* * +* Here's an example of AfterLabel alignment. Token b and all the x * +* tokens are AfterLabel aligned with token A. Tokens A and B and all the * +* other x tokens are also AfterLabel aligned with the first and last x * +* tokens. * +* * +* { B * +* l: x * +* m: z * +* x * +* l: A * +* z * +* x * +* l: x * +* } z * +* * +* Note that B is the only token whose alignment is due to the second * +* disjunction of the last conjunction. That disjunct is designed to try * +* to rule out bogus alignments such as the alignment of the y's with the * +* 0 in * +* * +* if (x = 0) { * +* lab: y := 1; * +* lab: y := 2 } * +* * +* while aligning the the y's in * +* * +* if (x = 0) { y := 0; * +* lab: y := 1; * +* lab: y := 2 } * +* * +* and in * +* * +* if (x = 0) { y := 0; * +* lab: y := 1; * +* lab: y := 2 } * +* * +* ---------------------------------------------------------------------- * +* * +* Note: A possible addition is a special case to recognize alignments * +* after a comma, as in * +* * +* <<abcdef, ghi + 7, jklmn>> * +* | | * +* <<a, bc, def>> * +* * +* However, that should be done only if there's a clear intention to align * +* them--as evidenced here by the extra spaces between the "a,", the "cb," * +* and the "def". Otherwise, there would be too many chance alignments. * +***************************************************************************/ +package tla2tex; + +public class FindAlignments +{ public static void FindAlignments(Token[][] spec) + { setSubscriptField(spec) ; + /******************************************************************* + * Set the subscript fields of the tokens. * + *******************************************************************/ + int line = 0 ; + + /********************************************************************* + * Skip all lines beginning with a prolog token. This means that if * + * the module begins on the same line as a prolog token, then the * + * first line of the spec is ignored for alignment. Tant pis! * + *********************************************************************/ + boolean inProlog = true ; + while (inProlog && (line < spec.length)) + { if ( (spec[line].length > 0) + && (spec[line][0].type != Token.PROLOG)) + { inProlog = false;} + else + { line = line + 1;} + }; + + /********************************************************************* + * Set aboveAlign and belowAlign "pointers". * + *********************************************************************/ + while (line < spec.length) + { int item = 0 ; + boolean prevInfixInner = false ; + /**************************************************************** + * True iff the previous item on the line was InfixInner * + * aligned. * + ****************************************************************/ + while (item < spec[line].length) + { + /*************************************************************** + * Set token, pos to the current token and its position. * + ***************************************************************/ + Position pos = new Position(line, item) ; + Token token = spec[line][item] ; + + if (! token.subscript) + {/************************************************************* + * Do not align subscript tokens. * + *************************************************************/ + if (isRightComment(spec, pos)) + { /*********************************************************** + * This is a RightComment. First check if it should be * + * CommentInner aligned. If not, check if it should be * + * FirstNonLeftComment aligned. * + ***********************************************************/ + + /*********************************************************** + * Set btoken, bpos to the blocking token of `token' and * + * its position. * + ***********************************************************/ + Position bpos = blockingPosition(spec, pos); + Token btoken = null ; + if (bpos.line != -1) {btoken = bpos.toToken(spec);}; + + /*********************************************************** + * Set ctok to be a CommentToken alias for token. * + ***********************************************************/ + CommentToken ctok = (CommentToken) token ; + if ( (ctok.subtype == CommentToken.MULTI) + || (ctok.subtype == CommentToken.NULL)) + { /******************************************************** + * This is the continuation of a multi-column comment. * + ********************************************************/ + Debug.Assert( (btoken != null) + && (btoken.type == Token.COMMENT), + "Bad blocking token for a MULTI or NULL token"); + + /******************************************************** + * Set token's aboveAlign to point to the BEGIN_MULTI * + * token beginning the commnet. * + ********************************************************/ + if ( ((CommentToken) btoken).subtype + == CommentToken.BEGIN_MULTI) + { token.aboveAlign = bpos ; } + else + { token.aboveAlign = btoken.aboveAlign ; }; + + /******************************************************** + * Make the blocking token's belowAlign pointer point * + * to token. * + ********************************************************/ + btoken.belowAlign = pos ; + } // END then OF if if ((ctok.subtype == .. )) + else + { /******************************************************** + * This is not the continuation of a multi-line * + * comment. Try to CommentInner align it. * + ********************************************************/ + + if ( (bpos.line != -1) + && isRightComment(spec, bpos) + && (btoken.column == token.column) + && ( ( (btoken.aboveAlign.line == -1) + && (bpos.item > 0)) + || ( (btoken.aboveAlign.line != -1) + && (btoken.aboveAlign. + toToken(spec).belowAlign.line != -1)) + /********************************************** + * Asserts that bpos is aboveAligned with a * + * token that is belowAligned with something, * + * which is possible only if bpos is * + * CommentInner aligned. * + **********************************************/ + ) + ) + { /**************************************************** + * CommentInner align. * + ****************************************************/ + btoken.belowAlign = pos ; + if (btoken.aboveAlign.line == -1) + { token.aboveAlign = bpos; } + else + { token.aboveAlign = btoken.aboveAlign ;}; + } // END then OF if ((bpos.line != -1)...) + else + { /******************************************************* + * FirstNonLeftComment align iff it is the first * + * non-left comment. * + *******************************************************/ + if ( (item == 0) + || ( (item == 1) + && (spec[line][0].type == Token.COMMENT))) + { pos.toToken(spec).aboveAlign = + coveringPosition(spec, pos, true) ; + } ; + }; // END else OF if ((bpos.line != -1)...) + + + } // END else OF if if ((ctok.subtype == .. )) + prevInfixInner = false ; + } // END then OF if (isRightComment(spec, pos)) + + { /*********************************************************** + * This is not a right-comment. Check for every kind of * + * alignment except CommentInner. * + ***********************************************************/ + if (prevInfixInner) + { /******************************************************** + * Check for AfterInfixInner alignment. * + * * + * In the following, the positions lPos, alPos, and * + * aPos are defined by * + * alPos --- aPos * + * | * + * | * + * lPos --- pos * + ********************************************************/ + Debug.Assert(pos.item > 0, + "prevInfixInner true for first item on line"); + Position lPos = new Position(pos.line, pos.item - 1); + Debug.Assert(lPos.toToken(spec).aboveAlign.line != -1, + "prevInfixInner true, but token to left not aligned"); + Position alPos = lPos.toToken(spec).aboveAlign ; + Token alToken = alPos.toToken(spec); + if (alPos.item + 1 < spec[alPos.line].length) + { Position aPos = + new Position(alPos.line, alPos.item + 1) ; + Token atoken = aPos.toToken(spec); + Position cPos = coveringPosition(spec, pos, true); + if ( (cPos.line == aPos.line) + && (cPos.item == aPos.item) + && (token.column == atoken.column) + && (atoken.type != Token.COMMENT) ) + { /************************************************* + * AfterInfixInner aligned. * + *************************************************/ + token.aboveAlign = aPos; + } ; + } // END if (alPos.item + 1 < spec[alPos.line].length) + prevInfixInner = false; + } // END then OF if (prevInfixInner) + else + { /******************************************************** + * Check for every kind of alignment except * + * AfterInfixInner and CommentInner. * + * * + * First, check if FirstNonLeftComment aligned. * + ********************************************************/ + if ( ((item == 0) && (token.type != Token.COMMENT)) + || ((item == 1) && (spec[line][0].type + == Token.COMMENT))) + { /***************************************************** + * FirstNonLeftCommentAligned. * + *****************************************************/ + token.aboveAlign = + coveringPosition(spec, pos, true); + } + else + { /***************************************************** + * Not FirstNonLeftCommentAligned. * + * * + * Next, check if LeftComment aligned. * + *****************************************************/ + if (isLeftComment(spec, pos)) + { token.aboveAlign = + coveringPosition(spec, pos, false); + } + else + { /************************************************* + * Next, check if InfixInner aligned. * + *************************************************/ + Position cpos = coveringPosition(spec, pos, true) ; + Token ctoken = null ; + /*********************************************** + * cpos and ctoken are the covering position * + * and covering token of the current token. * + ***********************************************/ + if (cpos.line != -1) + { ctoken = cpos.toToken(spec);}; + int alignClass = 0 ; // The alignment classes + int calignClass = 0; // of pos and cpos. + if (token.type == Token.BUILTIN) + { alignClass = + BuiltInSymbols.GetBuiltInSymbol( + token.string, true).alignmentType ; } ; + if ( (ctoken != null) + && (ctoken.type == Token.BUILTIN)) + { calignClass = + BuiltInSymbols.GetBuiltInSymbol( + ctoken.string, true).alignmentType ; } ; + if ( (ctoken != null) + && (token.column == ctoken.column) + && (alignClass != 0) + && (alignClass == calignClass)) + { /********************************************** + * InfixInner alignment. * + **********************************************/ + ctoken.belowAlign = pos ; + if (ctoken.aboveAlign.line == -1) + {token.aboveAlign = cpos ; } + else + { token.aboveAlign = ctoken.aboveAlign ; }; + prevInfixInner = true; + } // END then OF if ((token.column == ...)) + else + { /********************************************** + * Not InfixInner alignment. Check last * + * possibility, which is InfixArg alignment. * + **********************************************/ + if ( ( (item == 1) + || ( (item == 2) + && (spec[line][0].type == + Token.COMMENT))) + && (spec[line][item-1].type == Token.BUILTIN) + && (BuiltInSymbols.GetBuiltInSymbol( + spec[line][item-1].string, true).symbolType + == Symbol.INFIX) + /****************************************** + * Correction made 7 Nov 2001. * + * The conjunct above replaced the * + * following. * + * * + * && (BuiltInSymbols.GetBuiltInSymbol( * + * spec[line][item-1].string * + * ).alignmentType != 0) * + * * + * It seems reasonable that this * + * alignment should be performed only * + * when the token to the left is an infix * + * operator. * + ******************************************/ + && (ctoken != null) + && (token.column == ctoken.column) + && (spec[line][item-1].aboveAlign.line + != -1) + ) + { /******************************************* + * Possible InfixArg alignment. * + *******************************************/ + // This can happen and seems harmless. + // Debug.Assert(ctoken.belowAlign.line == -1, + // "Trying to InfixArg align with token " + // + "that is not aligned with token below it"); + + Token lTok = spec[line][item-1] ; + /**************************************** + * The token to the left of the current * + * token. * + ****************************************/ + Position alPos = lTok.aboveAlign ; + Token alTok = alPos.toToken(spec); + /***************************************** + * The token with which lTok is aligned * + * above. * + *****************************************/ + if ( (alPos.line == cpos.line) + && (alPos.item == cpos.item - 1) + ) + { /**************************************** + * InfixArg alignment with a token * + * having a token to its left, as in * + * * + * x == a * + * | * + * + b * + ****************************************/ + token.aboveAlign = cpos; + // following fixes the mis-alignment that occurs if + // the "x == " occupies less space than the "+". + // However, for safety, I'm only fixing it for the + // case of labels, which should be more common. + // However, I'm not doing it until I find + // some example of the bug for fear that it + // might break something else. + // + // ctoken.belowAlign = pos ; + } + else + { if ( (cpos.item == 0) + || ( (cpos.item == 1) + && (spec[cpos.line][0].type + == Token.COMMENT))) + { /***************************************** + * InfixArg alignment with a token * + * having no token to its left. In * + * this case, we can have a * + * situation like * + * * + * x == * + * a * + * + b * + * * + * where a is both above-aligned * + * with x and below-aligned with b. * + * I hope this doesn't cause * + * problems. * + *****************************************/ + token.aboveAlign = cpos; + ctoken.belowAlign = pos ; + } ; + } + }; + };// END else OF if ((token.column == ...)) + }; // END else of if (isLeftComment(spec, pos)) + }; // END else OF if ( ((item == 0) && ... )) + }; // END else OF if (prevInfixInner) + }; // END else OF (isRightComment(spec, pos)) + } // END then OF if (! token.subscript) + else + { prevInfixInner = false ; + /********************************************************** + * Need to reset prevInfixInner for the tokens following * + * a ^ or _. * + **********************************************************/ + } ; + item = item + 1 ; + } ; // END while (item < spec[line].length) + + line = line + 1; + /****************************************************************** + * Skip over epilog lines. * + ******************************************************************/ + if ( (line < spec.length) + && (spec[line].length > 0) + && (spec[line][0].type == Token.EPILOG)) + { line = spec.length ;} ; + }; // END while (line < spec.length) + + // Add the AfterLabel alignments. + FindLabelAlignments(spec) ; + + /********************************************************************* + * Set isAlignmentPoint flags. For simplicity, it is set true for * + * any token that is not the first on the line and is either the * + * source or target of a belowAlign pointer * + *********************************************************************/ + line = 0 ; + while (line < spec.length) + { int item = 0 ; + while (item < spec[line].length) + { Token token = spec[line][item] ; + if (token.aboveAlign.line != -1) + { if (item > 0) + {token.isAlignmentPoint = true ; + }; + if (token.aboveAlign.item != 0) + /******************************************************** + * Corrected apparent bug on 16 Jan 00: This if * + * condition was: token.aboveAlign.line != 0. * + ********************************************************/ + { token.aboveAlign.toToken(spec).isAlignmentPoint + = true; + }; + } ; + + if (token.belowAlign.line != -1) + { if (item > 0) + {token.isAlignmentPoint = true ; + }; + if (token.belowAlign.line != 0) + { token.belowAlign.toToken(spec).isAlignmentPoint + = true; + }; + } ; + + item = item + 1 ; + } ; // END while (item < spec[line].length) + + line = line + 1; + }; // END while (line < spec.length) + } ; + + /** + * Adds AfterLabel alignments to spec. More precisely, for something like + * + * if (x) { stmt + * label: stmt + * stmt + * label: stmt + * stmt + * stmt + * } + * + * it makes the belowAlign field of all stmts but the last one point to the + * stmt below it, and the aboveAlign field of all but the first stmt point to + * the one above it. In this case + * + * while x + * stmt + * label: stmt + * + * the first stmt's aboveAlign field should point to x by the FirstNonLeftComment + * alignment rule. For + * + * while x + * label: stmt + * label: stmt + * + * the method sets the first stmt's aboveAlign field + * + * @param spec + */ + public static void FindLabelAlignments(Token[][] spec) { + /* + * Do nothing if there is no PlusCal algorithm. + */ + if (!TokenizeSpec.hasPcal) { + return ; + } + + /* + * We get the first and last line that may begin with a label or a + * PlusCal statement. Since the algorithm begins with --algorithm + * or --fair, we skip the algorithm's first line. + */ + int pcalStartLine = TokenizeSpec.pcalStart.line + 1 ; + int pcalEndLine = TokenizeSpec.pcalEnd.line ; + + /* + * The algorithm works by repeatedly searching for the next line beginning + * with a label followed by a token tok. It then performs the following + * two steps + * + * 1. Searche downward for tokens AfterLabel aligned with tok, setting + * aboveAlign/belowAlign fields as it finds them. It stops when it reaches a + * line in which tok is not not aligned with the first token on the + * line. If there's a token etok on that line that is AfterLabel + * aligned with tok, so it must follow a label, then the aboveAlign + * field of etok is set to point to the AfterLabel aligned token above + * it, whose belowAlign field points to etok. + * + * 2. IF tok's aboveAlign field points to a token whose belowAlign field + * points to a field whose belowAlign field points to tok + * THEN do nothing. I think this is possible only if those fields were set + * by step 1 for a previous label. If I'm wrong and there's some weird + * situation that caused this alignment (which I think can only be + * the case if tok is a comment), then not stopping will probably + * do more harm than good. + * ELSE search upwards to set aboveAlign/belowAlign fields of tokens + * AfterLabel aligned with tok. As indicated above, if there + * are no tokens above tok that are AfterLabel aligned with it, + * then tok's aboveAlign field must be set to point to its + * covering token. + */ + int curLabelLine = pcalStartLine ; + + while ( (curLabelLine <= pcalEndLine) + && (curLabelLine < spec.length) ) { + /* + * set curLabelLine to the first line at or below its current + * position that begins with a label. + */ + if ( (spec[curLabelLine].length > 1) + && (spec[curLabelLine][0].type == Token.PCAL_LABEL) ) { + + Token tok = spec[curLabelLine][1] ; + int alignCol = tok.column ; + + // Perform step 1 for token tok + int curLine = curLabelLine + 1 ; + + // spec[alignLine][alignItem] is the token to which the + // next token to be aligned is aligned with. + int alignLine = curLabelLine ; + int alignItem = 1 ; + boolean notDone = true ; + // Test of curLine < spec.length added 4 Aug 2015 to fix + // bug caused by running off the end of the spec when called by + // TLATeX to format a snippet of PlusCal code. See comment from + // this date in TokenizeSpec. + while (notDone && (curLine < spec.length)) { + int curItem = 0 ; + // If spec[alignLine][alignItem] is to be aligned with a token + // on this line, then the token is spec[curLine][nextItem] + // + boolean shouldSkip = false ; + if (spec[curLine].length != 0) { + if (spec[curLine][0].type == Token.PCAL_LABEL) { + // line begins with label + if (spec[curLine].length > 1) { + curItem = 1 ; + } + else { + // The label is the only token on the line. Stop + // iff the label is to the left of the alignment + // column. + notDone = (spec[curLine][0].column >= alignCol) ; + shouldSkip = true ; + } + } + else { + // line doesn't begin with label + } + if (!shouldSkip) { + if (spec[curLine][curItem].column < alignCol) { + notDone = false ; + } + else if (spec[curLine][curItem].column == alignCol) { + spec[alignLine][alignItem].belowAlign = + new Position(curLine, curItem) ; + spec[curLine][curItem].aboveAlign = + new Position(alignLine, alignItem) ; + alignLine = curLine ; + alignItem = curItem ; + } + } + } + curLine++ ; + if ( (curLine > pcalEndLine) + || (curLine >= spec.length)) { + notDone = false ; + } + } + + // Perform step 2 for token tok + + if ( (tok.aboveAlign.line != -1) + && (tok.aboveAlign.toToken(spec).belowAlign.equals(new Position(curLabelLine, 1)))) { + // already aligned with above tokens so do nothing + } + else { + curLine = curLabelLine - 1 ; + alignLine = curLabelLine ; + alignItem = 1 ; + notDone = true ; + while (notDone) { + if ( (spec[curLine].length > 0 ) + && (spec[curLine][0].column <= alignCol) + && ( (spec[curLine][0].type != Token.PCAL_LABEL) + || ( (spec[curLine].length > 1) + && (spec[curLine][1].column <= alignCol) ) ) + // the conjunct above causes the line to be skipped + // if the only token to the left of tok on this + // line is an initial label followed by a token + // to the right of tok. + ) { + if (spec[curLine][0].column == alignCol) { + // spec[curLine][0] is to be aligned with + // spec[alignLine][alignItem] + spec[alignLine][alignItem].aboveAlign = + new Position(curLine, 0) ; + spec[curLine][0].belowAlign = + new Position(alignLine, alignItem) ; + alignLine = curLine ; + alignItem = 0 ; + } + else if (spec[alignLine][alignItem].aboveAlign.line == -1) { + // we need to align spec[alignLine][alignItem] with the + // right-most token on line curLine with column \leq alignCol + int item = 0 ; + while ( (item < spec[curLine].length) + && (spec[curLine][item].column <= alignCol)) { + item++; + } + // item is now either off the line or pointing to a token + // to the right of the alignment token ; + item-- ; + + // We set the aboveAlign pointer of + // to point to spec[curLine][item] if the latter token is either + // the first one on its line, or else the token to its left is + // either a label or a PlusCal token. + // If that's the case, we also set the belowAlign pointer of + // spec[curLine][item] if that token is in the same column + // as spec[alignLine][alignItem]. + // + Token altok = null ; + if (item > 0) { + altok = spec[curLine][item-1] ; + } + if ( (altok != null) + && ( (altok.type == Token.PCAL_LABEL) + || ( (altok.type == Token.BUILTIN) + && BuiltInSymbols.IsBuiltInSymbol(altok.string, true) + && ! BuiltInSymbols.IsBuiltInSymbol(altok.string, false) + ) + ) + ) { + spec[alignLine][alignItem].aboveAlign = + new Position(curLine, item) ; + + if (spec[curLine][item].column == alignCol) { + spec[curLine][item].belowAlign = + new Position(alignLine, alignItem) ; + } + } + // This ends step 2 + notDone = false ; + } + else { + notDone = false ; + } + } + curLine-- ; + if (curLine < pcalStartLine) { + notDone = false ; + } + } + } + } + curLabelLine++ ; + } + } + /************************************************************************* + * The following are functions used in FindAlignments. * + *************************************************************************/ + private static boolean isLeftComment(Token[][] spec, Position p) + /*********************************************************************** + * A left-comment is a comment token that is the first token on a line * + * and has another token to its right. This method returns true iff * + * the token at position p of spec is a left-comment. * + ***********************************************************************/ + { return (p.item == 0) + && (spec[p.line][p.item].type == Token.COMMENT) + && (spec[p.line].length > 1) ; + } ; + + private static boolean isRightComment(Token[][] spec, Position p) + /*********************************************************************** + * A right-comment is a comment token that is the last token on its * + * line. This method returns true iff the token at position p in spec * + * is a right-comment. * + ***********************************************************************/ + { return (p.item == spec[p.line].length - 1) + && (spec[p.line][p.item].type == Token.COMMENT) ; + } ; + + private static Position coveringPosition( + Token[][] spec, Position p, boolean ignore) + /*********************************************************************** + * A token t1 COVERS a token t2 if t1 lies on an earlier line than t2 * + * and t1.column \leq t2.column. This method searches upwards to find * + * the first line with a token that covers the token at position p, * + * and then returns the position of the right-most token on that line * + * that covers the token at p. When searching for the covering token, * + * left-comments are ignored iff the ignore parameter is true. * + ***********************************************************************/ + { /********************************************************************* + * Find covering line. * + *********************************************************************/ + int line = p.line - 1 ; + Token tok = p.toToken(spec) ; + boolean notDone = true ; + while ((line >= 0) && notDone) + { if (spec[line].length > 0) + { if (spec[line][0].type == Token.PROLOG) + { line = -1 ; + notDone = false; + } + else + { int item = 0 ; + if (ignore && isLeftComment(spec, new Position(line, 0))) + { item = 1 ; } + if (spec[line][item].column <= tok.column) + { notDone = false ;} ; + } + }; // END if (spec[line].length > 0) + if (notDone) {line = line - 1 ;}; + } // END while ((line >= 0) && notDone) + + /********************************************************************** + * If no covering line, return (-1, 0). * + **********************************************************************/ + if (line == -1) {return new Position(-1, 0);} ; + + /********************************************************************** + * Find covering item. * + **********************************************************************/ + int item = 0; + int nsItem = 0; + /******************************************************************** + * item is the current item. * + * nsItem is the last non-subscript item found that covers tok. * + ********************************************************************/ + boolean dashFound = false ; + if (spec[line][0].type == Token.DASHES) + { dashFound = true;} ; + while ( (! dashFound) + && (item + 1 < spec[line].length) + && (spec[line][item + 1].column <= tok.column)) + { if (spec[line][item+1].type == Token.DASHES) + {dashFound = true;} ; + item = item + 1; + if (!spec[line][item].subscript) + {nsItem = item;}; + }; + if (dashFound) {return new Position(-1, 0);} ; + + /********************************************************************** + * Return (line, nsItem). * + **********************************************************************/ + return new Position(line, nsItem); + } ; + + private static Position blockingPosition(Token[][] spec, Position p) + /*********************************************************************** + * Searches upwards from position p to find the first token at the * + * same column or to the right of the token at p that is not * + * a subscript token. * + ***********************************************************************/ + { int line = p.line - 1 ; + int item = 0 ; + Token tok = p.toToken(spec) ; + boolean notDone = true ; + while ((line >= 0) && notDone) + { if (spec[line].length > 0) + { if (spec[line][0].type == Token.PROLOG) + { line = -1 ; + notDone = false; + } + else + { item = 0 ; + if (isLeftComment(spec, new Position(line, 0))) + { item = 1 ; } ; + while (notDone && (item < spec[line].length)) + { if ( (spec[line][item].column >= tok.column) + && (! spec[line][item].subscript)) + { notDone = false ; } + else + { item = item+1; }; + } ; + } ; + }; // END if (spec[line].length > 0) + if (notDone) {line = line - 1 ;} ; + } // END while ((line >= 0) && notDone) + + /********************************************************************** + * If no token found, return (-1, 0). * + **********************************************************************/ + if (line == -1) {return new Position(-1, 0);} ; + + /********************************************************************** + * Return (line, item). * + **********************************************************************/ + return new Position(line, item); + } ; + + private static void setSubscriptField(Token[][] spec) + /*********************************************************************** + * Sets the subscript field of the tokens. (This field is true iff * + * the token is part of a sub- or superscript.) Upon encountering a * + * "^" or a token that is a built-in symbol with symbolType - * + * Symbol.SUBSCRIPTED token, the next token or all tokens in the * + * properly parenthesized expression that follows it are * + * subscripted--iff all those tokens lie on the same line. * + ***********************************************************************/ + { int line = 0 ; + while (line < spec.length) + { int item = 0 ; + int startSub = -1 ; + /**************************************************************** + * If a subscript has begun, then this is its first item; * + * otherwise, it equals -1. * + ****************************************************************/ + int nestingDepth = 0 ; + /**************************************************************** + * The current parenthesis nesting level inside a subscript, or * + * 0 if not in a subscript. * + ****************************************************************/ + + + while (item < spec[line].length) + { Token tok = spec[line][item] ; + /************************************************************* + * tok is the current token. * + *************************************************************/ + if (startSub == -1) + { /************************************************************ + * A subscript has not yet begun. * + ************************************************************/ + if ( (tok.type == Token.BUILTIN) + && ( (BuiltInSymbols.GetBuiltInSymbol( + tok.string, true).symbolType + == Symbol.SUBSCRIPTED) + || (tok.string.equals("^")))) + { + startSub = item + 1 ; + } // END then OF if ((tok.type == Token.BUILTIN) ...) + else + { /********************************************************* + * Do nothing. * + *********************************************************/ + }; // END else OF if ((tok.type = Token.BUILTIN) ...) + + } // END then OF if (startSub == -1) + else + { /************************************************************ + * A subscript has begun. * + * * + * Set symType to the symbol type of the token, or * + * NOT_A_SYMBOL if it isn't a symbol. * + ************************************************************/ + int symType = Symbol.NOT_A_SYMBOL ; + if (tok.type == Token.BUILTIN) + { symType = + BuiltInSymbols.GetBuiltInSymbol( + tok.string, true).symbolType ; + }; + + if ( ( (nestingDepth == 0) + && (symType != Symbol.LEFT_PAREN)) + || ( (nestingDepth == 1) + && (symType == Symbol.RIGHT_PAREN))) + { /********************************************************* + * This ends the subscript. Set the subscript field of * + * all tokens from startSub to item and reset startSub. * + *********************************************************/ + nestingDepth = 0 ; + while (startSub <= item) + { spec[line][startSub].subscript = true ; + startSub = startSub + 1; + } + startSub = -1 ; + } // END then OF if (((nestingDepth == 0)... )) + else + { /********************************************************* + * The subscript continues. * + *********************************************************/ + if (symType == Symbol.LEFT_PAREN) + { nestingDepth = nestingDepth + 1; } + else + { if (symType == Symbol.RIGHT_PAREN) + { nestingDepth = nestingDepth - 1; }; + }; + }; // END else OF if (((nestingDepth == 0)... )) + + }; // END else OF if (startSub == -1) + + item = item + 1 ; + } ; // END while (item < spec[line].length) + + + line = line + 1; + } // END while (line < spec.length) + + } ; + + +} + +/* last modified on Sun 5 August 2012 at 17:07:48 PST by lamport */ diff --git a/tlatools/src/tla2tex/TokenizeSpec.java b/tlatools/src/tla2tex/TokenizeSpec.java index 41ce234d505d5b747704a7e4e0d9fd34c3618044..3b3f10a5442db77ccc5abf38b292dad2ad22f84c 100644 --- a/tlatools/src/tla2tex/TokenizeSpec.java +++ b/tlatools/src/tla2tex/TokenizeSpec.java @@ -1737,6 +1737,17 @@ public class TokenizeSpec /* * Prevent null pointer exception in PlusCal algorithm that was * not ended. + * + * Note added 4 Aug 2015 by LL. It appears that we will get here with hasPcal + * = true and pcalEnd = null TLA2TeX is called to format a snippet of PCal + * code. So, I would expect setting pcalEnd to this dummy non-null value l + * often to cause bugs later on in the processing. However, today is the first + * time I encountered such a bug. I seem to have fixed this with a patch + * to FindAlignments.FindLabelAlignments described in a comment dated today. + * I didn't try to understand that code to know if the bug actually was a + * reasonable fix or whether it prevents an exception but can produce + * unreasonably bad formatting. Moreover, it seems likely that this dummy + * value of pcalEnd will trigger other bugs that I haven't encountered yet. */ if (hasPcal) { if (pcalEnd == null) { diff --git a/tlatools/src/tlc2/TLC.java b/tlatools/src/tlc2/TLC.java index f8e2b456b4b5d122316eeffee76b7159bd808f8f..90ed293db48622d72d2622728bab7adac14a055b 100644 --- a/tlatools/src/tlc2/TLC.java +++ b/tlatools/src/tlc2/TLC.java @@ -5,12 +5,17 @@ package tlc2; +import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; import java.net.UnknownHostException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; +import java.util.TimeZone; import model.InJarFilenameToStream; import model.ModelInJar; @@ -135,6 +140,8 @@ public class TLC * Defaults to 1000000 if not specified * o -metadir path: store metadata in the directory at path * Defaults to specdir/states if not specified + * o -userFile file: A full qualified/absolute path to a file to log user + * output (Print/PrintT/...) to * o -workers num: the number of TLC worker threads * Defaults to 1 * o -dfid num: use depth-first iterative deepening with initial depth num @@ -169,7 +176,7 @@ public class TLC * Defaults to 1 * o -maxSetSize num: the size of the largest set TLC will enumerate. * default: 1000000 - * + * */ public static void main(String[] args) throws UnknownHostException, FileNotFoundException { @@ -217,7 +224,6 @@ public class TLC { // SZ Feb 20, 2009: extracted this method to separate the // parameter handling from the actual processing - int index = 0; while (index < args.length) { @@ -466,6 +472,24 @@ public class TLC printErrorMsg("Error: need to specify the metadata directory."); return false; } + } else if (args[index].equals("-userFile")) + { + index++; + if (index < args.length) + { + try { + // Most problems will only show we TLC eventually tries + // to write to the file. + tlc2.module.TLC.OUTPUT = new BufferedWriter(new FileWriter(new File(args[index++]))); + } catch (IOException e) { + printErrorMsg("Error: Failed to create user output log file."); + return false; + } + } else + { + printErrorMsg("Error: need to specify the full qualified file."); + return false; + } } else if (args[index].equals("-workers")) { index++; @@ -682,6 +706,8 @@ public class TLC */ public void process() { + long startTime = System.currentTimeMillis(); + ToolIO.cleanToolObjects(TLCGlobals.ToolId); // UniqueString.initialize(); @@ -721,6 +747,7 @@ public class TLC MP.printMessage(EC.TLC_MODE_SIMU, String.valueOf(seed)); Simulator simulator = new Simulator(mainFile, configFile, null, deadlock, traceDepth, traceNum, rng, seed, true, resolver, specObj); + TLCGlobals.simulator = simulator; // The following statement moved to Spec.processSpec by LL on 10 March 2011 // MP.printMessage(EC.TLC_STARTING); instance = simulator; @@ -734,12 +761,12 @@ public class TLC if (TLCGlobals.DFIDMax == -1) { mc = new ModelChecker(mainFile, configFile, dumpFile, deadlock, fromChkpt, resolver, specObj, fpSetConfiguration); - TLCGlobals.mainChecker = (ModelChecker) mc; modelCheckerMXWrapper = new ModelCheckerMXWrapper((ModelChecker) mc); } else { mc = new DFIDModelChecker(mainFile, configFile, dumpFile, deadlock, fromChkpt, true, resolver, specObj); } + TLCGlobals.mainChecker = mc; // The following statement moved to Spec.processSpec by LL on 10 March 2011 // MP.printMessage(EC.TLC_STARTING); instance = mc; @@ -768,13 +795,44 @@ public class TLC } } finally { - modelCheckerMXWrapper.unregister(); - MP.printMessage(EC.TLC_FINISHED); - MP.flush(); + if (tlc2.module.TLC.OUTPUT != null) { + try { + tlc2.module.TLC.OUTPUT.flush(); + tlc2.module.TLC.OUTPUT.close(); + } catch (IOException e) { + } + } + modelCheckerMXWrapper.unregister(); + MP.printMessage(EC.TLC_FINISHED, + convertRuntimeToHumanReadable(System.currentTimeMillis() - startTime)); + MP.flush(); } } - @SuppressWarnings("unchecked") + /** + * @return The given milliseconds runtime converted into human readable form + * with SI unit and insignificant parts stripped (when runtime is + * days, nobody cares for minutes or seconds). + */ + public static String convertRuntimeToHumanReadable(long runtime) { + SimpleDateFormat df = null; + if (runtime > (60 * 60 * 24 * 1000L)) { + df = new SimpleDateFormat("D'd' HH'h'"); + runtime -= 86400000L; + } else if (runtime > (60 * 60 * 24 * 1000L)) { + df = new SimpleDateFormat("D'd' HH'h'"); + runtime -= 86400000L; + } else if (runtime > (60 * 60 * 1000L)) { + df = new SimpleDateFormat("HH'h' mm'min'"); + } else if (runtime > (60 * 1000L)) { + df = new SimpleDateFormat("mm'min' ss's'"); + } else { + df = new SimpleDateFormat("ss's'"); + } + df.setTimeZone(TimeZone.getTimeZone("UTC")); + return df.format(runtime); + } + public List<File> getModuleFiles() { final List<File> result = new ArrayList<File>(); diff --git a/tlatools/src/tlc2/TLCGlobals.java b/tlatools/src/tlc2/TLCGlobals.java index 44edadba17ddd5b0638451b1dd24ef5f5b642acc..ac15100471250b56aad267da9108a5b1ed62cb20 100644 --- a/tlatools/src/tlc2/TLCGlobals.java +++ b/tlatools/src/tlc2/TLCGlobals.java @@ -3,7 +3,8 @@ package tlc2; import tla2sany.semantic.FrontEnd; -import tlc2.tool.ModelChecker; +import tlc2.tool.AbstractChecker; +import tlc2.tool.Simulator; /** * Globals @@ -16,7 +17,7 @@ public class TLCGlobals { // The current version of TLC - public static String versionOfTLC = "Version 2.07 of 1 June 2015"; + public static String versionOfTLC = "Version 2.08 of 21 December 2015"; // The bound for set enumeration, used for pretty printing public static int enumBound = 2000; @@ -33,6 +34,15 @@ public class TLCGlobals */ public static double livenessThreshold = 0.1d; + public static double livenessGraphSizeThreshold = 0.1d; + + /** + * Ratio of runtime dedicated to safety checking (80%) and liveness checking + * (20%). Some aspects of liveness are also checked during state insertion + * (see ILiveCheck#addNextState) and thus part of safety checking.. + */ + public static double livenessRatio = 0.2d; + public synchronized static void setNumWorkers(int n) { numWorkers = n; @@ -62,8 +72,11 @@ public class TLCGlobals incNumWorkers(-1); } - // The main model checker object - public static ModelChecker mainChecker = null; + // The main model checker object (null if simulator non-null) + public static AbstractChecker mainChecker = null; + + // The main simulator object (null if mainChecker non-null) + public static Simulator simulator = null; // Enable collecting coverage information public static int coverageInterval = -1; diff --git a/tlatools/src/tlc2/module/TLC.java b/tlatools/src/tlc2/module/TLC.java index dd0969ba415ff9c6b6e031a78d40690470b5dd38..f955410ac13e18d349c6ee313591dc711d7a56d4 100644 --- a/tlatools/src/tlc2/module/TLC.java +++ b/tlatools/src/tlc2/module/TLC.java @@ -5,12 +5,16 @@ package tlc2.module; +import java.io.BufferedWriter; +import java.io.IOException; + import tlc2.TLCGlobals; import tlc2.output.EC; +import tlc2.output.MP; import tlc2.tool.EvalControl; import tlc2.tool.EvalException; import tlc2.tool.TLARegistry; -import tlc2.tool.Worker; +import tlc2.util.IdThread; import tlc2.util.RandomGenerator; import tlc2.value.Applicable; import tlc2.value.BoolValue; @@ -34,6 +38,7 @@ public class TLC implements ValueConstants { private static RandomGenerator rng; + public static BufferedWriter OUTPUT; static { @@ -57,7 +62,15 @@ public class TLC implements ValueConstants Value v2c = v2.deepCopy(); v1c.deepNormalize(); v2c.deepNormalize(); - ToolIO.out.println(Value.ppr(v1c.toString()) + " " + Value.ppr(v2c.toString())); + if (OUTPUT == null) { + ToolIO.out.println(Value.ppr(v1c.toString()) + " " + Value.ppr(v2c.toString())); + } else { + try { + OUTPUT.write(Value.ppr(v1c.toString()) + " " + Value.ppr(v2c.toString()) + "\n"); + } catch (IOException e) { + MP.printError(EC.GENERAL, e); + } + } return v2; } @@ -70,7 +83,16 @@ public class TLC implements ValueConstants { Value v1c = v1.deepCopy(); v1c.deepNormalize(); - ToolIO.out.println(Value.ppr(v1c.toString())); + if (OUTPUT == null) { + String ppr = Value.ppr(v1c.toString()); + ToolIO.out.println(ppr); + } else { + try { + OUTPUT.write(Value.ppr(v1c.toString("\n"))); + } catch (IOException e) { + MP.printError(EC.GENERAL, e); + } + } return ValTrue; } @@ -112,12 +134,15 @@ public class TLC implements ValueConstants { Thread th = Thread.currentThread(); Value res = null; - if (th instanceof Worker) + if (th instanceof IdThread) { - res = ((Worker) th).getLocalValue(idx); - } else + res = ((IdThread) th).getLocalValue(idx); + } else if (TLCGlobals.mainChecker != null) { res = tlc2.TLCGlobals.mainChecker.getValue(0, idx); + } else + { + res = tlc2.TLCGlobals.simulator.getLocalValue(idx); } if (res == null) { @@ -138,12 +163,15 @@ public class TLC implements ValueConstants if (idx >= 0) { Thread th = Thread.currentThread(); - if (th instanceof Worker) + if (th instanceof IdThread) { - ((Worker) th).setLocalValue(idx, val); - } else + ((IdThread) th).setLocalValue(idx, val); + } else if (TLCGlobals.mainChecker != null) { TLCGlobals.mainChecker.setAllValues(idx, val); + } else + { + tlc2.TLCGlobals.simulator.setLocalValue(idx, val); } return ValTrue; } diff --git a/tlatools/src/tlc2/output/EC.java b/tlatools/src/tlc2/output/EC.java index 379b4e356fefa51467ac1b8f6422335c85fc367b..e33dfde840ffd1938f76c71e70d035a6223fd0d1 100644 --- a/tlatools/src/tlc2/output/EC.java +++ b/tlatools/src/tlc2/output/EC.java @@ -36,6 +36,11 @@ public interface EC public static final int UNKNOWN = -1; // TODO remove all these public final static int UNIT_TEST = -123456; + /** + * A feature of TLA+/TLC is not supported by TLC's current mode. E.g. + * TLCGet/TLCSet operator cannot be used in distributed TLC. + */ + public static final int TLC_FEATURE_UNSUPPORTED = 2156; public static final int GENERAL = 1000; public static final int SYSTEM_OUT_OF_MEMORY = 1001; @@ -65,6 +70,7 @@ public interface EC public static final int TLC_ACTION_PROPERTY_EVALUATION_FAILED = 2113; public static final int TLC_DEADLOCK_REACHED = 2114; + public static final int TLC_STATES_AND_NO_NEXT_ACTION = 2115; public static final int TLC_TEMPORAL_PROPERTY_VIOLATED = 2116; public static final int TLC_FAILED_TO_RECOVER_NEXT = 2117; public static final int TLC_NO_STATES_SATISFYING_INIT = 2118; @@ -79,6 +85,7 @@ public interface EC public static final int SYSTEM_ERROR_READING_POOL = 2125; public static final int SYSTEM_CHECKPOINT_RECOVERY_CORRUPT = 2126; public static final int SYSTEM_ERROR_WRITING_POOL = 2127; + public static final int SYSTEM_ERROR_CLEANING_POOL = 2270; public static final int SYSTEM_INDEX_ERROR = 2134; public static final int SYSTEM_STREAM_EMPTY = 2135; public static final int SYSTEM_FILE_NULL = 2137; @@ -98,7 +105,7 @@ public interface EC public static final int TLC_CHOOSE_UPPER_BOUND = 2165; public static final int TLC_VALUE_ASSERT_FAILED = 2132; - public static final int TLC_MODULE_VALUE_JAVA_METHOD_OVERRIDE = 2154; + public static final int TLC_MODULE_VALUE_JAVA_METHOD_OVERRIDE = 2154; public static final int TLC_FP_NOT_IN_SET = 2133; public static final int TLC_FP_VALUE_ALREADY_ON_DISK = 2166; @@ -174,6 +181,7 @@ public interface EC public static final int TLC_INIT_GENERATED3 = 2207; public static final int TLC_INIT_GENERATED4 = 2208; public static final int TLC_CHECKING_TEMPORAL_PROPS = 2192; + public static final int TLC_CHECKING_TEMPORAL_PROPS_END = 2267; public static final int TLC_SUCCESS = 2193; public static final int TLC_SEARCH_DEPTH = 2194; public static final int TLC_CHECKPOINT_START = 2195; diff --git a/tlatools/src/tlc2/output/MP.java b/tlatools/src/tlc2/output/MP.java index 593529e5ecb2981b7f27b3574abc67be9449dd1d..6a536599934979710f5d0caa343c0a596b9347a1 100644 --- a/tlatools/src/tlc2/output/MP.java +++ b/tlatools/src/tlc2/output/MP.java @@ -247,6 +247,13 @@ public class MP case EC.SYSTEM_ERROR_WRITING_POOL: b.append("when writing the disk (StatePoolWriter.run):\n%1%"); break; + case EC.SYSTEM_ERROR_CLEANING_POOL: + if (messageClass == ERROR) { + b.append("Exception %2% cleaning up an obsolete disk files.\n%1%"); + } else if (messageClass == WARNING) { + b.append("Failed to clean up an obsolete disk file. Please manually delete %1% if free disk space is low."); + } + break; case EC.SYSTEM_DISKGRAPH_ACCESS: b.append("DiskGraph.toString()"); @@ -380,6 +387,9 @@ public class MP break; // this is a TLC bug + case EC.TLC_STATES_AND_NO_NEXT_ACTION: + b.append("No next state actions defined to generate successor states from."); + break; case EC.TLC_FAILED_TO_RECOVER_NEXT: b.append("Failed to recover the next state from its fingerprint."); break; @@ -404,9 +414,9 @@ public class MP if (TLCGlobals.tool) { // format same as state printing for easier // parsing by toolbox - b.append("%1%: Back to state.\n"); + b.append("%1%: Back to state: %2%\n"); } else { - b.append("Back to state %1%.\n"); + b.append("Back to state %1%: %2%\n"); } break; @@ -610,6 +620,9 @@ public class MP b.append("Attempted to apply the operator overridden by the Java method" + "\n%1%,\nbut it produced the following error:\n%2%"); break; + case EC.TLC_FEATURE_UNSUPPORTED: + b.append("%1%"); + break; /* Liveness errors */ case EC.TLC_LIVE_BEGRAPH_FAILED_TO_CONSTRUCT: @@ -729,7 +742,7 @@ public class MP b.append("Starting... (").append(SDF.format(new Date())).append(")"); break; case EC.TLC_FINISHED: - b.append("Finished. (").append(SDF.format(new Date())).append(")"); + b.append("Finished in %1% at (").append(SDF.format(new Date())).append(")"); break; case EC.TLC_MODE_MC: b.append("Running in Model-Checking mode."); @@ -744,7 +757,7 @@ public class MP b.append("Finished computing initial states: %1% distinct state%2% generated."); break; case EC.TLC_INIT_GENERATED2: - b.append("Finished computing initial states: %1% states generated, with %2% of them distinct."); + b.append("Finished computing initial states: %1% state%2% generated, with %3% of them distinct."); break; case EC.TLC_INIT_GENERATED3: b.append("Finished computing initial states: %1% states generated.\n" @@ -754,10 +767,12 @@ public class MP b.append("Finished computing initial states: %1% states generated, with %2% of them distinct."); break; case EC.TLC_CHECKING_TEMPORAL_PROPS: - b.append("Checking temporal properties for the %1% state space with %2% distinct states at (") + b.append("Checking %3%temporal properties for the %1% state space with %2% total distinct states at (") .append(SDF.format(new Date())).append(")"); break; - + case EC.TLC_CHECKING_TEMPORAL_PROPS_END: + b.append("Finished checking temporal properties in %1% at " + SDF.format(new Date())); + break; case EC.TLC_SUCCESS: b.append("Model checking completed. No error has been found.\n" + " Estimates of the probability that TLC did not check all reachable states\n" @@ -793,9 +808,14 @@ public class MP b.append("The number of states generated: %1%\nSimulation using seed %2% and aril %3%"); break; case EC.TLC_PROGRESS_STATS: - b.append("Progress(%1%) at " + SDF.format(new Date()) + ": %2% states generated (" - + df.format(Long.valueOf(parameters[4])) + " s/min), %3% distinct states found (" - + df.format(Long.valueOf(parameters[5])) + " ds/min), %4% states left on queue."); + if (parameters.length == 4) { + b.append("Progress(%1%) at " + SDF.format(new Date()) + ": %2% states generated, " + + "%3% distinct states found, " + "%4% states left on queue."); + } else { + b.append("Progress(%1%) at " + SDF.format(new Date()) + ": %2% states generated (" + + df.format(Long.valueOf(parameters[4])) + " s/min), %3% distinct states found (" + + df.format(Long.valueOf(parameters[5])) + " ds/min), %4% states left on queue."); + } break; case EC.TLC_PROGRESS_START_STATS_DFID: b.append("Starting level %1%: %2% states generated, %3% distinct states found."); @@ -932,7 +952,11 @@ public class MP b.append("%1%:\n%2%"); break; case EC.TLC_STATE_PRINT2: - b.append("%1%: %2%\n%3%"); + if (DO_DEBUG) { + b.append("%1%: %2%\n%3%fp: %4%\n"); + } else { + b.append("%1%: %2%\n%3%"); + } break; case EC.TLC_STATE_PRINT3: b.append("%1%: Stuttering"); @@ -1197,7 +1221,7 @@ public class MP } else { msg = msg + "\nThe error occurred when TLC was " + cause + "."; } - msg = msg + "\nThe exception was a " + throwable.getClass().getName(); + msg = msg + "\nThe exception was a " + throwable.getClass().getName() + "\n"; if (throwable.getMessage() != null) { msg = msg + ": " + throwable.getMessage(); diff --git a/tlatools/src/tlc2/output/StatePrinter.java b/tlatools/src/tlc2/output/StatePrinter.java index b3a0ecad6f1c4429c447a088ed0a709bfdc00be9..819cb666eac11404a0938d3f3b76d0feb88e6ae0 100644 --- a/tlatools/src/tlc2/output/StatePrinter.java +++ b/tlatools/src/tlc2/output/StatePrinter.java @@ -45,6 +45,15 @@ public class StatePrinter MP.printState(EC.TLC_STATE_PRINT1, new String[] { "", currentState.toString() }, currentState, -1); } + public static void printState(TLCStateInfo currentStateInfo) { + if (currentStateInfo.predecessorState == null) { + // It's an initial state + printState(currentStateInfo, null, (int) currentStateInfo.stateNumber); + } else { + printState(currentStateInfo, currentStateInfo.predecessorState.state, (int) currentStateInfo.stateNumber); + } + } + /** * Prints the state information * if the TLC runs in print-diff-only mode and the last state is set, it will print the diff only @@ -61,8 +70,14 @@ public class StatePrinter { stateString = currentStateInfo.state.toString(); } - MP.printState(EC.TLC_STATE_PRINT2, new String[] { String.valueOf(num), currentStateInfo.info.toString(), - stateString }, currentStateInfo, num); + if (currentStateInfo.state.allAssigned()) { + MP.printState(EC.TLC_STATE_PRINT2, new String[] { String.valueOf(num), currentStateInfo.info.toString(), + stateString, String.valueOf(currentStateInfo.fingerPrint()) }, currentStateInfo, num); + } else { + // fingerprint can't be calculated when state is incomplete, just return a random value. + MP.printState(EC.TLC_STATE_PRINT2, new String[] { String.valueOf(num), currentStateInfo.info.toString(), + stateString, "-1" }, currentStateInfo, num); + } OutputCollector.addStateToTrace(currentStateInfo); } @@ -71,7 +86,21 @@ public class StatePrinter */ public static void printStutteringState(int num) { - MP.printState(EC.TLC_STATE_PRINT3, new String[] { String.valueOf(num) }, (TLCState) null, num); + MP.printState(EC.TLC_STATE_PRINT3, new String[] { String.valueOf(num + 1) }, (TLCState) null, num + 1); } + /** + * Prints a marker (EC.TLC_BACK_TO_STATE) looping back to the state with the + * given stateNum. + * @param currentStateInfo + * + * @param stateNum + */ + public static void printBackToState(final TLCStateInfo currentStateInfo, final long stateNum) { + if (TLCGlobals.tool) { + MP.printState(EC.TLC_BACK_TO_STATE, new String[] { "" + stateNum, currentStateInfo.info.toString() }, (TLCState) null, -1); + } else { + MP.printMessage(EC.TLC_BACK_TO_STATE, new String[] {"" + stateNum, currentStateInfo.info.toString()}); + } + } } diff --git a/tlatools/src/tlc2/tool/AbstractChecker.java b/tlatools/src/tlc2/tool/AbstractChecker.java index 03ba61b2f8deed40b5e9da13bfc2be121029b8c4..e3b086e564fb498870adc134be7708f240aa910d 100644 --- a/tlatools/src/tlc2/tool/AbstractChecker.java +++ b/tlatools/src/tlc2/tool/AbstractChecker.java @@ -11,6 +11,7 @@ import tlc2.TLCGlobals; import tlc2.output.EC; import tlc2.output.MP; import tlc2.output.OutputCollector; +import tlc2.tool.liveness.AddAndCheckLiveCheck; import tlc2.tool.liveness.ILiveCheck; import tlc2.tool.liveness.LiveCheck; import tlc2.tool.liveness.Liveness; @@ -21,6 +22,7 @@ import tlc2.util.StateWriter; import tlc2.util.statistics.BucketStatistics; import tlc2.util.statistics.DummyBucketStatistics; import tlc2.util.statistics.IBucketStatistics; +import tlc2.value.Value; import util.DebugPrinter; import util.FileUtil; import util.FilenameToStream; @@ -32,6 +34,15 @@ import util.FilenameToStream; */ public abstract class AbstractChecker implements Cancelable { + /** + * True when unit tests explicitly request to use + * {@link AddAndCheckLiveCheck} to run liveness checking after each + * insertion into the behavior graph. This should only be true if you + * exactly know what you are doing. If you don't and this is true, make sure + * it's false. + */ + public static boolean LIVENESS_TESTING_IMPLEMENTATION = Boolean.getBoolean(ILiveCheck.class.getName() + ".testing"); + protected static final boolean LIVENESS_STATS = Boolean.getBoolean(Liveness.class.getPackage().getName() + ".statistics"); // SZ Mar 9, 2009: static modifier removed @@ -52,8 +63,17 @@ public abstract class AbstractChecker implements Cancelable public Action[] actions; protected StateWriter allStateWriter; protected boolean cancellationFlag; + private IdThread[] workers; protected final ILiveCheck liveCheck; + protected final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { + protected Integer initialValue() { + return 1; + } + }; + + protected static final int INITIAL_CAPACITY = 16; + /** * Constructor of the abstract model checker * @param specFile @@ -113,7 +133,11 @@ public abstract class AbstractChecker implements Cancelable stats = new BucketStatistics("Histogram vertex out-degree", LiveCheck.class.getPackage().getName(), "DiskGraphsOutDegree"); } - this.liveCheck = new LiveCheck(this.tool, this.actions, this.metadir, stats); + if (LIVENESS_TESTING_IMPLEMENTATION) { + this.liveCheck = new AddAndCheckLiveCheck(this.tool, this.actions, this.metadir, stats); + } else { + this.liveCheck = new LiveCheck(this.tool, this.actions, this.metadir, stats); + } report("liveness checking initialized"); } else { this.liveCheck = new NoOpLiveCheck(this.tool, this.metadir); @@ -136,7 +160,8 @@ public abstract class AbstractChecker implements Cancelable */ public boolean setErrState(TLCState curState, TLCState succState, boolean keep) { - if (!TLCGlobals.continuation && this.done) + assert Thread.holdsLock(this) : "Caller thread has to hold monitor!"; + if (!TLCGlobals.continuation && this.done) return false; this.predErrState = curState; this.errState = (succState == null) ? curState : succState; @@ -151,8 +176,9 @@ public abstract class AbstractChecker implements Cancelable */ protected void reportCoverage(IWorker[] workers) { - if (TLCGlobals.coverageInterval >= 0) - { + // Without actions (empty spec) there won't be any statistics anyway. + if (TLCGlobals.coverageInterval >= 0 && this.actions.length > 0) + { MP.printMessage(EC.TLC_COVERAGE_START); // First collecting all counts from all workers: ObjLongTable counts = this.tool.getPrimedLocs(); @@ -212,8 +238,7 @@ public abstract class AbstractChecker implements Cancelable return true; } - // Start all the workers: - IdThread[] workers = startWorkers(this, depth); + workers = startWorkers(this, depth); // Check progress periodically: // Comment added by LL on 9 April 2012. The coverage is printed @@ -292,6 +317,16 @@ public abstract class AbstractChecker implements Cancelable this.cancellationFlag = flag; } + public final void setAllValues(int idx, Value val) { + for (int i = 0; i < this.workers.length; i++) { + workers[i].setLocalValue(idx, val); + } + } + + public final Value getValue(int i, int idx) { + return workers[i].getLocalValue(idx); + } + /** * Debugging support * @param message diff --git a/tlatools/src/tlc2/tool/DFIDModelChecker.java b/tlatools/src/tlc2/tool/DFIDModelChecker.java index 6cf83959cfe510fa255c5168eafb90180dc4e0f2..6c0c9109a91d02cd2aadad14166dd287439bc232 100644 --- a/tlatools/src/tlc2/tool/DFIDModelChecker.java +++ b/tlatools/src/tlc2/tool/DFIDModelChecker.java @@ -16,6 +16,7 @@ import tlc2.tool.liveness.LiveException; import tlc2.util.IdThread; import tlc2.util.LongVec; import tlc2.util.ObjLongTable; +import tlc2.util.SetOfStates; import util.FileUtil; import util.FilenameToStream; import util.UniqueString; @@ -128,8 +129,12 @@ public class DFIDModelChecker extends AbstractChecker // Always check liveness properties at the end: if (this.checkLiveness) { - MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS, - new String[] { "complete", Long.toString(this.theFPSet.size()) }); + // Print progress statistics prior to liveness checking. + // Liveness checking can take a substantial amount of time + // and thus give the user some clues at what stage safety + // checking is. + MP.printMessage(EC.TLC_PROGRESS_STATS_DFID, new String[] { + String.valueOf(this.numOfGenStates), String.valueOf(theFPSet.size()) }); // SZ Jul 10, 2009: what for? // ToolIO.out.flush(); success = liveCheck.finalCheck(); @@ -337,13 +342,11 @@ public class DFIDModelChecker extends AbstractChecker { boolean deadLocked = true; TLCState succState = null; - StateVec liveNextStates = null; - LongVec liveNextFPs = null; + SetOfStates liveNextStates = null; if (this.checkLiveness && isLeaf) { - liveNextStates = new StateVec(2); - liveNextFPs = new LongVec(2); + liveNextStates = new SetOfStates(INITIAL_CAPACITY * threadLocal.get()); } try @@ -364,15 +367,11 @@ public class DFIDModelChecker extends AbstractChecker // Check if the state is a legal state. if (!this.tool.isGoodState(succState)) { - if (this.setErrState(curState, succState, false)) - { - this.printTrace(EC.TLC_STATE_NOT_COMPLETELY_SPECIFIED_NEXT, null, curState, succState); - - synchronized (this) - { - this.notify(); - } - } + synchronized (this) { + if (this.setErrState(curState, succState, false)) { + this.printTrace(EC.TLC_STATE_NOT_COMPLETELY_SPECIFIED_NEXT, null, curState, succState); + } + } return allSuccNonLeaf; } @@ -406,8 +405,7 @@ public class DFIDModelChecker extends AbstractChecker // For liveness checking: if (this.checkLiveness && isLeaf) { - liveNextStates.addElement(succState); - liveNextFPs.addElement(fp); + liveNextStates.put(fp, succState); } } @@ -447,13 +445,15 @@ public class DFIDModelChecker extends AbstractChecker continue; } catch (Exception e) { - if (this.setErrState(curState, succState, true)) - { - this.printTrace(EC.TLC_INVARIANT_EVALUATION_FAILED, new String[] { this.tool - .getInvNames()[k] }, curState, succState); - this.notify(); - } - return allSuccNonLeaf; + synchronized (this) { + if (this.setErrState(curState, succState, true)) + { + this.printTrace(EC.TLC_INVARIANT_EVALUATION_FAILED, new String[] { this.tool + .getInvNames()[k] }, curState, succState); + this.notify(); + } + return allSuccNonLeaf; + } } } // Check if the state violates any implied action. We need to do it @@ -494,13 +494,15 @@ public class DFIDModelChecker extends AbstractChecker continue; } catch (Exception e) { - if (this.setErrState(curState, succState, true)) - { - this.printTrace(EC.TLC_ACTION_PROPERTY_EVALUATION_FAILED, new String[] { this.tool - .getImpliedActNames()[k] }, curState, succState); - this.notify(); - } - return allSuccNonLeaf; + synchronized (this) { + if (this.setErrState(curState, succState, true)) + { + this.printTrace(EC.TLC_ACTION_PROPERTY_EVALUATION_FAILED, new String[] { this.tool + .getImpliedActNames()[k] }, curState, succState); + this.notify(); + } + } + return allSuccNonLeaf; } } @@ -527,10 +529,17 @@ public class DFIDModelChecker extends AbstractChecker { // Add a stuttering step for curState: long curStateFP = curState.fingerPrint(); - liveNextStates.addElement(curState); - liveNextFPs.addElement(curStateFP); + liveNextStates.put(curStateFP, curState); // Add curState to the behavior graph: - liveCheck.addNextState(curState, curStateFP, liveNextStates, liveNextFPs); + liveCheck.addNextState(curState, curStateFP, liveNextStates); + + // Poor man's version of a controller. If necessary, try e.g. + // PID controller instead. + final int multiplier = threadLocal.get(); + if (liveNextStates.capacity() > (multiplier * INITIAL_CAPACITY)) { + // Increase initial size for as long as the set has to grow + threadLocal.set(multiplier + 1); + } } // We set curState DONE if diff --git a/tlatools/src/tlc2/tool/EvalException.java b/tlatools/src/tlc2/tool/EvalException.java index 1a872c961848806e8a824a38b0b43ed6a6fcc944..c8bdfe0e3a87beab4126642a486ca1a8299c0aa8 100644 --- a/tlatools/src/tlc2/tool/EvalException.java +++ b/tlatools/src/tlc2/tool/EvalException.java @@ -20,21 +20,29 @@ public class EvalException extends RuntimeException // private int type; - public EvalException(int errorCode, String[] parameters) + private final int errorCode; + + public EvalException(int errorCode, String[] parameters) { super(MP.getMessage(errorCode, parameters)); + this.errorCode = errorCode; } public EvalException(int errorCode, String parameter) { super(MP.getMessage(errorCode, parameter)); + this.errorCode = errorCode; } public EvalException(int errorCode) { - super(MP.getMessage(errorCode)); + super(MP.getMessage(errorCode)); + this.errorCode = errorCode; } + public int getErrorCode() { + return errorCode; + } // SZ Jul 14, 2009: refactored and deprecated, all usage changed to standard constructor // public EvalException(int type, String message) diff --git a/tlatools/src/tlc2/tool/IStateFunctor.java b/tlatools/src/tlc2/tool/IStateFunctor.java new file mode 100644 index 0000000000000000000000000000000000000000..1bbb29c21233bdaa11696a5079248c8711dfa9c4 --- /dev/null +++ b/tlatools/src/tlc2/tool/IStateFunctor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +/** + * An {@link IStateFunctor}'s responsibility is to accept generated (init) + * states via addElement and to process them. As the time of writing, known + * implementors are StateVec which simply stores all states passed to it for + * later use and ModelChecker.DoInitFunctor which checks each state right + * away. + */ +public interface IStateFunctor { + + Object addElement(TLCState state); + +} \ No newline at end of file diff --git a/tlatools/src/tlc2/tool/ModelChecker.java b/tlatools/src/tlc2/tool/ModelChecker.java index 5af9e893e274d91863be03c6b3ca3e8d697ca1b7..4d2d52f34f6ee5cb0ebbc4c7272c9f167da30561 100644 --- a/tlatools/src/tlc2/tool/ModelChecker.java +++ b/tlatools/src/tlc2/tool/ModelChecker.java @@ -21,10 +21,9 @@ import tlc2.tool.liveness.LiveCheck; import tlc2.tool.queue.DiskStateQueue; import tlc2.tool.queue.IStateQueue; import tlc2.util.IdThread; -import tlc2.util.LongVec; import tlc2.util.ObjLongTable; +import tlc2.util.SetOfStates; import tlc2.util.statistics.BucketStatistics; -import tlc2.value.Value; import util.DebugPrinter; import util.FileUtil; import util.FilenameToStream; @@ -53,6 +52,18 @@ public class ModelChecker extends AbstractChecker // used to calculate the spm metric public long distinctStatesPerMinute, statesPerMinute = 0L; protected long oldNumOfGenStates, oldFPSetSize = 0L; + /** + * Timestamp of when model checking started. + */ + private final long startTime = System.currentTimeMillis(); + /** + * The ratio between time spend on safety checking and liveness checking. + */ + private double runtimeRatio = 0d; + /** + * Flag set via JMX if liveness checking should be triggered. + */ + private boolean forceLiveCheck = false; /* Constructors */ /** @@ -100,7 +111,6 @@ public class ModelChecker extends AbstractChecker report("entering modelCheck()"); // needed to calculate state/minute in final progress report - final long startTime = System.currentTimeMillis(); boolean recovered = this.recover(); if (!recovered) @@ -166,13 +176,13 @@ public class ModelChecker extends AbstractChecker return; } + final String plural = (this.numOfGenStates.get() == 1) ? "" : "s"; if (this.numOfGenStates.get() == this.theFPSet.size()) { - String plural = (this.numOfGenStates.get() == 1) ? "" : "s"; MP.printMessage(EC.TLC_INIT_GENERATED1, new String[] { String.valueOf(this.numOfGenStates), plural }); } else { - MP.printMessage(EC.TLC_INIT_GENERATED1, new String[] { String.valueOf(this.numOfGenStates), + MP.printMessage(EC.TLC_INIT_GENERATED2, new String[] { String.valueOf(this.numOfGenStates), plural, String.valueOf(this.theFPSet.size()) }); } } @@ -181,8 +191,12 @@ public class ModelChecker extends AbstractChecker // Finished if there is no next state predicate: if (this.actions.length == 0) { - reportSuccess(this.theFPSet, this.numOfGenStates.get()); - this.printSummary(true, startTime); + if (this.theStateQueue.isEmpty()) { + reportSuccess(this.theFPSet, this.numOfGenStates.get()); + this.printSummary(true, startTime); + } else { + MP.printError(EC.TLC_STATES_AND_NO_NEXT_ACTION); + } this.cleanup(true); report("exiting with actions.length == 0"); return; @@ -203,8 +217,14 @@ public class ModelChecker extends AbstractChecker // Always check liveness properties at the end: if (this.checkLiveness) { - MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS, - new String[] { "complete", Long.toString(this.theFPSet.size()) }); + // Print progress statistics prior to liveness checking. + // Liveness checking can take a substantial amount of time + // and thus give the user some clues at what stage safety + // checking is. + MP.printMessage(EC.TLC_PROGRESS_STATS, new String[] { String.valueOf(this.trace.getLevelForReporting()), + String.valueOf(this.numOfGenStates), String.valueOf(theFPSet.size()), + String.valueOf(this.theStateQueue.size()) }); + report("checking liveness"); success = liveCheck.finalCheck(); report("liveness check complete"); @@ -300,90 +320,33 @@ public class ModelChecker extends AbstractChecker */ public final boolean doInit(boolean ignoreCancel) throws Throwable { - // SZ Feb 23, 2009: cancel flag set, quit + // SZ Feb 23, 2009: cancel flag set, quit if (!ignoreCancel && this.cancellationFlag) { - return false; - } - - TLCState curState = null; - - try - { - // Generate the initial states: - StateVec theInitStates = this.tool.getInitStates(); - this.numOfGenStates.set(theInitStates.size()); - for (int i = 0; i < theInitStates.size(); i++) - { - curState = theInitStates.elementAt(i); - // Check if the state is a legal state - if (!this.tool.isGoodState(curState)) - { - MP.printError(EC.TLC_INITIAL_STATE, curState.toString()); - return false; - } - boolean inModel = this.tool.isInModel(curState); - boolean seen = false; - if (inModel) - { - long fp = curState.fingerPrint(); - seen = this.theFPSet.put(fp); - if (!seen) - { - if (this.allStateWriter != null) - { - this.allStateWriter.writeState(curState); - } - curState.uid = this.trace.writeState(fp); - this.theStateQueue.enqueue(curState); + return false; + } - // build behavior graph for liveness checking - if (this.checkLiveness) - { - liveCheck.addInitState(curState, fp); - } - } - } - // Check properties of the state: - OutputCollector.setInitialState(curState); - if (!seen) - { - for (int j = 0; j < this.invariants.length; j++) - { - if (!this.tool.isValid(this.invariants[j], curState)) - { - // We get here because of invariant violation: - MP.printError(EC.TLC_INVARIANT_VIOLATED_INITIAL, new String[] { - this.tool.getInvNames()[j].toString(), curState.toString() }); - if (!TLCGlobals.continuation) - return false; - } - } - for (int j = 0; j < this.impliedInits.length; j++) - { - if (!this.tool.isValid(this.impliedInits[j], curState)) - { - // We get here because of implied-inits violation: - MP.printError(EC.TLC_PROPERTY_VIOLATED_INITIAL, new String[] { - this.tool.getImpliedInitNames()[j], curState.toString() }); - return false; - } - } - } - } - } catch (Throwable e) - { - // Assert.printStack(e); - if (e instanceof OutOfMemoryError) - { - MP.printError(EC.SYSTEM_OUT_OF_MEMORY_TOO_MANY_INIT); - return false; - } - this.errState = curState; - throw e; - } - return true; - } + // Generate the initial states. + // + // The functor is passed to getInitStates() to - instead of adding all + // init states into an intermediate StateVec to check and add each state + // in a subsequent loop - directly check each state one-by-one and add + // it to the queue, fingerprint set and trace file. This avoids + // allocating memory for StateVec (which depending on the number of init + // states can grow to be GBs) and the subsequent loop over StateVec. + final DoInitFunctor functor = new DoInitFunctor(); + this.tool.getInitStates(functor); + + // Iff one of the init states' checks violates any properties, the + // functor will record it. + if (functor.errState != null) { + this.errState = functor.errState; + throw functor.e; + } + + // Return whatever the functor has recorded. + return functor.returnValue; + } /** * Compute the set of the next states. For each next state, check that @@ -403,95 +366,109 @@ public class ModelChecker extends AbstractChecker boolean deadLocked = true; TLCState succState = null; - StateVec liveNextStates = null; - LongVec liveNextFPs = null; + SetOfStates liveNextStates = null; if (this.checkLiveness) { - liveNextStates = new StateVec(2); - liveNextFPs = new LongVec(2); + liveNextStates = new SetOfStates(INITIAL_CAPACITY * threadLocal.get()); } try { - int k = 0; - // <-- - // <-- + int k = 0; + // <-- + // <-- for (int i = 0; i < this.actions.length; i++) { - // SZ Feb 23, 2009: cancel the calculation + // SZ Feb 23, 2009: cancel the calculation if (this.cancellationFlag) { - return false; - } + return false; + } - StateVec nextStates = this.tool.getNextStates(this.actions[i], curState); - int sz = nextStates.size(); - this.incNumOfGenStates(sz); - deadLocked = deadLocked && (sz == 0); + //TODO Implement IStateFunctor pattern for getNextStates() too + // to reduce memory and runtime overhead of allocating and + // looping StateVec. However - contrary to doInit() - doNext() + // is incompatible to the functor when liveness checking is + // turned on. Liveness checking does not support adding + // nextStates one-by-one but expects to be given the whole set + // of nextStates in a single invocation + // (LiveCheck#addNextState(..). If this limitation is ever + // removed, the functor pattern could be applied to doNext too. + StateVec nextStates = this.tool.getNextStates(this.actions[i], curState); + int sz = nextStates.size(); + this.incNumOfGenStates(sz); + deadLocked = deadLocked && (sz == 0); - for (int j = 0; j < sz; j++) + SUCCESSORS: for (int j = 0; j < sz; j++) { - succState = nextStates.elementAt(j); - // Check if succState is a legal state. + succState = nextStates.elementAt(j); + // Check if succState is a legal state. if (!this.tool.isGoodState(succState)) { - if (this.setErrState(curState, succState, false)) - { - MP.printError(EC.TLC_STATE_NOT_COMPLETELY_SPECIFIED_NEXT); - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - - synchronized (this) - { - this.notify(); - } - } - return true; - } + synchronized (this) { + if (this.setErrState(curState, succState, false)) + { + MP.printError(EC.TLC_STATE_NOT_COMPLETELY_SPECIFIED_NEXT); + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + + synchronized (this) + { + this.notify(); + } + } + return true; + } + } if (TLCGlobals.coverageInterval >= 0) { - ((TLCStateMutSource) succState).addCounts(counts); - } + ((TLCStateMutSource) succState).addCounts(counts); + } - boolean inModel = (this.tool.isInModel(succState) && this.tool.isInActions(curState, succState)); - boolean seen = false; + final boolean inModel = (this.tool.isInModel(succState) && this.tool.isInActions(curState, succState)); + boolean seen = false; if (inModel) { - long fp = succState.fingerPrint(); - seen = this.theFPSet.put(fp); + long fp = succState.fingerPrint(); + seen = this.theFPSet.put(fp); if (!seen) { - // Write out succState when needed: + // Write out succState when needed: if (this.allStateWriter != null) { - this.allStateWriter.writeState(succState); - } - // Enqueue succState only if it satisfies the model constraints: - long loc = this.trace.writeState(curState, fp); - succState.uid = loc; - this.theStateQueue.sEnqueue(succState); - } - // For liveness checking: + this.allStateWriter.writeState(succState); + } + // Write succState to trace only if it satisfies the + // model constraints. Do not enqueue it yet, but wait + // for implied actions and invariants to be checked. + // Those checks - if violated - will cause model checking + // to terminate. Thus we cannot let concurrent workers start + // exploring this new state. Conversely, the state has to + // be in the trace in case either invariant or implied action + // checks want to print the trace. + long loc = this.trace.writeState(curState, fp); + succState.uid = loc; + } + // For liveness checking: if (this.checkLiveness) { - liveNextStates.addElement(succState); - liveNextFPs.addElement(fp); - } - } - // Check if succState violates any invariant: + liveNextStates.put(fp, succState); + } + } + // Check if succState violates any invariant: if (!seen) { try { - int len = this.invariants.length; - for (k = 0; k < len; k++) + int len = this.invariants.length; + INVARIANTS: for (k = 0; k < len; k++) { - // SZ Feb 23, 2009: cancel the calculation + // SZ Feb 23, 2009: cancel the calculation if (this.cancellationFlag) { - return false; - } + return false; + } if (!tool.isValid(this.invariants[k], succState)) { @@ -500,53 +477,67 @@ public class ModelChecker extends AbstractChecker { if (TLCGlobals.continuation) { - MP.printError(EC.TLC_INVARIANT_VIOLATED_BEHAVIOR, - this.tool.getInvNames()[k]); - this.trace.printTrace(curState, succState); - break; + MP.printError(EC.TLC_INVARIANT_VIOLATED_BEHAVIOR, + this.tool.getInvNames()[k]); + this.trace.printTrace(curState, succState); + break INVARIANTS; } else { if (this.setErrState(curState, succState, false)) { MP.printError(EC.TLC_INVARIANT_VIOLATED_BEHAVIOR, this.tool .getInvNames()[k]); - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - this.notify(); - } - return true; - } - } - } - } - if (k < len) - continue; + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + this.notify(); + } + return true; + } + } + } + } + if (k < len) { + if (inModel && !seen) { + // Even though the state violates an + // invariant, add it to the queue. After + // all, the user selected to continue model + // checking even if an invariant is + // violated. + this.theStateQueue.sEnqueue(succState); + } + // Continue with next successor iff an + // invariant is violated and + // TLCGlobals.continuation is true. + continue SUCCESSORS; + } } catch (Exception e) { - if (this.setErrState(curState, succState, true)) - { - MP.printError(EC.TLC_INVARIANT_EVALUATION_FAILED, new String[] { - this.tool.getInvNames()[k], - (e.getMessage()==null)?e.toString():e.getMessage() }); - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - this.notify(); - } - throw e; - } - } + synchronized (this) { + if (this.setErrState(curState, succState, true)) + { + MP.printError(EC.TLC_INVARIANT_EVALUATION_FAILED, new String[] { + this.tool.getInvNames()[k], + (e.getMessage() == null) ? e.toString() : e.getMessage() }); + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + this.notify(); + } + throw e; + } + } + } // Check if the state violates any implied action. We need to do it // even if succState is not new. try { - int len = this.impliedActions.length; - for (k = 0; k < len; k++) + int len = this.impliedActions.length; + IMPLIED: for (k = 0; k < len; k++) { - // SZ Feb 23, 2009: cancel the calculation + // SZ Feb 23, 2009: cancel the calculation if (this.cancellationFlag) { - return false; - } + return false; + } if (!tool.isValid(this.impliedActions[k], curState, succState)) { @@ -557,92 +548,123 @@ public class ModelChecker extends AbstractChecker { MP.printError(EC.TLC_ACTION_PROPERTY_VIOLATED_BEHAVIOR, this.tool .getImpliedActNames()[k]); - this.trace.printTrace(curState, succState); - break; + this.trace.printTrace(curState, succState); + break IMPLIED; } else { if (this.setErrState(curState, succState, false)) { MP.printError(EC.TLC_ACTION_PROPERTY_VIOLATED_BEHAVIOR, this.tool .getImpliedActNames()[k]); - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - this.notify(); - } - return true; - } - } - } - } - if (k < len) - continue; + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + this.notify(); + } + return true; + } + } + } + } + if (k < len) { + if (inModel && !seen) { + // Even though the state violates an + // invariant, add it to the queue. After + // all, the user selected to continue model + // checking even if an implied action is + // violated. + this.theStateQueue.sEnqueue(succState); + } + // Continue with next successor iff an + // implied action is violated and + // TLCGlobals.continuation is true. + continue SUCCESSORS; + } } catch (Exception e) { - if (this.setErrState(curState, succState, true)) - { - MP.printError(EC.TLC_ACTION_PROPERTY_EVALUATION_FAILED, new String[] { - this.tool.getImpliedActNames()[k], - (e.getMessage()==null)?e.toString():e.getMessage() }); - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - this.notify(); - } - throw e; + synchronized (this) { + if (this.setErrState(curState, succState, true)) + { + MP.printError(EC.TLC_ACTION_PROPERTY_EVALUATION_FAILED, new String[] { + this.tool.getImpliedActNames()[k], + (e.getMessage() == null) ? e.toString() : e.getMessage() }); + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + this.notify(); + } + throw e; + } + } + if (inModel && !seen) { + // The state is inModel, unseen and neither invariants + // nor implied actions are violated. It is thus eligible + // for further processing by other workers. + this.theStateQueue.sEnqueue(succState); } - } - // Must set state to null!!! - succState = null; - } - // Check for deadlock: + } + // Must set state to null!!! + succState = null; + } + // Check for deadlock: if (deadLocked && this.checkDeadlock) { synchronized (this) { if (this.setErrState(curState, null, false)) { - MP.printError(EC.TLC_DEADLOCK_REACHED); - this.trace.printTrace(curState, null); - this.theStateQueue.finishAll(); - this.notify(); - } - } - return true; - } + MP.printError(EC.TLC_DEADLOCK_REACHED); + this.trace.printTrace(curState, null); + this.theStateQueue.finishAll(); + this.notify(); + } + } + return true; + } // Finally, add curState into the behavior graph for liveness checking: if (this.checkLiveness) { - // Add the stuttering step: - long curStateFP = curState.fingerPrint(); - liveNextStates.addElement(curState); - liveNextFPs.addElement(curStateFP); - liveCheck.addNextState(curState, curStateFP, liveNextStates, liveNextFPs); + // Add the stuttering step: + long curStateFP = curState.fingerPrint(); + liveNextStates.put(curStateFP, curState); + liveCheck.addNextState(curState, curStateFP, liveNextStates); + + // Poor man's version of a controller. If necessary, try e.g. + // PID controller instead. + final int multiplier = threadLocal.get(); + if (liveNextStates.capacity() > (multiplier * INITIAL_CAPACITY)) { + // Increase initial size for as long as the set has to grow + threadLocal.set(multiplier + 1); + } } - return false; + return false; } catch (Throwable e) { - // Assert.printStack(e); - boolean keep = ((e instanceof StackOverflowError) || (e instanceof OutOfMemoryError)); + // Assert.printStack(e); + boolean keep = ((e instanceof StackOverflowError) || (e instanceof OutOfMemoryError) + || (e instanceof AssertionError)); synchronized (this) { if (this.setErrState(curState, succState, !keep)) { if (e instanceof StackOverflowError) { - MP.printError(EC.SYSTEM_STACK_OVERFLOW, e); + MP.printError(EC.SYSTEM_STACK_OVERFLOW, e); } else if (e instanceof OutOfMemoryError) { - MP.printError(EC.SYSTEM_OUT_OF_MEMORY, e); + MP.printError(EC.SYSTEM_OUT_OF_MEMORY, e); + } else if (e instanceof AssertionError) + { + MP.printError(EC.TLC_BUG, e); } else if (e.getMessage() != null) { MP.printError(EC.GENERAL, e); // LL changed call 7 April 2012 - } - this.trace.printTrace(curState, succState); - this.theStateQueue.finishAll(); - this.notify(); - } - } - throw e; - } + } + this.trace.printTrace(curState, succState); + this.theStateQueue.finishAll(); + this.notify(); + } + } + throw e; + } } /** @@ -653,16 +675,37 @@ public class ModelChecker extends AbstractChecker */ public final boolean doPeriodicWork() throws Exception { + // Remember if checkpointing should be run. doCheckPoint() when called + // internally diffs the time expired since its last invocation which is + // only milliseconds here when called twice. + final boolean createCheckPoint = TLCGlobals.doCheckPoint(); + if ((!this.checkLiveness || runtimeRatio > TLCGlobals.livenessRatio || !liveCheck.doLiveCheck()) && !forceLiveCheck && !createCheckPoint) { + updateRuntimeRatio(0L); + + // Do not suspend the state queue if neither check-pointing nor + // liveness-checking is going to happen. Suspending is expensive. + // It stops all workers. + return true; + } + if (this.theStateQueue.suspendAll()) { // Run liveness checking, if needed: - if (this.checkLiveness) + // The ratio set in TLCGlobals defines an upper bound for the + // runtime dedicated to liveness checking. + if (this.checkLiveness && (runtimeRatio < TLCGlobals.livenessRatio || forceLiveCheck)) { - if (!liveCheck.check(false)) - return false; + final long preLivenessChecking = System.currentTimeMillis(); + if (!liveCheck.check(forceLiveCheck)) { + return false; + } + forceLiveCheck = false; + updateRuntimeRatio(System.currentTimeMillis() - preLivenessChecking); + } else if (runtimeRatio > TLCGlobals.livenessRatio) { + updateRuntimeRatio(0L); } - if (TLCGlobals.doCheckPoint()) { + if (createCheckPoint) { // Checkpoint: MP.printMessage(EC.TLC_CHECKPOINT_START, this.metadir); @@ -694,6 +737,46 @@ public class ModelChecker extends AbstractChecker return true; } + public void forceLiveCheck() { + forceLiveCheck = true; + } + + protected void updateRuntimeRatio(final long delta) { + assert delta >= 0L; + + // Absolute runtime from TLC startup to now (includes liveness + // checking, even the current delta). + long totalRuntime = System.currentTimeMillis() - startTime; + + // Subtract a progressInterval to account for the fact that the + // previously recorded runtimeRatio was calculated with totalRuntime + // from the previous progressReporting interval. updateRuntimeRatio is + // called from doPeriodicWork which executes every progressIntervall. + // This is an approximation because the last invocation could have + // happened longer ago than progressInterval if e.g. checkpointing + // blocked the doPeriodicWork thread. + totalRuntime = totalRuntime - TLCGlobals.progressInterval; + + // Subtract delta from the totalRuntime + totalRuntime = totalRuntime - delta; + + // Absolute time spent on all liveness checks from TLC + // startup up to now (without delta). Iff no liveness checking has been + // executed so far, the absolute time is obviously 0. totalRuntime + // can also be negative. + final double absLivenessRuntime = Math.max(totalRuntime * runtimeRatio, 0); + + // Sum up the absLivenessRuntime with the new delta. It is the current + // absolute time for liveness checking. Divide it by overall + // totalRuntime (including progressInterval and delta) to calculate the + // new ratio. + runtimeRatio = (delta + absLivenessRuntime) / (totalRuntime + TLCGlobals.progressInterval + delta); + } + + public double getRuntimeRatio() { + return runtimeRatio; + } + public final boolean recover() throws IOException { boolean recovered = false; @@ -972,19 +1055,6 @@ public class ModelChecker extends AbstractChecker // } // } - public final void setAllValues(int idx, Value val) - { - for (int i = 0; i < this.workers.length; i++) - { - workers[i].setLocalValue(idx, val); - } - } - - public final Value getValue(int i, int idx) - { - return workers[i].getLocalValue(idx); - } - /** * Spawn the worker threads */ @@ -1064,4 +1134,101 @@ public class ModelChecker extends AbstractChecker public long getStatesGenerated() { return numOfGenStates.get(); } + + /** + * An implementation of {@link IStateFunctor} for + * {@link ModelChecker#doInit(boolean)}. + */ + private class DoInitFunctor implements IStateFunctor { + + /** + * Non-Null iff a violation occurred. + */ + private TLCState errState; + private Throwable e; + + /** + * The return values of addElement are meaningless, but doInit wants to + * know the actual outcome when all init states have been processed. + * This outcome is stored as returnValue. + */ + private boolean returnValue = true; + + /* (non-Javadoc) + * @see tlc2.tool.IStateFunctor#addElement(tlc2.tool.TLCState) + */ + public Object addElement(final TLCState curState) { + incNumOfGenStates(1); + + // getInitStates() does not support aborting init state generation + // once a violation has been found (that is why the return values of + // addElement are meaningless). It continues until all init + // states have been generated. Thus, the functor simply ignores + // subsequent states once a violation has been recorded. + if (errState != null) { + returnValue = false; + return returnValue; + } + + try { + // Check if the state is a legal state + if (!tool.isGoodState(curState)) { + MP.printError(EC.TLC_INITIAL_STATE, curState.toString()); + return returnValue; + } + boolean inModel = tool.isInModel(curState); + boolean seen = false; + if (inModel) { + long fp = curState.fingerPrint(); + seen = theFPSet.put(fp); + if (!seen) { + if (allStateWriter != null) { + allStateWriter.writeState(curState); + } + curState.uid = trace.writeState(fp); + theStateQueue.enqueue(curState); + + // build behavior graph for liveness checking + if (checkLiveness) { + liveCheck.addInitState(curState, fp); + } + } + } + // Check properties of the state: + OutputCollector.setInitialState(curState); + if (!seen) { + for (int j = 0; j < invariants.length; j++) { + if (!tool.isValid(invariants[j], curState)) { + // We get here because of invariant violation: + MP.printError(EC.TLC_INVARIANT_VIOLATED_INITIAL, + new String[] { tool.getInvNames()[j].toString(), curState.toString() }); + if (!TLCGlobals.continuation) { + returnValue = false; + return returnValue; + } + } + } + for (int j = 0; j < impliedInits.length; j++) { + if (!tool.isValid(impliedInits[j], curState)) { + // We get here because of implied-inits violation: + MP.printError(EC.TLC_PROPERTY_VIOLATED_INITIAL, + new String[] { tool.getImpliedInitNames()[j], curState.toString() }); + returnValue = false; + return returnValue; + } + } + } + } catch (Throwable e) { + // Assert.printStack(e); + if (e instanceof OutOfMemoryError) { + MP.printError(EC.SYSTEM_OUT_OF_MEMORY_TOO_MANY_INIT); + returnValue = false; + return returnValue; + } + this.errState = curState; + this.e = e; + } + return returnValue; + } + } } diff --git a/tlatools/src/tlc2/tool/Simulator.java b/tlatools/src/tlc2/tool/Simulator.java index 6edceb687878dad53fd38dcdd03733b9c46f6454..7bdeb0343076c361be15187232f9efb290188b2a 100644 --- a/tlatools/src/tlc2/tool/Simulator.java +++ b/tlatools/src/tlc2/tool/Simulator.java @@ -22,6 +22,7 @@ import tlc2.tool.liveness.NoOpLiveCheck; import tlc2.util.ObjLongTable; import tlc2.util.RandomGenerator; import tlc2.util.statistics.DummyBucketStatistics; +import tlc2.value.Value; import util.FileUtil; import util.FilenameToStream; @@ -112,6 +113,7 @@ public class Simulator implements Cancelable { private long aril; private final ObjLongTable astCounts; private boolean isCancelled; // SZ Feb 24, 2009: cancellation added + private Value[] localValues = new Value[4]; /* * This method does simulation on a TLA+ spec. Its argument specifies the @@ -363,6 +365,22 @@ public class Simulator implements Cancelable { return null; } + public Value getLocalValue(int idx) { + if (idx < this.localValues.length) { + return this.localValues[idx]; + } + return null; + } + + public void setLocalValue(int idx, Value val) { + if (idx >= this.localValues.length) { + Value[] vals = new Value[idx + 1]; + System.arraycopy(this.localValues, 0, vals, 0, this.localValues.length); + this.localValues = vals; + } + this.localValues[idx] = val; + } + /** * Prints the summary */ diff --git a/tlatools/src/tlc2/tool/Spec.java b/tlatools/src/tlc2/tool/Spec.java index 382e6291e92aca3ed1460724dcc57eed5fe32c4a..ab5c1f9a6f6f9572b3acce050e98ab85850ed466 100644 --- a/tlatools/src/tlc2/tool/Spec.java +++ b/tlatools/src/tlc2/tool/Spec.java @@ -12,6 +12,7 @@ import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; +import java.util.Set; import tla2sany.drivers.FrontEndException; import tla2sany.drivers.SANY; @@ -70,7 +71,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable protected ModelConfig config; // The model configuration. protected ExternalModuleTable moduleTbl; // The external modules reachable from root protected ModuleNode rootModule; // The root module. - protected HashSet processedDefs ; + protected Set<OpDefNode> processedDefs ; // The set of OpDefNodes on which processSpec has been called. // Added by LL & YY on 25 June 2014 to eliminate infinite // loop when a recursively defined operator is used as an @@ -100,7 +101,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable public Spec(String specDir, String file, FilenameToStream resolver) { - this.processedDefs = new HashSet(); + this.processedDefs = new HashSet<OpDefNode>(); this.specDir = specDir; this.rootFile = file; this.rootModule = null; @@ -264,7 +265,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable // here since we use defns. Things added into defns later will make it // wrong to use it in the method processConstants. ModuleNode[] mods = this.moduleTbl.getModuleNodes(); - HashSet modSet = new HashSet(); + Set<String> modSet = new HashSet<String>(); for (int i = 0; i < mods.length; i++) { this.processConstants(mods[i]); @@ -347,7 +348,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable { // Override with a user defined Java class for the TLA+ module. // Collects new definitions: - Hashtable javaDefs = new Hashtable(); + Hashtable<UniqueString, Value> javaDefs = new Hashtable<UniqueString, Value>(); Method[] mds = userModule.getDeclaredMethods(); for (int j = 0; j < mds.length; j++) { @@ -378,7 +379,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable } } - HashSet overriden = new HashSet(); + Set<String> overriden = new HashSet<String>(); // Apply config file overrides to constants: for (int i = 0; i < rootConsts.length; i++) { @@ -527,7 +528,7 @@ public class Spec implements ValueConstants, ToolGlobals, Serializable Object def = opDefs[i].getToolObject(TLCGlobals.ToolId); if (def instanceof OpDefNode) { - this.processedDefs.add(def); + this.processedDefs.add((OpDefNode) def); this.processConstants(((OpDefNode) def).getBody()); } this.processConstants(opDefs[i].getBody()); diff --git a/tlatools/src/tlc2/tool/StateVec.java b/tlatools/src/tlc2/tool/StateVec.java index b31de9d6c166da9a1faa7a6d502f22cb62a12fa2..82465eb6477090e0a08ab1a0bc8a56d933e2c7be 100644 --- a/tlatools/src/tlc2/tool/StateVec.java +++ b/tlatools/src/tlc2/tool/StateVec.java @@ -18,7 +18,7 @@ import util.UniqueString; * updates are used for improved performance and reduced * allocation. */ -public final class StateVec { +public final class StateVec implements IStateFunctor { private TLCState v[]; private int size; @@ -84,6 +84,9 @@ public final class StateVec { return this; } + /* (non-Javadoc) + * @see tlc2.tool.IStateFunction#addElement(tlc2.tool.TLCState) + */ public final StateVec addElement(TLCState state) { if (this.size >= this.v.length) { grow(1); } this.v[this.size++] = state; diff --git a/tlatools/src/tlc2/tool/TLARegistry.java b/tlatools/src/tlc2/tool/TLARegistry.java index 4350e3ddf8f8da20d3b36b8021e381f4d913153c..286752857afbbcf768b3c2cffe1a907bc806eccf 100644 --- a/tlatools/src/tlc2/tool/TLARegistry.java +++ b/tlatools/src/tlc2/tool/TLARegistry.java @@ -13,14 +13,13 @@ import java.util.Hashtable; * <br><b>Note:</b> * * @author Simon Zambrovski - * @version $Id$ */ public class TLARegistry { - private static Hashtable javaToTLA = new Hashtable(); + private static final Hashtable<String, String> javaToTLA = new Hashtable<String, String>(); public static String get(String name) { - return (String)javaToTLA.get(name); + return javaToTLA.get(name); } /** @@ -30,7 +29,7 @@ public class TLARegistry { * @return the previous value, if one */ public static String put(String tname, String jname) { - return (String)javaToTLA.put(tname, jname); + return javaToTLA.put(tname, jname); } public static String mapName(String name) { @@ -41,7 +40,7 @@ public class TLARegistry { /* Used only for debugging. */ public static String allNames() { StringBuffer sb = new StringBuffer("{"); - Enumeration eNames = javaToTLA.keys(); + Enumeration<String> eNames = javaToTLA.keys(); if (eNames.hasMoreElements()) { sb.append(eNames.nextElement()); } diff --git a/tlatools/src/tlc2/tool/TLCStateInfo.java b/tlatools/src/tlc2/tool/TLCStateInfo.java index e76fa69aca581122db6ee9f0baba32e153e13901..9bcb2d8bd0a8e90f089da16b14e16a4916340f8b 100644 --- a/tlatools/src/tlc2/tool/TLCStateInfo.java +++ b/tlatools/src/tlc2/tool/TLCStateInfo.java @@ -10,17 +10,42 @@ public class TLCStateInfo { public long stateNumber; public TLCState state; public Object info; + public Long fp; public TLCStateInfo(TLCState s, Object info) { this.state = s; this.info = info; } + public TLCStateInfo(TLCState s, String info, int stateNum, long fp) { + this(s, info); + stateNumber = stateNum; + this.fp = fp; + } + public final long fingerPrint() { - return this.state.fingerPrint(); + if (fp == null) { + fp = this.state.fingerPrint(); + } + return fp.longValue(); } public final String toString() { return this.state.toString(); } + + public boolean equals(Object other) { + if (other instanceof TLCStateInfo) { + TLCStateInfo sinfo = (TLCStateInfo) other; + return this.state.equals(sinfo.state); + } else if (other instanceof TLCState) { + TLCState state = (TLCState) other; + return this.state.equals(state); + } + return false; + } + + public int hashCode() { + return this.state.hashCode(); + } } diff --git a/tlatools/src/tlc2/tool/TLCStateMut.java b/tlatools/src/tlc2/tool/TLCStateMut.java index 985f3e638c6b0d0ba45d1eb755bb1b93b0661cdf..a6705ec418ace02a9eaed62b54aaf30a96b90cbb 100644 --- a/tlatools/src/tlc2/tool/TLCStateMut.java +++ b/tlatools/src/tlc2/tool/TLCStateMut.java @@ -59,6 +59,7 @@ public final class TLCStateMut extends TLCState implements Cloneable, Serializab return new TLCStateMut(vals); } + //TODO equals without hashcode! public final boolean equals(Object obj) { if (obj instanceof TLCStateMut) { TLCStateMut state = (TLCStateMut)obj; @@ -145,59 +146,67 @@ public final class TLCStateMut extends TLCState implements Cloneable, Serializab * via the state queue. They have to be normalized before adding to * the state queue. We do that here. */ - public final long fingerPrint() { - int sz = this.values.length; + public final long fingerPrint() { + int sz = this.values.length; - Value[] minVals = this.values; - if (perms != null) { - Value[] vals = new Value[sz]; - // Find the "smallest" state under the symmetry permutations: - for (int i = 0; i < perms.length; i++) { - int cmp = 0; - for (int j = 0; j < sz; j++) { - vals[j] = this.values[j].permute(perms[i]); - if (cmp == 0) { - cmp = vals[j].compareTo(minVals[j]); - } + // minVals is what is used to calculate/generate the fingerprint below. + // If this state is part of a symmetry permutation and not the smallest + // member, its current minVals will be replaced temporarily with the + // values of the smallest state for the calculation of the fingerprint. + Value[] minVals = this.values; + if (perms != null) { + Value[] vals = new Value[sz]; + // Find the "smallest" state under the symmetry permutations: + for (int i = 0; i < perms.length; i++) { + int cmp = 0; + // For each value in values succinctly permute the current value + // and compare it to minVals. + for (int j = 0; j < sz; j++) { + vals[j] = this.values[j].permute(perms[i]); + if (cmp == 0) { + // Compare the permuted values to the minVals unless an + // earlier compare has found a different already. + cmp = vals[j].compareTo(minVals[j]); + } + } + // cmp < 0 means the current state is part of a symmetry + // permutation set/group and not the "smallest" one. + if (cmp < 0) { + if (minVals == this.values) { + minVals = vals; + vals = new Value[sz]; + } else { + Value[] temp = minVals; + minVals = vals; + vals = temp; + } + } + } + } + // Fingerprint the state: + long fp = FP64.New(); + if (viewMap == null) { + for (int i = 0; i < sz; i++) { + fp = minVals[i].fingerPrint(fp); + } + if (this.values != minVals) { + for (int i = 0; i < sz; i++) { + this.values[i].deepNormalize(); + } + } + } else { + for (int i = 0; i < sz; i++) { + this.values[i].deepNormalize(); + } + TLCStateMut state = this; + if (minVals != this.values) { + state = new TLCStateMut(minVals); + } + Value val = mytool.eval(viewMap, Context.Empty, state); + fp = val.fingerPrint(fp); + } + return fp; } - if (cmp < 0) { - if (minVals == this.values) { - minVals = vals; - vals = new Value[sz]; - } - else { - Value[] temp = minVals; - minVals = vals; - vals = temp; - } - } - } - } - // Fingerprint the state: - long fp = FP64.New(); - if (viewMap == null) { - for (int i = 0; i < sz; i++) { - fp = minVals[i].fingerPrint(fp); - } - if (this.values != minVals) { - for (int i = 0; i < sz; i++) { - this.values[i].deepNormalize(); - } - } - } - else { - for (int i = 0; i < sz; i++) { - this.values[i].deepNormalize(); - } - TLCStateMut state = this; - if (minVals != this.values) { - state = new TLCStateMut(minVals); - } - Value val = mytool.eval(viewMap, Context.Empty, state); - fp = val.fingerPrint(fp); - } - return fp; - } public final boolean allAssigned() { int len = this.values.length; diff --git a/tlatools/src/tlc2/tool/Tool.java b/tlatools/src/tlc2/tool/Tool.java index 594fbc36dfdc032ce84823b761d35939004b6724..8c7992bf4ba8884acb688d8ce89e230591788dfb 100644 --- a/tlatools/src/tlc2/tool/Tool.java +++ b/tlatools/src/tlc2/tool/Tool.java @@ -6,6 +6,7 @@ package tlc2.tool; import tla2sany.modanalyzer.SpecObj; +import tla2sany.semantic.APSubstInNode; import tla2sany.semantic.ExprNode; import tla2sany.semantic.ExprOrOpArgNode; import tla2sany.semantic.FormalParamNode; @@ -19,7 +20,6 @@ import tla2sany.semantic.OpDefNode; import tla2sany.semantic.SemanticNode; import tla2sany.semantic.Subst; import tla2sany.semantic.SubstInNode; -import tla2sany.semantic.APSubstInNode; import tla2sany.semantic.SymbolNode; import tla2sany.semantic.ThmOrAssumpDefNode; import tlc2.TLCGlobals; @@ -306,9 +306,14 @@ public class Tool * probably make tools like TLC useless. */ public final StateVec getInitStates() { + final StateVec initStates = new StateVec(0); + getInitStates(initStates); + return initStates; + } + + public final void getInitStates(IStateFunctor functor) { Vect init = this.getInitStateSpec(); ActionItemList acts = ActionItemList.Empty; - StateVec initStates = new StateVec(0); for (int i = 1; i < init.size(); i++) { Action elem = (Action)init.elementAt(i); acts = acts.cons(elem.pred, elem.con, -1); @@ -316,9 +321,8 @@ public class Tool if (init.size() != 0) { Action elem = (Action)init.elementAt(0); TLCState ps = TLCState.Empty.createEmpty(); - this.getInitStates(elem.pred, acts, elem.con, ps, initStates); + this.getInitStates(elem.pred, acts, elem.con, ps, functor); } - return initStates; } /* Create the state specified by pred. */ @@ -338,7 +342,7 @@ public class Tool } private final void getInitStates(SemanticNode init, ActionItemList acts, - Context c, TLCState ps, StateVec states) { + Context c, TLCState ps, IStateFunctor states) { switch (init.getKind()) { case OpApplKind: { @@ -396,7 +400,7 @@ public class Tool } } - private final void getInitStates(ActionItemList acts, TLCState ps, StateVec states) { + private final void getInitStates(ActionItemList acts, TLCState ps, IStateFunctor states) { if (acts.isEmpty()) { states.addElement(ps.copy()); } @@ -408,7 +412,7 @@ public class Tool } private final void getInitStatesAppl(OpApplNode init, ActionItemList acts, - Context c, TLCState ps, StateVec states) { + Context c, TLCState ps, IStateFunctor states) { ExprOrOpArgNode[] args = init.getArgs(); int alen = args.length; SymbolNode opNode = init.getOperator(); @@ -700,7 +704,7 @@ public class Tool * This method returns the set of next states when taking the action * in the given state. */ - public final StateVec getNextStates(Action action, TLCState state) { + public /*final*/ StateVec getNextStates(Action action, TLCState state) { ActionItemList acts = ActionItemList.Empty; TLCState s1 = TLCState.Empty.createEmpty(); StateVec nss = new StateVec(0); @@ -2093,7 +2097,7 @@ public class Tool } /* This method determines if a state satisfies the model constraints. */ - public final boolean isInModel(TLCState state) throws EvalException { + public /*final*/ boolean isInModel(TLCState state) throws EvalException { ExprNode[] constrs = this.getModelConstraints(); for (int i = 0; i < constrs.length; i++) { Value bval = this.eval(constrs[i], Context.Empty, state); @@ -2106,7 +2110,7 @@ public class Tool } /* This method determines if a pair of states satisfy the action constraints. */ - public final boolean isInActions(TLCState s1, TLCState s2) throws EvalException { + public /*final*/ boolean isInActions(TLCState s1, TLCState s2) throws EvalException { ExprNode[] constrs = this.getActionConstraints(); for (int i = 0; i < constrs.length; i++) { Value bval = this.eval(constrs[i], Context.Empty, s1, s2, EvalControl.Clear); @@ -2710,12 +2714,31 @@ public class Tool long nfp = state.fingerPrint(); if (fp == nfp) { String info = "<Initial predicate>"; - return new TLCStateInfo(state, info); + final TLCStateInfo tlcStateInfo = new TLCStateInfo(state, info, 1, fp); + return tlcStateInfo; } } return null; } + /** + * Reconstruct the next state of state s whose fingerprint is fp. + * + * @return Returns the TLCState wrapped in TLCStateInfo. TLCStateInfo stores + * the stateNumber (relative to the given sinfo) and a pointer to + * the predecessor. + */ + public final TLCStateInfo getState(long fp, TLCStateInfo sinfo) { + final TLCStateInfo tlcStateInfo = getState(fp, sinfo.state); + if (tlcStateInfo == null) { + throw new EvalException(EC.TLC_FAILED_TO_RECOVER_NEXT); + } + tlcStateInfo.stateNumber = sinfo.stateNumber + 1; + tlcStateInfo.predecessorState = sinfo; + tlcStateInfo.fp = fp; + return tlcStateInfo; + } + /* Reconstruct the next state of state s whose fingerprint is fp. */ public final TLCStateInfo getState(long fp, TLCState s) { for (int i = 0; i < this.actions.length; i++) { @@ -2766,6 +2789,14 @@ public class Tool return MVPerm.permutationSubgroup(Enum); } + public boolean hasSymmetry() { + if (this.config == null) { + return false; + } + final String name = this.config.getSymmetry(); + return name.length() > 0; + } + public final Context getFcnContext(FcnLambdaValue fcn, ExprOrOpArgNode[] args, Context c, TLCState s0, TLCState s1, int control) { diff --git a/tlatools/src/tlc2/tool/Worker.java b/tlatools/src/tlc2/tool/Worker.java index 3b98529ead182a74fc13c6b90bf1f0f5fc473a86..f365a11ab576c7184850efdf9bb9743aa9fbb8de 100644 --- a/tlatools/src/tlc2/tool/Worker.java +++ b/tlatools/src/tlc2/tool/Worker.java @@ -10,7 +10,6 @@ import tlc2.output.MP; import tlc2.tool.queue.IStateQueue; import tlc2.util.IdThread; import tlc2.util.ObjLongTable; -import tlc2.value.Value; public class Worker extends IdThread implements IWorker { @@ -22,7 +21,6 @@ public class Worker extends IdThread implements IWorker { private ModelChecker tlc; private IStateQueue squeue; private ObjLongTable astCounts; - private Value[] localValues; // SZ Feb 20, 2009: changed due to super type introduction public Worker(int id, AbstractChecker tlc) { @@ -32,28 +30,11 @@ public class Worker extends IdThread implements IWorker { this.tlc = (ModelChecker) tlc; this.squeue = this.tlc.theStateQueue; this.astCounts = new ObjLongTable(10); - this.localValues = new Value[4]; this.setName("TLCWorkerThread-" + String.format("%03d", id)); } public final ObjLongTable getCounts() { return this.astCounts; } - public Value getLocalValue(int idx) { - if (idx < this.localValues.length) { - return this.localValues[idx]; - } - return null; - } - - public void setLocalValue(int idx, Value val) { - if (idx >= this.localValues.length) { - Value[] vals = new Value[idx + 1]; - System.arraycopy(this.localValues, 0, vals, 0, this.localValues.length); - this.localValues = vals; - } - this.localValues[idx] = val; - } - /** * This method gets a state from the queue, generates all the * possible next states of the state, checks the invariants, and diff --git a/tlatools/src/tlc2/tool/distributed/TLCServer.java b/tlatools/src/tlc2/tool/distributed/TLCServer.java index 51649a13303d0f1f668f38d2de5d3cd8d9c1fe64..95e65ac587eb597b2d2cf735885861a80774d975 100644 --- a/tlatools/src/tlc2/tool/distributed/TLCServer.java +++ b/tlatools/src/tlc2/tool/distributed/TLCServer.java @@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicLong; import tlc2.TLCGlobals; import tlc2.output.EC; import tlc2.output.MP; +import tlc2.tool.EvalException; import tlc2.tool.ModelChecker; import tlc2.tool.TLCState; import tlc2.tool.TLCTrace; @@ -444,20 +445,32 @@ public class TLCServer extends UnicastRemoteObject implements TLCServerRMI, } catch (Throwable e) { // Assert.printStack(e); done = true; - // LL modified error message on 7 April 2012 - MP.printError(EC.GENERAL, "initializing the server", e); // LL changed call 7 April 2012 - if (errState != null) { - MP.printMessage(EC.TLC_INITIAL_STATE, "While working on the initial state: " + errState); - } - // We redo the work on the error state, recording the call - // stack. - work.setCallStack(); - try { - initFPs = doInit(); - } catch (Throwable e1) { - MP.printError(EC.GENERAL, "evaluating the nested" // LL changed call 7 April 2012 - + "\nexpressions at the following positions:\n" - + work.printCallStack(), e); + + // Distributed TLC does not support TLCGet/TLCSet operator. It + // would require synchronization among all (distributed) + // workers. In distributed mode, it is of limited use anyway. + if (e instanceof EvalException + && ((EvalException) e).getErrorCode() == EC.TLC_MODULE_VALUE_JAVA_METHOD_OVERRIDE + && (((EvalException) e).getMessage().contains("tlc2.module.TLC.TLCSet") + || ((EvalException) e).getMessage().contains("tlc2.module.TLC.TLCGet"))) { + MP.printError(EC.TLC_FEATURE_UNSUPPORTED, + "TLCSet & TLCGet operators not supported by distributed TLC."); + } else { + // LL modified error message on 7 April 2012 + MP.printError(EC.GENERAL, "initializing the server", e); // LL changed call 7 April 2012 + if (errState != null) { + MP.printMessage(EC.TLC_INITIAL_STATE, "While working on the initial state: " + errState); + } + // We redo the work on the error state, recording the call + // stack. + work.setCallStack(); + try { + initFPs = doInit(); + } catch (Throwable e1) { + MP.printError(EC.GENERAL, "evaluating the nested" // LL changed call 7 April 2012 + + "\nexpressions at the following positions:\n" + + work.printCallStack(), e); + } } } } @@ -704,17 +717,20 @@ public class TLCServer extends UnicastRemoteObject implements TLCServerRMI, } } finally { tlcServerMXWrapper.unregister(); - boolean send = mail.send(); - // In case sending the mail has failed we treat this as an error. - // This is needed when TLC runs on another host and email is - // the only means for the user to get access to the model checking - // results. - // With distributed TLC and CloudDistributedTLCJob in particular, - // the cloud node is immediately turned off once the TLC process has - // finished. If we were to shutdown the node even when sending out - // the email has failed, the result would be lost. - if (!send) { - System.exit(1); + // When creation of TLCApp fails, we get here as well. + if (mail != null) { + boolean send = mail.send(); + // In case sending the mail has failed we treat this as an error. + // This is needed when TLC runs on another host and email is + // the only means for the user to get access to the model checking + // results. + // With distributed TLC and CloudDistributedTLCJob in particular, + // the cloud node is immediately turned off once the TLC process has + // finished. If we were to shutdown the node even when sending out + // the email has failed, the result would be lost. + if (!send) { + System.exit(1); + } } } } diff --git a/tlatools/src/tlc2/tool/distributed/management/TLCServerMXWrapper.java b/tlatools/src/tlc2/tool/distributed/management/TLCServerMXWrapper.java index b91b47ba4a3ed2c95ab158043b25533446b4d189..728d9ab09fe81bef047478815f2bddcea81cdc27 100644 --- a/tlatools/src/tlc2/tool/distributed/management/TLCServerMXWrapper.java +++ b/tlatools/src/tlc2/tool/distributed/management/TLCServerMXWrapper.java @@ -105,4 +105,19 @@ public class TLCServerMXWrapper extends TLCStandardMBean implements TLCStatistic public long getAverageBlockCnt() { return tlcServer.getAverageBlockCnt(); } + + /* (non-Javadoc) + * @see tlc2.tool.distributed.management.TLCStatisticsMXBean#getRuntimeRatio() + */ + public double getRuntimeRatio() { + // Distributed TLC does not support liveness checking + return 0d; + } + + /* (non-Javadoc) + * @see tlc2.tool.distributed.management.TLCStatisticsMXBean#liveCheck() + */ + public void liveCheck() { + // Distributed TLC does not support liveness checking + } } diff --git a/tlatools/src/tlc2/tool/distributed/management/TLCStatisticsMXBean.java b/tlatools/src/tlc2/tool/distributed/management/TLCStatisticsMXBean.java index ed30f3929999f1e37965cce63f65a8ce8e2867e3..9619c4a7d3f46b57948175933d7f4b9429882505 100644 --- a/tlatools/src/tlc2/tool/distributed/management/TLCStatisticsMXBean.java +++ b/tlatools/src/tlc2/tool/distributed/management/TLCStatisticsMXBean.java @@ -57,4 +57,14 @@ public interface TLCStatisticsMXBean { * Creates a checkpoint next time possible */ void checkpoint(); + + /** + * @return The ratio between time dedicated to safety and liveness checking. + */ + double getRuntimeRatio(); + + /** + * Force new progress interval to check liveness + */ + void liveCheck(); } diff --git a/tlatools/src/tlc2/tool/fp/DiskFPSet.java b/tlatools/src/tlc2/tool/fp/DiskFPSet.java index a6bc57708194b2d172dd0a70812ea52aa31bc24c..01e28591a9db9dce0904452a6597aded04c3f9c0 100644 --- a/tlatools/src/tlc2/tool/fp/DiskFPSet.java +++ b/tlatools/src/tlc2/tool/fp/DiskFPSet.java @@ -730,7 +730,7 @@ public abstract class DiskFPSet extends FPSet implements FPSetStatistic { /* (non-Javadoc) * @see tlc2.tool.fp.FPSet#exit(boolean) */ - public final void exit(boolean cleanup) throws IOException { + public void exit(boolean cleanup) throws IOException { if (cleanup) { // Delete the metadata directory: FileUtil.deleteDir(this.metadir, true); diff --git a/tlatools/src/tlc2/tool/liveness/AbstractDiskGraph.java b/tlatools/src/tlc2/tool/liveness/AbstractDiskGraph.java index b2865fbd0a95d58389e5a29af446c35719fd8e45..151d0599f79d32e55086f04318fd80e286d084ef 100644 --- a/tlatools/src/tlc2/tool/liveness/AbstractDiskGraph.java +++ b/tlatools/src/tlc2/tool/liveness/AbstractDiskGraph.java @@ -4,14 +4,17 @@ package tlc2.tool.liveness; +import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import tlc2.output.EC; @@ -26,10 +29,11 @@ import util.FileUtil; * * - A {@link DiskGraph} has a 1:1 relationship with {@link OrderOfSolution} * - * - Logically stores the triple of state, tableaux, link (transitions) + * - Logically stores a set of triples that represent the liveness/behavior graph (see Manna/Pnuelli book). + * - Technically, it stores the triple of <<state (fingerprint), tableau node index, link (transitions)>> * -- Technically does *not* store States, but only a state's fingerprints * --- Stores a fingerprint split into 2 ints (low & high part of a fingerprint) - * -- Stores the index of the tableaux, not the tableaux itself + * -- Stores the index of the tableau node, not the tableau node itself * --- The TableauGraphNode (TBGraphNode) instance can be obtained by reading * the DiskGraph triple into a GraphNode instance and calling * GraphNode#getTNode(TBGraph). One obviously has to have access to the TBGraph @@ -69,11 +73,16 @@ public abstract class AbstractDiskGraph { public static final long MAX_LINK = 0x7FFFFFFFFFFFFFFFL; public static boolean isFilePointer(long loc) { + // TODO Does not check >= 0 and thus accepts TableauDiskGraph.UNDONE as + // ptr. return loc < MAX_PTR; } private final String chkptName; protected final String metadir; + /** + * @see tlatools/test/tlc2/tool/liveness/AbstractDiskGraph.JPG + */ protected final BufferedRandomAccessFile nodeRAF; protected final BufferedRandomAccessFile nodePtrRAF; protected final LongVec initNodes; @@ -107,11 +116,26 @@ public abstract class AbstractDiskGraph { return this.initNodes; } + /** + * Creates a fixed size in-memory cache of {@link GraphNode}'s. A disk + * lookup is avoid in {@link AbstractDiskGraph#getNode(long, int, long)} on + * each cache hit. The cache is destroyed by + * {@link AbstractDiskGraph#destroyCache()}. + */ public final void createCache() { - // Make array length a function of the available (heap) memory + // Make array length a function of the available (heap) memory. Could + // approximate the required memory by taking the size of the on-disk + // files into account, but think of hash collisions! this.gnodes = new GraphNode[65536]; } + /** + * Destroys the fixed size in-memory cache created by + * {@link AbstractDiskGraph#createCache()}. This should be done if liveness + * checking wants to destroy in-memory {@link GraphNode} nodes to start a + * new liveness check on them (e.g. to replace SCC link numbers with the + * original disk ptr location). + */ public final void destroyCache() { this.gnodes = null; } @@ -161,11 +185,18 @@ public abstract class AbstractDiskGraph { return ptr; } + /** + * @return true iff the given {@link GraphNode} has already been added to + * this {@link AbstractDiskGraph}. + */ + protected abstract boolean checkDuplicate(GraphNode node); + public abstract GraphNode getNode(long fingerprint, int tableauIdx) throws IOException; /** * @return true iff the given GraphNode belongs to the set of initial - * states. + * states. Inefficient, only use for auxiliary use cases (e.g. + * visualization of the liveness graph (toDotViz())). */ protected boolean isInitState(final GraphNode gnode) { final int numOfInits = initNodes.size(); @@ -199,7 +230,7 @@ public abstract class AbstractDiskGraph { return gnode1; } - public final GraphNode getNodeFromDisk(final long stateFP, final int tidx, final long ptr) throws IOException { + protected final GraphNode getNodeFromDisk(final long stateFP, final int tidx, final long ptr) throws IOException { // If the node is not found in the in-memory cache, the ptr has to be // positive. BufferedRandomAccessFile#seek will throw an IOException due // to "negative seek offset" anyway. Lets catch it early on! @@ -228,20 +259,133 @@ public abstract class AbstractDiskGraph { this.nodePtrRAF.seek(ptr); } + /** + * This methods reads the node PTR file from disk (the ptr file is the + * smaller file ptrs_N of the pair ptrs_N and nodes_N). + * <p> + * The ptr file contains tuples <<fingerprint, tableau idx, ptr location>> + * for all fingerprints times all tableau indices (the corresponding nodes + * file contains the outgoing arcs of the node described in the ptr file). + * <p> + * The reason why the nodePtrTable has to be re-made by calling this method + * prior to running the SCC search, is because the ptr location is + * eventually overwritten with the nodes link number used by SCC search. + * <p> + * makeNodePtrTbl maintains/does not overwrite the isDone state of the node, + * which - iff true - causes SCC search to skip/ignore the node. + * + * @param ptr + * The length of the ptr file up to which this method reads. + * @throws IOException + * Reading the file failed + */ protected abstract void makeNodePtrTbl(final long ptr) throws IOException; - /* Return the link assigned to the node. */ + /* Link information for SCC search */ + + /** + * Return the link assigned to the node via putLink() or -1 if the node has + * no link assigned yet. Unless -1, the link is in interval [ + * {@link AbstractDiskGraph#MAX_PTR}, {@link AbstractDiskGraph#MAX_LINK}] + * + * @param state + * The state's fingerprint + * @param tidx + * The corresponding tableau index + */ public abstract long getLink(long state, int tidx); /** - * Assign link to node. If a link has already been assigned to the node, - * does nothing by simply returning the existing link. Otherwise, add <node, - * link> into the table and return -1. + * Assign link to node during SCC search. If a link has already been + * assigned to the node, does nothing by simply returning the existing link. + * Otherwise, add <node, link> into the table and return -1. The link + * overwrites the previous value of elem (file pointer into nodes_N) in the + * nodePtrTable. + * <p> + * The link has to be in the range [{@link AbstractDiskGraph#MAX_PTR}, + * {@link AbstractDiskGraph#MAX_LINK}). {AbstractDiskGraph#MAX_LINK} is used + * to exclude nodes from being explored by SCC search twice (see + * {@link AbstractDiskGraph#setMaxLink(long, int)}. + * + * @param state + * The state's fingerprint + * @param tidx + * The corresponding tableau index */ public abstract long putLink(long state, int tidx, long link); + /** + * Assigns the maximum possible link number to the given node <state, + * tidx>. This results in that the node is skipped/ignored if it turns up + * as a node during SCC's depth-first-search. + * + * @param state + * The state's fingerprint + * @param tidx + * The corresponding tableau index + */ public abstract void setMaxLink(long state, int tidx); + /* End link information for SCC search */ + + public boolean checkInvariants(final int slen, final int alen) { + // Make sure there are no redundant transitions. + final Iterator<GraphNode> itr = iterator(); + while (itr.hasNext()) { + final GraphNode gn = itr.next(); + if (!gn.checkInvariants(slen, alen)) { + return false; + } + } + return true; + } + + /* start iteration */ + + private Iterator<GraphNode> iterator() { + try { + // reverse ptr file to beginning + this.nodePtrRAF.seek(0); + + final long length = this.nodePtrRAF.length(); + + return new Iterator<GraphNode>() { + + /* (non-Javadoc) + * @see java.util.Iterator#hasNext() + */ + public boolean hasNext() { + return nodePtrRAF.getFilePointer() < length; + } + + /* (non-Javadoc) + * @see java.util.Iterator#next() + */ + public GraphNode next() { + try { + long fp = nodePtrRAF.readLong(); + int tidx = nodePtrRAF.readInt(); + long loc = nodePtrRAF.readLongNat(); + return getNodeFromDisk(fp, tidx, loc); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /* (non-Javadoc) + * @see java.util.Iterator#remove() + */ + public void remove() { + throw new UnsupportedOperationException("Not supported!"); + } + }; + } catch (IOException e1) { + throw new RuntimeException(e1); + } + } + + /* end iteration */ + /** * Return the shortest path (inclusive and in reverse order) from some * initial state to state. The path is a vector of states <s1, s2, ..., sn>, @@ -261,6 +405,17 @@ public abstract class AbstractDiskGraph { */ public abstract long size(); + /** + * @return The size of both disk files (ptrs and nodes) measured in bytes. + * Can be incorrect during short periods when the graph is being + * recreated ({@link #makeNodePtrTbl()}) or nodes are read from + * disk ({@link #getNodeFromDisk(long, int, long)}). It is up to + * the caller to take this into account. + * @throws IOException + */ + public long getSizeOnDisk() throws IOException { + return this.nodePtrRAF.length() + this.nodeRAF.length(); + } public long getSizeAtLastCheck() { return sizeAtCheck; @@ -279,8 +434,48 @@ public abstract class AbstractDiskGraph { * Copy&Paste output "digraph DiskGraph {...} to a file called graphviz.txt * and call something similar to: 'dot -T svg graphviz.txt -o * "Graphviz.svg"'. It obviously needs Graphviz (http://www.graphviz.org). + * + * @param slen Length of state checks + * @param alen Length of action checks */ - public abstract String toDotViz(); + public abstract String toDotViz(final int slen, final int alen); + + /** + * Only useful for debugging. + * + * Writes the current {@link AbstractDiskGraph} to the given {@link File}. + * <p> + * For the Eclipse IDE there exists a handy plug-in that automatically + * renders a .dot file when selected in the package explorer. Just follow + * the installation instructions at + * https://github.com/abstratt/eclipsegraphviz + * + * @param slen + * Length of state checks + * @param alen + * Length of action checks + * @param file + * Destination + */ + public final void writeDotViz(final int slen, final int alen, final File file) { + this.createCache(); + + try { + final BufferedWriter bwr = new BufferedWriter(new FileWriter(file)); + + // write contents of StringBuffer to a file + bwr.write(toDotViz(slen, alen)); + + // flush the stream + bwr.flush(); + + // close the stream + bwr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + this.destroyCache(); + } /* Checkpoint. */ public synchronized final void beginChkpt() throws IOException { diff --git a/tlatools/src/tlc2/tool/liveness/AbstractGraphNode.java b/tlatools/src/tlc2/tool/liveness/AbstractGraphNode.java index f22e9b260bebae973731c6e6000c13bebe58fcd0..346fb99f8b7fe7d199fc7fa49a7071ed1a9de003 100644 --- a/tlatools/src/tlc2/tool/liveness/AbstractGraphNode.java +++ b/tlatools/src/tlc2/tool/liveness/AbstractGraphNode.java @@ -40,6 +40,14 @@ public abstract class AbstractGraphNode { return this.checks.get(i); } + public BitVector getCheckAction(int slen, int alen, int nodeIdx) { + final BitVector bv = new BitVector(alen); + for (int j = 0; j < alen; j++) { + bv.set(j, getCheckAction(slen, alen, nodeIdx, j)); + } + return bv; + } + public boolean getCheckAction(int slen, int alen, int nodeIdx, int i) { int pos = slen + alen * nodeIdx + i; return this.checks.get(pos); diff --git a/tlatools/src/tlc2/tool/liveness/AddAndCheckLiveCheck.java b/tlatools/src/tlc2/tool/liveness/AddAndCheckLiveCheck.java new file mode 100644 index 0000000000000000000000000000000000000000..b5c54f5871671fac520b04fb33b6bbb1395ac3d7 --- /dev/null +++ b/tlatools/src/tlc2/tool/liveness/AddAndCheckLiveCheck.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import java.io.IOException; + +import tlc2.output.EC; +import tlc2.output.MP; +import tlc2.tool.Action; +import tlc2.tool.TLCState; +import tlc2.tool.Tool; +import tlc2.tool.Worker; +import tlc2.util.SetOfStates; +import tlc2.util.statistics.IBucketStatistics; + +/** + * {@link AddAndCheckLiveCheck} is a subclass of LiveCheck that always runs + * liveness checking after each and every insertion of a new node into the + * behavior graph. It is meant for TESTING ONLY!!! as it severely + * degrades TLC's performance/scalability. + * <p> + * The key difference between {@link AddAndCheckLiveCheck} and {@link LiveCheck} + * is that both methods are synchronized to only allow one {@link Worker} to add + * a state at a time and that liveness checking is after every insertion. + */ +public class AddAndCheckLiveCheck extends LiveCheck { + + public AddAndCheckLiveCheck(Tool tool, Action[] actions, String metadir, IBucketStatistics stats) throws IOException { + super(tool, actions, metadir, stats); + MP.printWarning(EC.UNIT_TEST, "!!!WARNING: TLC is running in inefficient unit testing mode!!!"); + } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.LiveCheck#addInitState(tlc2.tool.TLCState, long) + */ + @Override + public synchronized void addInitState(TLCState state, long stateFP) { + super.addInitState(state, stateFP); + try { + check0(false); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.LiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates) + */ + @Override + public synchronized void addNextState(TLCState s0, long fp0, SetOfStates nextStates) throws IOException { + super.addNextState(s0, fp0, nextStates); + try { + check0(false); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/tlatools/src/tlc2/tool/liveness/DiskGraph.java b/tlatools/src/tlc2/tool/liveness/DiskGraph.java index 060aaf3d3fb0e9a89b53085c9ecd9dcd75f63b82..be8f5ba689c83d279b4ee6805ef3670e27d82b21 100644 --- a/tlatools/src/tlc2/tool/liveness/DiskGraph.java +++ b/tlatools/src/tlc2/tool/liveness/DiskGraph.java @@ -65,23 +65,31 @@ public class DiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#putNode(tlc2.tool.liveness.GraphNode, long) + * @see tlc2.tool.liveness.AbstractDiskGraph#putNode(tlc2.tool.liveness.GraphNode, long) */ protected void putNode(GraphNode node, long ptr) { this.nodePtrTbl.put(node.stateFP, ptr); } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#getLink(long, int) + * @see tlc2.tool.liveness.AbstractDiskGraph#checkDuplicate(tlc2.tool.liveness.GraphNode) + */ + protected boolean checkDuplicate(final GraphNode node) { + return this.nodePtrTbl.get(node.stateFP) != -1; + } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.AbstractDiskGraph#getLink(long, int) */ public long getLink(long state, int tidx) { return this.nodePtrTbl.get(state); } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#putLink(long, int, long) + * @see tlc2.tool.liveness.AbstractDiskGraph#putLink(long, int, long) */ public long putLink(long state, int tidx, long link) { + assert MAX_PTR <= link && link < MAX_LINK; int loc = this.nodePtrTbl.getLoc(state); long oldLink = this.nodePtrTbl.getByLoc(loc); if (!isFilePointer(oldLink)) { @@ -105,9 +113,7 @@ public class DiskGraph extends AbstractDiskGraph { this.nodePtrRAF.seek(0); while (this.nodePtrRAF.getFilePointer() < ptr) { long fp = this.nodePtrRAF.readLong(); - // SZ Jul 13, 2009: removed to kill the warning - // SZ Feb 20, 2009: variable never read locally - // int tidx = + // skip the tableau idx that is not used by DiskGraph. this.nodePtrRAF.readInt(); long loc = this.nodePtrRAF.readLongNat(); this.nodePtrTbl.put(fp, loc); @@ -168,9 +174,9 @@ public class DiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#toDotViz() + * @see tlc2.tool.liveness.AbstractDiskGraph#toDotViz(int, int) */ - public final String toDotViz() { + public final String toDotViz(final int slen, final int alen) { // The following code relies on gnodes not being null, thus safeguard // against accidental invocations. @@ -195,7 +201,7 @@ public class DiskGraph extends AbstractDiskGraph { int tidx = nodePtrRAF.readInt(); long loc = nodePtrRAF.readLongNat(); GraphNode gnode = this.getNode(fp, tidx, loc); - sb.append(gnode.toDotViz(isInitState(gnode), false)); + sb.append(gnode.toDotViz(isInitState(gnode), false, slen, alen)); } sb.append("}"); this.nodeRAF.seek(nodePtr); diff --git a/tlatools/src/tlc2/tool/liveness/GraphNode.java b/tlatools/src/tlc2/tool/liveness/GraphNode.java index 69b43598bbf9a84c68e4a885570990c2cadf38f8..03b668546b328d44402c4fc40d0a299256b78303 100644 --- a/tlatools/src/tlc2/tool/liveness/GraphNode.java +++ b/tlatools/src/tlc2/tool/liveness/GraphNode.java @@ -6,11 +6,19 @@ package tlc2.tool.liveness; import java.io.IOException; +import java.util.HashSet; +import java.util.Set; import tlc2.util.BitVector; import tlc2.util.BufferedRandomAccessFile; public class GraphNode extends AbstractGraphNode { + /** + * The record size indicates the number of integers used by each transition + * in the array of nnodes (2x32bit to keep the fp and 32bit to keep the tableau + * idx). + */ + private static final int NNODE_RECORD_SIZE = 3; /** * GraphNode is a node in the behaviour graph. We're going to only store * fingerprints of states, rather than actual states. So, as we encounter @@ -77,34 +85,36 @@ public class GraphNode extends AbstractGraphNode { } public final long getStateFP(int i) { - long high = this.nnodes[3 * i]; - long low = this.nnodes[3 * i + 1]; + long high = this.nnodes[NNODE_RECORD_SIZE * i]; + long low = this.nnodes[NNODE_RECORD_SIZE * i + 1]; return (high << 32) | (low & 0xFFFFFFFFL); } public final int getTidx(int i) { - return this.nnodes[3 * i + 2]; + return this.nnodes[NNODE_RECORD_SIZE * i + 2]; } public final int succSize() { - // offset being != -1 indicates that the nnodes array has been + // offset being != NO_FREE_SLOTS indicates that the nnodes array has been // overallocated in preparation to batch-insert transitions but the // transitions have not been added yet. In this case the nnodes.length / - // 3 is *not* the actual number of transitions, offset / 3 is! - if (this.offset != -1) { - return this.offset / 3; + // NNODE_RECORD_SIZE is *not* the actual number of transitions, offset / NNODE_RECORD_SIZE is! + if (this.offset != NO_FREE_SLOTS) { + return this.offset / NNODE_RECORD_SIZE; } - return this.nnodes.length / 3; + return this.nnodes.length / NNODE_RECORD_SIZE; } /** * Points to the first available slot in {@link GraphNode#nnodes} iff free - * slots are available. "-1" indicates no free slots are available. + * slots are available. "NO_FREE_SLOTS" indicates no free slots are available. * * @see GraphNode#allocate(int) */ - private int offset = -1; + private int offset = NO_FREE_SLOTS; + private static final int NO_FREE_SLOTS = -1; + /** * Allocates memory for subsequent * {@link GraphNode#addTransition(long, int, int, int, boolean[])} calls. @@ -135,7 +145,7 @@ public class GraphNode extends AbstractGraphNode { */ private final void allocate(final int transitions) { final int len = this.nnodes.length; - int[] newNodes = new int[len + (3 * transitions)]; + int[] newNodes = new int[len + (NNODE_RECORD_SIZE * transitions)]; System.arraycopy(this.nnodes, 0, newNodes, 0, len); this.nnodes = newNodes; @@ -188,7 +198,7 @@ public class GraphNode extends AbstractGraphNode { } } } - if (this.offset == -1) { + if (this.offset == NO_FREE_SLOTS) { // Have to create a new slot regardless of 0 or negative hint, thus // Math.max... this.allocate(Math.max(allocationHint, 1)); @@ -196,9 +206,9 @@ public class GraphNode extends AbstractGraphNode { this.nnodes[this.offset] = (int) (fp >>> 32); this.nnodes[this.offset + 1] = (int) (fp & 0xFFFFFFFFL); this.nnodes[this.offset + 2] = tidx; - this.offset = this.offset + 3; + this.offset = this.offset + NNODE_RECORD_SIZE; if (this.offset == this.nnodes.length) { - this.offset = -1; + this.offset = NO_FREE_SLOTS; } } @@ -213,14 +223,14 @@ public class GraphNode extends AbstractGraphNode { */ public int realign() { int result = 0; - // It is a noop iff offset == -1 - if (this.offset != -1) { - result = (this.nnodes.length - this.offset) / 3; + // It is a noop iff offset == NO_FREE_SLOTS + if (this.offset != NO_FREE_SLOTS) { + result = (this.nnodes.length - this.offset) / NNODE_RECORD_SIZE; // shrink newNodes to correct size int[] newNodes = new int[this.offset]; System.arraycopy(this.nnodes, 0, newNodes, 0, newNodes.length); this.nnodes = newNodes; - this.offset = -1; + this.offset = NO_FREE_SLOTS; } return result; } @@ -240,14 +250,14 @@ public class GraphNode extends AbstractGraphNode { // been // reached. The free slot detection work with the allocation offset that // points to the end of the filled slots (slots are filled in ascending - // order). If offset is marked invalid ("-1"), the nnodes buffer is + // order). If offset is marked invalid ("NO_FREE_SLOTS"), the nnodes buffer is // completely occupied and has to be searched to the end. - if (this.offset != -1) { + if (this.offset != NO_FREE_SLOTS) { len = offset; } int high = (int) (fp >>> 32); int low = (int) (fp & 0xFFFFFFFFL); - for (int i = 0; i < len; i += 3) { + for (int i = 0; i < len; i += NNODE_RECORD_SIZE) { if (this.nnodes[i] == high && this.nnodes[i + 1] == low && this.nnodes[i + 2] == tidx) { return true; } @@ -255,6 +265,71 @@ public class GraphNode extends AbstractGraphNode { return false; } + public boolean checkInvariants(final int slen, final int alen) { + final Set<Transition> transitions = new HashSet<Transition>(); + for (int i = 0; i < succSize(); i++) { + final Transition t = new Transition(getStateFP(i), getTidx(i), getCheckAction(slen, alen, i)); + transitions.add(t); + } + return transitions.size() == succSize(); + } + + public Set<Transition> getTransition() { + final Set<Transition> transitions = new HashSet<Transition>(); + for (int i = 0; i < succSize(); i++) { + transitions.add(new Transition(getStateFP(i), getTidx(i), new BitVector(0))); + } + return transitions; + } + + public static class Transition { + + private final long fp; + private final int tidx; + private final BitVector bv; + + public Transition(long fp, int tidx, BitVector bv) { + this.fp = fp; + this.tidx = tidx; + this.bv = bv; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((bv == null) ? 0 : bv.hashCode()); + result = prime * result + (int) (fp ^ (fp >>> 32)); + result = prime * result + tidx; + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Transition other = (Transition) obj; + if (bv == null) { + if (other.bv != null) + return false; + } else if (!bv.equals(other.bv)) + return false; + if (fp != other.fp) + return false; + if (tidx != other.tidx) + return false; + return true; + } + } + /* Return the tableau graph node used by this. */ public final TBGraphNode getTNode(TBGraph tableau) { return tableau.getNode(this.tindex); @@ -268,6 +343,7 @@ public class GraphNode extends AbstractGraphNode { * @throws IOException */ void write(final BufferedRandomAccessFile nodeRAF) throws IOException { + assert offset == NO_FREE_SLOTS; // assert that nnodes hasn't been overallocated. // Write nnodes final int cnt = nnodes.length; nodeRAF.writeNat(cnt); @@ -288,50 +364,64 @@ public class GraphNode extends AbstractGraphNode { // Read checks checks = new BitVector(); checks.read(nodeRAF); + + assert offset == NO_FREE_SLOTS; } public final String toString() { StringBuffer buf = new StringBuffer(); buf.append("<" + this.stateFP + "," + this.tindex + "> --> "); - int size = this.nnodes.length; - if (size != 0) { - long high = this.nnodes[0]; - long low = this.nnodes[1]; - long fp = (high << 32) | (low & 0xFFFFFFFFL); - buf.append("<" + fp + "," + this.nnodes[2] + ">"); - } - for (int i = 3; i < size; i += 3) { + for (int i = 0; i < succSize(); i++) { + buf.append("<" + getStateFP(i) + "," + getTidx(i) + ">"); buf.append(", "); - long high = this.nnodes[i]; - long low = this.nnodes[i + 1]; - long fp = (high << 32) | (low & 0xFFFFFFFFL); - buf.append("<" + fp + "," + this.nnodes[i + 2] + ">"); } - return buf.toString(); + return buf.substring(0, buf.length() - ", ".length()); // chop off dangling ", " } - public String toDotViz(final boolean isInitState, final boolean hasTableau) { - String id = (""+this.stateFP).substring(0,3); + public String toDotViz(final boolean isInitState, final boolean hasTableau, final int slen, final int alen) { + // The node's id including its tidx if any + String id = Long.toString(this.stateFP); + if (id.length() >=6) { + id = id.substring(0, 6); + } if (hasTableau) { id += "." + this.tindex; } + // marker if it is an init state final StringBuffer buf = new StringBuffer(); if (isInitState) { - buf.append(id + " [style = filled]\n"); // node's label + buf.append("\"" + id + "\" [style = filled]\n"); // node's label } - int size = this.nnodes.length; - for (int i = 0; i < size; i += 3) { - buf.append(id + " -> "); - long high = this.nnodes[i]; - long low = this.nnodes[i + 1]; - long fp = (high << 32) | (low & 0xFFFFFFFFL); + + // Each outgoing transition + for (int i = 0; i < succSize(); i++) { + String fp = Long.toString(getStateFP(i)); + if (fp.length() >= 6) { + fp = fp.substring(0, 6); + } +// if (fp == this.stateFP) { +// // skip self loops if edge count to large for dotViz to handle. +// continue; +// } + buf.append("\"" + id + "\" -> "); if (hasTableau) { - buf.append(("" + fp).substring(0, 3) + "." + this.nnodes[i + 2]); + final int tidx = getTidx(i); + buf.append(("\"" + fp) + "." + tidx + "\""); } else { //Omit tableau index when it's -1 (indicating no tableau) - buf.append(("" + fp).substring(0, 3)); + buf.append(("\"" + fp) + "\""); + } + + buf.append(" [label=\""); + for (int j = 0; j < alen; j++) { + if (getCheckAction(slen, alen, i, j)) { + buf.append("t"); + } else { + buf.append("f"); + } } + buf.append("\"];"); buf.append("\n"); } return buf.toString(); diff --git a/tlatools/src/tlc2/tool/liveness/ILiveCheck.java b/tlatools/src/tlc2/tool/liveness/ILiveCheck.java index 171a00672f42ed8b829429dc06ea9a16959de998..2c37303b8cfaaf8a289e1488efd37bfca6312a7a 100644 --- a/tlatools/src/tlc2/tool/liveness/ILiveCheck.java +++ b/tlatools/src/tlc2/tool/liveness/ILiveCheck.java @@ -1,3 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ package tlc2.tool.liveness; import java.io.IOException; @@ -5,7 +30,7 @@ import java.io.IOException; import tlc2.tool.StateVec; import tlc2.tool.TLCState; import tlc2.tool.Tool; -import tlc2.util.LongVec; +import tlc2.util.SetOfStates; import tlc2.util.statistics.IBucketStatistics; public interface ILiveCheck { @@ -20,7 +45,12 @@ public interface ILiveCheck { * This method adds new nodes into the behavior graph induced by s0. It is * called after the successors of s0 are computed. */ - void addNextState(TLCState s0, long fp0, StateVec nextStates, LongVec nextFPs) throws IOException; + void addNextState(TLCState s0, long fp0, SetOfStates nextStates) throws IOException; + + /** + * true iff a call to {@link ILiveCheck#check(boolean)} would indeed result in liveness checking. + */ + boolean doLiveCheck(); /** * Check liveness properties for the current (potentially partial) state graph. Returns diff --git a/tlatools/src/tlc2/tool/liveness/ILiveChecker.java b/tlatools/src/tlc2/tool/liveness/ILiveChecker.java index 2c5bb45180eadf427157d006fa25bb7592eb7845..44d685656caa5ab7122452f130476de5ccc17d59 100644 --- a/tlatools/src/tlc2/tool/liveness/ILiveChecker.java +++ b/tlatools/src/tlc2/tool/liveness/ILiveChecker.java @@ -1,11 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + package tlc2.tool.liveness; import java.io.IOException; -import tlc2.tool.StateVec; import tlc2.tool.TLCState; import tlc2.util.BitVector; -import tlc2.util.LongVec; +import tlc2.util.SetOfStates; public interface ILiveChecker { @@ -19,7 +44,7 @@ public interface ILiveChecker { * This method adds new nodes into the behavior graph induced by s0. It is * called after the successors of s0 are computed. */ - void addNextState(TLCState s0, long fp0, StateVec nextStates, LongVec nextFPs, BitVector checkActionResults, + void addNextState(TLCState s0, long fp0, SetOfStates nextStates, BitVector checkActionResults, boolean[] checkStateResults) throws IOException; AbstractDiskGraph getDiskGraph(); diff --git a/tlatools/src/tlc2/tool/liveness/LiveCheck.java b/tlatools/src/tlc2/tool/liveness/LiveCheck.java index 4aa5c699bbbef22f729027b8c661c26bd6daad1c..c5f2e36fdb1486efd248f854668e20847436bed2 100644 --- a/tlatools/src/tlc2/tool/liveness/LiveCheck.java +++ b/tlatools/src/tlc2/tool/liveness/LiveCheck.java @@ -10,16 +10,18 @@ import java.util.Enumeration; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; +import tlc2.TLC; import tlc2.TLCGlobals; import tlc2.output.EC; import tlc2.output.MP; import tlc2.tool.Action; +import tlc2.tool.IStateFunctor; import tlc2.tool.StateVec; import tlc2.tool.TLCState; import tlc2.tool.Tool; import tlc2.util.BitVector; import tlc2.util.FP64; -import tlc2.util.LongVec; +import tlc2.util.SetOfStates; import tlc2.util.statistics.DummyBucketStatistics; import tlc2.util.statistics.IBucketStatistics; import util.Assert; @@ -33,11 +35,6 @@ public class LiveCheck implements ILiveCheck { private final IBucketStatistics outDegreeGraphStats; private final ILiveChecker[] checker; - // SZ: fields not read locally - // private static OrderOfSolution currentOOS; - // private static DiskGraph currentDG; - // private static PossibleErrorModel currentPEM; - public LiveCheck(Tool tool, Action[] acts, String mdir, IBucketStatistics bucketStatistics) throws IOException { this(tool, acts, Liveness.processLiveness(tool), mdir, bucketStatistics); } @@ -67,9 +64,9 @@ public class LiveCheck implements ILiveCheck { } /* (non-Javadoc) - * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.tool.StateVec, tlc2.util.LongVec) + * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates) */ - public void addNextState(TLCState s0, long fp0, StateVec nextStates, LongVec nextFPs) throws IOException { + public void addNextState(TLCState s0, long fp0, SetOfStates nextStates) throws IOException { for (int i = 0; i < checker.length; i++) { final ILiveChecker check = checker[i]; final OrderOfSolution oos = check.getSolution(); @@ -83,15 +80,64 @@ public class LiveCheck implements ILiveCheck { // to hold the result and loop over actions x successors twice // (here and down below). This is a little price to pay for significantly // increased concurrency. + // + // The actions have to be checked here because - in the light of + // symmetry - while we still have access to the actual successor + // state rather than just its fingerprint that represents all states + // in the symmetry set. Unless super-symmetry is in place (the + // actions checks for all states in the symmetry set evaluate to the + // same value), the "smallest" (see + // tlc2.tool.TLCStateMut.fingerPrint()) cannot be used as a + // replacement state to check the actions. final BitVector checkActionResults = new BitVector(alen * nextStates.size()); for (int sidx = 0; sidx < nextStates.size(); sidx++) { - final TLCState s1 = nextStates.elementAt(sidx); + final TLCState s1 = nextStates.next(); oos.checkAction(s0, s1, checkActionResults, alen * sidx); } - check.addNextState(s0, fp0, nextStates, nextFPs, checkActionResults, oos.checkState(s0)); + nextStates.resetNext(); + check.addNextState(s0, fp0, nextStates, checkActionResults, oos.checkState(s0)); + + // Write the content of the current graph to a file in GraphViz + // format. Useful when debugging! +// check.getDiskGraph().writeDotViz(oos.getCheckState().length, alen, new java.io.File( +// metadir + java.io.File.separator + "dgraph_" + i + "_" + System.currentTimeMillis() + ".dot")); } } + /* (non-Javadoc) + * @see tlc2.tool.liveness.ILiveCheck#doLiveCheck() + */ + public boolean doLiveCheck() { + for (int i = 0; i < checker.length; i++) { + // If one of the disk graph's size has increased by the given + // percentage, run liveness checking. + // + // TODO Alternatively: + // + // - LL suggest to dedicate a fixed fraction of model checking time + // to liveness checking. + // + // - The level could be taken into account. Unless the level + // (height) of the graph increases, no new cycle won't be found + // anyway (all other aspects of liveness checking are checked as + // part of regular safety checking). + // + // - The authors of the Divine model checker describe an algorithm + // in http://dx.doi.org/10.1109/ASE.2003.1240299 + // that counts the "Back-level Edges" and runs liveness checking upon + // a counter reaching a certain (user defined?!) threshold. + // + final AbstractDiskGraph diskGraph = checker[i].getDiskGraph(); + final long sizeAtLastCheck = diskGraph.getSizeAtLastCheck(); + final long sizeCurrently = diskGraph.size(); + final double delta = (sizeCurrently - sizeAtLastCheck) / (sizeAtLastCheck * 1.d); + if (delta > TLCGlobals.livenessThreshold) { + return true; + } + } + return false; + } + /* (non-Javadoc) * @see tlc2.tool.liveness.ILiveCheck#check(boolean) */ @@ -100,14 +146,7 @@ public class LiveCheck implements ILiveCheck { return check0(false); } for (int i = 0; i < checker.length; i++) { - // If anyone of the disk graphs has increased by the given - // percentage, run liveness checking. This is the best heuristic I - // can come up with quickly. - // - // TODO Alternatively the level could be taken - // into account. Unless the level (height) of the graph increases, - // no new cycle won't be found anyway. All other aspects of liveness - // checking are checked as part of regular safety checking. + // see note in doLiveCheck() above! final AbstractDiskGraph diskGraph = checker[i].getDiskGraph(); final long sizeAtLastCheck = diskGraph.getSizeAtLastCheck(); final long sizeCurrently = diskGraph.size(); @@ -116,7 +155,6 @@ public class LiveCheck implements ILiveCheck { return check0(false); } } - return true; } @@ -135,13 +173,17 @@ public class LiveCheck implements ILiveCheck { * liveness check. If this is the final/last check, it's pointless * to re-create the nodePtrTable. */ - private boolean check0(final boolean finalCheck) throws InterruptedException, IOException { + protected boolean check0(final boolean finalCheck) throws InterruptedException, IOException { + final long startTime = System.currentTimeMillis(); + + // Sum up the number of nodes in all disk graphs to indicate the amount + // of work to be done by liveness checking. long sum = 0L; for (int i = 0; i < checker.length; i++) { sum += checker[i].getDiskGraph().size(); } - MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS, - new String[] { "current", Long.toString(sum) }); + MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS, new String[] { finalCheck ? "complete" : "current", + Long.toString(sum), checker.length == 1 ? "" : checker.length + " branches of " }); // Copy the array of checkers into a concurrent-enabled queue // that allows LiveWorker threads to easily get the next @@ -164,12 +206,12 @@ public class LiveCheck implements ILiveCheck { int wNum = Math.min(slen, TLCGlobals.getNumWorkers()); if (wNum == 1) { - LiveWorker worker = new LiveWorker(0, this, queue); + LiveWorker worker = new LiveWorker(0, 1, this, queue, finalCheck); worker.run(); } else { final LiveWorker[] workers = new LiveWorker[wNum]; for (int i = 0; i < wNum; i++) { - workers[i] = new LiveWorker(i, this, queue); + workers[i] = new LiveWorker(i, wNum, this, queue, finalCheck); workers[i].start(); } for (int i = 0; i < wNum; i++) { @@ -178,15 +220,18 @@ public class LiveCheck implements ILiveCheck { } if (LiveWorker.hasErrFound()) { + MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS_END, TLC.convertRuntimeToHumanReadable(System.currentTimeMillis() - startTime)); return false; } - + // Reset after checking unless it's the final check: if (finalCheck == false) { for (int i = 0; i < checker.length; i++) { checker[i].getDiskGraph().makeNodePtrTbl(); } } + MP.printMessage(EC.TLC_CHECKING_TEMPORAL_PROPS_END, TLC.convertRuntimeToHumanReadable(System.currentTimeMillis() - startTime)); + return true; } @@ -198,33 +243,30 @@ public class LiveCheck implements ILiveCheck { addInitState(stateTrace.elementAt(0), stateTrace.elementAt(0).fingerPrint()); // Add the remaining states... - final StateVec successor = new StateVec(2); - final LongVec successorFP = new LongVec(2); + final SetOfStates successors = new SetOfStates(stateTrace.size() * 2); // For all states except last one add the successor // (which is the next state in stateTrace). for (int i = 0; i < stateTrace.size() - 1; i++) { // Empty out old successors. - successor.clear(); - successorFP.reset(); + successors.clear(); // Calculate the current state's fingerprint final TLCState tlcState = stateTrace.elementAt(i); final long fingerPrint = tlcState.fingerPrint(); // Add state itself to allow stuttering - successor.addElement(tlcState); - successorFP.addElement(fingerPrint); + successors.put(tlcState); // Add the successor in the trace - successor.addElement(stateTrace.elementAt(i + 1)); - successorFP.addElement(stateTrace.elementAt(i + 1).fingerPrint()); - addNextState(tlcState, fingerPrint, successor, successorFP); + final TLCState successor = stateTrace.elementAt(i + 1); + successors.put(successor); + addNextState(tlcState, fingerPrint, successors); } // Add last state in trace for which *no* successors have been generated final TLCState lastState = stateTrace.elementAt(stateTrace.size() - 1); - addNextState(lastState, lastState.fingerPrint(), new StateVec(0), new LongVec(0)); + addNextState(lastState, lastState.fingerPrint(), new SetOfStates(0)); // Do *not* re-create the nodePtrTbl when it is thrown away anyway. if (!check0(true)) { @@ -375,9 +417,9 @@ public class LiveCheck implements ILiveCheck { } /* (non-Javadoc) - * @see tlc2.tool.liveness.LiveCheck.ILiveChecker#addNextState(tlc2.tool.TLCState, long, tlc2.tool.StateVec, tlc2.util.LongVec, tlc2.util.BitVector, boolean[]) + * @see tlc2.tool.liveness.ILiveChecker#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates, tlc2.util.BitVector, boolean[]) */ - public void addNextState(final TLCState s0, final long fp0, final StateVec nextStates, final LongVec nextFPs, + public void addNextState(final TLCState s0, final long fp0, final SetOfStates nextStates, final BitVector checkActionResults, final boolean[] checkStateResults) throws IOException { int cnt = 0; // if there is no tableau ... @@ -388,7 +430,7 @@ public class LiveCheck implements ILiveCheck { final int s = node0.succSize(); node0.setCheckState(checkStateResults); for (int sidx = 0; sidx < succCnt; sidx++) { - final long successor = nextFPs.elementAt(sidx); + final long successor = nextStates.next().fingerPrint(); // Only add the transition if: // a) The successor itself has not been written to disk // TODO Why is an existing successor ignored? @@ -415,6 +457,7 @@ public class LiveCheck implements ILiveCheck { cnt++; } } + nextStates.resetNext(); // In simulation mode (see Simulator), it's possible that this // method is called multiple times for the same state (s0/fp0) // but with changing successors caused by the random successor @@ -465,16 +508,16 @@ public class LiveCheck implements ILiveCheck { for (int i = 0; i < initCnt; i++) { TBGraphNode tnode = oos.getTableau().getNode(i); if (tnode.isConsistent(state, myTool)) { - dgraph.addInitNode(stateFP, tnode.index); - dgraph.recordNode(stateFP, tnode.index); + dgraph.addInitNode(stateFP, tnode.getIndex()); + dgraph.recordNode(stateFP, tnode.getIndex()); } } } /* (non-Javadoc) - * @see tlc2.tool.liveness.LiveChecker#addNextState(tlc2.tool.TLCState, long, tlc2.tool.StateVec, tlc2.util.LongVec) + * @see tlc2.tool.liveness.ILiveChecker#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates, tlc2.util.BitVector, boolean[]) */ - public void addNextState(final TLCState s0, final long fp0, final StateVec nextStates, final LongVec nextFPs, + public void addNextState(final TLCState s0, final long fp0, final SetOfStates nextStates, final BitVector checkActionResults, final boolean[] checkStateResults) throws IOException { int cnt = 0; final int succCnt = nextStates.size(); @@ -492,16 +535,17 @@ public class LiveCheck implements ILiveCheck { while(elements.hasMoreElements()) { final TBGraphNode tableauNode = elements.nextElement(); for (int sidx = 0; sidx < succCnt; sidx++) { - final TLCState s1 = nextStates.elementAt(sidx); + final TLCState s1 = nextStates.next(); if(tableauNode.isConsistent(s1, myTool)) { // BitVector is divided into a segment for each // tableau node. Inside each segment, addressing is done // via each state. Use identical addressing below // where the lookup is done (plus 1 accounts for // zero-based addressing). - consistency.set((tableauNode.index * succCnt) + sidx); + consistency.set((tableauNode.getIndex() * succCnt) + sidx); } } + nextStates.resetNext(); } // At this point only constant time operations are allowed => @@ -537,38 +581,34 @@ public class LiveCheck implements ILiveCheck { final int s = node0.succSize(); node0.setCheckState(checkStateResults); for (int sidx = 0; sidx < succCnt; sidx++) { - final TLCState s1 = nextStates.elementAt(sidx); - final long successor = nextFPs.elementAt(sidx); + final TLCState s1 = nextStates.next(); + final long successor = s1.fingerPrint(); final boolean isDone = dgraph.isDone(successor); for (int k = 0; k < tnode0.nextSize(); k++) { final TBGraphNode tnode1 = tnode0.nextAt(k); // Check if the successor is new - long ptr1 = dgraph.getPtr(successor, tnode1.index); - if (ptr1 == -1) { - if (consistency.get((tnode1.index * succCnt) + sidx)) { // see note on addressing above - node0.addTransition(successor, tnode1.index, checkStateResults.length, - alen, checkActionResults, sidx * alen, - allocationHint - cnt++); - // Record that we have seen <fp1, - // tnode1>. If fp1 is done, we have - // to compute the next states for <fp1, - // tnode1>. - dgraph.recordNode(successor, tnode1.index); + final long ptr1 = dgraph.getPtr(successor, tnode1.getIndex()); + if (consistency.get((tnode1.getIndex() * succCnt) + sidx) + && (ptr1 == -1 || !node0.transExists(successor, tnode1.getIndex()))) { + node0.addTransition(successor, tnode1.getIndex(), checkStateResults.length, alen, + checkActionResults, sidx * alen, allocationHint - cnt); + // Record that we have seen <fp1, + // tnode1>. If fp1 is done, we have + // to compute the next states for <fp1, + // tnode1>. + if (ptr1 == -1) { + dgraph.recordNode(successor, tnode1.getIndex()); if (isDone) { addNextState(s1, successor, tnode1, oos, dgraph); } } - } else if (!node0.transExists(successor, tnode1.index)) { - node0.addTransition(successor, tnode1.index, checkStateResults.length, - alen, checkActionResults, sidx * alen, allocationHint - - cnt++); - } else { - // Increment cnt even if addTrasition is not called. After all, - // the for loop has completed yet another iteration. - cnt++; } + // Increment cnt even if addTrasition is not called. After all, + // the for loop has completed yet another iteration. + cnt++; } } + nextStates.resetNext(); // See same case in LiveChecker#addNextState if (s < node0.succSize()) { node0.realign(); // see node0.addTransition() hint @@ -584,44 +624,51 @@ public class LiveCheck implements ILiveCheck { } /** - * This method takes care of the case that a new node (s, t) is generated - * after s has been done. In this case, we will have to compute the children - * of (s, t). Hopefully, this case does not occur very frequently. + * This method takes care of the case that a new node <<state, tableau>> + * in the (state X tableau) graph is generated after the state itself + * has been done. Done means that the state has been found during safety + * checking in the state graph already, except that the node <<state, + * tableau>> not been created. + * <p> + * In this case, we will have to generate the state graph successors of + * the state and create the permutation of all successors with all + * tableau nodes . + * <p> + * Hopefully, this case does not occur very frequently because it + * generates successor nodes. */ private void addNextState(final TLCState s, final long fp, final TBGraphNode tnode, final OrderOfSolution oos, final TableauDiskGraph dgraph) throws IOException { final boolean[] checkStateRes = oos.checkState(s); final int slen = checkStateRes.length; final int alen = oos.getCheckAction().length; - final GraphNode node = dgraph.getNode(fp, tnode.index); + final GraphNode node = dgraph.getNode(fp, tnode.getIndex()); final int numSucc = node.succSize(); node.setCheckState(checkStateRes); // see allocationHint of node.addTransition() invocations below int cnt = 0; - // Add edges induced by s -> s: - final BitVector checkActionResults = oos.checkAction(s, s, new BitVector(alen), 0); - + // Add edges induced by s -> s (self-loop) coming from the tableau + // graph: final int nextSize = tnode.nextSize(); + final BitVector checkActionResults = nextSize > 0 ? oos.checkAction(s, s, new BitVector(alen), 0) : null; for (int i = 0; i < nextSize; i++) { final TBGraphNode tnode1 = tnode.nextAt(i); - final int tidx1 = tnode1.index; + final int tidx1 = tnode1.getIndex(); final long ptr1 = dgraph.getPtr(fp, tidx1); - if (ptr1 == -1) { - if (tnode1.isConsistent(s, myTool)) { - node.addTransition(fp, tidx1, slen, alen, checkActionResults, 0, (nextSize - cnt++)); - dgraph.recordNode(fp, tnode1.index); + if (tnode1.isConsistent(s, myTool) && (ptr1 == -1 || !node.transExists(fp, tidx1))) { + node.addTransition(fp, tidx1, slen, alen, checkActionResults, 0, (nextSize - cnt)); + if (ptr1 == -1) { + dgraph.recordNode(fp, tnode1.getIndex()); addNextState(s, fp, tnode1, oos, dgraph); - } else { - cnt++; } - } else { - node.addTransition(fp, tidx1, slen, alen, checkActionResults, 0, (nextSize - cnt++)); } + cnt++; } - // Add edges induced by s -> s1: + // Add edges induced by s -> s1 (where s1 is a successor of s in the + // state graph): cnt = 0; for (int i = 0; i < actions.length; i++) { final StateVec nextStates = myTool.getNextStates(actions[i], s); @@ -634,25 +681,22 @@ public class LiveCheck implements ILiveCheck { boolean isDone = dgraph.isDone(fp1); for (int k = 0; k < tnode.nextSize(); k++) { final TBGraphNode tnode1 = tnode.nextAt(k); - final int tidx1 = tnode1.index; + final int tidx1 = tnode1.getIndex(); long ptr1 = dgraph.getPtr(fp1, tidx1); final int total = actions.length * nextCnt * tnode.nextSize(); - if (ptr1 == -1) { - if (tnode1.isConsistent(s1, myTool)) { - node.addTransition(fp1, tidx1, slen, alen, checkActionRes, 0, (total - cnt++)); - // Record that we have seen <fp1, tnode1>. If - // fp1 is done, we have to compute the next - // states for <fp1, tnode1>. + if (tnode1.isConsistent(s1, myTool) && (ptr1 == -1 || !node.transExists(fp1, tidx1))) { + node.addTransition(fp1, tidx1, slen, alen, checkActionRes, 0, (total - cnt)); + // Record that we have seen <fp1, tnode1>. If + // fp1 is done, we have to compute the next + // states for <fp1, tnode1>. + if (ptr1 == -1) { dgraph.recordNode(fp1, tidx1); if (isDone) { addNextState(s1, fp1, tnode1, oos, dgraph); } } - } else if (!node.transExists(fp1, tidx1)) { - node.addTransition(fp1, tidx1, slen, alen, checkActionRes, 0, (total - cnt++)); - } else { - cnt++; } + cnt++; } } else { cnt++; @@ -715,11 +759,15 @@ public class LiveCheck implements ILiveCheck { //recover(); // After recovery, one has to redo the init states - final StateVec initStates = tool.getInitStates(); - for (int i = 0; i < initStates.size(); i++) { - TLCState state = initStates.elementAt(i); - liveCheck.addInitState(state, state.fingerPrint()); - } + tool.getInitStates(new IStateFunctor() { + /* (non-Javadoc) + * @see tlc2.tool.IStateFunctor#addElement(tlc2.tool.TLCState) + */ + public Object addElement(TLCState state) { + liveCheck.addInitState(state, state.fingerPrint()); + return true; + } + }); return liveCheck; } diff --git a/tlatools/src/tlc2/tool/liveness/LiveCheck1.java b/tlatools/src/tlc2/tool/liveness/LiveCheck1.java index d09308dc91ab06ba25f5c6faa7d55649b96854d0..8792007a7ba1b20dcdb97c43fd008274eb6a0317 100644 --- a/tlatools/src/tlc2/tool/liveness/LiveCheck1.java +++ b/tlatools/src/tlc2/tool/liveness/LiveCheck1.java @@ -19,9 +19,9 @@ import tlc2.tool.TLCStateInfo; import tlc2.tool.Tool; import tlc2.util.FP64; import tlc2.util.LongObjTable; -import tlc2.util.LongVec; import tlc2.util.MemObjectStack; import tlc2.util.ObjectStack; +import tlc2.util.SetOfStates; import tlc2.util.Vect; import tlc2.util.statistics.DummyBucketStatistics; import tlc2.util.statistics.IBucketStatistics; @@ -155,11 +155,11 @@ public class LiveCheck1 implements ILiveCheck { for (int i = 0; i < initCnt; i++) { TBGraphNode tnode = os.getTableau().getNode(i); if (tnode.isConsistent(srcState, myTool)) { - BEGraphNode destNode = new BTGraphNode(srcFP, tnode.index); + BEGraphNode destNode = new BTGraphNode(srcFP, tnode.getIndex()); destNode.setCheckState(checkStateRes); initNodes.addElement(destNode); srcNodes.addElement(destNode); - allNodes.put(FP64.Extend(srcFP, tnode.index), destNode); + allNodes.put(FP64.Extend(srcFP, tnode.getIndex()), destNode); } } for (int i = 0; i < srcNodes.size(); i++) { @@ -167,7 +167,7 @@ public class LiveCheck1 implements ILiveCheck { TBGraphNode tnode = srcNode.getTNode(os.getTableau()); for (int j = 0; j < tnode.nextSize(); j++) { TBGraphNode tnode1 = tnode.nextAt(j); - long destFP = FP64.Extend(srcFP, tnode1.index); + long destFP = FP64.Extend(srcFP, tnode1.getIndex()); BEGraphNode destNode = (BEGraphNode) allNodes.get(destFP); if (destNode != null) { srcNode.addTransition(destNode, slen, alen, checkActionRes); @@ -185,11 +185,11 @@ public class LiveCheck1 implements ILiveCheck { TBGraphNode tnode = srcNode.getTNode(os.getTableau()); for (int k = 0; k < tnode.nextSize(); k++) { TBGraphNode tnode1 = tnode.nextAt(k); - long destFP = FP64.Extend(destStateFP, tnode1.index); + long destFP = FP64.Extend(destStateFP, tnode1.getIndex()); BEGraphNode destNode = (BEGraphNode) allNodes.get(destFP); if (destNode == null) { if (tnode1.isConsistent(destState, myTool)) { - destNode = new BTGraphNode(destStateFP, tnode1.index); + destNode = new BTGraphNode(destStateFP, tnode1.getIndex()); destNode.setCheckState(checkStateRes); srcNode.addTransition(destNode, slen, alen, checkActionRes); destNodes.addElement(destNode); @@ -206,11 +206,11 @@ public class LiveCheck1 implements ILiveCheck { TBGraphNode tnode = srcNode.getTNode(os.getTableau()); for (int k = 0; k < tnode.nextSize(); k++) { TBGraphNode tnode1 = tnode.nextAt(k); - long destFP = FP64.Extend(destStateFP, tnode1.index); + long destFP = FP64.Extend(destStateFP, tnode1.getIndex()); BEGraphNode destNode = (BEGraphNode) allNodes.get(destFP); if (destNode == null) { if (tnode1.isConsistent(destState, myTool)) { - destNode = new BTGraphNode(destStateFP, tnode1.index); + destNode = new BTGraphNode(destStateFP, tnode1.getIndex()); destNode.setCheckState(checkStateRes); srcNode.addTransition(destNode, slen, alen, checkActionRes); destNodes.addElement(destNode); @@ -255,7 +255,7 @@ public class LiveCheck1 implements ILiveCheck { for (int i = 0; i < initCnt; i++) { TBGraphNode tnode = os.getTableau().getNode(i); if (tnode.isConsistent(state, myTool)) { - BTGraphNode destNode = new BTGraphNode(stateFP, tnode.index); + BTGraphNode destNode = new BTGraphNode(stateFP, tnode.getIndex()); destNode.setCheckState(checkStateRes); bgraph.addInitNode(destNode); bgraph.allNodes.putBTNode(destNode); @@ -270,14 +270,15 @@ public class LiveCheck1 implements ILiveCheck { } /* (non-Javadoc) - * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.tool.StateVec, tlc2.util.LongVec) + * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates) */ - public void addNextState(TLCState s0, long fp0, StateVec nextStates, LongVec nextFPs) throws IOException { + public void addNextState(TLCState s0, long fp0, SetOfStates nextStates) throws IOException { for (int i = 0; i < nextStates.size(); i++) { - final TLCState s2 = nextStates.elementAt(i); - final long fp2 = nextFPs.elementAt(i); + final TLCState s2 = nextStates.next(); + final long fp2 = s2.fingerPrint(); addNextState(s0, fp0, s2, fp2); } + nextStates.resetNext(); } /** @@ -322,10 +323,10 @@ public class LiveCheck1 implements ILiveCheck { TBGraphNode tnode = os.getTableau().getNode(srcNode.getIndex()); for (int j = 0; j < tnode.nextSize(); j++) { TBGraphNode tnode1 = tnode.nextAt(j); - BTGraphNode destNode = bgraph.allNodes.getBTNode(fp2, tnode1.index); + BTGraphNode destNode = bgraph.allNodes.getBTNode(fp2, tnode1.getIndex()); if (destNode == null) { if (tnode1.isConsistent(s2, myTool)) { - destNode = new BTGraphNode(fp2, tnode1.index); + destNode = new BTGraphNode(fp2, tnode1.getIndex()); if (checkStateRes == null) { checkStateRes = os.checkState(s2); } @@ -363,10 +364,10 @@ public class LiveCheck1 implements ILiveCheck { TBGraphNode tnode = node.getTNode(os.getTableau()); for (int i = 0; i < tnode.nextSize(); i++) { TBGraphNode tnode1 = tnode.nextAt(i); - BTGraphNode destNode = bgraph.allNodes.getBTNode(fp, tnode1.index); + BTGraphNode destNode = bgraph.allNodes.getBTNode(fp, tnode1.getIndex()); if (destNode == null) { if (tnode1.isConsistent(state, myTool)) { - destNode = new BTGraphNode(fp, tnode1.index); + destNode = new BTGraphNode(fp, tnode1.getIndex()); destNode.setCheckState(checkState); node.addTransition(destNode, slen, alen, checkAction); bgraph.allNodes.putBTNode(destNode); @@ -399,10 +400,10 @@ public class LiveCheck1 implements ILiveCheck { boolean[] checkActionRes1 = null; for (int k = 0; k < tnode.nextSize(); k++) { TBGraphNode tnode1 = tnode.nextAt(k); - BTGraphNode destNode = bgraph.allNodes.getBTNode(fp1, tnode1.index); + BTGraphNode destNode = bgraph.allNodes.getBTNode(fp1, tnode1.getIndex()); if (destNode == null) { if (tnode1.isConsistent(s1, myTool)) { - destNode = new BTGraphNode(fp1, tnode1.index); + destNode = new BTGraphNode(fp1, tnode1.getIndex()); if (checkStateRes == null) { checkStateRes = os.checkState(s1); } @@ -436,6 +437,13 @@ public class LiveCheck1 implements ILiveCheck { bgraphs[soln].allNodes.setDone(fp); } } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.ILiveCheck#doLiveCheck() + */ + public boolean doLiveCheck() { + return true; + } /** * Checks if the partial behavior graph constructed up to now contains any @@ -671,7 +679,7 @@ public class LiveCheck1 implements ILiveCheck { } } if (node.stateFP == lastState.fingerPrint()) { - StatePrinter.printStutteringState(++stateNum); + StatePrinter.printStutteringState(stateNum); } else { if (TLCGlobals.tool) { // The parser in Tool mode is picky and does not detect the Back to State unless it's printed via MP.printState. diff --git a/tlatools/src/tlc2/tool/liveness/LiveExprNode.java b/tlatools/src/tlc2/tool/liveness/LiveExprNode.java index 9e111bf648ccc7512db3d69a8468d31b43634a42..51a37a377a378b7f9b47d90a8646b2bdcacb6b7e 100644 --- a/tlatools/src/tlc2/tool/liveness/LiveExprNode.java +++ b/tlatools/src/tlc2/tool/liveness/LiveExprNode.java @@ -42,6 +42,12 @@ public abstract class LiveExprNode { /* Returns true iff the expression contains action. */ public abstract boolean containAction(); + /** + * @param s1 First state + * @param s2 Second (successor) state + * @param tool (Technical Tool implementation) + * @return true iff both states are consistent with this {@link LiveExprNode}. + */ public abstract boolean eval(Tool tool, TLCState s1, TLCState s2); /* The string representation. */ diff --git a/tlatools/src/tlc2/tool/liveness/LiveWorker.java b/tlatools/src/tlc2/tool/liveness/LiveWorker.java index 00dffb898622c0ae567a23fb182cea525eb68901..5284677491dc41daeb20d73825ec05f16410253b 100644 --- a/tlatools/src/tlc2/tool/liveness/LiveWorker.java +++ b/tlatools/src/tlc2/tool/liveness/LiveWorker.java @@ -5,43 +5,80 @@ package tlc2.tool.liveness; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import tlc2.TLCGlobals; import tlc2.output.EC; import tlc2.output.MP; import tlc2.output.StatePrinter; import tlc2.tool.EvalException; -import tlc2.tool.TLCState; import tlc2.tool.TLCStateInfo; import tlc2.util.IdThread; +import tlc2.util.IntStack; import tlc2.util.LongVec; import tlc2.util.MemIntQueue; import tlc2.util.MemIntStack; +import tlc2.util.SynchronousDiskIntStack; import tlc2.util.statistics.BucketStatistics; import tlc2.util.statistics.IBucketStatistics; +/** + * {@link LiveWorker} is doing the heavy lifting of liveness checking: + * <ul> + * <li>Searches for strongly connected components (SCC) a.k.a. cycles in the + * liveness/behavior graph.</li> + * <li>Checks each SCC if it violates the liveness properties.</li> + * <li>In case of a violation, reconstructs and prints the error trace.</li> + * </ul> + */ public class LiveWorker extends IdThread { + /** + * A marker that is pushed onto the dfsStack during SCC depth-first-search + * to marker an explored nodes on the stack. + * <p> + * A node with a marker is on the comStack. + */ + private static final long SCC_MARKER = -42L; + public static final IBucketStatistics STATS = new BucketStatistics("Histogram SCC sizes", LiveWorker.class .getPackage().getName(), "StronglyConnectedComponent sizes"); private static int errFoundByThread = -1; - private static Object workerLock = new Object(); + private static final Object workerLock = new Object(); private OrderOfSolution oos = null; private AbstractDiskGraph dg = null; private PossibleErrorModel pem = null; private final ILiveCheck liveCheck; private final BlockingQueue<ILiveChecker> queue; + private final boolean isFinalCheck; + /** + * Total number of LiveWorkers simultaneously checking liveness. + */ + private final int numWorkers; - public LiveWorker(int id, final ILiveCheck liveCheck, final BlockingQueue<ILiveChecker> queue) { + public LiveWorker(int id, int numWorkers, final ILiveCheck liveCheck, final BlockingQueue<ILiveChecker> queue, final boolean finalCheck) { super(id); + this.numWorkers = numWorkers; this.liveCheck = liveCheck; this.queue = queue; + this.isFinalCheck = finalCheck; + + // Set the name to something more indicative than "Thread-4711". + this.setName("TLCLiveWorkerThread-" + String.format("%03d", id)); } - // Returns true iff an error has already found. + /** + * Returns true iff an error has already been found. + */ public static boolean hasErrFound() { synchronized (workerLock) { return (errFoundByThread != -1); @@ -49,8 +86,11 @@ public class LiveWorker extends IdThread { } /** - * Returns true iff either an error has not found or the error is found by - * this thread. + * Returns true iff either an error has not been found or the error is found + * by this thread. + * <p> + * This is used so that only one of the threads which have found an error + * prints it. */ private/* static synchronized */boolean setErrFound() { synchronized (workerLock) { @@ -69,34 +109,83 @@ public class LiveWorker extends IdThread { * http://en.wikipedia.org/wiki/Strongly_connected_component), and checks * each of them to see if it contains a counterexample. * <p> - * It seems to be using the Path-based strong component algorithm - * (http://en.wikipedia.org/wiki/Path-based_strong_component_algorithm). - * This assumption however is incorrect! The second stack is not related to - * the SCC algorithm. Thus, it is Tarjan's SCC algorithm at work - * (http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm). + * It is Tarjan's SCC algorithm at work: + * <p> + * The notable differences to the text book algorithm are: + * <ul> + * <li>It is implemented iteratively (probably to prevent StackOverflows) + * </li> + * <li>The lowLink number gets pushed onto the DFS stack</li> + * <li>If a node is on the DFS stack is determined by checking if it has a + * link number assigned</li> + * <li>Once an SCC has been found, it is checked immediately for liveness + * violations (there is no point it searching all SCCs if the first SCC + * found already violates liveness)</li> + * <li>Not all states are added to the set of unexplored nodes initially, + * but only the model checking init states (all successors are known to be + * reachable from the init states).</li> + * <li>Liveness is checked periodically during model checking and thus + * checkSccs runs on a partial graph. Thus some nodes are marked undone. + * Those nodes are skipped by the SCC search.</li> + * </ul> + * @throws ExecutionException + * @throws InterruptedException + * + * @see http://en.wikipedia.org/wiki/Tarjan' + * s_strongly_connected_components_algorithm + * @see http://dx.doi.org/10.1137%2F0201010 + * */ - private final void checkSccs() throws IOException { + private final void checkSccs() throws IOException, InterruptedException, ExecutionException { // Initialize this.dg: this.dg.makeNodePtrTbl(); // Initialize nodeQueue with initial states. The initial states stored // separately in the DiskGraph are resolved to their pointer location // in the on-disk part of the DiskGraph. - // The pointer location is obviously used to: + // The pointer location generally is obviously used to: // * Speed up disk lookups in the RandomAccessFile(s) backing up the DiskGraph - // * Seems to serve as a marker for when a node during SCCs is fully explored //VERIFY assumption! + // * Is replaced by the SCC link number the moment the node's successors + // are explored during DFS search. At this point the ptr location isn't + // needed anymore. The successors have been resolved. + // + // From each node in nodeQueue the SCC search is started down below, + // which can subsequently add additional nodes into nodeQueue. // - final MemIntQueue nodeQueue = new MemIntQueue(liveCheck.getMetaDir(), "root"); + // Contrary to plain Tarjan, not all vertices are added to the + // nodeQueue of unexplored states, but only the initial states. Since we + // know that all non-initial states are reachable from the set of + // initial states, this is sufficient to start with. final LongVec initNodes = this.dg.getInitNodes(); final int numOfInits = initNodes.size(); + // Allocate space for all initial states, assuming the majority of + // initial nodes will be done. Multiplied by 5 because of + // <<long, int, long>> per "record. + final MemIntQueue nodeQueue = new MemIntQueue(liveCheck.getMetaDir(), "root", (numOfInits / 2) * 5); for (int j = 0; j < numOfInits; j += 2) { final long state = initNodes.elementAt(j); final int tidx = (int) initNodes.elementAt(j + 1); final long ptr = this.dg.getLink(state, tidx); - if (ptr >= 0) { //QUESTION When is a ptr < 0? + // Check if the node <<state, tidx>> s is done. A node s is undone + // if it is an initial state which hasn't been explored yet. This is + // the case if s has been added via LiveChecker#addInitState but not + // yet via LiveChecker#addNextState. LiveChecker#addNextState fully + // explores the given init state s because it has access to s' + // successors. + if (ptr >= 0) { + // Make sure none of the init states has already been assigned a + // link number. That would indicate a bug in makeNodePtrTbl + // which is supposed to reset all link numbers to file ptrs. + assert DiskGraph.isFilePointer(ptr); nodeQueue.enqueueLong(state); nodeQueue.enqueueInt(tidx); nodeQueue.enqueueLong(ptr); + } else { + // If this is the final check on the complete graph, no node is + // allowed to be undone. If it's not the final check, ptr has to + // be UNDONE (a non-UNDONE negative pointer is probably a bug). + // isFinalCheck => ptr # UNDONE + assert !isFinalCheck || ptr != TableauNodePtrTable.UNDONE; } } @@ -105,84 +194,136 @@ public class LiveWorker extends IdThread { final int alen = this.oos.getCheckAction().length; // Tarjan's stack - final MemIntStack dfsStack = new MemIntStack(liveCheck.getMetaDir(), "dfs"); + // Append thread id to name for unique disk files during concurrent SCC search + final IntStack dfsStack = getStack(liveCheck.getMetaDir(), "dfs" + this.myGetId()); // comStack is only being added to during the deep first search. It is passed - // to the checkComponent method while in DFS though. - // - // An Eclipse detailed formatter: - // StringBuffer buf = new StringBuffer(this.size); - // for (int i = 1; i < this.size; i+=5) { - // buf.append("state: "); - // buf.append(peakLong(size - i)); - // buf.append("\n"); - // buf.append(" Tableaux idx: "); - // buf.append(peakInt(size - i - 2)); - // buf.append("\n"); - // buf.append(" ptr loc: "); - // buf.append(peakLong(size - i - 3)); - // buf.append("\n"); - // } - // return buf.toString(); + // to the checkComponent method while in DFS though. Note that the nodes pushed + // onto comStack don't necessarily form a strongly connected component (see + // comment above this.checkComponent(...) below for more details). // - final MemIntStack comStack = new MemIntStack(liveCheck.getMetaDir(), "com"); + // See tlc2.tool.liveness.LiveWorker.DetailedFormatter.toString(MemIntStack) + // which is useful during debugging. + final IntStack comStack = getStack(liveCheck.getMetaDir(), "com" + this.myGetId()); - // Generate the SCCs and check if they contain any "bad" cycle. - while (nodeQueue.length() > 0) { + // Generate the SCCs and check if they contain a "bad" cycle. + while (nodeQueue.size() > 0) { + // Pick one of the unexplored nodes as root and start searching the + // reachable SCCs from it. final long state = nodeQueue.dequeueLong(); final int tidx = nodeQueue.dequeueInt(); final long loc = nodeQueue.dequeueLong(); - // Start computing SCCs with <state, tidx> as the root node: + // Reset (remove all elements) the stack. Logically a new SCC search + // is being started unrelated to the previous one. dfsStack.reset(); + // Push the first node onto the DFS stack which makes it the node + // from which the depth-first-search is being started. dfsStack.pushLong(state); dfsStack.pushInt(tidx); dfsStack.pushLong(loc); + // Push the smallest possible link number (confusingly called + // MAX_PTR here but only because file pointers are < MAX_PTR) as the + // first link number. + // [0, MAX_PTR) for file pointers + // [MAX_PTR, MAX_LINK] for links dfsStack.pushLong(DiskGraph.MAX_PTR); long newLink = DiskGraph.MAX_PTR; - while (dfsStack.size() > 2) { - long lowLink = dfsStack.popLong(); + while (dfsStack.size() >= 7) { + final long lowLink = dfsStack.popLong(); final long curLoc = dfsStack.popLong(); final int curTidx = dfsStack.popInt(); final long curState = dfsStack.popLong(); + // At this point curLoc is still a file pointer (small MAX_PTR) + // and not yet replaced by a link (MAX_PTR < curLoc < MAX_LINK). + assert DiskGraph.isFilePointer(curLoc); // The current node is explored iff curLoc < 0. If it is indeed fully explored, // it means it has potentially found an SCC. Thus, check if this is the case // for the current GraphNode. - if (curLoc < 0) { + // A node is fully explored if the nested loop over its + // successors down below in the else branch has not revealed any + // unexplored successors. + if (curLoc == SCC_MARKER) { + // Check if the current node's link is lowLink which + // indicates that the nodes on comStack up to <<curState, + // curTidx>> form an SCC. + // If curLink # lowLink, continue by pop'ing the next node + // from dfsStack. It can either be: + // - unexplored in which case the else branch is taken and + // DFS continues. + // - be an intermediate node of the SCC and thus curLink # + // lowLink for it too. + // - can be the start of the SCC (curLink = lowLink). final long curLink = this.dg.getLink(curState, curTidx); + assert curLink < AbstractDiskGraph.MAX_LINK; if (curLink == lowLink) { - // The states on the comStack from top to curState form - // a SCC. - // Check for "bad" cycle. + // The states on the comStack from "top" to <<curState, + // curTidx>> form an SCC, thus check for "bad" cycle. + // + // The cycle does not necessarily include all states in + // comStack. "top" might very well be curState in which + // case only a single state is checked by + // checkComponent. + // + // The aforementioned case happens regularly when the + // behaviors to check don't have cycles at all (leaving + // single node cycles aside for the moment). The DFS + // followed each behavior from its initial state (see + // nodeQueue) all the way to the behavior's end state at + // which point DFS halts. Since DFS cannot continue + // (there are no successors) it calls checkComponent now + // with the current comStack and the end state as + // <<curState, curTidx>> effectively checking the + // topmost element of comStack. Unless this single state + // violates any liveness properties, it gets removed + // from comStack and DFS continues. Iff DFS still cannot + // continue because the predecessor to endstate + // (endstate - 1) has no more successors to explore + // either, it again calls checkComponent for the single + // element (endstate - 1). This goes on until either the + // initial state is reached or an intermediate state has + // unexplored successors with DFS. final boolean isOK = this.checkComponent(curState, curTidx, comStack); if (!isOK) { - // Found a "bad" cycle, no point in searching for more SCCs. + // Found a "bad" cycle of one to comStack.size() + // nodes, no point in searching for more SCCs as we + // are only interested in one counter-example at a + // time. + // checkComponent will have printed the + // counter-example by now. return; } } - long plowLink = dfsStack.popLong(); - if (lowLink < plowLink) { - plowLink = lowLink; - } - dfsStack.pushLong(plowLink); + // Replace previous lowLink (plowLink) with the minimum of + // the current lowLink and plowLink on the stack. + final long plowLink = dfsStack.popLong(); + dfsStack.pushLong(Math.min(plowLink, lowLink)); // No SCC found yet } else { // Assign newLink to curState: final long link = this.dg.putLink(curState, curTidx, newLink); + // link is -1 if newLink has been assigned to pair + // <<curState, curTidx>>. If the pair had been assigned a + // link before, the previous link in range [MAX_PTR, + // MAX_LINK] is returned. If the link is not -1, it means + // the node has been explored by this DFS search before. if (link == -1) { // Push curState back onto dfsStack, but make curState // explored: dfsStack.pushLong(lowLink); dfsStack.pushLong(curState); dfsStack.pushInt(curTidx); - dfsStack.pushLong(-1); + // Push a marker onto the stack that, if pop'ed as + // curLoc above causes branching to enter the true case + // of the if block. + dfsStack.pushLong(SCC_MARKER); - // Add curState to comStack: + // Add the tuple <<curState, curTidx, curLoc>> to comStack: comStack.pushLong(curLoc); comStack.pushInt(curTidx); comStack.pushLong(curState); @@ -190,52 +331,179 @@ public class LiveWorker extends IdThread { // Look at all the successors of curState: final GraphNode gnode = this.dg.getNode(curState, curTidx, curLoc); final int succCnt = gnode.succSize(); - long nextLowLink = newLink++; + long nextLowLink = newLink; + // DFS moved on to a new node, thus increment the newLink + // number by 1 for subsequent exploration. + newLink = newLink + 1; for (int i = 0; i < succCnt; i++) { final long nextState = gnode.getStateFP(i); final int nextTidx = gnode.getTidx(i); final long nextLink = this.dg.getLink(nextState, nextTidx); + // If <<nextState, nextTidx>> node's link is < 0 it + // means the node isn't "done" yet (see + // tlc2.tool.liveness.TableauNodePtrTable.UNDONE). + // A successor node t of gnode is undone if it is: + // - An initial state which hasn't been explored yet + // - t has not been added to the liveness disk graph + // itself (only as the successor (transition) of + // gnode). + // + // If it is >= 0, it either is a: + // - file pointer location + // - a previously assigned link (>= MAX_PTR) + // + // Iff nextLink == MAX_PTR, it means that the + // <<nextState, nextTidx>> successor node has been + // processed by checkComponent. The checks below + // will result in the successor node being skipped. + // + // It is possible that <<nextState, nextTidx>> = + // <<curState, curTid>> due to self loops. This is + // intended, as checkAction has to be evaluated for + // self loops too. if (nextLink >= 0) { + // Check if the arc/transition from <<curState, + // curTidx>> to <<nextState, nextTidx>> + // satisfies ("P-satisfiable" MP page 422ff) + // its PEM's EAAction. If it does, 1/3 of the + // conditions for P-satisfiability are + // satisfied. Thus it makes sense to check the + // other 2/3 in checkComponent (AEAction & + // Fulfilling promises). If the EAAction does + // not hold, there is no point in checking the + // other 2/3. All must hold for + // P-satisfiability. + // + // This check is related to the fairness spec. + // Usually, it evals to true when no or weak + // fairness have been specified. False on strong + // fairness. if (gnode.getCheckAction(slen, alen, i, eaaction)) { + // If the node's nextLink still points to + // disk, it means it has no link assigned + // yet which is the case if this node gets + // explored during DFS search the first + // time. Since it is new, add it to dfsStack + // to have it explored subsequently by DFS. if (DiskGraph.isFilePointer(nextLink)) { dfsStack.pushLong(nextState); dfsStack.pushInt(nextTidx); - dfsStack.pushLong(nextLink); - } else if (nextLink < nextLowLink) { - nextLowLink = nextLink; + dfsStack.pushLong(nextLink); // nextLink is logically a ptr/loc here + // One would expect a (logical) lowLink + // being pushed (additionally to the + // ptr/loc in previous line) onto the + // stack here. However, it is pushed + // down below after all successors are + // on the stack and valid for the + // topmost successor. For the other + // successors below the topmost, a link + // number will be assigned subsequently. + } else { + // The node has been processed + // already, thus use the minimum of its link + // (nextLink) and nextLowLink. + nextLowLink = Math.min(nextLowLink, nextLink); } - } else if (DiskGraph.isFilePointer(nextLink)) { + } else { + // The transition from <<curState, curTidx>> + // to <<nextState, nextTidx>> is not + // P-satisfiable and thus does not need to + // be checkComponent'ed. However, since we + // only added initial but no intermediate + // states to nodeQueue above, we have to add + // <<nextState, nextTidx>> to nodeQueue if + // it's still unprocessed (indicated by its + // on disk state). The current path + // potentially might be the only one by + // which DFS can reach it. + if (DiskGraph.isFilePointer(nextLink)) { nodeQueue.enqueueLong(nextState); nodeQueue.enqueueInt(nextTidx); - nodeQueue.enqueueLong(nextLink); + nodeQueue.enqueueLong(nextLink); // nextLink is logically a ptr/loc here + } } + } else { + // If this is the final check on the complete + // graph, no node is allowed to be undone. If + // it's not the final check, nextLink has to be + // UNDONE (a non-UNDONE negative nextLink is + // probably a bug). + // isFinalCheck => nextLink # UNDONE + assert !isFinalCheck || nextLink != TableauNodePtrTable.UNDONE; } } + // Push the next lowLink onto stack on top of all + // successors. It is assigned to the topmost + // successor only though. dfsStack.pushLong(nextLowLink); } else { - if (link < lowLink) { - lowLink = link; - } - dfsStack.pushLong(lowLink); + // link above wasn't "-1", thus it has to be a valid + // link in the known interval. + assert AbstractDiskGraph.MAX_PTR <= link && link <= AbstractDiskGraph.MAX_LINK; + // Push the minimum of the two links onto the stack. If + // link == DiskGraph.MAX_PTR lowLink will always be the + // minimum (unless this graph has a gigantic amount of + // SCCs exceeding (MAX_LINK - MAX_PTR). + dfsStack.pushLong(Math.min(lowLink, link)); } } } } + // Make sure all nodes on comStack have been checkComponent()'ed + assert comStack.size() == 0; + } - // After completing the checks, clean up: - // dfsStack.cleanup(); - // comStack.cleanup(); + private IntStack getStack(final String metaDir, final String name) throws IOException { + // Synchronize all LiveWorker instances to consistently read free + // memory. This method is only called during initialization of SCC + // search, thus synchronization should not cause significant thread + // contention. + synchronized (LiveWorker.class) { + // It is unlikely that the stacks will fit into memory if the + // size of the behavior graph is larger relative to the available + // memory. Also take the total number of simultaneously running + // workers into account that have to share the available memory + // among each other. + final double freeMemoryInBytes = (Runtime.getRuntime().freeMemory() / (numWorkers * 1d)); + final long graphSizeInBytes = this.dg.getSizeOnDisk(); + final double ratio = graphSizeInBytes / freeMemoryInBytes; + if (ratio > TLCGlobals.livenessGraphSizeThreshold) { + // Double SDIS's bufSize/pageSize by how much the graph size + // overshoots the free memory size, but limit page size to 1gb. + // Also, don't allocate more than what is available. + final int capacityInBytes = SynchronousDiskIntStack.BufSize << Math.min((int) ratio, 5); + if (capacityInBytes < freeMemoryInBytes) { + return new SynchronousDiskIntStack(metaDir, name, capacityInBytes); + } else { + // Use default SDIS which is 32mb of in-memory size + return new SynchronousDiskIntStack(metaDir, name); + } + } + // If the disk graph as a whole fits into memory, do not use a + // disk-backed SynchronousDiskIntStack. + return new MemIntStack(metaDir, name); + } } /** - * For currentPEM, this method checks if the current scc satisfies its AEs - * and is fulfilling. (We know the current scc satisfies the pem's EA.) If - * satisfiable, this pem contains a counterexample, and this method then - * calls printErrorTrace to print an error trace and returns false. + * For currentPEM, this method checks if the current SCC satisfies its AEs + * and is fulfilling (we know the current SCC satisfies the PEM's EA by the + * nested EAaction in checkSccs() above.) If satisfiable, this PEM + * contains a counterexample, and this method then calls printErrorTrace to + * print an error trace and returns false. + * <p> + * Speaking in words of Manna & Pnueli (Page 422ff), it checks if ¬φ + * (which is PEM) is "P-satisfiable" (i.e. is there a computation that + * satisfies ψ). ¬φ (called ψ by MP) is the negation of the + * liveness formula φ which has to be "P-valid" for the liveness + * properties to be valid. + * @throws ExecutionException + * @throws InterruptedException */ - private boolean checkComponent(final long state, final int tidx, final MemIntStack comStack) throws IOException { -// final int comStackSize = comStack.size(); -// Assert.check(comStackSize > 0, EC.GENERAL); + private boolean checkComponent(final long state, final int tidx, final IntStack comStack) throws IOException, InterruptedException, ExecutionException { + final long comStackSize = comStack.size(); + // There is something to pop and each is a well formed tuple <<fp, tidx, loc>> + assert comStackSize >= 5 && comStackSize % 5 == 0; // long + int + long long state1 = comStack.popLong(); int tidx1 = comStack.popInt(); @@ -259,10 +527,20 @@ public class LiveWorker extends IdThread { // to be kept in com. However, it would destroy NodePtrTable's // collision handling. NodePtrTable uses open addressing (see // http://en.wikipedia.org/wiki/Open_addressing). - TableauNodePtrTable com = new TableauNodePtrTable(128); + // + // Initializing the NTPT with 128 buckets/slows is a significant memory + // overhead (especially when comStack contains < 10 elements) which + // regularly results in OutOfMemoryErrors being thrown. To alleviate the + // problem the key-space of the comStack elements could be checked and + // the minimum possible collision-free TNPT size be calculated. + // (Btw. the implementation uses a TNPT in the first place because it is + // passed on to printTrace iff an error is found. The implementation + // here could use a simple java.util.Map or HashTable technically.) + final TableauNodePtrTable com = new TableauNodePtrTable(128); while (true) { // Add <state1, tidx1> into com: com.put(state1, tidx1, loc1); + assert AbstractDiskGraph.isFilePointer(loc1); this.dg.setMaxLink(state1, tidx1); // Get the next node of the component: @@ -275,7 +553,7 @@ public class LiveWorker extends IdThread { loc1 = comStack.popLong(); } // Just parameter node in com OR com subset of comStack -// Assert.check(com.size() <= (comStackSize / 5), EC.GENERAL); + assert com.size() <= (comStackSize / 5); STATS.addSample(com.size()); @@ -295,26 +573,46 @@ public class LiveWorker extends IdThread { // NodePtrTable internally hashes the elements to buckets // and isn't filled start to end. Thus, the code // below iterates NodePtrTable front to end skipping null buckets. - int tsz = com.getSize(); + // + // Note that the nodes are processed in random order (depending on a + // node's hash in TableauNodePtrTbl) and not in the order given by + // comStack. This is fine because the all checks have been evaluated + // eagerly during insertion into the liveness graph long before the + // SCC search started. Thus, the code here only has to check the + // check results which can happen in any order. + final int tsz = com.getSize(); for (int ci = 0; ci < tsz; ci++) { - int[] nodes = com.getNodesByLoc(ci); + final int[] nodes = com.getNodesByLoc(ci); if (nodes == null) { // miss in NotePtrTable (null bucket) continue; } state1 = TableauNodePtrTable.getKey(nodes); - for (int nidx = 2; nidx < nodes.length; nidx += com.getElemLength()) { + for (int nidx = 2; nidx < nodes.length; nidx += com.getElemLength()) { // nidx starts with 2 because [0][1] are the long fingerprint state1. tidx1 = TableauNodePtrTable.getTidx(nodes, nidx); loc1 = TableauNodePtrTable.getElem(nodes, nidx); - GraphNode curNode = this.dg.getNode(state1, tidx1, loc1); + final GraphNode curNode = this.dg.getNode(state1, tidx1, loc1); // Check AEState: for (int i = 0; i < aeslen; i++) { + // Only ever set AEStateRes[i] to true, but never to false + // once it was true. It only matters if one state in com + // satisfies PEM's liveness property due to []<>¬p (which is + // the inversion of <>[]p). + // + // It obviously has to check all nodes in the component + // (com) if either of them violates AEState unless all + // elements of AEStateRes are true. From that point onwards, + // checking further states wouldn't make a difference. if (!AEStateRes[i]) { int idx = this.pem.AEState[i]; AEStateRes[i] = curNode.getCheckState(idx); + // Can stop checking AEStates the moment AEStateRes + // is completely set to true. However, most of the time + // aeslen is small and the compiler will probably optimize + // out. } } @@ -322,15 +620,25 @@ public class LiveWorker extends IdThread { // between the current node and a successor state. The current // node has n successor states. For each pair, see iff the // successor is in the "com" NodePtrTablecheck, check actions - // and store the results in AEActionRes(ult). - int succCnt = curNode.succSize(); + // and store the results in AEActionRes(ult). Note that the + // actions have long been checked in advance when the node was + // added to the graph and the actual state and not just its + // fingerprint was available. Here, the result is just being + // looked up. + final int succCnt = aealen > 0 ? curNode.succSize() : 0; // No point in looping successors if there are no AEActions to check on them. for (int i = 0; i < succCnt; i++) { - long nextState = curNode.getStateFP(i); - int nextTidx = curNode.getTidx(i); + final long nextState = curNode.getStateFP(i); + final int nextTidx = curNode.getTidx(i); + // For each successor <<nextState, nextTdix>> of curNode's + // successors check, if it is part of the currently + // processed SCC (com). Successors, which are not part of + // the current SCC have obviously no relevance here. After + // all, we check the SCC. if (com.getLoc(nextState, nextTidx) != -1) { for (int j = 0; j < aealen; j++) { + // Only set false to true, but never true to false. if (!AEActionRes[j]) { - int idx = this.pem.AEAction[j]; + final int idx = this.pem.AEAction[j]; AEActionRes[j] = curNode.getCheckAction(slen, alen, i, idx); } } @@ -340,8 +648,8 @@ public class LiveWorker extends IdThread { // Check that the component is fulfilling. (See MP page 453.) // Note that the promises are precomputed and stored in oos. for (int i = 0; i < plen; i++) { - LNEven promise = this.oos.getPromises()[i]; - TBPar par = curNode.getTNode(this.oos.getTableau()).getPar(); + final LNEven promise = this.oos.getPromises()[i]; + final TBPar par = curNode.getTNode(this.oos.getTableau()).getPar(); if (par.isFulfilling(promise)) { promiseRes[i] = true; } @@ -349,7 +657,18 @@ public class LiveWorker extends IdThread { } } - // We find a counterexample if all three conditions are satisfied. + // We find a counterexample if all three conditions are satisfied. If + // either of the conditions is false, it means the PEM does not hold and + // thus the liveness properties are not violated by the SCC. + // + // All AEState properties, AEActions and promises of PEM must be + // satisfied. If a single one isn't satisfied, the PEM as a whole isn't + // P-satisfiable. That's why it returns on the first false. As stated + // before, EAAction have already been checked if satisfiable. + // checkComponent is only called if the EA actions are satisfiable. + // + // Technically: No error is found if any of the AEStateRes, AEActionRes + // or promiseRes booleans is false. for (int i = 0; i < aeslen; i++) { if (!AEStateRes[i]) { return true; @@ -366,7 +685,8 @@ public class LiveWorker extends IdThread { } } // This component must contain a counter-example because all three - // conditions are satisfied. So, print a counter-example! + // conditions are satisfied. So, print a counter-example (if this thread + // is the first one to find a counter-example)! if (setErrFound()) { this.printTrace(state, tidx, com); } @@ -375,49 +695,314 @@ public class LiveWorker extends IdThread { /* Check if the node <state, tidx> stutters. */ private boolean isStuttering(long state, int tidx, long loc) throws IOException { - int slen = this.oos.getCheckState().length; - int alen = this.oos.getCheckAction().length; + final int slen = this.oos.getCheckState().length; + final int alen = this.oos.getCheckAction().length; - GraphNode gnode = this.dg.getNode(state, tidx, loc); - int succCnt = gnode.succSize(); + // Find the self loop and check its <>[]action + final GraphNode gnode = this.dg.getNode(state, tidx, loc); + final int succCnt = gnode.succSize(); for (int i = 0; i < succCnt; i++) { - long nextState = gnode.getStateFP(i); - int nextTidx = gnode.getTidx(i); + final long nextState = gnode.getStateFP(i); + final int nextTidx = gnode.getTidx(i); if (state == nextState && tidx == nextTidx) { return gnode.getCheckAction(slen, alen, i, this.pem.EAAction); } } + // <state, tidx> has no self loop, thus cannot stutter return false; } /** - * Print out the error state trace. The method first generates a "bad" cycle - * from the current scc, and then generates a prefix path from some initial - * state to the "bad" cycle in the state graph. The prefix path and the - * "bad" cycle together forms a counter-example. + * Print out the error state trace by finding a cycle in the given SCC. The + * method first generates a "bad" cycle from the current scc, and then + * generates a prefix path from some initial state to the "bad" cycle in the + * state graph. The prefix path and the "bad" cycle together forms a + * counter-example. + * <p> + * Additionally, the first part can be divided into the two while loops A) + * and B). A) re-creates the sub-path of the error trace starting at the + * start state of the SCC as given by the parameters and ends when all + * states have be accumulated that -combined- violate the liveness + * properties. Iff the last state after termination of A) is not equal to + * the start state, there is a gap in the cycle. Thus, B) task is to close + * the gap. + * <p> + * + * @see tlatools/test-model/symmetry/ErrorTraceConstructionPhases.png for a + * sketch. + * @see tlc2.tool.liveness.ErrorTraceConstructionTest which runs a spec that + * exemplifies the three staged error trace composition + * + * @param state + * fingerprint of the state which is the "starting" state of the + * SCC in nodeTbl. + * @param tidx + * tableau index pointing to the {@link TBGraph}. Corresponds to + * the state fingerprint. Combined <<state, tidx>> unique + * identify a node in the liveness/behavior graph. + * @param nodeTbl + * The current SCC which is known to satisfy the + * {@link PossibleErrorModel} and thus violates the liveness + * properties. + * @throws ExecutionException + * @throws InterruptedException */ - private void printTrace(final long state, final int tidx, final TableauNodePtrTable nodeTbl) throws IOException { + private void printTrace(final long state, final int tidx, final TableauNodePtrTable nodeTbl) throws IOException, InterruptedException, ExecutionException { +// System.out.println(toDotViz(state, tidx, nodeTbl)); MP.printError(EC.TLC_TEMPORAL_PROPERTY_VIOLATED); MP.printError(EC.TLC_COUNTER_EXAMPLE); + + /* + * Use a dedicated thread to concurrently search a prefix-path from some + * initial node to the state identified by <<state, tidx>>. + */ + final ExecutorService executor = Executors.newFixedThreadPool(1); + final Future<List<TLCStateInfo>> future = executor.submit(new Callable<List<TLCStateInfo>>() { + /* (non-Javadoc) + * @see java.util.concurrent.Callable#call() + */ + public List<TLCStateInfo> call() throws Exception { + // Print the error trace. We first construct the prefix that + // led to the bad cycle. The nodes on prefix and cycleStack then + // form the complete counter example. + final LongVec prefix = LiveWorker.this.dg.getPath(state, tidx); + final int plen = prefix.size(); + final List<TLCStateInfo> states = new ArrayList<TLCStateInfo>(plen); + + // Recover the initial state: + //TODO This throws an ArrayIndexOutOfBounds if getPath returned a + // LongVec with just a single element. This happens when the parameter + // state is one of the init states already. + long fp = prefix.elementAt(plen - 1); + TLCStateInfo sinfo = liveCheck.getTool().getState(fp); + if (sinfo == null) { + throw new EvalException(EC.TLC_FAILED_TO_RECOVER_INIT); + } + states.add(sinfo); + + // Recover the successor states: + for (int i = plen - 2; i >= 0; i--) { + long curFP = prefix.elementAt(i); + // The prefix might contain duplicates if the path happens to walk + // along two (or more distinct states which differ in the tableau + // idx only (same fingerprint). From the counterexample perspective, + // this is irrelevant iff the identical fingerprints are contiguous. + // It won't be correct to shorten a path <<fp1,fp2,fp1>> to + // <<fp2,fp1>> though. + if (curFP != fp) { + sinfo = liveCheck.getTool().getState(curFP, sinfo); + states.add(sinfo); + fp = curFP; + } + } + + // Print the prefix in reverse order of previous loop: + for (int i = 0; i < states.size(); i++) { + StatePrinter.printState(states.get(i)); + } + return states; + } + }); + + /* + * With the executor concurrently working on the prefix, let this thread + * work on the postfix (cycle). + */ + final MemIntStack cycleStack = new MemIntStack(liveCheck.getMetaDir(), "cycle"); + GraphNode curNode = dfsPostFix(state, tidx, nodeTbl, cycleStack); + + /* + * If the cycle is not closed/completed (complete when startState == + * state), continue from the curNode at which the previous while loop + * terminated and follow its successors until the start state shows up. + */ + final LongVec postfix = bfsPostFix(state, tidx, nodeTbl, curNode); + + /* + * At this point the cycle part of the error trace has been constructed. + * cycleStack contains the states from the start state of the SCC up to + * the state that violates all liveness properties. postfix contains the + * suffix from the violating state back to the start state of the SCC. + * Thus, append the reversed cycleStack onto postfix (cycleStack has the + * last state at the top). Postfix then contains the minimal path in the + * SCC that violates the liveness property. + */ + while (cycleStack.size() > 0) { + // Do not filter successive <<fp,tidx,permId>> here but do it below + // when the actual states get printed. See Test3.tla for reason why. + postfix.addElement(cycleStack.popLong()); + cycleStack.popInt(); // ignore tableau idx. The tableau idx is + // irrelevant as <<fpA, tidx1>> and <<fpA, + // tidx2>> both map to the same state in the + // error trace. + } + + // Wait for the prefix-path to be searched/generated and fully printed. + // get() is a blocking call that makes this thread wait for the executor + // to finish its job of searching and printing the prefix-path. + final List<TLCStateInfo> states = future.get(); + + /* + * At this point everything from the initial state up to the start state + * of the SCC has been printed. Now, print the states in postfix. Obtain + * the last state from the prefix (which corresponds to <<state, tidx>>) + * to use it to generate the next state. Obviously, we have to wait for + * the prefix thread to be done for two reasons: a) the trace has to be + * printed and b) we need the TLCState instance to generate the + * successor states in the cycle. + */ + final TLCStateInfo cycleState = states.get(states.size() - 1); + + TLCStateInfo sinfo = cycleState; + for (int i = postfix.size() - 1; i >= 0; i--) { + final long curFP = postfix.elementAt(i); + // Only print the state if it differs from its predecessor. We don't + // want to print an identical state twice. This can happen if the + // loops A) and B) above added an identical state multiple times + // into cycleStack/postfix. + // The reason we don't simply compare the actual states is for + // efficiency reason. Regenerating the next state might be + // expensive. + if (curFP != sinfo.fingerPrint()) { + sinfo = liveCheck.getTool().getState(curFP, sinfo); + StatePrinter.printState(sinfo); + } + } + + /* All error trace states have been printed (prefix + cycleStack + + * postfix). What is left is to print either the stuttering or the + * back-to-cyclePos marker. + */ + + final int stateNumber = (int) cycleState.stateNumber; // if the cast causes problems the trace won't be comprehensible anyway. + if (sinfo.fingerPrint() == cycleState.fingerPrint()) { + StatePrinter.printStutteringState(stateNumber); + } else { + sinfo = liveCheck.getTool().getState(cycleState.fingerPrint(), sinfo); + // The print stmts below claim there is a cycle, thus assert that + // there is indeed one. Index-based lookup into states array is + // reduced by one because cyclePos is human-readable. + assert cycleState.state.equals(sinfo.state); + StatePrinter.printBackToState(sinfo, stateNumber); + } + } + // BFS search + private LongVec bfsPostFix(final long state, final int tidx, final TableauNodePtrTable nodeTbl, GraphNode curNode) + throws IOException { + final LongVec postfix = new LongVec(16); + final long startState = curNode.stateFP; + final long startTidx = curNode.tindex; + + // B) + if (startState != state || startTidx != tidx) { + final MemIntQueue queue = new MemIntQueue(liveCheck.getMetaDir(), null); + long curState = startState; + int ploc = TableauNodePtrTable.NO_PARENT; + int curLoc = nodeTbl.getNodesLoc(curState); + int[] nodes = nodeTbl.getNodesByLoc(curLoc); + TableauNodePtrTable.setSeen(nodes); + + // B1) + _done: while (true) { + // tloc, the index of the various tableau indices in nodes array + int tloc = TableauNodePtrTable.startLoc(nodes); + // Loop over all GraphNodes (differing by tableau indices only) + // in nodes until at end. When all GraphNodes are explored, the + // next GraphNode to explore is taken from the FIFO/queue and + // the search continues. If one GraphNode happens to match the + // searched for <<fp, tidx>>, the (forward) search stops and + // the reverse path is followed up to the start node (inner + // while loop). + while (tloc != TableauNodePtrTable.END_MARKER) { + final int curTidx = TableauNodePtrTable.getTidx(nodes, tloc); + final long curPtr = TableauNodePtrTable.getPtr(TableauNodePtrTable.getElem(nodes, tloc)); + curNode = this.dg.getNode(curState, curTidx, curPtr); + final int succCnt = curNode.succSize(); + + // for each successor of curNode s, check if s is the + // destination state. + SUCCESSORS: for (int j = 0; j < succCnt; j++) { + final long nextState = curNode.getStateFP(j); + final int nextTidx = curNode.getTidx(j); + + // Ignore self-loop because it cannot close the + // cycle/lasso. The seen state flag partially + // prevents exploring self-loops, but only if + // the tableau idx is the base idx (the seen + // flag ignores the tableau idx entirely). + if (curState == nextState && curTidx == nextTidx) { + assert TableauNodePtrTable.isSeen(nodes); + continue SUCCESSORS; + } + + if (nextState == state && nextTidx == tidx) { + // We have found a path from startState to state, + // now backtrack the path the outer loop took to get + // us here and add each state to postfix. + while (curState != startState) { + postfix.addElement(curState); + nodes = nodeTbl.getNodesByLoc(ploc); + curState = TableauNodePtrTable.getKey(nodes); + ploc = TableauNodePtrTable.getParent(nodes); + } + postfix.addElement(startState); + break _done; + } + + // s is not equal to the destination state 'startState'. + // If s's successors are still unseen, add s to the + // queue to later explore it as well. Mark it seen + // to not explore it twice. + final int[] nodes1 = nodeTbl.getNodes(nextState); + if (nodes1 != null && !TableauNodePtrTable.isSeen(nodes1)) { + TableauNodePtrTable.setSeen(nodes1); + queue.enqueueLong(nextState); + queue.enqueueInt(curLoc); + } + } + tloc = TableauNodePtrTable.nextLoc(nodes, tloc); + } + // Create a parent pointer to later reverse the path in B2) + TableauNodePtrTable.setParent(nodes, ploc); + // Dequeue the next unexplored state from the queue. + curState = queue.dequeueLong(); + ploc = queue.dequeueInt(); + curLoc = nodeTbl.getNodesLoc(curState); + nodes = nodeTbl.getNodesByLoc(curLoc); + } + } + return postfix; + } + + private GraphNode dfsPostFix(final long state, final int tidx, final TableauNodePtrTable nodeTbl, final MemIntStack cycleStack) throws IOException { // First, find a "bad" cycle from the "bad" scc. - int slen = this.oos.getCheckState().length; - int alen = this.oos.getCheckAction().length; - boolean[] AEStateRes = new boolean[this.pem.AEState.length]; - boolean[] AEActionRes = new boolean[this.pem.AEAction.length]; - boolean[] promiseRes = new boolean[this.oos.getPromises().length]; + final int slen = this.oos.getCheckState().length; + final int alen = this.oos.getCheckAction().length; + // The 3 boolean arrays are used to make sure that the same check result + // is exactly counted once. + final boolean[] AEStateRes = new boolean[this.pem.AEState.length]; + final boolean[] AEActionRes = new boolean[this.pem.AEAction.length]; + final boolean[] promiseRes = new boolean[this.oos.getPromises().length]; + // The number/count of all liveness checks. The while loop A) terminates + // once it has accumulated all states that violate all checks (we know + // that the states in nodeTbl have to violate the liveness property + // because we are in printTrace already. checkComponent has already + // determined that there is a violation). int cnt = AEStateRes.length + AEActionRes.length + promiseRes.length; - MemIntStack cycleStack = new MemIntStack(liveCheck.getMetaDir(), "cycle"); - // Mark state as visited: int[] nodes = nodeTbl.getNodes(state); int tloc = nodeTbl.getIdx(nodes, tidx); - long ptr = TableauNodePtrTable.getElem(nodes, tloc); + final long ptr = TableauNodePtrTable.getElem(nodes, tloc); TableauNodePtrTable.setSeen(nodes, tloc); + // A) + // + // Greedy DFS search for a path satisfying the PossibleErrorModel. GraphNode curNode = this.dg.getNode(state, tidx, ptr); + while (cnt > 0) { int cnt0 = cnt; @@ -445,7 +1030,8 @@ public class LiveWorker extends IdThread { break; } - // Check AEAction: + // Check AEAction (which is a check of the out-arc of curNode to + // one of its successors): long nextState1 = 0, nextState2 = 0; int nextTidx1 = 0, nextTidx2 = 0; int tloc1 = -1, tloc2 = -1; @@ -537,144 +1123,19 @@ public class LiveWorker extends IdThread { TableauNodePtrTable.setSeen(nodes2, tloc2); } } - - // All the conditions are satisfied. Find a path from curNode - // to state to form a cycle. Note that: + // All the conditions are satisfied. // 1. curNode has not been pushed on cycleStack. - // 2. nodeTbl is trashed after this operation. + // 2. nodeTbl is trashed after this operation, thus reset. Trashed means + // that some nodes are still marked seen being left-overs from the + // Depth-First search. nodeTbl.resetElems(); - final LongVec postfix = new LongVec(16); - long startState = curNode.stateFP; - - if (startState != state) { - MemIntQueue queue = new MemIntQueue(liveCheck.getMetaDir(), null); - long curState = startState; - int ploc = -1; - int curLoc = nodeTbl.getNodesLoc(curState); - nodes = nodeTbl.getNodesByLoc(curLoc); - TableauNodePtrTable.setSeen(nodes); - - _done: while (true) { - tloc = TableauNodePtrTable.startLoc(nodes); - while (tloc != -1) { - int curTidx = TableauNodePtrTable.getTidx(nodes, tloc); - long curPtr = TableauNodePtrTable.getPtr(TableauNodePtrTable.getElem(nodes, tloc)); - curNode = this.dg.getNode(curState, curTidx, curPtr); - int succCnt = curNode.succSize(); - - for (int j = 0; j < succCnt; j++) { - long nextState = curNode.getStateFP(j); - - if (nextState == state) { - // we have found a path from startState to state: - while (curState != startState) { - postfix.addElement(curState); - nodes = nodeTbl.getNodesByLoc(ploc); - curState = TableauNodePtrTable.getKey(nodes); - ploc = TableauNodePtrTable.getParent(nodes); - } - postfix.addElement(startState); - break _done; - } - - int[] nodes1 = nodeTbl.getNodes(nextState); - if (nodes1 != null && !TableauNodePtrTable.isSeen(nodes1)) { - TableauNodePtrTable.setSeen(nodes1); - queue.enqueueLong(nextState); - queue.enqueueInt(curLoc); - } - } - tloc = TableauNodePtrTable.nextLoc(nodes, tloc); - } - TableauNodePtrTable.setParent(nodes, ploc); - curState = queue.dequeueLong(); - ploc = queue.dequeueInt(); - curLoc = nodeTbl.getNodesLoc(curState); - nodes = nodeTbl.getNodesByLoc(curLoc); - } - } - - // Now, print the error trace. We first construct the prefix that - // led to the bad cycle. The nodes on prefix and cycleStack then - // form the complete counter example. - int stateNum = 0; - LongVec prefix = this.dg.getPath(state, tidx); - int plen = prefix.size(); - TLCStateInfo[] states = new TLCStateInfo[plen]; - - // Recover the initial state: - //TODO This throws an ArrayIndexOutOfBounds if getPath returned a - // LongVec with just a single element. This happens when the parameter - // state is one of the init states already. - long fp = prefix.elementAt(plen - 1); - TLCStateInfo sinfo = liveCheck.getTool().getState(fp); - if (sinfo == null) { - throw new EvalException(EC.TLC_FAILED_TO_RECOVER_INIT); - } - states[stateNum++] = sinfo; - - // Recover the successor states: - //TODO Check if path.size has elements - for (int i = plen - 2; i >= 0; i--) { - long curFP = prefix.elementAt(i); - // The prefix might contain duplicates if the path happens to walk - // along two distinct states which differ in the tableau idx only - // (same fingerprint). From the counterexample perspective, this is - // irrelevant. - if (curFP != fp) { - sinfo = liveCheck.getTool().getState(curFP, sinfo.state); - if (sinfo == null) { - throw new EvalException(EC.TLC_FAILED_TO_RECOVER_NEXT); - } - states[stateNum++] = sinfo; - fp = curFP; - } - } - - // Print the prefix: - TLCState lastState = null; - for (int i = 0; i < stateNum; i++) { - StatePrinter.printState(states[i], lastState, i + 1); - lastState = states[i].state; - } - - // Print the cycle: - int cyclePos = stateNum; - long cycleFP = fp; - while (cycleStack.size() > 0) { - postfix.addElement(cycleStack.popLong()); - cycleStack.popInt(); // ignore tableau idx - } - - // Assert.assert(fps.length > 0); - for (int i = postfix.size() - 1; i >= 0; i--) { - long curFP = postfix.elementAt(i); - if (curFP != fp) { - sinfo = liveCheck.getTool().getState(curFP, sinfo.state); - if (sinfo == null) { - throw new EvalException(EC.TLC_FAILED_TO_RECOVER_NEXT); - } - StatePrinter.printState(sinfo, lastState, ++stateNum); - lastState = sinfo.state; - fp = curFP; - } - } - - if (fp == cycleFP) { - StatePrinter.printStutteringState(++stateNum); - } else { - sinfo = liveCheck.getTool().getState(cycleFP, sinfo.state); - if (sinfo == null) { - throw new EvalException(EC.TLC_FAILED_TO_RECOVER_NEXT); - } - if (TLCGlobals.tool) { - MP.printState(EC.TLC_BACK_TO_STATE, new String[] { "" + cyclePos }, (TLCState) null, -1); - } else { - MP.printMessage(EC.TLC_BACK_TO_STATE, "" + cyclePos); - } - } + + return curNode; } + /* (non-Javadoc) + * @see java.lang.Thread#run() + */ public final void run() { try { while (true) { @@ -704,6 +1165,9 @@ public class LiveWorker extends IdThread { // information is later used to decide if it it makes sense to // run the next check on the larger but still *partial* graph. this.dg.recordSize(); + // If assertions are on (e.g. during unit testing) make sure + // that the disk graph's invariants hold. + assert this.dg.checkInvariants(oos.getCheckState().length, oos.getCheckAction().length); } } catch (Exception e) { MP.printError(EC.GENERAL, "checking liveness", e); // LL changed @@ -714,4 +1178,114 @@ public class LiveWorker extends IdThread { } } + public String toDotViz(final long state, final int tidx, TableauNodePtrTable tnpt) throws IOException { + final StringBuffer sb = new StringBuffer(tnpt.size() * 10); + sb.append("digraph TableauNodePtrTable {\n"); + sb.append("nodesep = 0.7\n"); + sb.append("rankdir=LR;\n"); // Left to right rather than top to bottom + + final int tsz = tnpt.getSize(); + for (int ci = 0; ci < tsz; ci++) { + final int[] nodes = tnpt.getNodesByLoc(ci); + if (nodes == null) { + // miss in TableauNodePtrTable (null bucket) + continue; + } + + long state1 = TableauNodePtrTable.getKey(nodes); + for (int nidx = 2; nidx < nodes.length; nidx += tnpt.getElemLength()) { // nidx starts with 2 because [0][1] are the long fingerprint state1. + int tidx1 = TableauNodePtrTable.getTidx(nodes, nidx); + long loc1 = TableauNodePtrTable.getElem(nodes, nidx); + + final GraphNode curNode = this.dg.getNode(state1, tidx1, loc1); + sb.append(curNode.toDotViz((state1 == state && tidx1 == tidx), true, oos.getCheckState().length, + oos.getCheckAction().length)); + } + } + + sb.append("}"); + return sb.toString(); + } + + /* + * The detailed formatter below can be activated in Eclipse's variable view + * by choosing "New detailed formatter" from the MemIntQueue context menu. + * Insert "LiveWorker.DetailedFormatter.toString(this);". + */ + public static class DetailedFormatter { + public static String toString(final MemIntStack comStack) { + final int size = (int) comStack.size(); + final StringBuffer buf = new StringBuffer(size / 5); + for (int i = 0; i < comStack.size(); i+=5) { + long loc = comStack.peakLong(size - i - 5); + int tidx = comStack.peakInt(size - i - 3); + long state = comStack.peakLong(size - i - 2); + buf.append("state: "); + buf.append(state); + buf.append(" tidx: "); + buf.append(tidx); + buf.append(" loc: "); + buf.append(loc); + buf.append("\n"); + } + return buf.toString(); + } + } + + /* + * The detailed formatter below can be activated in Eclipse's variable view + * by choosing "New detailed formatter" from the MemIntQueue context menu. + * Insert "LiveWorker.DFSStackDetailedFormatter.toString(this);". + * Unfortunately it collides with the comStack DetailedFormatter as both use + * the same type MemIntStack. So you have to chose what you want to look at + * while debugging. + * Note that toString treats pops/pushes of nodes and + * states atomically. If called during a node is only partially pushed onto + * the stack, the detailed formatter will crash. + */ + public static class DFSStackDetailedFormatter { + public static String toString(final MemIntStack dfsStack) { + final int size = (int) dfsStack.size(); + final StringBuffer buf = new StringBuffer(size / 7); // approximate the size needed (buf will grow or shrink if needed) + int i = 0; + for (; i < dfsStack.size();) { + // Peak element to see if it's a marker or not + final long topElement = dfsStack.peakLong(size - i - 2); + if (topElement == SCC_MARKER) { + // It is the marker element + buf.append("node ["); + buf.append(" fp: "); + buf.append(dfsStack.peakLong(size - i - 5)); + buf.append(" tidx: "); + buf.append(dfsStack.peakInt(size - i - 3)); + buf.append(" lowLink: "); + buf.append(dfsStack.peakLong(size - i - 7) - DiskGraph.MAX_PTR); + buf.append("]\n"); + // Increase i by the number of elements peaked + i += 7; + } else if (DiskGraph.isFilePointer(topElement)) { + final long location = topElement; + buf.append("succ ["); + buf.append(" fp: "); + buf.append(dfsStack.peakLong(size - i - 5)); + buf.append(" tidx: "); + buf.append(dfsStack.peakInt(size - i - 3)); + buf.append(" location: "); + buf.append(location); + buf.append("]\n"); + // Increase i by the number of elements peaked + i += 5; + } else if (topElement >= DiskGraph.MAX_PTR) { + final long pLowLink = topElement - DiskGraph.MAX_PTR; + buf.append("pLowLink: "); + buf.append(pLowLink); + buf.append("\n"); + i += 2; + } + } + // Assert all elements are used up + assert i == size; + return buf.toString(); + } + } } diff --git a/tlatools/src/tlc2/tool/liveness/Liveness.java b/tlatools/src/tlc2/tool/liveness/Liveness.java index 9396869f9d55dcbe0d9b71621d050bb1fde09db9..9c79bdaeef682a48b801be59166a3b9b02172b10 100644 --- a/tlatools/src/tlc2/tool/liveness/Liveness.java +++ b/tlatools/src/tlc2/tool/liveness/Liveness.java @@ -355,7 +355,7 @@ public class Liveness implements ToolGlobals, ASTConstants { } // Assign each node in the tableau an index. for (int i = 0; i < allnodes.size(); i++) { - allnodes.getNode(i).index = idx++; + allnodes.getNode(i).setIndex(idx++); } return allnodes; } @@ -538,20 +538,20 @@ public class Liveness implements ToolGlobals, ASTConstants { if ((lexpr instanceof LNBool) && !((LNBool) lexpr).b) { return new OrderOfSolution[0]; // must be unsatisfiable } - LNDisj dnf = (lexpr instanceof LNDisj) ? (LNDisj) lexpr : (new LNDisj(lexpr)); + final LNDisj dnf = (lexpr instanceof LNDisj) ? (LNDisj) lexpr : (new LNDisj(lexpr)); // Now we will turn DNF into a format that can be tested by the // tableau method. The first step is to collect everything into // pems+lexps: listof-(listof-<>[],[]<> /\ tf) - OSExprPem[] pems = new OSExprPem[dnf.getCount()]; - LiveExprNode[] tfs = new LiveExprNode[dnf.getCount()]; + final OSExprPem[] pems = new OSExprPem[dnf.getCount()]; + final LiveExprNode[] tfs = new LiveExprNode[dnf.getCount()]; for (int i = 0; i < dnf.getCount(); i++) { // Flatten junctions, because DNF may contain singleton junctions - LiveExprNode ln = dnf.getBody(i).flattenSingleJunctions(); - OSExprPem pem = new OSExprPem(); + final LiveExprNode ln = dnf.getBody(i).flattenSingleJunctions(); + final OSExprPem pem = new OSExprPem(); pems[i] = pem; if (ln instanceof LNConj) { - LNConj lnc2 = (LNConj) ln; + final LNConj lnc2 = (LNConj) ln; for (int j = 0; j < lnc2.getCount(); j++) { classifyExpr(lnc2.getBody(j), pem); } @@ -562,7 +562,7 @@ public class Liveness implements ToolGlobals, ASTConstants { if (pem.tfs.size() == 1) { tfs[i] = (LiveExprNode) pem.tfs.elementAt(0); } else if (pem.tfs.size() > 1) { - LNConj lnc2 = new LNConj(pem.tfs.size()); + final LNConj lnc2 = new LNConj(pem.tfs.size()); for (int j = 0; j < pem.tfs.size(); j++) { lnc2.addConj((LiveExprNode) pem.tfs.elementAt(j)); } @@ -579,13 +579,13 @@ public class Liveness implements ToolGlobals, ASTConstants { // haven't done any real rearrangement of them, apart from munging // up \/ and /\ above them. tfbin contains the different tf's. // pembin is a vect of vect-of-pems collecting each tf's pems. - TBPar tfbin = new TBPar(dnf.getCount()); - Vect pembin = new Vect(dnf.getCount()); + final TBPar tfbin = new TBPar(dnf.getCount()); + final Vect pembin = new Vect(dnf.getCount()); for (int i = 0; i < dnf.getCount(); i++) { int found = -1; - LiveExprNode tf = tfs[i]; + final LiveExprNode tf = tfs[i]; for (int j = 0; j < tfbin.size() && found == -1; j++) { - LiveExprNode tf0 = tfbin.exprAt(j); + final LiveExprNode tf0 = tfbin.exprAt(j); if ((tf == null && tf0 == null) || (tf != null && tf0 != null && tf.equals(tf0))) { found = j; } @@ -599,15 +599,15 @@ public class Liveness implements ToolGlobals, ASTConstants { } // We then create an OrderOfSolution for each tf in tfbin. - OrderOfSolution[] oss = new OrderOfSolution[tfbin.size()]; + final OrderOfSolution[] oss = new OrderOfSolution[tfbin.size()]; for (int i = 0; i < tfbin.size(); i++) { - LiveExprNode tf = tfbin.exprAt(i); + final LiveExprNode tf = tfbin.exprAt(i); if (tf == null) { oss[i] = new OrderOfSolution(new LNEven[0], tool); } else { - LiveExprNode tf1 = tf.makeBinary(); - TBPar promises = new TBPar(10); + final LiveExprNode tf1 = tf.makeBinary(); + final TBPar promises = new TBPar(10); tf1.extractPromises(promises); oss[i] = new OrderOfSolution(constructTableau(tf1, 0), new LNEven[promises.size()], tool); for (int j = 0; j < promises.size(); j++) { @@ -617,16 +617,14 @@ public class Liveness implements ToolGlobals, ASTConstants { // We lump all the pems into a single checkState and checkAct, // and oss[i].pems will simply be integer lookups into them. - Vect stateBin = new Vect(); - Vect actionBin = new Vect(); - Vect tfPems = (Vect) pembin.elementAt(i); + final Vect stateBin = new Vect(); + final Vect actionBin = new Vect(); + final Vect tfPems = (Vect) pembin.elementAt(i); oss[i].setPems(new PossibleErrorModel[tfPems.size()]); for (int j = 0; j < tfPems.size(); j++) { - OSExprPem pem = (OSExprPem) tfPems.elementAt(j); - oss[i].getPems()[j] = new PossibleErrorModel(); - oss[i].getPems()[j].AEAction = addToBin(pem.AEAction, actionBin); - oss[i].getPems()[j].AEState = addToBin(pem.AEState, stateBin); - oss[i].getPems()[j].EAAction = addToBin(pem.EAAction, actionBin); + final OSExprPem pem = (OSExprPem) tfPems.elementAt(j); + oss[i].getPems()[j] = new PossibleErrorModel(addToBin(pem.AEAction, actionBin), + addToBin(pem.AEState, stateBin), addToBin(pem.EAAction, actionBin)); } // Finally, store the bins with the order of solution. oss[i].setCheckState(new LiveExprNode[stateBin.size()]); diff --git a/tlatools/src/tlc2/tool/liveness/NoOpLiveCheck.java b/tlatools/src/tlc2/tool/liveness/NoOpLiveCheck.java index 5466402f300ba70f4faf0ad69f290186d745f80c..c22ac5d7987da114672d30fcf20098bde5e8c2cf 100644 --- a/tlatools/src/tlc2/tool/liveness/NoOpLiveCheck.java +++ b/tlatools/src/tlc2/tool/liveness/NoOpLiveCheck.java @@ -31,7 +31,7 @@ import java.io.IOException; import tlc2.tool.StateVec; import tlc2.tool.TLCState; import tlc2.tool.Tool; -import tlc2.util.LongVec; +import tlc2.util.SetOfStates; import tlc2.util.statistics.DummyBucketStatistics; import tlc2.util.statistics.IBucketStatistics; @@ -54,9 +54,16 @@ public class NoOpLiveCheck implements ILiveCheck { } /* (non-Javadoc) - * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.tool.StateVec, tlc2.util.LongVec) + * @see tlc2.tool.liveness.ILiveCheck#addNextState(tlc2.tool.TLCState, long, tlc2.util.SetOfStates) */ - public void addNextState(TLCState s0, long fp0, StateVec nextStates, LongVec nextFPs) throws IOException { + public void addNextState(TLCState s0, long fp0, SetOfStates nextStates) throws IOException { + } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.ILiveCheck#doLiveCheck() + */ + public boolean doLiveCheck() { + return false; } /* (non-Javadoc) diff --git a/tlatools/src/tlc2/tool/liveness/NodePtrTable.java b/tlatools/src/tlc2/tool/liveness/NodePtrTable.java index 7c8a90a87d9eeee3fcdb8b68a7548ae48d6aa8ee..52b811424bd844ef54c4d17c3b7fe29e8fb4f401 100644 --- a/tlatools/src/tlc2/tool/liveness/NodePtrTable.java +++ b/tlatools/src/tlc2/tool/liveness/NodePtrTable.java @@ -4,6 +4,9 @@ package tlc2.tool.liveness; +/** + * @see TableauNodePtrTable + */ public class NodePtrTable { private int count; diff --git a/tlatools/src/tlc2/tool/liveness/OrderOfSolution.java b/tlatools/src/tlc2/tool/liveness/OrderOfSolution.java index 74b935cd9b0d9ca16de444c098eaec23c492d811..69e7b514163f8d84fe87a77caab15b128dbca079 100644 --- a/tlatools/src/tlc2/tool/liveness/OrderOfSolution.java +++ b/tlatools/src/tlc2/tool/liveness/OrderOfSolution.java @@ -48,6 +48,17 @@ public class OrderOfSolution { * in the temporal formulas. */ private final TBGraph tableau; // tableau graph + + /** + * A promise φ that a property expressed by a formula will eventually hold. + * + * @see Page 409ff of Manna & Pnueli + * "Temporal Verification of Reactive Systems: Safety" + * <p> + * @see https://books.google.de/books?id=lfIGCAAAQBAJ&lpg=PR5&ots=_YBX09o5tM + * &dq=manna%20pnueli%20temporal%20verification%20of%20reactive% + * 20systems%20safety%20doi&pg=PA409 + */ private final LNEven[] promises; // promises in the tableau private LiveExprNode[] checkState; // state subformula private LiveExprNode[] checkAction; // action subformula diff --git a/tlatools/src/tlc2/tool/liveness/PossibleErrorModel.java b/tlatools/src/tlc2/tool/liveness/PossibleErrorModel.java index eba04bfdd62248c8407dbac43275b0f64ba6531a..c8310971a2a7561f175212807097dc3345c4cf20 100644 --- a/tlatools/src/tlc2/tool/liveness/PossibleErrorModel.java +++ b/tlatools/src/tlc2/tool/liveness/PossibleErrorModel.java @@ -5,10 +5,59 @@ package tlc2.tool.liveness; +import tlc2.util.BitVector; + +/** + * A {@link PossibleErrorModel} is technically a lookup table into its + * corresponding {@link OrderOfSolution} Eventually Always, Infinitely often + * action checks and infinitely often state checks. The + * {@link PossibleErrorModel} basically selects the set of checks stored in + * {@link OrderOfSolution} that a relevant for this {@link PossibleErrorModel}. + * The mapping is done by storing the integer index of the relevant + * {@link OrderOfSolution} checks in EAAction, AEState and AEAction. + * <p> + * The integer index is used in two places: Lookup the set of relevant OOS + * checks + * <ul> + * <li>When the states and transitions (see + * {@link GraphNode#addTransition(long, int, int, int, BitVector, int, int)}) + * are evaluated during "normal" model checking the PEM index selects the set of + * relevant OOS checks. The result of each check is stored in a per node + * {@link BitVector} index again by the corresponding PEM mapping.</li> + * <li>During liveness checking (see {@link LiveWorker#checkSccs()} when the + * pre-computed check result is being looked up in the node's {@link BitVector}. + * </li> + * </ul> + * <p> + * This class can be seen as a C-like struct (PEM's method are all related to + * OOS#toString). + * <p> + * <p> + * Theorie-wise, a {@link PossibleErrorModel} (there are as many PEMs as + * disjuncts in the normal form (DNF) produced by + * {@link Liveness#processLiveness(tlc2.tool.Tool)}) represents the negation of + * the stated liveness properties. It is then applied onto the discovered + * strongly-connected-components in {@link LiveWorker#checkComponent()} to check + * if the PEM is "P-satisfiable". If it is, it shows that a violation of the + * original liveness properties has been found (meaning they can't be + * "P-valid"). A counter-example can be created. + * <p> + * A simplified example:<br> + * Suppose Spec has the (trivial) liveness property '<>[](x \in Nat)' ( + * "eventually the variable x is always a natural number") defined. This will + * result in a PEM looking for a state such that x \notin Nat. If there exists + * such a behavior (SCC), it would violate the liveness property. + */ public class PossibleErrorModel { - int[] EAAction; // <>[]act's (Eventually Always actions) (Strong fairness) - int[] AEState; // []<>state's (Infinitely Often states) - int[] AEAction; // []<>act's (Infinitely Often actions) (Weak fairness) + final int[] EAAction; // <>[]act's (Eventually Always actions) (Strong fairness) + final int[] AEState; // []<>state's (Infinitely Often states) + final int[] AEAction; // []<>act's (Infinitely Often actions) (Weak fairness) + + public PossibleErrorModel(int[] aeAction, int[] aeState, int[] eaAction) { + this.AEAction = aeAction; + this.AEState = aeState; + this.EAAction = eaAction; + } public final boolean isEmpty() { return (this.EAAction.length == 0 && this.AEState.length == 0 && this.AEAction.length == 0); diff --git a/tlatools/src/tlc2/tool/liveness/TBGraph.java b/tlatools/src/tlc2/tool/liveness/TBGraph.java index c5114097b7cc2d9ae62785ff0fe295e6bc0d7436..b95d84852cdb7851b0f552d3932120364231cd94 100644 --- a/tlatools/src/tlc2/tool/liveness/TBGraph.java +++ b/tlatools/src/tlc2/tool/liveness/TBGraph.java @@ -7,6 +7,7 @@ package tlc2.tool.liveness; import tlc2.util.Vect; +@SuppressWarnings("serial") public class TBGraph extends Vect { /** * TBGraph represents the nodes in the tableau graph. @@ -39,7 +40,7 @@ public class TBGraph extends Vect { tnode.getPar().toString(sb, padding); sb.append(" --> "); for (int j = 0; j < tnode.nexts.size(); j++) { - sb.append(tnode.nextAt(j).index + " "); + sb.append(tnode.nextAt(j).getIndex() + " "); } sb.append("\n"); } diff --git a/tlatools/src/tlc2/tool/liveness/TBGraphNode.java b/tlatools/src/tlc2/tool/liveness/TBGraphNode.java index 3cd44fa6c3fa300ff509c8bf654ca04e96384471..551eed17c8ca07d44a0736b0dbc3557bb92c46ae 100644 --- a/tlatools/src/tlc2/tool/liveness/TBGraphNode.java +++ b/tlatools/src/tlc2/tool/liveness/TBGraphNode.java @@ -20,7 +20,7 @@ public class TBGraphNode { */ private final TBPar par; // particle public final Vect nexts; // outlinks - public int index; // unique id for this node + private int index; // unique id for this node private final LiveExprNode[] statePreds; // state predicates in the particle public static TBGraphNode dummyNode = new TBGraphNode(); @@ -54,6 +54,14 @@ public class TBGraphNode { } } + public int getIndex() { + return index; + } + + public void setIndex(final int i) { + this.index = i; + } + public final TBPar getPar() { return this.par; } @@ -76,7 +84,12 @@ public class TBGraphNode { return false; } - /* Checks if this particle node is consistent with a state. */ + /** + * Checks if this particle node is consistent with the given + * {@link TLCState}. In other words, it checks if {@link TLCState}'s truth + * values according to all state predicates of the tableau node. The state + * predicates are deduced from the particles during tableau construction. + */ public boolean isConsistent(TLCState state, Tool tool) { for (int j = 0; j < this.statePreds.length; j++) { if (!this.statePreds[j].eval(tool, state, null)) { diff --git a/tlatools/src/tlc2/tool/liveness/TBPar.java b/tlatools/src/tlc2/tool/liveness/TBPar.java index 0ef57fd1b4c53b54bdf472a5ab80dff408094b88..7a886d6342b87815f4fefaf71964f4637d59fb30 100644 --- a/tlatools/src/tlc2/tool/liveness/TBPar.java +++ b/tlatools/src/tlc2/tool/liveness/TBPar.java @@ -231,7 +231,14 @@ public class TBPar extends Vect { } /** - * This methods returns true iff this particle fulfills the promise. + * This methods returns true iff this particle (TBPar) fulfills the given + * promise. + * <p> + * A particle A is said to fulfill formula φ which promises r if either: + * <ul> + * <li>φ \notin A</li> + * <li>r \in A</li> + * </ul> */ public final boolean isFulfilling(LNEven promise) { return !this.member(promise) || this.member(promise.getBody()); diff --git a/tlatools/src/tlc2/tool/liveness/TableauDiskGraph.java b/tlatools/src/tlc2/tool/liveness/TableauDiskGraph.java index 5ba39a9848a92c5558f5e67aac852137ca921852..7960a2cac0c7e9b11bc6d0fb9bf991610d70f578 100644 --- a/tlatools/src/tlc2/tool/liveness/TableauDiskGraph.java +++ b/tlatools/src/tlc2/tool/liveness/TableauDiskGraph.java @@ -112,11 +112,18 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#putNode(tlc2.tool.liveness.GraphNode, long) + * @see tlc2.tool.liveness.AbstractDiskGraph#putNode(tlc2.tool.liveness.GraphNode, long) */ protected void putNode(GraphNode node, long ptr) { this.nodePtrTbl.put(node.stateFP, node.tindex, ptr); } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.AbstractDiskGraph#checkDuplicate(tlc2.tool.liveness.GraphNode) + */ + protected boolean checkDuplicate(final GraphNode node) { + return this.nodePtrTbl.get(node.stateFP, node.tindex) != -1; + } /** * Get the graph node. Returns a new GraphNode if the node is not in this. @@ -156,16 +163,17 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#getLink(long, int) + * @see tlc2.tool.liveness.AbstractDiskGraph#getLink(long, int) */ public long getLink(long state, int tidx) { return this.nodePtrTbl.get(state, tidx); } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#putLink(long, int, long) + * @see tlc2.tool.liveness.AbstractDiskGraph#putLink(long, int, long) */ public long putLink(long state, int tidx, long link) { + assert MAX_PTR <= link && link < MAX_LINK; int[] node = this.nodePtrTbl.getNodes(state); int cloc = this.nodePtrTbl.getIdx(node, tidx); long oldLink = TableauNodePtrTable.getElem(node, cloc); @@ -177,7 +185,7 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#setMaxLink(long, int) + * @see tlc2.tool.liveness.AbstractDiskGraph#setMaxLink(long, int) */ public void setMaxLink(long state, int tidx) { this.nodePtrTbl.put(state, tidx, MAX_LINK); @@ -197,7 +205,7 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#makeNodePtrTbl(long) + * @see tlc2.tool.liveness.AbstractDiskGraph#makeNodePtrTbl(long) */ protected void makeNodePtrTbl(final long ptr) throws IOException { makeNodePtrTbl(ptr, nodePtrTbl); @@ -256,9 +264,9 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.DiskGraph#toDotViz() + * @see tlc2.tool.liveness.AbstractDiskGraph#toDotViz(int, int) */ - public final String toDotViz() { + public final String toDotViz(final int slen, final int alen) { // The following code relies on gnodes not being null, thus safeguard // against accidental invocations. @@ -284,7 +292,7 @@ public class TableauDiskGraph extends AbstractDiskGraph { int tidx = nodePtrRAF.readInt(); long loc = nodePtrRAF.readLongNat(); GraphNode gnode = this.getNode(fp, tidx, loc); - sb.append(gnode.toDotViz(isInitState(gnode), true)); + sb.append(gnode.toDotViz(isInitState(gnode), true, slen, alen)); } sb.append("}"); this.nodeRAF.seek(nodePtr); @@ -380,7 +388,21 @@ public class TableauDiskGraph extends AbstractDiskGraph { continue; } if (nextState == state && nextTidx == tidx) { - // Stop BFS (see MemIntQUEUE above), we found a path to state. + // Stop BFS (see MemIntQUEUE above), we found a path to + // state. The path might not necessarily be the shortest one + // possible. A non-optimal path is returned if the BFS + // search happens to produce a path <<fp1, fp2, fp3>> first + // before it finds <<fp1, fp1, fp2>> (the first two elements + // share the same fingerprint and only differ in the + // discarded tableau id). The caller LiveWorker#printTrace + // collapses contiguous segments (identical fingerprint) in + // the path into a single state. E.g. the second example + // above will be printed as two states: <<State(fp1), + // State(fp2)>>. Hence, it is shorter than <<State(fp1), + // State(fp2), State(fp3)>>. Note that the search does *not* + // stop after the second node from the end in <<fp1, fp1, fp2>> + // because this node - due to its tableau id - does not + // correspond to an initial state in reversablePtrTable. return reconstructReversePath(reversablePtrTable, curState, curTidx, nextState, nextTidx); } @@ -503,6 +525,12 @@ public class TableauDiskGraph extends AbstractDiskGraph { return res; } + /** + * This implementation extends {@link TableauNodePtrTable} to additionally + * store the tableau index of the predecessor node. It is needed to traverse + * the {@link ReverseTraversableTableauNodePtrTable} backwards once a error + * trace path has been created. + */ private class ReverseTraversableTableauNodePtrTable extends TableauNodePtrTable { public ReverseTraversableTableauNodePtrTable(final int size) { @@ -543,9 +571,9 @@ public class TableauDiskGraph extends AbstractDiskGraph { } /* (non-Javadoc) - * @see tlc2.tool.liveness.TableauNodePtrTable#addElem(int[], int, long) + * @see tlc2.tool.liveness.TableauNodePtrTable#appendElem(int[], int, long) */ - protected int[] addElem(final int[] node, final int tidx, final long elem) { + protected int[] appendElem(final int[] node, final int tidx, final long elem) { int len = node.length; int[] newNode = new int[len + getElemLength()]; System.arraycopy(node, 0, newNode, 0, len); diff --git a/tlatools/src/tlc2/tool/liveness/TableauNodePtrTable.java b/tlatools/src/tlc2/tool/liveness/TableauNodePtrTable.java index 1fd7b918351ef7470defea52b8b0347341c75c38..7a5385138aed0e203b419767018d0eb4c983a97a 100644 --- a/tlatools/src/tlc2/tool/liveness/TableauNodePtrTable.java +++ b/tlatools/src/tlc2/tool/liveness/TableauNodePtrTable.java @@ -26,8 +26,62 @@ package tlc2.tool.liveness; +/** + * {@link TableauNodePtrTable} (and its sibling {@link NodePtrTable} for + * tableau-less liveness checking) is a - highly optimized - set of all nodes in + * the behavior graph {@link TableauDiskGraph}. + * <p> + * Each node in the behavior graph consists of the pair <<state, tidx>> (where + * state is a state's fingerprint) and auxiliary information. The auxiliary + * information is: + * <ul> + * <li>An offset into the second set (@see {@link AbstractDiskGraph#nodeRAF}) + * which represents the arcs between the nodes (logically outgoing transitions). + * Technically this is a pointer location into the second disk file of + * {@link TableauDiskGraph}.</li> + * <li>The node's link number during Tarjan's SCC search.</li> + * <li>A flag if the node is done or not (see {@link TableauNodePtrTable#UNDONE} + * below).</li> + * <li>A flag that marks a node an initial node.</li> + * <li>A flag if the node has been seen before during error trace re-creation + * (see {@link LiveWorker#printTrace}.</li> + * </ul> + * <p> + * The last item indicates that this class is used in two scenarios. It's + * primary purpose is to be the backing store of the liveness/behavior disc + * graph. Additionally though, LiveWorker#printTrace independently instantiates + * a new {@link TableauNodePtrTable} to do its work. + * <p> + * To minimize {@link TableauNodePtrTable}'s space/memory requirements, the + * auxiliary information replace each other depending on the phase of liveness + * checking.<br> + * During model checking (safety checking) the auxiliary information is set to a + * pointer location (long) pointing into the arcs set and the high bits of the + * long are used to mark nodes as done or undone.<br> + * As soon as the SCC search starts, the pointer location is replaced by the SCC + * link number.<br> + * Once a liveness violation has been detected, the seen flag is set during the + * error trace path reconstruction. + * <p> + * Internally {@link TableauNodePtrTable} hashes the node's fingerprint to a + * bucket address. In case of hash collision, open addressing is used. + */ public class TableauNodePtrTable { + /** + * A node is marked UNDONE if it is: + * <ul> + * <li>An initial node <b>s0</b> and not yet visited again by + * LiveChecker#addNextState(<b>s0</b>)</li> + * <li>A previously unseen successor node <b>t</b> of a node <b>s</b> that + * is added as an outgoing transition of <b>s</b> when <b>s</b> is being + * added via LiveChecker#addNextState(<b>s</b> )</li> + * </ul> + * <p> + * It logically markers the successor node <b>t</b> to be incomplete which + * can only happen during liveness checking of a <b>partial</b> liveness + * graph. + */ public static final long UNDONE = 0xFFFFFFFE00000000L; private int count; @@ -102,10 +156,10 @@ public class TableauNodePtrTable { int cloc = getIdx(node, tidx); if (cloc == -1) { // The list of nodes does not contain the give tableau idx - // yet, thus add a new element. Technically, it means we + // yet, thus append a new element. Technically, it means we // grow the nodes array by three and insert the tableau idx // and its element. - this.nodes[loc] = addElem(node, tidx, elem); + this.nodes[loc] = appendElem(node, tidx, elem); } else { // Nodes already contains an entry for the given tableau. // Update its element. The element is either a pointer @@ -200,6 +254,7 @@ public class TableauNodePtrTable { return node[3] != -2; } + // Called by addNextState public final int setDone(long k) { if (this.count >= this.thresh) { this.grow(); @@ -235,12 +290,22 @@ public class TableauNodePtrTable { } } + /** + * Clears the seen flag of all records set by static setSeen(..) calls + * earlier. + * <p> + * Post-Condition: None of the records is marked seen. + * + * @see TableauNodePtrTable#setSeen(int[]) + * @see TableauNodePtrTable#setSeen(int[], int) + */ public final void resetElems() { + // Only called when the error trace is being printed. for (int i = 0; i < this.nodes.length; i++) { int[] node = this.nodes[i]; if (node != null) { for (int j = 3; j < node.length; j += getElemLength()) { - node[j] &= 0x7FFFFFFF; + node[j] &= 0x7FFFFFFF; // Clear the MSB set by setSeen(..) } } } @@ -288,7 +353,7 @@ public class TableauNodePtrTable { return node; } - protected int[] addElem(int[] node, int tidx, long elem) { + protected int[] appendElem(int[] node, int tidx, long elem) { int len = node.length; int[] newNode = new int[len + getElemLength()]; System.arraycopy(node, 0, newNode, 0, len); @@ -345,47 +410,80 @@ public class TableauNodePtrTable { return node[loc]; } + /* + * Helper methods used by LiveWorker#printTrace(..) only. Note that + * printTrace does not use the TNPT instance of the DiskGraph but its own + * instance only containing a single SCC. + */ + public static final int END_MARKER = -1; + public static int startLoc(int[] node) { - return (node.length > 2) ? 2 : -1; + return (node.length > 2) ? 2 : END_MARKER; } public static int nextLoc(int[] node, int curLoc) { int loc = curLoc + 3; - return (loc < node.length) ? loc : -1; + return (loc < node.length) ? loc : END_MARKER; } + /** + * @param nodes + * @return True, iff the record at tloc has been marked seen. + * @see TableauNodePtrTable#setSeen(int[], int) + */ public static boolean isSeen(int[] nodes, int tloc) { return getElem(nodes, tloc) < 0; } + /** + * Marks the record at tloc seen. + * + * @param nodes + * @see TableauNodePtrTable#setSeen(int[]) + * @see TableauNodePtrTable#resetElems() + */ public static void setSeen(int[] nodes, int tloc) { long ptr = getElem(nodes, tloc); - putElem(nodes, (ptr | 0x8000000000000000L), tloc); + putElem(nodes, (ptr | 0x8000000000000000L), tloc); // Set the MSB } public static long getPtr(long ptr) { return (ptr & 0x7FFFFFFFFFFFFFFFL); } + /** + * @see TableauNodePtrTable#setSeen(int[]) + * @param nodes + * @return True, iff the record has been marked seen. + */ public static boolean isSeen(int[] nodes) { return nodes[3] < 0; } + /** + * Marks this record seen. + * + * @param nodes + * @see TableauNodePtrTable#resetElems() + */ public static void setSeen(int[] nodes) { - nodes[3] |= 0x80000000; + nodes[3] |= 0x80000000; // Set the MSB } + public static final int NO_PARENT = -1; + public static int getParent(int[] nodes) { return nodes[4]; } public static void setParent(int[] nodes, int loc) { + assert loc >= NO_PARENT && loc <= AbstractDiskGraph.MAX_PTR; nodes[4] = loc; } /* * The detailed formatter below can be activated in Eclipse's variable view - * by choosing "New detailed formatter" from the MemIntQueue context menu. + * by choosing "New detailed formatter" from the nodePtrTable's context menu. * Insert "TableauNodePtrTable.DetailedFormatter.toString(this);". */ public static class DetailedFormatter { @@ -402,8 +500,11 @@ public class TableauNodePtrTable { buf.append(" isDone: " + (node.length == 2 || (node.length > 2 && node[3] != -2))); buf.append("\n"); - // A node maintains n records. Each record logically contains information about a node's successor. - // fingerprint + // A node maintains n records. Each record logically + // contains information - combined with the fingerprint - + // about the full tuple <<fp, tidx, loc>>. + // Depending on the state of the record, the loc might + // also be overwritten by the SCC link number. int j = 2; for (; j < node.length - 1; j+=table.getElemLength()) { // don't miss the ptr at the end buf.append("\t"); @@ -413,7 +514,11 @@ public class TableauNodePtrTable { // element final long elem = getElem(node, j); if (AbstractDiskGraph.isFilePointer(elem)) { - buf.append(" ptr: " + elem); + if (table.isDone(fp)) { + buf.append(" ptr: " + elem); + } else { + buf.append(" ptr: undone"); + } } else if (AbstractDiskGraph.MAX_PTR == elem){ buf.append(" elem: Init State"); } else { diff --git a/tlatools/src/tlc2/tool/management/ModelCheckerMXWrapper.java b/tlatools/src/tlc2/tool/management/ModelCheckerMXWrapper.java index 1a8c8c0c481edbfec2458e614b16e686d7d2ab69..2c5ffea763739e7903dccbf843e85ae7cf47cdd7 100644 --- a/tlatools/src/tlc2/tool/management/ModelCheckerMXWrapper.java +++ b/tlatools/src/tlc2/tool/management/ModelCheckerMXWrapper.java @@ -102,4 +102,18 @@ public class ModelCheckerMXWrapper extends TLCStandardMBean implements TLCStatis //TODO adapt once Workers can support units of work greater than 1 return 1; } + + /* (non-Javadoc) + * @see tlc2.tool.distributed.management.TLCStatisticsMXBean#getRuntimeRatio() + */ + public double getRuntimeRatio() { + return modelChecker.getRuntimeRatio(); + } + + /* (non-Javadoc) + * @see tlc2.tool.distributed.management.TLCStatisticsMXBean#liveCheck() + */ + public void liveCheck() { + modelChecker.forceLiveCheck(); + } } diff --git a/tlatools/src/tlc2/tool/queue/DiskStateQueue.java b/tlatools/src/tlc2/tool/queue/DiskStateQueue.java index 14b4e6d17099d31559f125bbd43852e49fb735fb..d2ca4cbd138a351f15fbb833566c935216d8ba47 100644 --- a/tlatools/src/tlc2/tool/queue/DiskStateQueue.java +++ b/tlatools/src/tlc2/tool/queue/DiskStateQueue.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; import tlc2.output.EC; +import tlc2.output.MP; import tlc2.tool.TLCState; import tlc2.util.StatePoolReader; import tlc2.util.StatePoolWriter; @@ -23,7 +24,7 @@ import util.FileUtil; */ public class DiskStateQueue extends StateQueue { // TODO dynamic bufsize based on current VM parameters? - private final static int BufSize = Integer.getInteger(DiskStateQueue.class.getName() + ".BufSize", 8192);; + private final static int BufSize = Integer.getInteger(DiskStateQueue.class.getName() + ".BufSize", 8192); /* * Invariants: I1. Entries in deqBuf are in the indices: [deqIndex, @@ -40,6 +41,13 @@ public class DiskStateQueue extends StateQueue { protected int deqIndex, enqIndex; protected StatePoolReader reader; protected StatePoolWriter writer; + /** + * The SPC takes care or deleting swap files on the lower end of the range + * (loPool, hiPool). It terminates, when the first checkpoint is written at + * which point checkpointing itself takes care of removing obsolete swap + * files. + */ + protected final StatePoolCleaner cleaner; private int loPool, hiPool, lastLoPool, newLastLoPool; private File loFile; @@ -61,6 +69,9 @@ public class DiskStateQueue extends StateQueue { this.writer = new StatePoolWriter(BufSize, this.reader); this.writer.setDaemon(true); this.writer.start(); + this.cleaner = new StatePoolCleaner(); + this.cleaner.setDaemon(true); + this.cleaner.start(); } final void enqueueInner(TLCState state) { @@ -117,6 +128,14 @@ public class DiskStateQueue extends StateQueue { this.enqIndex = 0; } } + // Notify the cleaner to do its job unless its waits for more work + // to pile up. + if ((loPool - lastLoPool) > 100) { //TODO Take BufSize into account. It defines the disc file size. + synchronized (this.cleaner) { + this.cleaner.deleteUpTo = loPool - 1; + this.cleaner.notifyAll(); + } + } } catch (Exception e) { Assert.fail(EC.SYSTEM_ERROR_READING_STATES, new String[] { "queue", (e.getMessage() == null) ? e.toString() : e.getMessage() }); @@ -125,6 +144,14 @@ public class DiskStateQueue extends StateQueue { /* Checkpoint. */ public final void beginChkpt() throws IOException { + synchronized (this.cleaner) { + // Checkpointing takes precedence over periodic cleaning + // (cleaner would otherwise delete checkpoint files as it know + // nothing of checkpoints). + this.cleaner.finished = true; + this.cleaner.notifyAll(); + } + String filename = this.filePrefix + "queue.tmp"; ValueOutputStream vos = new ValueOutputStream(filename); vos.writeLongNat(this.len); @@ -195,6 +222,52 @@ public class DiskStateQueue extends StateQueue { this.reader.setFinished(); this.reader.notifyAll(); } + synchronized (this.cleaner) { + this.cleaner.finished = true; + this.cleaner.notifyAll(); + } } + private class StatePoolCleaner extends Thread { + + private volatile boolean finished = false; + public int deleteUpTo; + + private StatePoolCleaner() { + super("TLCStatePoolCleaner"); + } + + /* (non-Javadoc) + * @see java.lang.Thread#run() + */ + public void run() { + try { + synchronized (this) { + while (!this.finished) { + this.wait(); + if (this.finished) { + return; + } + + for (int i = lastLoPool; i < deleteUpTo; i++) { + final File oldPoolFile = new File(filePrefix + Integer.toString(i)); + if (!oldPoolFile.delete()) { + // No reason to terminate/kill TLC when the cleanup fails. + // Contrary to StatePoolReader/Write, cleanup is optional + // functionality whose purpose is to prevent the disc from + // filling up. If the cleaner fails, the user can still + // manually delete the files. + MP.printWarning(EC.SYSTEM_ERROR_CLEANING_POOL, oldPoolFile.getCanonicalPath()); + } + } + lastLoPool = deleteUpTo; + } + } + } catch (Exception e) { + // Assert.printStack(e); + MP.printError(EC.SYSTEM_ERROR_CLEANING_POOL, e.getMessage(), e); + System.exit(1); + } + } + } } diff --git a/tlatools/src/tlc2/util/DiskIntStack.java b/tlatools/src/tlc2/util/DiskIntStack.java index 0fdb6874e644e1e3c2e023bf55c3c7905102b362..1d55b7b5d69b296fa24bcd258ef91a3bcf0580e7 100644 --- a/tlatools/src/tlc2/util/DiskIntStack.java +++ b/tlatools/src/tlc2/util/DiskIntStack.java @@ -14,12 +14,15 @@ import util.BufferedDataOutputStream; import util.FileUtil; /** - * Alternative implementation - * currently not used + * Alternative implementation. Currently not used + * + * This implementation is likely to have never worked and should be regarded as + * a sketch for a how a concurrent implementation could look like. For the + * moment, {@link SynchronousDiskIntStack} does the job just fine. Albeit - as + * the name suggests - in a synchronous fashion. * - * @version $Id$ */ -public final class DiskIntStack { +public final class DiskIntStack implements IntStack { private final static int BufSize = 16384; private long size; @@ -168,4 +171,10 @@ public final class DiskIntStack { } } + /* (non-Javadoc) + * @see tlc2.util.IntStack#reset() + */ + public void reset() { + // TODO Auto-generated method stub + } } diff --git a/tlatools/src/tlc2/util/IdThread.java b/tlatools/src/tlc2/util/IdThread.java index 33a0b8bf959d1803bfb0119005c14a877947c258..140d2052e3f4b3216931c235a252fd179741e861 100644 --- a/tlatools/src/tlc2/util/IdThread.java +++ b/tlatools/src/tlc2/util/IdThread.java @@ -2,12 +2,15 @@ // Portions Copyright (c) 2003 Microsoft Corporation. All rights reserved. package tlc2.util; +import tlc2.value.Value; + /** An <code>IdThread</code> is a <code>Thread</code> with an integer identifier. */ public class IdThread extends Thread { private final int id; - + private Value[] localValues = new Value[4]; + /** Create a new thread with ID <code>id</code>. */ public IdThread(int id) { this.id = id; @@ -40,4 +43,20 @@ public class IdThread extends Thread { Thread th = Thread.currentThread(); return (th instanceof IdThread) ? ((IdThread)th).id : otherId; } + + public Value getLocalValue(int idx) { + if (idx < this.localValues.length) { + return this.localValues[idx]; + } + return null; + } + + public void setLocalValue(int idx, Value val) { + if (idx >= this.localValues.length) { + Value[] vals = new Value[idx + 1]; + System.arraycopy(this.localValues, 0, vals, 0, this.localValues.length); + this.localValues = vals; + } + this.localValues[idx] = val; + } } diff --git a/tlatools/src/tlc2/util/IntStack.java b/tlatools/src/tlc2/util/IntStack.java new file mode 100644 index 0000000000000000000000000000000000000000..4747027378442c9cd7b33eff562a151d2d687d44 --- /dev/null +++ b/tlatools/src/tlc2/util/IntStack.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +public interface IntStack { + + /** + * Return the number of items on the stack. + */ + long size(); + + /** + * Push an integer onto the stack. + */ + void pushInt(final int x); + + /** + * Push a long integer onto the stack. + */ + void pushLong(final long x); + + /** + * Pop the integer from the top of the stack. + */ + int popInt(); + + /** + * Pop the long integer from the top of the stack. + */ + long popLong(); + + /** + * Removes all elements from the stack + */ + void reset(); +} \ No newline at end of file diff --git a/tlatools/src/tlc2/util/MemBasedSet.java b/tlatools/src/tlc2/util/MemBasedSet.java new file mode 100644 index 0000000000000000000000000000000000000000..c9b4c119a6fa369cb52727ca2a1b4b2ab25a207f --- /dev/null +++ b/tlatools/src/tlc2/util/MemBasedSet.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +public abstract class MemBasedSet { + + protected int size; + + protected int[] elems; + + public MemBasedSet(final int minCapacity) { + this.size = 0; + this.elems = new int[minCapacity]; + } + + protected int[] ensureCapacity(final int minCapacity) { + // If the internal storage reaches its capacity, double the size of + // the internal storage. This strategy is great for as long as size + // is smaller than 2^31. Once size is >= 2^31, doubling it means it + // becomes negative resulting in a NegativeArraySizeException. Additionally + // if size gets larger and larger, an OutOfMemory exception becomes + // more likely when MemIntStacks memory requirements get doubled. + // From the literature and popular implementations (e.g. Java's ArrayList), + // a growth factor of 1.5 seems to be practical. + final int newSize = (int) ((this.size * 3L) / 2) + 1; // multiply with *long* 3L to stay positive + // If newSize is negative (happens when size become too large for + // int), just increase the array by MIN_CAPACITY. This obviously + // doesn't prevent a NegativeArraySizeException either, but it + // postpones it to the moment when the int-sized array will be + // completely full. At this point, nothing can be done except using + // two MemIntStack instances which requires changes in the places + // where MemIntStack is used or swapping MemIntStack to disk (which - + // from looking at the ctor's parameters - appears to be the preferred + // solution of the original MemIntStack authors). + // + // Performance obviously goes south the moment the array is + // increased in MIN_CAPACITY steps. It's a trade off between risking + // an OutOfMemory exception and performance. + return new int[Math.max(newSize, this.size + minCapacity)]; + } + + /** + * Return the number of items on the stack. + */ + public final long size() { + return this.size; + } + + public final boolean hasElements() { + return this.size > 0; + } +} diff --git a/tlatools/src/tlc2/util/MemIntQueue.java b/tlatools/src/tlc2/util/MemIntQueue.java index ad119d5abca7fc01dfb1acc1db3e05a2a43fd38c..257a9bb0765c36133d8fde777c8b8b8b9b27c5b9 100644 --- a/tlatools/src/tlc2/util/MemIntQueue.java +++ b/tlatools/src/tlc2/util/MemIntQueue.java @@ -13,137 +13,143 @@ import util.BufferedDataInputStream; import util.BufferedDataOutputStream; import util.FileUtil; -/** - * - * @version $Id$ - */ -public final class MemIntQueue { - private final static int InitialSize = 4096; - private final static int GrowthFactor = 2; - - private int len; - private int[] elems; - private int start; - private String diskdir; - private String filename; - - public MemIntQueue(String metadir, String filename) { - this.len = 0; - this.elems = new int[InitialSize]; - this.start = 0; - this.diskdir = metadir; - this.filename = filename; - } - - public final int length() { return this.len; } - - public final boolean hasElements() { return this.len > 0; } - - public final void enqueueInt(int elem) { - if (this.len == this.elems.length) { - // grow the array - int newLen = Math.max(1, this.len * GrowthFactor); - int[] newElems = new int[newLen]; - int copyLen = this.elems.length - this.start; - System.arraycopy(this.elems, this.start, newElems, 0, copyLen); - System.arraycopy(this.elems, 0, newElems, copyLen, this.start); - this.elems = newElems; - this.start = 0; - } - int last = (this.start + this.len) % this.elems.length; - this.elems[last] = elem; - this.len++; - } - - public final void enqueueLong(long elem) { - this.enqueueInt((int)(elem >>> 32)); - this.enqueueInt((int)(elem & 0xFFFFFFFFL)); - } - - public final int dequeueInt() { - // Assert.check(this.len > 0); - if (this.len < 1) { - throw new NoSuchElementException(); +public final class MemIntQueue extends MemBasedSet { + private final static int InitialSize = 4096; + + private int start; + private String diskdir; + private String filename; + + public MemIntQueue(String metadir, String filename) { + this(metadir, filename, InitialSize); + } + + public MemIntQueue(String metadir, String filename, final int initialCapacity) { + super(initialCapacity); + this.start = 0; + this.diskdir = metadir; + this.filename = filename; + } + + public final void enqueueInt(int elem) { + if (this.size == this.elems.length) { + // grow the array + final int[] newElems = ensureCapacity(InitialSize); + // copy old content to new array + int copyLen = this.elems.length - this.start; + System.arraycopy(this.elems, this.start, newElems, 0, copyLen); + System.arraycopy(this.elems, 0, newElems, copyLen, this.start); + this.elems = newElems; + this.start = 0; + } + int last = (this.start + this.size) % this.elems.length; + this.elems[last] = elem; + this.size++; + } + + public final void enqueueLong(long elem) { + this.enqueueInt((int) (elem >>> 32)); + this.enqueueInt((int) (elem & 0xFFFFFFFFL)); + } + + public final int dequeueInt() { + // Assert.check(this.len > 0); + if (this.size < 1) { + throw new NoSuchElementException(); + } + int res = this.elems[this.start]; + this.size--; + this.start = (this.start + 1) % this.elems.length; + return res; + } + + public final long dequeueLong() { + long high = this.dequeueInt(); + long low = this.dequeueInt(); + return (high << 32) | (low & 0xFFFFFFFFL); + } + + public int popInt() { + // Assert.check(this.len > 0); + if (this.size < 1) { + throw new NoSuchElementException(); + } + return this.elems[--this.size]; + } + + public long popLong() { + long low = this.popInt(); + long high = this.popInt(); + return (high << 32) | (low & 0xFFFFFFFFL); + } + + // Checkpoint. + public final void beginChkpt() throws IOException { + String tmpName = this.diskdir + FileUtil.separator + this.filename + ".tmp"; + BufferedDataOutputStream bos = new BufferedDataOutputStream(tmpName); + bos.writeInt(this.size); + int index = this.start; + for (int i = 0; i < this.size; i++) { + bos.writeInt(this.elems[index++]); + if (index == this.elems.length) + index = 0; + } + bos.close(); + } + + public final void commitChkpt() throws IOException { + String oldName = this.diskdir + FileUtil.separator + this.filename + ".chkpt"; + File oldChkpt = new File(oldName); + String newName = this.diskdir + FileUtil.separator + this.filename + ".tmp"; + File newChkpt = new File(newName); + if ((oldChkpt.exists() && !oldChkpt.delete()) || !newChkpt.renameTo(oldChkpt)) { + throw new IOException("MemStateQueue.commitChkpt: cannot delete " + oldChkpt); + } } - int res = this.elems[this.start]; - this.len--; - this.start = (this.start + 1) % this.elems.length; - return res; - } - - public final long dequeueLong() { - long high = this.dequeueInt(); - long low = this.dequeueInt(); - return (high << 32) | (low & 0xFFFFFFFFL); - } - - // Checkpoint. - public final void beginChkpt() throws IOException { - String tmpName = this.diskdir + FileUtil.separator + this.filename + ".tmp"; - BufferedDataOutputStream bos = new BufferedDataOutputStream(tmpName); - bos.writeInt(this.len); - int index = this.start; - for (int i = 0; i < this.len; i++) { - bos.writeInt(this.elems[index++]); - if (index == this.elems.length) index = 0; - } - bos.close(); - } - - public final void commitChkpt() throws IOException { - String oldName = this.diskdir + FileUtil.separator + this.filename + ".chkpt"; - File oldChkpt = new File(oldName); - String newName = this.diskdir + FileUtil.separator + this.filename + ".tmp"; - File newChkpt = new File(newName); - if ((oldChkpt.exists() && !oldChkpt.delete()) || - !newChkpt.renameTo(oldChkpt)) { - throw new IOException("MemStateQueue.commitChkpt: cannot delete " + oldChkpt); - } - } - - public final void recover() throws IOException { - String chkptName = this.diskdir + FileUtil.separator + this.filename + ".chkpt"; - BufferedDataInputStream bis = new BufferedDataInputStream(chkptName); - this.len = bis.readInt(); - for (int i = 0; i < this.len; i++) { - this.elems[i] = bis.readInt(); - } - bis.close(); - } - - /* + + public final void recover() throws IOException { + String chkptName = this.diskdir + FileUtil.separator + this.filename + ".chkpt"; + BufferedDataInputStream bis = new BufferedDataInputStream(chkptName); + this.size = bis.readInt(); + for (int i = 0; i < this.size; i++) { + this.elems[i] = bis.readInt(); + } + bis.close(); + } + + /* * The detailed formatter below can be activated in Eclipse's variable view * by choosing "New detailed formatter" from the MemIntQueue context menu. * Insert "MemIntQueue.DetailedFormatter.fpAndtidxAndptr(this);". */ - public static class DetailedFormatter { - - // An Eclipse detailed formatter for when this Queue holds pairs of long - // (fp) and int (tableau idx) - public static String fpAndtidx(MemIntQueue aQueue) { - final StringBuffer buf = new StringBuffer(aQueue.len / 3); - for (int i = 0; i < aQueue.len; i+=3) { - final long fp = ((long) aQueue.elems[i] << 32) | ((long) (aQueue.elems[i + 1]) & 0xFFFFFFFFL); - buf.append("fp: " + fp); - buf.append(" tidx: " + aQueue.elems[i + 2]); - buf.append("\n"); - } - return buf.toString(); - } - - // An Eclipse detailed formatter for when this Queue holds pairs of long - // (fp), int (tableau idx) and long (disk graph pointer). - public static String fpAndtidxAndptr(MemIntQueue aQueue) { - final StringBuffer buf = new StringBuffer(aQueue.len / 5); - for (int i = 0; i < aQueue.len; i += 5) { - final long fp = ((long) aQueue.elems[i] << 32) | ((long) (aQueue.elems[i + 1]) & 0xFFFFFFFFL); - buf.append("fp: " + fp); - buf.append(" tidx: " + aQueue.elems[i + 2]); - final long ptr = ((long) aQueue.elems[i + 3] << 32) | ((long) (aQueue.elems[i+ 4] ) & 0xFFFFFFFFL); - buf.append(" ptr: " + ptr); - buf.append("\n"); - } - return buf.toString(); - } - } + public static class DetailedFormatter { + + // An Eclipse detailed formatter for when this Queue holds pairs of long + // (fp) and int (tableau idx) + public static String fpAndtidx(MemIntQueue aQueue) { + final StringBuffer buf = new StringBuffer(aQueue.size / 3); + for (int i = 0; i < aQueue.size; i += 3) { + final long fp = ((long) aQueue.elems[i] << 32) | ((long) (aQueue.elems[i + 1]) & 0xFFFFFFFFL); + buf.append("fp: " + fp); + buf.append(" tidx: " + aQueue.elems[i + 2]); + buf.append("\n"); + } + return buf.toString(); + } + + // An Eclipse detailed formatter for when this Queue holds pairs of long + // (fp), int (tableau idx) and long (disk graph pointer). + public static String fpAndtidxAndptr(MemIntQueue aQueue) { + final StringBuffer buf = new StringBuffer(aQueue.size / 5); + for (int i = 0; i < aQueue.size; i += 5) { + final long fp = ((long) aQueue.elems[i] << 32) | ((long) (aQueue.elems[i + 1]) & 0xFFFFFFFFL); + buf.append("fp: " + fp); + buf.append(" tidx: " + aQueue.elems[i + 2]); + final long ptr = ((long) aQueue.elems[i + 3] << 32) | ((long) (aQueue.elems[i + 4]) & 0xFFFFFFFFL); + buf.append(" ptr: " + ptr); + buf.append("\n"); + } + return buf.toString(); + } + } } diff --git a/tlatools/src/tlc2/util/MemIntStack.java b/tlatools/src/tlc2/util/MemIntStack.java index e8dae0c97e9b29d63886a947de86a67aee9088e5..ce74548de265fcfa4d66699d3002def008d29808 100644 --- a/tlatools/src/tlc2/util/MemIntStack.java +++ b/tlatools/src/tlc2/util/MemIntStack.java @@ -5,66 +5,74 @@ package tlc2.util; -public final class MemIntStack { - private int size; - private int[] elems; - - public MemIntStack(String diskdir, String name) { - this.size = 0; - this.elems = new int[1024]; - } +public final class MemIntStack extends MemBasedSet implements IntStack { + private static final int MIN_CAPACITY = 1024; - /* Return the number of items on the stack. */ - public final int size() { return this.size; } - - /* Push an integer onto the stack. */ - public final synchronized void pushInt(int x) { - if (this.size == this.elems.length) { - int[] elems1 = new int[2*this.size]; - System.arraycopy(elems, 0, elems1, 0, this.size); - this.elems = elems1; - } - this.elems[this.size] = x; - this.size++; - } - - /* Push a long integer onto the stack. */ - public final synchronized void pushLong(long x) { - this.pushInt((int)(x & 0xFFFFFFFFL)); - this.pushInt((int)(x >>> 32)); - } + public MemIntStack(String diskdir, String name) { + super(MIN_CAPACITY); + } - /* Pop the integer on top of the stack. */ - public final synchronized int popInt() { - return this.elems[--this.size]; - } + /* (non-Javadoc) + * @see tlc2.util.IntStack#pushInt(int) + */ + public final synchronized void pushInt(int x) { + if (this.size == this.elems.length) { + final int[] newElems = ensureCapacity(MIN_CAPACITY); + System.arraycopy(elems, 0, newElems, 0, this.size); + this.elems = newElems; + } + this.elems[this.size] = x; + this.size++; + } - public final synchronized int peakInt() { - return peakInt(size - 1); - } + /* (non-Javadoc) + * @see tlc2.util.IntStack#pushLong(long) + */ + public final synchronized void pushLong(long x) { + this.pushInt((int) (x & 0xFFFFFFFFL)); + this.pushInt((int) (x >>> 32)); + } - public final synchronized int peakInt(int pos) { - return this.elems[pos]; -} + /* (non-Javadoc) + * @see tlc2.util.IntStack#popInt() + */ + public final synchronized int popInt() { + return this.elems[--this.size]; + } + + public final synchronized int peakInt() { + return peakInt(size - 1); + } + + public final synchronized int peakInt(int pos) { + return this.elems[pos]; + } - /* Pop the long integer on top of the stack. */ - public final synchronized long popLong() { - long high = this.popInt(); - long low = this.popInt(); - return (high << 32) | (low & 0xFFFFFFFFL); - } + /* (non-Javadoc) + * @see tlc2.util.IntStack#popLong() + */ + public final synchronized long popLong() { + long high = this.popInt(); + long low = this.popInt(); + return (high << 32) | (low & 0xFFFFFFFFL); + } - public final synchronized long peakLong() { - long high = this.peakInt(); - long low = this.peakInt(); - return (high << 32) | (low & 0xFFFFFFFFL); - } + public final synchronized long peakLong() { + long high = this.peakInt(); + long low = this.peakInt(); + return (high << 32) | (low & 0xFFFFFFFFL); + } - public final synchronized long peakLong(int pos) { - long high = this.peakInt(pos); - long low = this.peakInt(pos + 1); - return (high << 32) | (low & 0xFFFFFFFFL); - } + public final synchronized long peakLong(int pos) { + long high = this.peakInt(pos + 1); + long low = this.peakInt(pos); + return (high << 32) | (low & 0xFFFFFFFFL); + } - public final void reset() { this.size = 0; } + /* (non-Javadoc) + * @see tlc2.util.IntStack#reset() + */ + public final void reset() { + this.size = 0; + } } diff --git a/tlatools/src/tlc2/util/SetOfStates.java b/tlatools/src/tlc2/util/SetOfStates.java new file mode 100644 index 0000000000000000000000000000000000000000..5c486b5395b3a720b3a1249a3f7b65f3b32401ff --- /dev/null +++ b/tlatools/src/tlc2/util/SetOfStates.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +import tlc2.tool.ModelChecker; +import tlc2.tool.TLCState; + +/** + * A {@link SetOfStates} is a hash set with open addressing that is intended to + * be used in TLC's {@link ModelChecker#getNextStates()} implementation. In this + * are the number of {@link TLCState}s generated is relatively small and thus + * the likelihood of consecutive ranges in the fingerprint domain low. In turn, + * this means that the {@link TLCState}s in {@link SetOfStates} are evenly + * distributed assuming the {@link SetOfStates#length} is sufficiently large. + */ +public final class SetOfStates { + + private TLCState[] states; + private int count; + private int length; + private int thresh; + + public SetOfStates(final int size) { + this.count = 0; + this.length = size; + this.thresh = length / 2; + this.states = new TLCState[length]; + } + + public void clear() { + this.count = 0; + this.states = new TLCState[length]; + } + + private final void grow() { + final TLCState[] old = states; + this.count = 0; + this.length = 2 * this.length + 1; + this.thresh = this.length / 2; + this.states = new TLCState[this.length]; + for (int i = 0; i < old.length; i++) { + final TLCState s = old[i]; + // This is where we have to redundantly compute the state's + // fingerprint. Thus, try to minimize the number of grow operations. + if (s != null) { + this.put(s.fingerPrint(), s); + } + } + } + + public final boolean put(final TLCState aState) { + return put(aState.fingerPrint(), aState); + } + + public final boolean put(final long fingerprint, final TLCState aState) { + if (count >= thresh) { + this.grow(); + } + int loc = ((int) fingerprint & 0x7FFFFFFF) % this.length; + // This loop keep going until either a match or a null bucket is found. + while (true) { + final TLCState ent = this.states[loc]; + if (ent == null) { + states[loc] = aState; + count++; + return false; + } + // Compare with equals here to correctly handle symmetry where two + // symmetric states will be hashed to neighboring buckets. The + // assumption is that we will end up with only doing a few equality + // checks because the primary comparison is still the fingerprint + // and that the states[] is sparsely populated. + if (aState.equals(ent)) { + return true; + } + loc = (loc + 1) % this.length; + } + } + + /** + * @return The current capacity of this set. [](capacity > size) + */ + public int capacity() { + return this.length; + } + + /** + * @return The number of {@link TLCState}s in this set. [](capacity > size) + */ + public final int size() { + return this.count; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuffer buf = new StringBuffer("{"); + for (int i = 0; i < states.length; i++) { + final TLCState tlcState = states[i]; + if (tlcState != null) { + buf.append("<<"); + buf.append(tlcState.fingerPrint()); + buf.append(","); + final String toStr = tlcState.toString(); + buf.append(toStr.substring(0, toStr.length() - 1)); // chop off "\n" + buf.append(">>,\n"); + } + } + buf.append("}"); + return buf.toString(); + } + + /* + * Iterate (avoids creating an iterator object at the price of the mandatory + * resetNext() method). + */ + + private int iteratorIndex = 0; + + public final TLCState next() { + TLCState next = null; + while ((next = this.states[iteratorIndex++]) == null) { + // No-op loop + } + return next; + } + + public void resetNext() { + iteratorIndex = 0; + } +} diff --git a/tlatools/src/tlc2/util/StatePoolReader.java b/tlatools/src/tlc2/util/StatePoolReader.java index 7690191f2beeb7e847c19bcc795ed125cd2d5bfa..fcaefce99a894a7bda210893d72eef75fe38c869 100644 --- a/tlatools/src/tlc2/util/StatePoolReader.java +++ b/tlatools/src/tlc2/util/StatePoolReader.java @@ -23,6 +23,7 @@ public class StatePoolReader extends Thread { } public StatePoolReader(int bufSize, File file) { + super("TLCStatePoolReader"); this.buf = new TLCState[bufSize]; this.poolFile = file; this.isFull = false; diff --git a/tlatools/src/tlc2/util/StatePoolWriter.java b/tlatools/src/tlc2/util/StatePoolWriter.java index 2f4a45a26fae73d0433fc41d770a4ff55a752fa7..b01461bfd405cab8f741586c67517329c79e9d25 100644 --- a/tlatools/src/tlc2/util/StatePoolWriter.java +++ b/tlatools/src/tlc2/util/StatePoolWriter.java @@ -28,6 +28,7 @@ public class StatePoolWriter extends Thread { } public StatePoolWriter(int bufSize, StatePoolReader reader) { + super("TLCStatePoolWriter"); this.buf = new TLCState[bufSize]; this.poolFile = null; this.reader = reader; diff --git a/tlatools/src/tlc2/util/SynchronousDiskIntStack.java b/tlatools/src/tlc2/util/SynchronousDiskIntStack.java new file mode 100644 index 0000000000000000000000000000000000000000..15c34bfea156b75a4a1f9ae38c46ceba7e8b25cf --- /dev/null +++ b/tlatools/src/tlc2/util/SynchronousDiskIntStack.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +import java.io.File; + +import tlc2.output.EC; +import util.Assert; +import util.BufferedDataInputStream; +import util.BufferedDataOutputStream; +import util.FileUtil; + +public class SynchronousDiskIntStack implements IntStack { + + public final static int BufSize = 8388608; // ~32mb + private final static int BufSizeMax = BufSize << 5; // ~1024mb + + private final int bufSize; + private final String filePrefix; + + private long size = 0L; + private int index = 0; + + private int hiPool = 0; + + private int[] buf; + + public SynchronousDiskIntStack(String diskdir, String name) { + this(diskdir, name, BufSize); + } + + public SynchronousDiskIntStack(String diskdir, String name, int capacity) { + // Hard-limit capacity to 1gb per page file + capacity = Math.min(BufSizeMax, Math.max(capacity, BufSize)); + this.filePrefix = diskdir + FileUtil.separator + name; + this.bufSize = capacity; + this.buf = new int[capacity]; + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#size() + */ + public long size() { + return this.size; + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#pushInt(int) + */ + public void pushInt(int x) { + if (this.index == bufSize) { + // flush to disk + try { + final File poolFile = new File(this.filePrefix + Integer.toString(this.hiPool)); + poolFile.deleteOnExit(); + final BufferedDataOutputStream bdos = FileUtil.newBdFOS(false, poolFile); + final int len = buf.length; + for (int i = 0; i < len; i++) { + bdos.writeInt(buf[i]); + } + bdos.close(); + this.hiPool++; + this.index = 0; + } catch (Exception e) { + Assert.fail(EC.SYSTEM_ERROR_WRITING_STATES, new String[] { "stack", e.getMessage() }); + } + } + this.buf[this.index++] = x; + this.size++; + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#pushLong(long) + */ + public void pushLong(long x) { + this.pushInt((int) (x & 0xFFFFFFFFL)); + this.pushInt((int) (x >>> 32)); + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#popInt() + */ + public int popInt() { + if (this.index == 0 && hasPool()) { + // fill buffer + try { + final File poolFile = new File(this.filePrefix + Integer.toString(this.hiPool - 1)); + final BufferedDataInputStream bdis = FileUtil.newBdFIS(false, poolFile); + final int len = buf.length; + for (int i = 0; i < len; i++) { + buf[i] = bdis.readInt(); + } + bdis.close(); + this.hiPool--; + this.index = len; + } catch (Exception e) { + Assert.fail(EC.SYSTEM_ERROR_WRITING_STATES, new String[] { "stack", e.getMessage() }); + } + } + this.size--; + return this.buf[--this.index]; + } + + private boolean hasPool() { + return this.hiPool >= 0; + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#popLong() + */ + public long popLong() { + long high = this.popInt(); + long low = this.popInt(); + return (high << 32) | (low & 0xFFFFFFFFL); + } + + /* (non-Javadoc) + * @see tlc2.util.IntStack#reset() + */ + public void reset() { + this.size = 0L; + this.index = 0; + + this.hiPool = 0; + } +} diff --git a/tlatools/src/tlc2/util/statistics/IBucketStatistics.java b/tlatools/src/tlc2/util/statistics/IBucketStatistics.java index 05bda1b1f06ca1144191efe56b3b526da0acc109..d00cd9c237c441d01e5a9b372902ddd03c98cced 100644 --- a/tlatools/src/tlc2/util/statistics/IBucketStatistics.java +++ b/tlatools/src/tlc2/util/statistics/IBucketStatistics.java @@ -1,10 +1,13 @@ package tlc2.util.statistics; +/** + * Keeps statistics about any samples added. + */ public interface IBucketStatistics { /** * @param amount - * Add a sample to the stastics. Allowed range is 0 <= sample <= + * Add a sample to the statistics. Allowed range is 0 <= sample <= * Integer.MAX_VALUE */ void addSample(int amount); diff --git a/tlatools/src/tlc2/value/RecordValue.java b/tlatools/src/tlc2/value/RecordValue.java index 3dd1281069177fe6296207d949e2a23908dfcbe0..3995be054ac126d043eec12315fdd7c6ea4302ec 100644 --- a/tlatools/src/tlc2/value/RecordValue.java +++ b/tlatools/src/tlc2/value/RecordValue.java @@ -5,6 +5,8 @@ package tlc2.value; +import java.util.Arrays; + import tlc2.output.EC; import tlc2.output.MP; import tlc2.util.FP64; @@ -262,7 +264,14 @@ public class RecordValue extends Value implements Applicable { for (int i = 0; i < this.values.length; i++) { vals[i] = this.values[i].deepCopy(); } - return new RecordValue(this.names, vals, this.isNorm); + // Following code modified 16 June 2015 by adding Arrays.copyOf to fix + // the following bug that seems to have manifested itself only in TLC.Print and + // TLC.PrintT: Calling normalize on the original modifies the + // order of the names array in the deepCopy (and vice-versa) without doing the + // corresponding modification on the values array. Thus, the names are + // copied too to prevent any modification/normalization done to the + // original to appear in the deepCopy. + return new RecordValue(Arrays.copyOf(this.names, this.names.length), vals, this.isNorm); } public final boolean assignable(Value val) { diff --git a/tlatools/src/tlc2/value/Value.java b/tlatools/src/tlc2/value/Value.java index 17d48ad4d1b51229f232fff7933eccaa0a12fbd1..1b37f3cadb46dc75cbe6b1a67180de7502166b83 100644 --- a/tlatools/src/tlc2/value/Value.java +++ b/tlatools/src/tlc2/value/Value.java @@ -359,6 +359,13 @@ public abstract class Value implements ValueConstants, Serializable { StringBuffer sb = new StringBuffer(); return this.toString(sb, 0).toString(); } + + public final String toString(String delim) { + StringBuffer sb = new StringBuffer(); + sb = this.toString(sb, 0); + sb.append(delim); + return sb.toString(); + } public static String ppr(String s) { return PrettyPrint.mypp(s, 80) ; diff --git a/tlatools/test-concurrent/tlc2/tool/fp/MultiThreadedFPSetTest.java b/tlatools/test-concurrent/tlc2/tool/fp/MultiThreadedFPSetTest.java index d7d5a294223e33529afdb81c1dd05669f5f921a3..e26c1af0252d4b336ac70de86d062efa8cbff65b 100644 --- a/tlatools/test-concurrent/tlc2/tool/fp/MultiThreadedFPSetTest.java +++ b/tlatools/test-concurrent/tlc2/tool/fp/MultiThreadedFPSetTest.java @@ -1,12 +1,16 @@ // Copyright (c) 2012 Markus Alexander Kuppe. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Date; import java.util.concurrent.CountDownLatch; - +import org.junit.Before; +import org.junit.Test; import tlc2.tool.fp.generator.BatchedFingerPrintGenerator; import tlc2.tool.fp.generator.FingerPrintGenerator; import tlc2.tool.fp.generator.LongVecFingerPrintGenerator; @@ -21,8 +25,8 @@ public abstract class MultiThreadedFPSetTest extends AbstractFPSetTest { /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { System.out.println("Insertions: " + df.format(INSERTIONS) + " (approx: " + df.format(INSERTIONS * FPSet.LongSize >> 20) + " GiB)"); System.out.println("Thread count: " + NUM_THREADS); @@ -32,6 +36,7 @@ public abstract class MultiThreadedFPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with random fingerprints using multiple * threads in ordered batches */ + @Test public void testMaxFPSetSizeRndBatched() throws IOException, InterruptedException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { doTest(BatchedFingerPrintGenerator.class); } @@ -40,6 +45,7 @@ public abstract class MultiThreadedFPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with random fingerprints using multiple * threads in ordered LongVecs using putBlock/containsBlock */ + @Test public void testMaxFPSetSizeRndBlock() throws IOException, InterruptedException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { doTest(LongVecFingerPrintGenerator.class); } @@ -48,6 +54,7 @@ public abstract class MultiThreadedFPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with max int + 2L random using multiple * threads */ + @Test public void testMaxFPSetSizeRnd() throws IOException, InterruptedException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { doTest(FingerPrintGenerator.class); } diff --git a/tlatools/test-concurrent/tlc2/tool/fp/generator/BatchedFingerPrintGenerator.java b/tlatools/test-concurrent/tlc2/tool/fp/generator/BatchedFingerPrintGenerator.java index 9ebb8bf255fd76b9c40f0f24ae52ef0d208f665c..8d1385ebc3d6e52ebb643faf977a282ae0d9d4e6 100644 --- a/tlatools/test-concurrent/tlc2/tool/fp/generator/BatchedFingerPrintGenerator.java +++ b/tlatools/test-concurrent/tlc2/tool/fp/generator/BatchedFingerPrintGenerator.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import org.junit.Assert; + import tlc2.tool.fp.FPSet; import tlc2.tool.fp.MultiThreadedFPSetTest; @@ -28,7 +30,7 @@ public class BatchedFingerPrintGenerator extends FingerPrintGenerator { if (initialized) { for (int i = 0; i < predecessors.length; i++) { long predecessor = predecessors[i]; - MultiThreadedFPSetTest.assertTrue(fpSet.contains(predecessor)); + Assert.assertTrue(fpSet.contains(predecessor)); } } @@ -57,7 +59,7 @@ public class BatchedFingerPrintGenerator extends FingerPrintGenerator { } catch (IOException e) { e.printStackTrace(); - MultiThreadedFPSetTest.fail("Unexpected"); + Assert.fail("Unexpected"); } } latch.countDown(); diff --git a/tlatools/test-concurrent/tlc2/tool/fp/generator/FingerPrintGenerator.java b/tlatools/test-concurrent/tlc2/tool/fp/generator/FingerPrintGenerator.java index 728c0bfdbf164c54e5f80b5d48345a46766a0d53..46c8208d3bb6325b1efef15c4af545e997141579 100644 --- a/tlatools/test-concurrent/tlc2/tool/fp/generator/FingerPrintGenerator.java +++ b/tlatools/test-concurrent/tlc2/tool/fp/generator/FingerPrintGenerator.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.util.Random; import java.util.concurrent.CountDownLatch; +import org.junit.Assert; + import tlc2.tool.fp.FPSet; import tlc2.tool.fp.MultiThreadedFPSetTest; @@ -37,7 +39,7 @@ public class FingerPrintGenerator implements Runnable { try { // make sure set still contains predecessor if (predecessor != 0L) { - MultiThreadedFPSetTest.assertTrue(fpSet.contains(predecessor)); + Assert.assertTrue(fpSet.contains(predecessor)); } predecessor = rnd.nextLong(); @@ -56,7 +58,7 @@ public class FingerPrintGenerator implements Runnable { } catch (IOException e) { e.printStackTrace(); - MultiThreadedFPSetTest.fail("Unexpected"); + Assert.fail("Unexpected"); } } latch.countDown(); diff --git a/tlatools/test-concurrent/tlc2/tool/fp/generator/LongVecFingerPrintGenerator.java b/tlatools/test-concurrent/tlc2/tool/fp/generator/LongVecFingerPrintGenerator.java index 851a5a45ec627dee62401113e7ef03f8504a5f0d..1253fb9dd0d244f58970f0b89d5e6610f6829c3a 100644 --- a/tlatools/test-concurrent/tlc2/tool/fp/generator/LongVecFingerPrintGenerator.java +++ b/tlatools/test-concurrent/tlc2/tool/fp/generator/LongVecFingerPrintGenerator.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.util.Arrays; import java.util.concurrent.CountDownLatch; +import org.junit.Assert; + import tlc2.tool.fp.FPSet; import tlc2.tool.fp.MultiThreadedFPSetTest; import tlc2.util.BitVector; @@ -29,7 +31,7 @@ public class LongVecFingerPrintGenerator extends FingerPrintGenerator { // Make sure set still contains predecessors if (initialized) { final BitVector bitVector = fpSet.containsBlock(predecessors); - MultiThreadedFPSetTest.assertTrue(bitVector.trueCnt() == batch); + Assert.assertTrue(bitVector.trueCnt() == batch); } // Fill new fingerprints and sort them @@ -51,7 +53,7 @@ public class LongVecFingerPrintGenerator extends FingerPrintGenerator { } catch (IOException e) { e.printStackTrace(); - MultiThreadedFPSetTest.fail("Unexpected"); + Assert.fail("Unexpected"); } } latch.countDown(); diff --git a/tlatools/test-long/tlc2/tool/fp/FPSetTest.java b/tlatools/test-long/tlc2/tool/fp/FPSetTest.java index 8d758ddfe87384d9a811f682ee6dc5e6910ba488..2ab4d40eb5e837d02c0787fefba0461a56bfe30d 100644 --- a/tlatools/test-long/tlc2/tool/fp/FPSetTest.java +++ b/tlatools/test-long/tlc2/tool/fp/FPSetTest.java @@ -1,8 +1,13 @@ package tlc2.tool.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.util.Date; import java.util.Random; +import org.junit.Test; public abstract class FPSetTest extends AbstractFPSetTest { @@ -10,6 +15,7 @@ public abstract class FPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with four linearly incrementing values * @throws IOException */ + @Test public void testSimpleFill() throws IOException { final FPSet fpSet = getFPSet(new FPSetConfiguration()); fpSet.init(1, tmpdir, filename); @@ -29,6 +35,7 @@ public abstract class FPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with max int + 1 random * @throws IOException */ + @Test public void testMaxFPSetSizeRnd() throws IOException { Random rnd = new Random(RNG_SEED); @@ -69,6 +76,7 @@ public abstract class FPSetTest extends AbstractFPSetTest { * Test filling a {@link FPSet} with max int + 1 * @throws IOException */ + @Test public void testMaxFPSetSize() throws IOException { // diff --git a/tlatools/test-long/tlc2/tool/fp/OffHeapDiskFPSetTest.java b/tlatools/test-long/tlc2/tool/fp/OffHeapDiskFPSetTest.java index a619498f84f614c16e37c4be4a6a82d9f5b8947e..8bbba505b34b5ade1838109971638e514a212e8f 100644 --- a/tlatools/test-long/tlc2/tool/fp/OffHeapDiskFPSetTest.java +++ b/tlatools/test-long/tlc2/tool/fp/OffHeapDiskFPSetTest.java @@ -1,15 +1,19 @@ // Copyright (c) 2012 Markus Alexander Kuppe. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.util.Random; - +import org.junit.Test; import util.TLCRuntime; public class OffHeapDiskFPSetTest extends FPSetTest { private static final int FLUSHES = 4; + @Test public void testCollisionBucket() throws IOException { final FPSet fpSet = getFPSet(new FPSetConfiguration()); fpSet.init(1, tmpdir, filename); @@ -20,6 +24,7 @@ public class OffHeapDiskFPSetTest extends FPSetTest { } } + @Test public void testPosition() throws IOException { final FPSet fpSet = getFPSet(new FPSetConfiguration()); fpSet.init(1, tmpdir, filename); @@ -36,6 +41,7 @@ public class OffHeapDiskFPSetTest extends FPSetTest { /** * */ + @Test public void testMultipleFlushes() throws IOException { final FPSet fpSet = getFPSet(new FPSetConfiguration()); fpSet.init(1, tmpdir, filename); diff --git a/tlatools/test-long/tlc2/tool/liveness/MultiThreadedSpecTest.java b/tlatools/test-long/tlc2/tool/liveness/MultiThreadedSpecTest.java index f2e0f4dcf5db43857d0b3340afadda9b23bfae0a..caedceda530ae0769488c6adb955b8386f3e2c9f 100644 --- a/tlatools/test-long/tlc2/tool/liveness/MultiThreadedSpecTest.java +++ b/tlatools/test-long/tlc2/tool/liveness/MultiThreadedSpecTest.java @@ -26,6 +26,9 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; @@ -35,7 +38,8 @@ import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - +import org.junit.Before; +import org.junit.Test; import tlc2.TLCGlobals; import tlc2.output.EC; import tlc2.tool.WorkerMonitor; @@ -74,6 +78,7 @@ public abstract class MultiThreadedSpecTest extends ModelCheckerTestCase { this.waitedRatio = waitedRatio; } + @Test public void testSpec() throws BrokenBarrierException, InterruptedException, TimeoutException { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); @@ -128,6 +133,7 @@ public abstract class MultiThreadedSpecTest extends ModelCheckerTestCase { } } + @Before public void setUp() { // Set the threshold before TLC (periodically) checks liveness to // the largest possible value. This essentially stops TLC from checking @@ -155,8 +161,6 @@ public abstract class MultiThreadedSpecTest extends ModelCheckerTestCase { latch.countDown(); } }); - - super.setUp(); } protected int getNumberOfThreads() { diff --git a/tlatools/test-long/tlc2/tool/queue/DiskStateQueueTest.java b/tlatools/test-long/tlc2/tool/queue/DiskStateQueueTest.java index f5fccba4fa918552c46cc34617cb80a11aa54b9c..b093369427fea8a3e111f49f4cee7376dc1585e8 100644 --- a/tlatools/test-long/tlc2/tool/queue/DiskStateQueueTest.java +++ b/tlatools/test-long/tlc2/tool/queue/DiskStateQueueTest.java @@ -1,7 +1,11 @@ package tlc2.tool.queue; -import java.io.File; +import static org.junit.Assert.assertTrue; +import java.io.File; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import tlc2.tool.TLCState; public class DiskStateQueueTest extends StateQueueTest { @@ -11,9 +15,8 @@ public class DiskStateQueueTest extends StateQueueTest { /* (non-Javadoc) * @see tlc2.tool.queue.StateQueueTest#setUp() */ - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() throws Exception { // create a temp folder in java.io.tmpdir and have it deleted on VM exit final String diskdir = System.getProperty("java.io.tmpdir") + File.separator + "MultiDiskStateQueueTest_" + System.currentTimeMillis(); @@ -27,6 +30,7 @@ public class DiskStateQueueTest extends StateQueueTest { /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ + @After public void tearDown() { // delete all nested files final File[] listFiles = file.listFiles(); @@ -39,6 +43,7 @@ public class DiskStateQueueTest extends StateQueueTest { // add Integer.MAX_VALUE states and check growth of MultiStateQueue. // Reuse the same state to speed up instantiation and space requirements + @Test public void testGrowBeyondIntMaxValue() { final TLCState state = new DummyTLCState(); diff --git a/tlatools/test-model/AS/AS.cfg b/tlatools/test-model/AS/AS.cfg new file mode 100644 index 0000000000000000000000000000000000000000..2aacadae1f61d029d84eab28111b6caae7aabb1c --- /dev/null +++ b/tlatools/test-model/AS/AS.cfg @@ -0,0 +1,6 @@ +\* CONSTANT definitions +CONSTANT +c = 4 +\* SPECIFICATION definition +SPECIFICATION +ASInit diff --git a/tlatools/test-model/AS/AS.tla b/tlatools/test-model/AS/AS.tla new file mode 100644 index 0000000000000000000000000000000000000000..20482a157b7c15884ae910d82a2ecdfe3539d682 --- /dev/null +++ b/tlatools/test-model/AS/AS.tla @@ -0,0 +1,30 @@ +--------------------------------- MODULE AS --------------------------------- +EXTENDS Integers, FiniteSets + +CONSTANT c +ASSUME c \in Nat \ {0} + +R == -42 + + +VARIABLES x, z + +ASTypeOK == /\ x \in 1..c \cup {R} + /\ z \subseteq 1..c + +ASInit == /\ x = R + /\ z = {} + +ASChoose == /\ Cardinality(z) # c + /\ \E n \in 1..c \ z : /\ x' = n + /\ z' = z \cup {n} + +ASRest == /\ Cardinality(z) = c + /\ x' = R + /\ z' = {} + +ASNext == ASChoose \/ ASRest + +AS == ASInit /\ [][ASNext]_<<x, z>> + +============================================================================= diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.cfg b/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.cfg new file mode 100644 index 0000000000000000000000000000000000000000..00592a934cbecee867aa47abbdde199473d8fd74 --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.cfg @@ -0,0 +1,6 @@ +CONSTRAINT +const_4711 +SPECIFICATION +Spec +INVARIANT +NotNine diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.tla b/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.tla new file mode 100644 index 0000000000000000000000000000000000000000..c71fb286d50ae1b6eddeba21be20e72cc613ea33 --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorEvalException.tla @@ -0,0 +1,19 @@ +--------------------------- MODULE DoInitFunctorEvalException --------------------------- +EXTENDS Integers + +VARIABLES x +vars == <<x>> + +Init == x \in 1..10 + +Next == x' = 0 + +Spec == Init /\ [][Next]_vars + +(* This function is used as a model constraint to trigger an EvalException (because it doesn't return Bool) *) +F(a) == {} +const_4711 == F(x) + +(* This property is violated by one of the init states *) +NotNine == x # 9 +============================================================================= diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.cfg b/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.cfg new file mode 100644 index 0000000000000000000000000000000000000000..5fd6a98a18bd4dcdd7201895f3989c0a7a472a0c --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.cfg @@ -0,0 +1,4 @@ +SPECIFICATION +Spec +INVARIANT +NotNine diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.tla b/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.tla new file mode 100644 index 0000000000000000000000000000000000000000..73c16c5702db1d15d92a4e20a20f69e6dddb84e4 --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorInvariant.tla @@ -0,0 +1,15 @@ +--------------------------- MODULE DoInitFunctorInvariant --------------------------- +EXTENDS Integers + +VARIABLES x +vars == <<x>> + +Init == x \in 1..10 + +Next == x' = 0 + +Spec == Init /\ [][Next]_vars + +(* This property is violated by one of the init states *) +NotNine == x # 9 +============================================================================= diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.cfg b/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.cfg new file mode 100644 index 0000000000000000000000000000000000000000..b92bb43293dbff997539b4da5c9374702f3e0507 --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.cfg @@ -0,0 +1,4 @@ +SPECIFICATION +Spec +PROPERTY +NotNine diff --git a/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.tla b/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.tla new file mode 100644 index 0000000000000000000000000000000000000000..d1acd346b9b89ce6607e41148ea71b277946ccbd --- /dev/null +++ b/tlatools/test-model/DoInitFunctor/DoInitFunctorProperty.tla @@ -0,0 +1,15 @@ +--------------------------- MODULE DoInitFunctorProperty --------------------------- +EXTENDS Integers + +VARIABLES x +vars == <<x>> + +Init == x \in 1..10 + +Next == x' = 0 + +Spec == Init /\ [][Next]_vars + +(* This property is violated by one of the init states *) +NotNine == x # 9 +============================================================================= diff --git a/tlatools/test-model/Empty.cfg b/tlatools/test-model/Empty.cfg new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tlatools/test-model/Empty.tla b/tlatools/test-model/Empty.tla new file mode 100644 index 0000000000000000000000000000000000000000..3e6c3492f671ebc8e947dc4fcecc22505bf3bb71 --- /dev/null +++ b/tlatools/test-model/Empty.tla @@ -0,0 +1,2 @@ +------------------------------- MODULE Empty ------------------------------- +============================================================================ diff --git a/tlatools/test-model/IncompleteNext.cfg b/tlatools/test-model/IncompleteNext.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bc224b9def594b0819de3f0b45ede63ea8213c7b --- /dev/null +++ b/tlatools/test-model/IncompleteNext.cfg @@ -0,0 +1,2 @@ +SPECIFICATION +Spec diff --git a/tlatools/test-model/IncompleteNext.tla b/tlatools/test-model/IncompleteNext.tla new file mode 100644 index 0000000000000000000000000000000000000000..8ce0581a7d9a03619b0a8c51c4f78386fbc94c84 --- /dev/null +++ b/tlatools/test-model/IncompleteNext.tla @@ -0,0 +1,7 @@ +--------------------------- MODULE IncompleteNext --------------------------- +EXTENDS Integers, TLC + +VARIABLES x, y + +Spec == (x=0) /\ (y=0) /\ [][x'=x+1]_<<x,y>> +============================================================================= diff --git a/tlatools/test-model/Loop/SystemLoop.cfg b/tlatools/test-model/Loop/SystemLoop.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bc6955541b374c7b1e9d24f2f1380e98a172e650 --- /dev/null +++ b/tlatools/test-model/Loop/SystemLoop.cfg @@ -0,0 +1,4 @@ +SPECIFICATION +Spec +PROPERTY +Liveness \ No newline at end of file diff --git a/tlatools/test-model/Loop/SystemLoop.tla b/tlatools/test-model/Loop/SystemLoop.tla new file mode 100644 index 0000000000000000000000000000000000000000..cdb0b7a89aa75c55e659be950b49ddfbd29ab819 --- /dev/null +++ b/tlatools/test-model/Loop/SystemLoop.tla @@ -0,0 +1,23 @@ +----------------------------- MODULE SystemLoop ----------------------------- + +(* System LOOP as described by Manna & Pneuli on page 423ff *) + +VARIABLES x +vars == <<x>> + +Init == x = 0 + +One == x = 0 /\ x' = 1 +Two == x = 1 /\ x' = 2 +Three == x = 2 /\ x' = 3 +Back == x = 3 /\ x' = 0 + +Next == One \/ Two \/ Three \/ Back + +Spec == Init /\ [][Next]_vars + +SpecWeakFair == Spec /\ WF_vars(Next) + +Liveness == []<>(x=3) + +============================================================================= diff --git a/tlatools/test-model/Loop/SystemLoopWeakFair.cfg b/tlatools/test-model/Loop/SystemLoopWeakFair.cfg new file mode 100644 index 0000000000000000000000000000000000000000..b8fb187f59c22cd4118b7eac56293c021598a9ca --- /dev/null +++ b/tlatools/test-model/Loop/SystemLoopWeakFair.cfg @@ -0,0 +1,4 @@ +SPECIFICATION +SpecWeakFair +PROPERTY +Liveness \ No newline at end of file diff --git a/tlatools/test-model/PrintTraceRace/MC.cfg b/tlatools/test-model/PrintTraceRace/MC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..99575a812c2dc63dbacd75840a606638206d29f9 --- /dev/null +++ b/tlatools/test-model/PrintTraceRace/MC.cfg @@ -0,0 +1,10 @@ +\* INIT definition +INIT +init_143958263805435000 +\* NEXT definition +NEXT +next_143958263806436000 +\* INVARIANT definition +INVARIANT +inv_143958263807437000 +\* Generated on Fri Aug 14 13:03:58 PDT 2015 \ No newline at end of file diff --git a/tlatools/test-model/PrintTraceRace/MC.tla b/tlatools/test-model/PrintTraceRace/MC.tla new file mode 100644 index 0000000000000000000000000000000000000000..d2d57c2754978f29f3831b7ac0977c7d043294cc --- /dev/null +++ b/tlatools/test-model/PrintTraceRace/MC.tla @@ -0,0 +1,16 @@ +---- MODULE MC ---- +EXTENDS PrintTraceRace, TLC + +\* INIT definition @modelBehaviorInit:0 +init_143958263805435000 == +TestInit +---- +\* NEXT definition @modelBehaviorNext:0 +next_143958263806436000 == +TestNext +---- +\* INVARIANT definition @modelCorrectnessInvariants:0 +inv_143958263807437000 == +TestTypeInv +---- +============================================================================= diff --git a/tlatools/test-model/PrintTraceRace/PrintTraceRace.tla b/tlatools/test-model/PrintTraceRace/PrintTraceRace.tla new file mode 100644 index 0000000000000000000000000000000000000000..671671164094477aa0481a60d452b820f7f13826 --- /dev/null +++ b/tlatools/test-model/PrintTraceRace/PrintTraceRace.tla @@ -0,0 +1,19 @@ +---------------------- MODULE PrintTraceRace ------------------------- +EXTENDS Naturals, Sequences +VARIABLES S + +TestTypeInv == S \in [ q: Seq({1..2}), + i: 0..3] + +TestInit == S = [q |-> << >>, + i |-> 1] + +Send == /\ (S.i \leq 2) + /\ (S' = [S EXCEPT !.i = (S.i + 1),!.q = Append(S.q,S.i)]) + +Done == /\ S.i > 2 + /\ UNCHANGED << S >> + +TestNext == \/ Send + \/ Done +============================================================ \ No newline at end of file diff --git a/tlatools/test-model/TLCSet.cfg b/tlatools/test-model/TLCSet.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bc224b9def594b0819de3f0b45ede63ea8213c7b --- /dev/null +++ b/tlatools/test-model/TLCSet.cfg @@ -0,0 +1,2 @@ +SPECIFICATION +Spec diff --git a/tlatools/test-model/TLCSet.tla b/tlatools/test-model/TLCSet.tla new file mode 100644 index 0000000000000000000000000000000000000000..a40245ee617860d02ac40866fccf7ab3eebe18d9 --- /dev/null +++ b/tlatools/test-model/TLCSet.tla @@ -0,0 +1,5 @@ +--------------------------- MODULE TLCSet --------------------------- +EXTENDS Integers, TLC + +Spec == TLCSet(1, {}) +============================================================================= diff --git a/tlatools/test-model/TSnapShot/MC.cfg b/tlatools/test-model/TSnapShot/MC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..445d96f9cfbddc7d9ae4d51d67bff194b463e7e8 --- /dev/null +++ b/tlatools/test-model/TSnapShot/MC.cfg @@ -0,0 +1,25 @@ +\* MV CONSTANT declarations +CONSTANTS +w1 = w1 +\* MV CONSTANT declarations +CONSTANTS +r1 = r1 +\* MV CONSTANT definitions +CONSTANT +Writer <- const_1446808092434108000 +\* MV CONSTANT definitions +CONSTANT +Reader <- const_1446808092444109000 +\* CONSTANT definition +CONSTANT +Nat <- def_ov_1446808092454110000 +\* CONSTRAINT definition +CONSTRAINT +constr_1446808092464111000 +\* SPECIFICATION definition +SPECIFICATION +spec_1446808092484113000 +\* INVARIANT definition +INVARIANT +inv_1446808092494114000 +\* Generated on Fri Nov 06 12:08:12 CET 2015 \ No newline at end of file diff --git a/tlatools/test-model/TSnapShot/MC.tla b/tlatools/test-model/TSnapShot/MC.tla new file mode 100644 index 0000000000000000000000000000000000000000..177b7ca354c4e015c2e11cfb6b624e71dc8cf45d --- /dev/null +++ b/tlatools/test-model/TSnapShot/MC.tla @@ -0,0 +1,52 @@ +---- MODULE MC ---- +EXTENDS TSnapShot, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +w1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +r1 +---- + +\* MV CONSTANT definitions Writer +const_1446808092434108000 == +{w1} +---- + +\* MV CONSTANT definitions Reader +const_1446808092444109000 == +{r1} +---- + +\* CONSTANT definition @modelParameterDefinitions:0 +def_ov_1446808092454110000 == +0..2 +---- +\* CONSTRAINT definition @modelParameterContraint:0 +constr_1446808092464111000 == +LET maxNat == CHOOSE n \in Nat : \A m \in Nat : n >= m IN +\A w \in Writer : iwriter[w] < maxNat +---- +\* Constant expression definition @modelExpressionEval +const_expr_1446808092474112000 == +(w1 :> (w1 :> (w1 :> 0)) @@ r1 :> (w1 :> (w1 :> 0))) \in [Process -> [Writer -> Memory]] +---- + +\* Constant expression ASSUME statement @modelExpressionEval +ASSUME PrintT(<<"$!@$!@$!@$!@$!",const_expr_1446808092474112000>>) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_1446808092484113000 == +Spec +---- +\* INVARIANT definition @modelCorrectnessInvariants:0 +inv_1446808092494114000 == +TypeOK2 +---- +============================================================================= +\* Modification History +\* Created Fri Nov 06 12:08:12 CET 2015 by markus diff --git a/tlatools/test-model/TSnapShot/TSnapShot.tla b/tlatools/test-model/TSnapShot/TSnapShot.tla new file mode 100644 index 0000000000000000000000000000000000000000..b180942842e901d257db35976f5527cd903fe346 --- /dev/null +++ b/tlatools/test-model/TSnapShot/TSnapShot.tla @@ -0,0 +1,170 @@ +----------------------------- MODULE TSnapShot ----------------------------- +(**************************************************************************** +This spec describes an algorithm by Afek et al. described in + + https://en.wikipedia.org/wiki/Shared_snapshot_objects + +This is the unbounded memory algorithm described there. + +This implementation satisfies the TSnapSpec spec, with internal +variables hidden. +****************************************************************************) +EXTENDS Integers, TLC + +CONSTANTS Reader, Writer + +ASSUME Reader \cap Writer = { } + +Process == Reader \cup Writer +(***************************************************************************) +(* To make it easier to modify the spec to one in which processes write *) +(* values in some arbitrary set, we write `Value' instead of `Nat'. *) +(***************************************************************************) +Value == Nat +Memory == [Writer -> Value] +InitMemory == [w \in Writer |-> 0] +NotAMemory == [w \in Writer |-> -1] + +VARIABLES + rstate, wstate, \* These variables are the same as in TSnapSpec, since + \* they describe the externally visible state. + rwstate, \* Like rstate, except has a copy for the writer's state + \* of reading as well + mem, iwriter, \* The same as in TSnapSpec + snap, \* snap[w] is the last snapshot value obtained by + \* writer w during its read. + \* The following are arrays indexed by Reader \cup Writer, since + \* both readers and writers perform a read operation. + rd1, rd2, \* Two arrays of arrays holding the values obtained in + \* the two reads. + (*view1,*) view2, \* Two arrays of arrays holding the snap obtained in + \* the second reads. + notRd1, notRd2, \* Two arrays holding the sets of writers whose values + \* haven't yet been read by each of the reads. + moved \* moved[r][w] is the number of times different + \* values of mem[w] have been read by r during its + \* current read operation. + +vars == <<rstate, wstate, rwstate, mem, iwriter, rd1, rd2, + (*view1,*) view2, notRd1, notRd2, moved, snap>> + +TypeOK == (* /\ rstate \in [Reader -> Memory \cup {NotAMemory}] + /\ wstate \in [Writer -> {"idle", "busy"}] + /\ rwstate \in [Process -> Memory \cup {NotAMemory}] + /\ mem \in Memory + /\ iwriter \in [Writer -> Nat] + /\ snap \in [Writer -> Memory] + /\ rd1 \in [Process -> Memory] + /\ rd2 \in [Process -> Memory] *) +\* /\ view1 \in [Process -> [Writer -> Memory]] + /\ view2 \in [Process -> [Writer -> Memory]] +(* /\ notRd1 \in [Process -> SUBSET Writer] + /\ notRd2 \in [Process -> SUBSET Writer] + /\ moved \in [Process -> [Writer -> BOOLEAN]] *) +TypeOK2 == view2 \in [Process -> [Writer -> Memory]] +Init == /\ rstate = [r \in Reader |-> InitMemory] + /\ rwstate = [r \in Process |-> InitMemory] + /\ wstate = [r \in Writer |-> "idle"] + /\ mem = InitMemory + /\ iwriter = [r \in Writer |-> 0] + /\ rd1 = [r \in Process |-> InitMemory] + /\ rd2 = [r \in Process |-> InitMemory] +\* /\ view1 = [r \in Process |-> [w \in Writer |-> Memory]] + /\ view2 = [r \in Process |-> [w \in Writer |-> InitMemory]] + /\ notRd1 = [r \in Process |-> Writer] + /\ notRd2 = [r \in Process |-> Writer] + /\ moved = [r \in Process |-> [w \in Writer |-> FALSE]] + /\ snap = [r \in Writer |-> InitMemory] + +TypeOK3 == PrintT(TypeOK2) +(***************************************************************************) +(* Define Assign(array, idx, val) to essentially mean *) +(* *) +(* array[idx] := val *) +(* *) +(* so the reader of the spec doesn't have to deal with EXCEPT. *) +(***************************************************************************) +Assign(array, idx, val) == array' = [array EXCEPT ![idx] = val] + +(***************************************************************************) +(* Define Assign(array, idx1, idx2, val) to essentially mean *) +(* *) +(* array[idx1][idx2] := val *) +(* *) +(* so the reader of the spec doesn't have to deal with EXCEPT. *) +(***************************************************************************) +AAssign(array, idx1, idx2, val) == array' = [array EXCEPT ![idx1][idx2] = val] + + +BeginRead(r) == /\ rstate[r] # NotAMemory + /\ Assign(rstate, r, NotAMemory) + /\ Assign(rwstate, r, NotAMemory) + /\ UNCHANGED <<wstate, mem, iwriter, rd1, rd2, + view2, notRd1, notRd2, moved, snap>> + +\*vars == <<rstate, wstate, rwstate, mem, iwriter, rd1, rd2, +\* (*view1,*) view2, notRd1, notRd2, moved, snap>> +DoRd1(r) == /\ rwstate[r] = NotAMemory + /\ \E w \in notRd1[r] : /\ Assign(notRd1, r, notRd1[r] \ {w}) + /\ AAssign(rd1, r, w, mem[w]) +\* /\ AAssign(view1, r, w, scan[w]) + /\ UNCHANGED <<rstate, rwstate, wstate, mem, iwriter, rd2, view2, notRd2, + moved, snap>> + +DoRd2(r) == /\ (rwstate[r] = NotAMemory) /\ (notRd1[r] = {}) + /\ \E w \in notRd2[r] : /\ Assign(notRd2, r, notRd2[r] \ {w}) + /\ AAssign(rd2, r, w, mem[w]) + /\ AAssign(view2, r, w, snap[w]) + /\ UNCHANGED <<rstate, rwstate, wstate, mem, iwriter, rd1, notRd1, + moved, snap>> + +TryEnding(r) == /\ (rwstate[r] = NotAMemory) /\ (notRd1[r] = {}) + /\ IF rd1[r] = rd2[r] + THEN Assign(rwstate, r, rd1[r]) + ELSE LET F(w) == /\ rd1[r][w] # rd2[r][w] + /\ moved[r][w] + IN IF \E w \in Writer : F(w) + THEN Assign(rwstate,r, + view2[CHOOSE w \in Writer : F(w)]) + ELSE /\ UNCHANGED rwstate + /\ moved[r] = [w \in Writer |-> + moved[r] \/ (rd1[r][w] # rd2[r][w])] + /\ IF r \in Reader + THEN Assign(rstate, r, rwstate'[r]) + ELSE UNCHANGED rstate + /\ Assign(moved, r, [w \in Writer |-> FALSE]) + /\ Assign(notRd1, r, Writer) + /\ Assign(notRd2, r, Writer) + /\ Assign(rd1, r, InitMemory) \* To reduce state space + /\ Assign(rd2, r, InitMemory) \* To reduce state space + /\ Assign(view2, r, InitMemory) \* To reduce state space + /\ UNCHANGED <<wstate, mem, snap, iwriter>> + + +BeginWrite(w) == /\ wstate[w] = "idle" + /\ Assign(wstate, w, "busy") + /\ Assign(iwriter, w, iwriter[w] + 1) + /\ Assign(rwstate, w, NotAMemory) + /\ UNCHANGED <<rstate, mem, rd1, rd2, view2, notRd1, notRd2, moved, snap>> + +DoWrite(w) == /\ iwriter[w] # mem[w] + /\ rwstate[w] # NotAMemory + /\ Assign(mem, w, mem[w]+1) + /\ Assign(snap, w, rwstate[w]) + /\ UNCHANGED <<rstate, wstate, iwriter, rd1, rd2, view2, notRd1, + notRd2, moved>> + +EndWrite(w) == /\ wstate[w] = "busy" + /\ Assign(wstate, w, "idle") + /\ UNCHANGED <<rstate, rwstate, mem, snap, iwriter, rd1, rd2, + view2, notRd1, notRd2, moved>> + +Next == \/ \E w \in Writer : BeginWrite(w) \/ DoWrite(w) \/ EndWrite(w) + \/ \E r \in Reader : + BeginRead(r) \/ DoRd1(r) \/ DoRd2(r) \/ TryEnding(r) + +Spec == Init /\ [][Next]_vars +============================================================================= +\* Modification History +\* Last modified Mon Nov 02 18:16:48 PST 2015 by lamport +\* Created Mon Nov 02 13:33:53 PST 2015 by lamport diff --git a/tlatools/test-model/simulation/NQSpec/MC.cfg b/tlatools/test-model/simulation/NQSpec/MC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..ed49c011a10f683b730d990530404af87d6889ee --- /dev/null +++ b/tlatools/test-model/simulation/NQSpec/MC.cfg @@ -0,0 +1,47 @@ +\* MV CONSTANT declarations +CONSTANTS +e1 = e1 +\* MV CONSTANT declarations +CONSTANTS +id1 = id1 +\* MV CONSTANT declarations +CONSTANTS +d1 = d1 +\* MV CONSTANT declarations +CONSTANTS +v1 = v1 +\* MV CONSTANT definitions +CONSTANT +EnQers <- const_14476073153412000 +\* MV CONSTANT definitions +CONSTANT +Ids <- const_14476073153513000 +\* MV CONSTANT definitions +CONSTANT +DeQers <- const_14476073153614000 +\* MV CONSTANT definitions +CONSTANT +Data <- const_14476073153715000 +\* CONSTANT declarations +CONSTANT Busy = Busy +\* CONSTANT declarations +CONSTANT NoData = NoData +\* CONSTANT definitions +CONSTANT +InitData <- const_14476073153816000 +\* CONSTANT definition +CONSTANT +Done = Done +NotAnId = NotAnId +NotAnElement = NotAnElement +top = top +\* SPECIFICATION definition +SPECIFICATION +spec_144760731543311000 +\* INVARIANT definition +INVARIANT +inv_144760731544312000 +\* PROPERTY definition +PROPERTY +prop_144760731545313000 +\* Generated on Sun Nov 15 18:08:35 CET 2015 \ No newline at end of file diff --git a/tlatools/test-model/simulation/NQSpec/MC.tla b/tlatools/test-model/simulation/NQSpec/MC.tla new file mode 100644 index 0000000000000000000000000000000000000000..88d4a9a0075de01213b05a9a2c52d6dafb5709c4 --- /dev/null +++ b/tlatools/test-model/simulation/NQSpec/MC.tla @@ -0,0 +1,63 @@ +---- MODULE MC ---- +EXTENDS NQSpecImpliesQSpec, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +e1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +id1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +d1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +v1 +---- + +\* MV CONSTANT definitions EnQers +const_14476073153412000 == +{e1} +---- + +\* MV CONSTANT definitions Ids +const_14476073153513000 == +{id1} +---- + +\* MV CONSTANT definitions DeQers +const_14476073153614000 == +{d1} +---- + +\* MV CONSTANT definitions Data +const_14476073153715000 == +{v1} +---- + +\* CONSTANT definitions @modelParameterConstants:4InitData +const_14476073153816000 == +v1 +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144760731543311000 == +SpecI +---- +\* INVARIANT definition @modelCorrectnessInvariants:0 +inv_144760731544312000 == +TypeOKI +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144760731545313000 == +I!Spec +---- +============================================================================= +\* Modification History +\* Created Sun Nov 15 18:08:35 CET 2015 by markus diff --git a/tlatools/test-model/simulation/NQSpec/NQSpec.tla b/tlatools/test-model/simulation/NQSpec/NQSpec.tla new file mode 100644 index 0000000000000000000000000000000000000000..985d91f6a98ed8a9b0b7ad0ee750e1e230f058e1 --- /dev/null +++ b/tlatools/test-model/simulation/NQSpec/NQSpec.tla @@ -0,0 +1,87 @@ +------------------------------- MODULE NQSpec ------------------------------- +EXTENDS Integers, Sequences + +CONSTANTS EnQers, DeQers, Data, InitData, Ids, Busy, NoData + +ASSUME InitData \in Data + +Done == CHOOSE D : D \notin Data +\*Busy == CHOOSE D : D \notin Data +NotAnId == CHOOSE i : i \notin Ids +\*NoData == CHOOSE D : D \notin Data +Elements == [data : Data, id : Ids] +NotAnElement == CHOOSE E : E \notin Elements + +VARIABLES enq, deq, after, adding +vars == <<enq, deq, after, adding>> + +elts == DOMAIN after + +doneElts == elts \ {adding[e] : e \in EnQers} + +TypeOK == /\ enq \in [EnQers -> Data \cup {Done}] + /\ deq \in [DeQers -> Data \cup {Busy}] + /\ elts \in SUBSET Elements + /\ after \in [elts -> SUBSET doneElts] + /\ adding \in [EnQers -> Elements \cup {NotAnElement}] + +Init == /\ enq = [e \in EnQers |-> Done] + /\ deq = [d \in DeQers |-> InitData] + /\ after = << >> + /\ adding = [e \in EnQers |-> NotAnElement] +----------------------------------------------------------------------------- + +Assign(var, idx, val) == var' = [var EXCEPT ![idx] = val] + +IdsBeingAdded == {adding[el].id : el \in {e \in EnQers : adding[e] # NotAnElement}} + +UnusedIds == (Ids \ {el.id : el \in elts}) \ IdsBeingAdded + + +AddElt(el) == + after' = [x \in elts \cup {el} |-> + IF x = el THEN doneElts + ELSE after[x] ] + +RemoveElt(el) == + after' = [x \in elts \ {el} |-> after[x] \ {el}] + +MinimalElts(After) == {el \in DOMAIN After : After[el] = {}} +minimalElts == MinimalElts(after) + +BeginEnq(e) == /\ enq[e] = Done + /\ \E D \in Data, id \in UnusedIds : + LET el == [data |-> D, id |-> id] + IN /\ Assign(enq, e, D) + /\ AddElt(el) + /\ Assign(adding, e, el) + /\ UNCHANGED deq + +EndEnq(e) == /\ enq[e] # Done + /\ Assign(enq, e, Done) + /\ Assign(adding, e, NotAnElement) + /\ UNCHANGED <<deq, after>> + +\* enq, deq, elts, after, adding +BeginDeq(d) == /\ deq[d] # Busy + /\ Assign(deq, d, Busy) + /\ UNCHANGED <<enq, after, adding>> + +EndDeq(d) == /\ deq[d] = Busy + /\ \E el \in minimalElts : + /\ RemoveElt(el) + /\ Assign(deq, d, el.data) + /\ UNCHANGED <<enq, adding>> + +Next == \/ \E e \in EnQers : BeginEnq(e) \/ EndEnq(e) + \/ \E d \in DeQers : BeginDeq(d) \/ EndDeq(d) + +Liveness == /\ \A e \in EnQers : WF_vars(BeginEnq(e) \/ EndEnq(e)) + /\ \A d \in DeQers : WF_vars(BeginDeq(d) \/ EndDeq(d)) + +Spec == Init /\ [][Next]_vars /\ Liveness + +============================================================================= +\* Modification History +\* Last modified Sat Nov 14 16:00:53 PST 2015 by lamport +\* Created Thu Nov 05 15:07:25 PST 2015 by lamport diff --git a/tlatools/test-model/simulation/NQSpec/NQSpecImpliesQSpec.tla b/tlatools/test-model/simulation/NQSpec/NQSpecImpliesQSpec.tla new file mode 100644 index 0000000000000000000000000000000000000000..c7a3b125d26cafea1c77e442d42a633f05706b99 --- /dev/null +++ b/tlatools/test-model/simulation/NQSpec/NQSpecImpliesQSpec.tla @@ -0,0 +1,212 @@ +------------------------- MODULE NQSpecImpliesQSpec ------------------------- +EXTENDS NQSpec, FiniteSets, TLC + +\*NoData == NotData +NatSetMax(S) == IF S = {} THEN 0 ELSE CHOOSE n \in S : \A m \in S : n >=m +VARIABLE p, \* The prophecy variable = sequence of next values read, + \* of length Cardinality(elts) + Q, \* The queue of QSpec, except as Elements with their Ids, + \* and except that it is changed only on the actions + \* of NQSpec. Added as a history variable. + s, + \* A stuttering variable to add the actions + \* corresponding to the DoEnq and DoDeq actions of QSpec + enqInner, deqInner + \* History variables implementing the corresponding variables + \* of QSpec, except deqInner gets set to an Element el instead + \* of el.data + +\* The order of adding the variables is: +\* s, p, Q, {queue, enqInner, deqInner} +varsI == <<vars, p, Q, s, enqInner, deqInner>> + +top == CHOOSE t : t \notin {} +bot == [EndEnq |-> TRUE, EndDeq |-> TRUE] + +TypeOKI == /\ TypeOK + /\ Q \in Seq(Elements) + /\ p \in Seq(Elements) + /\ s \in {top} + \cup [type : {"EndDeq"}, name : DeQers \X Elements, val : {FALSE, TRUE}] + \cup [type : {"EndEnq"}, name : EnQers, val : {TRUE}] + \cup [type : {"BeginEnq"}, name : EnQers, val : Seq(Elements)] + /\ enqInner \in [EnQers -> {Done, Busy}] + /\ deqInner \in [DeQers -> {NoData} \cup Elements] + +InitI == /\ Init + /\ Q = << >> + /\ enqInner = [e \in EnQers |-> Done] + /\ \E i \in Ids : deqInner = [d \in DeQers |-> [data |-> InitData, + id |-> i]] + /\ p = << >> + /\ s = top + /\ TLCSet(1, {}) + +RemoveFrom(E, After) == [x \in (DOMAIN After) \ E |-> After[x] \ E] +RECURSIVE IsConsistent(_, _) +IsConsistent(seq, After) == + IF seq = << >> + THEN TRUE + ELSE LET hd == Head(seq) + Elts == DOMAIN After + IN /\ (hd \in DOMAIN After) /\ (After[hd] = {}) + /\ IsConsistent(Tail(seq), + RemoveFrom({hd}, After)) + +IsBlocking(el, After) == LET Elts == DOMAIN After + MinElts == MinimalElts(After) + IN (el \notin MinElts) /\ (doneElts # {}) + + +AllEnabled == s = top + +PostStutterEnabled(type, name) == (s # top) /\ (s.type = type) /\ (s.name = name) +PreStutterEnabled(type, name) == (s # top) => + /\ (s.type = type) /\ (s.name = name) + /\ s.val # bot[s.type] +AfterPreStutterEnabled(type, name) == /\ s # top + /\ (s.type = type) /\ (s.name = name) + /\ s.val = bot[s.type] + +SetStutter(type, name, val) == + /\ (s # top) => Assert(s.type = type /\ s.name = name, + "SetStutter not executed by owner of stuttering variable") + /\ s' = [type |-> type, name |-> name, val |-> val] + +Prefix(n, seq) == [i \in 1..n |-> seq[i]] +Suffix(n, seq) == [i \in 1..(Len(seq)-n) |-> seq[i+n]] +SeqElts(seq) == {seq[i] : i \in 1..Len(seq)} + +PInv == /\ Cardinality(elts) = Len(p) + +QInv == /\ Cardinality(SeqElts(Q)) = Len(Q) + /\ SeqElts(Q) \subseteq elts + /\ IsConsistent(Q, after) + /\ LET n == NatSetMax({i \in 1..Len(Q) : Prefix(i, Q) = Prefix(i, p)}) + IN (n < Len(Q)) => IsBlocking(p[n+1], + RemoveFrom(SeqElts(Prefix(n, Q)), after)) + +Writer(elt) == CHOOSE e \in EnQers : adding[e] = elt + +BeginEnqI(e) == /\ AllEnabled + /\ BeginEnq(e) + /\ \E el \in Elements : p' = Append(p, el) +\* /\ TLCSet(1, {p'} \cup TLCGet(1)) +\* /\ IF (Elements \X Elements \subseteq TLCGet(1)) +\* THEN /\ PrintT(TLCGet(1)) +\* /\ Assert (FALSE, "There") +\* ELSE TRUE +\*\* /\ PrintT(<<e, p, p', after'>>) + /\ LET RECURSIVE ToAdd(_, _) + ToAdd(i, After) == + IF (i > Len(p')) \/ p'[i] \notin MinimalElts(After) + THEN << >> + ELSE <<p'[i]>> \o ToAdd(i+1, RemoveFrom({p'[i]}, After)) + TA == ToAdd(Len(Q)+1, RemoveFrom(SeqElts(Q), after')) + IN IF TA # << >> + THEN SetStutter("BeginEnq", e, TA) + ELSE s' = s + /\ Assign(enqInner, e, Busy) + /\ UNCHANGED <<Q, deqInner>> + +BeginEnqIDo(e) == /\ PostStutterEnabled("BeginEnq", e) + /\ Q' = Append(Q, Head(s.val)) + /\ Assign(enqInner, Writer(Head(s.val)), Done) + /\ IF Tail(s.val) # << >> THEN SetStutter("BeginEnq", e, Tail(s.val)) + ELSE s' = top + /\ UNCHANGED <<vars, p, deqInner>> + +DoEnqI(e) == /\ PreStutterEnabled("EndEnq", e) + /\ enq[e] # Done + /\ IF enqInner[e] # Done \* adding[e] \notin SeqElts(Q) + THEN /\ Q' = Append(Q, adding[e]) + /\ Assign(enqInner, e, Done) + ELSE UNCHANGED <<Q, enqInner>> + /\ SetStutter("EndEnq", e, TRUE) + /\ UNCHANGED <<vars, p, deqInner>> + +EndEnqI(e) == /\ AfterPreStutterEnabled("EndEnq", e) + /\ EndEnq(e) + /\ s' = top + /\ UNCHANGED << p, Q, enqInner, deqInner>> + +BeginDeqI(d) == /\ AllEnabled + /\ BeginDeq(d) + /\ Assign(deqInner, d, NoData) + /\ UNCHANGED <<p, Q, s, enqInner>> + +\* OLD DEFS: +\*DoDeqI(d) == /\ PreStutterEnabled("EndDeq", d) +\* /\ deq[d] = Busy +\* /\ minimalElts # {} +\* /\ IF s = top /\ Q = << >> +\* THEN /\ \E el \in minimalElts : +\* /\ Q' = <<el>> +\* /\ Assign(enqInner, Writer(el), Done) +\* /\ SetStutter("EndDeq", d, FALSE) +\* /\ UNCHANGED deqInner +\* ELSE /\ Q' = Tail(Q) +\* /\ SetStutter("EndDeq", d, TRUE) +\* /\ Assign(deqInner, d, Head(Q)) +\* /\ UNCHANGED enqInner +\* /\ UNCHANGED <<vars, p>> +\* +\*EndDeqI(d) == /\ AfterPreStutterEnabled("EndDeq", d) +\* /\ EndDeq(d) +\* /\ p = << >> \/ Head(p) = deqInner[d] +\* /\ p' = Tail(p) +\* /\ s' = top +\* /\ UNCHANGED <<Q, enqInner, deqInner>> +(*************************************************************************** +XEndDeq(d, el) defined so that + + EndDeq(d) == \E el \in Elements : XEndDeq(d, el) + ***************************************************************************) +XEndDeq(d, el) == /\ deq[d] = Busy + /\ el \in minimalElts + /\ RemoveElt(el) + /\ Assign(deq, d, el.data) + /\ UNCHANGED <<enq, adding>> + +DoDeqI(d, el) == /\ PreStutterEnabled("EndDeq", <<d, el>>) + /\ deq[d] = Busy + /\ el \in minimalElts + /\ \/ p = << >> /\ p' = << >> /\ Assert(FALSE, "shouldn't happen") + \/ Head(p) = el /\ p' = Tail(p) + /\ IF s = top /\ Q = << >> + THEN \* I believe this case should never occur + /\ Q' = <<el>> + /\ Assign(enqInner, Writer(el), Done) + /\ SetStutter("EndDeq", <<d, el>>, FALSE) + /\ UNCHANGED deqInner + ELSE /\ el = Head(Q) + /\ Q' = Tail(Q) + /\ SetStutter("EndDeq", <<d, el>>, TRUE) + /\ Assign(deqInner, d, Head(Q)) + /\ UNCHANGED enqInner + /\ UNCHANGED <<vars>> + +EndDeqI(d, el) == /\ AfterPreStutterEnabled("EndDeq", <<d, el>>) + /\ XEndDeq(d, el) +\* /\ p = << >> \/ Head(p) = deqInner[d] +\* /\ p' = Tail(p) + /\ s' = top + /\ UNCHANGED <<Q, enqInner, deqInner, p>> + +NextI == \/ \E e \in EnQers : BeginEnqI(e) \/ BeginEnqIDo(e) + \/ DoEnqI(e) \/ EndEnqI(e) + \/ \E d \in DeQers : \/ BeginDeqI(d) + \/ \E el \in Elements : DoDeqI(d, el) \/ EndDeqI(d, el) + +SpecI == InitI /\ [][NextI]_varsI +----------------------------------------------------------------------------- +queue == [i \in 1..Len(Q) |-> Q[i].data] + +deqInnerBar == [d \in DeQers |-> IF deqInner[d] = NoData THEN NoData + ELSE deqInner[d].data] +I == INSTANCE QSpec WITH deqInner <- deqInnerBar + +============================================================================= +\* Modification History +\* Last modified Sat Nov 14 17:54:40 PST 2015 by lamport +\* Created Fri Nov 06 11:05:33 PST 2015 by lamport diff --git a/tlatools/test-model/simulation/NQSpec/QSpec.tla b/tlatools/test-model/simulation/NQSpec/QSpec.tla new file mode 100644 index 0000000000000000000000000000000000000000..19c85329e61bbe55f748ea4222f85398375fd9b1 --- /dev/null +++ b/tlatools/test-model/simulation/NQSpec/QSpec.tla @@ -0,0 +1,66 @@ +------------------------------- MODULE QSpec ------------------------------- +EXTENDS Integers, Sequences, TLC + +CONSTANTS EnQers, DeQers, Data, Done, Busy, NoData, InitData + +ASSUME /\ Done \notin Data + /\ Busy \notin Data + /\ NoData \notin Data + /\ InitData \in Data + +VARIABLES enq, deq, queue, enqInner, deqInner +vars == <<enq, deq, queue, enqInner, deqInner>> + +TypeOK == /\ enq \in [EnQers -> Data \cup {Done}] + /\ deq \in [DeQers -> Data \cup {Busy}] + /\ queue \in Seq(Data) + /\ enqInner \in [EnQers -> {Done, Busy}] + /\ deqInner \in [DeQers -> {NoData} \cup Data] + +Init == \* /\ PrintT("begin") + /\ enq = [e \in EnQers |-> Done] + /\ deq = [d \in DeQers |-> InitData] + /\ queue = << >> + /\ enqInner = [e \in EnQers |-> Done] \* /\ PrintT("foo") + /\ deqInner = [d \in DeQers |-> InitData] \* /\ PrintT("bar") + +BeginEnq(e) == /\ enq[e] = Done + /\ \E D \in Data : enq' = [enq EXCEPT ![e] = D] + /\ enqInner' = [enqInner EXCEPT ![e] = Busy] + /\ UNCHANGED <<deq, queue, deqInner>> + +DoEnq(e) == /\ enqInner[e] = Busy + /\ queue' = Append(queue, enq[e]) + /\ enqInner' = [enqInner EXCEPT ![e] = Done] + /\ UNCHANGED <<deq, enq, deqInner>> + +EndEnq(e) == /\ enq[e] # Done + /\ enqInner[e] = Done + /\ enq' = [enq EXCEPT ![e] = Done] + /\ UNCHANGED <<deq, queue, enqInner, deqInner>> + +BeginDeq(d) == /\ deq[d] # Busy + /\ deq' = [deq EXCEPT ![d] = Busy] + /\ deqInner' = [deqInner EXCEPT ![d] = NoData] + /\ UNCHANGED <<enq, queue, enqInner>> + +DoDeq(d) == /\ deq[d] = Busy + /\ deqInner[d] = NoData + /\ queue # << >> + /\ deqInner' = [deqInner EXCEPT ![d] = Head(queue)] + /\ queue' = Tail(queue) + /\ UNCHANGED <<enq, deq, enqInner>> + +EndDeq(d) == /\ deq[d] = Busy + /\ deqInner[d] # NoData + /\ deq' = [deq EXCEPT ![d] = deqInner[d]] + /\ UNCHANGED <<enq, queue, enqInner, deqInner>> + +Next == \/ \E e \in EnQers : BeginEnq(e) \/ DoEnq(e) \/ EndEnq(e) + \/ \E d \in DeQers : BeginDeq(d) \/ DoDeq(d) \/ EndDeq(d) + +Spec == Init /\ [][Next]_vars +============================================================================= +\* Modification History +\* Last modified Sat Nov 14 12:56:11 PST 2015 by lamport +\* Created Wed Nov 04 15:29:04 PST 2015 by lamport diff --git a/tlatools/test-model/symmetry/ChooseTableauSymmetry.tla b/tlatools/test-model/symmetry/ChooseTableauSymmetry.tla new file mode 100644 index 0000000000000000000000000000000000000000..14ae2fe6835294f1df023f4a019bef612cc9351f --- /dev/null +++ b/tlatools/test-model/symmetry/ChooseTableauSymmetry.tla @@ -0,0 +1,29 @@ +-------------------------- MODULE ChooseTableauSymmetry -------------------------- +CONSTANT Val +VARIABLE arr + +Init == arr = [v \in Val |-> "ready"] + +Ready(v) == /\ arr[v] = "ready" + /\ arr' = [arr EXCEPT ![v]= "busy"] + +Busy(v) == /\ arr[v] = "busy" + /\ arr' = [arr EXCEPT ![v]= "done"] + +Done(v) == /\ arr[v] = "done" + /\ arr' = [arr EXCEPT ![v]= "ready"] + +Next == \E v \in Val : Ready(v) \/ Busy(v) \/ Done(v) + +Spec == Init /\[][Next]_<<arr>> /\ WF_<<arr>>(Next) + +Some == CHOOSE e \in Val: TRUE +Other(v) == CHOOSE e \in Val \ {v}: TRUE + +Liveness == LET v == Some + IN (arr[v] = "busy") ~> (arr[v] = "ready") + +LivenessO == LET v == Other(Some) + IN (arr[v] = "busy") ~> (arr[v] = "ready") + +============================================================================= diff --git a/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.cfg b/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..823aee7b2c78dafe6e5cc779c7a2de726802bbba --- /dev/null +++ b/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +Val <- const_144481269486926000 +\* SYMMETRY definition +SYMMETRY symm_144481269487927000 +\* SPECIFICATION definition +SPECIFICATION +spec_144481269488928000 +\* PROPERTY definition +PROPERTY +prop_144481269489929000 diff --git a/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.tla b/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..36b43db6bfdb214fcdf67d5a716386dccea6eff4 --- /dev/null +++ b/tlatools/test-model/symmetry/ChooseTableauSymmetryMC.tla @@ -0,0 +1,27 @@ +---- MODULE ChooseTableauSymmetryMC ---- +EXTENDS ChooseTableauSymmetry, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions Val +const_144481269486926000 == +{a, b} +---- + +\* SYMMETRY definition +symm_144481269487927000 == +Permutations(const_144481269486926000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144481269488928000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144481269489929000 == +Liveness +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.cfg b/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.cfg new file mode 100644 index 0000000000000000000000000000000000000000..823aee7b2c78dafe6e5cc779c7a2de726802bbba --- /dev/null +++ b/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +Val <- const_144481269486926000 +\* SYMMETRY definition +SYMMETRY symm_144481269487927000 +\* SPECIFICATION definition +SPECIFICATION +spec_144481269488928000 +\* PROPERTY definition +PROPERTY +prop_144481269489929000 diff --git a/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.tla b/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.tla new file mode 100644 index 0000000000000000000000000000000000000000..5f807d01b9677db2b1351565608f6bbeb74dbc86 --- /dev/null +++ b/tlatools/test-model/symmetry/ChooseTableauSymmetryMCa.tla @@ -0,0 +1,27 @@ +---- MODULE ChooseTableauSymmetryMCa ---- +EXTENDS ChooseTableauSymmetry, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions Val +const_144481269486926000 == +{a, b} +---- + +\* SYMMETRY definition +symm_144481269487927000 == +Permutations(const_144481269486926000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144481269488928000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144481269489929000 == +LivenessO +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/ErrorTraceConstruction.tla b/tlatools/test-model/symmetry/ErrorTraceConstruction.tla new file mode 100644 index 0000000000000000000000000000000000000000..a07f60b96cb6b4cdf21efb21b1e6b0406fd30124 --- /dev/null +++ b/tlatools/test-model/symmetry/ErrorTraceConstruction.tla @@ -0,0 +1,49 @@ +------------------------- MODULE ErrorTraceConstruction ------------------------- +EXTENDS Integers, TLC + +S == {0,1} +Other(n) == CHOOSE x \in S \ {n} : TRUE + +VARIABLES x, y + +Init == x = 0 /\ y = 0 + +N0 == /\ y = 0 + /\ y' = y+1 + /\ x' = x +N1 == /\ y = 1 + /\ y' = y+1 + /\ x' = x +N2 == /\ y = 2 + /\ y' = y+1 + /\ x' = x +N3 == /\ y = 3 + /\ y' = y+1 + /\ x' = x +N4 == /\ y = 4 + /\ y' = y+1 + /\ x' = Other(x) \* flip x to violate Prop1 +N5 == /\ y = 5 + /\ y' = y+1 + /\ x' = Other(x) \* flip x back to reduce the number of overall states +N6 == /\ y = 6 + /\ y' = y+1 + /\ x' = x +N7 == /\ y = 7 + /\ y' = 3 \* loop to N3 + /\ x' = x + +Next == \/ N0 + \/ N1 + \/ N2 + \/ N3 + \/ N4 + \/ N5 + \/ N6 + \/ N7 + +Spec == Init /\ [][Next]_<<x, y>> /\ WF_<<x,y>>(Next) + +Prop == <>[][x'=x]_<<x, y>> + +============================================================================= diff --git a/tlatools/test-model/symmetry/ErrorTraceConstructionMC.cfg b/tlatools/test-model/symmetry/ErrorTraceConstructionMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..8a9daaec38db0a8f607f72ea76dad6e58afc573a --- /dev/null +++ b/tlatools/test-model/symmetry/ErrorTraceConstructionMC.cfg @@ -0,0 +1,6 @@ +\* SPECIFICATION definition +SPECIFICATION +spec_14411937652404000 +\* PROPERTY definition +PROPERTY +prop_14411937652505000 diff --git a/tlatools/test-model/symmetry/ErrorTraceConstructionMC.tla b/tlatools/test-model/symmetry/ErrorTraceConstructionMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..17c7bd11b547e285884077de5a4bf8ea6beeb271 --- /dev/null +++ b/tlatools/test-model/symmetry/ErrorTraceConstructionMC.tla @@ -0,0 +1,12 @@ +---- MODULE ErrorTraceConstructionMC ---- +EXTENDS ErrorTraceConstruction, TLC + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_14411937652404000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_14411937652505000 == +Prop +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/ErrorTraceConstructionPhases.png b/tlatools/test-model/symmetry/ErrorTraceConstructionPhases.png new file mode 100644 index 0000000000000000000000000000000000000000..63209449e9b3237e960f8d14e05dc557de4d4d5f Binary files /dev/null and b/tlatools/test-model/symmetry/ErrorTraceConstructionPhases.png differ diff --git a/tlatools/test-model/symmetry/LongMC.cfg b/tlatools/test-model/symmetry/LongMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..edacc0bc0474c6c8a2fe290ac875c9641fed7e4c --- /dev/null +++ b/tlatools/test-model/symmetry/LongMC.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_143263724205431000 +\* SYMMETRY definition +SYMMETRY symm_143263724206532000 +\* SPECIFICATION definition +SPECIFICATION +spec_143263724207533000 +\* PROPERTY definition +PROPERTY +prop_143263724208534000 diff --git a/tlatools/test-model/symmetry/LongMC.tla b/tlatools/test-model/symmetry/LongMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..d606b420e83bc9e9b8049cfa86388162b90d1620 --- /dev/null +++ b/tlatools/test-model/symmetry/LongMC.tla @@ -0,0 +1,27 @@ +---- MODULE LongMC ---- +EXTENDS SymmetryLivenessLong, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_143263724205431000 == +{a, b} +---- + +\* SYMMETRY definition +symm_143263724206532000 == +Permutations(const_143263724205431000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_143263724207533000 == +Spec2 +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_143263724208534000 == +Prop1 +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/LongMCa.cfg b/tlatools/test-model/symmetry/LongMCa.cfg new file mode 100644 index 0000000000000000000000000000000000000000..edacc0bc0474c6c8a2fe290ac875c9641fed7e4c --- /dev/null +++ b/tlatools/test-model/symmetry/LongMCa.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_143263724205431000 +\* SYMMETRY definition +SYMMETRY symm_143263724206532000 +\* SPECIFICATION definition +SPECIFICATION +spec_143263724207533000 +\* PROPERTY definition +PROPERTY +prop_143263724208534000 diff --git a/tlatools/test-model/symmetry/LongMCa.tla b/tlatools/test-model/symmetry/LongMCa.tla new file mode 100644 index 0000000000000000000000000000000000000000..dadfdfbb68daff61b295a9146aafc17171114beb --- /dev/null +++ b/tlatools/test-model/symmetry/LongMCa.tla @@ -0,0 +1,27 @@ +---- MODULE LongMCa ---- +EXTENDS SymmetryLivenessLong, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_143263724205431000 == +{a, b} +---- + +\* SYMMETRY definition +symm_143263724206532000 == +Permutations(const_143263724205431000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_143263724207533000 == +Spec1 +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_143263724208534000 == +Prop1 +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/MC.cfg b/tlatools/test-model/symmetry/MC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..ed287f880ec454f7d7e3019a83302a49af39c8fd --- /dev/null +++ b/tlatools/test-model/symmetry/MC.cfg @@ -0,0 +1,16 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_143263724205431000 +\* SYMMETRY definition +SYMMETRY symm_143263724206532000 +\* SPECIFICATION definition +SPECIFICATION +spec_143263724207533000 +\* PROPERTY definition +PROPERTY +prop_143263724208534000 +\* Generated on Tue May 26 12:47:22 CEST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/MC.tla b/tlatools/test-model/symmetry/MC.tla new file mode 100644 index 0000000000000000000000000000000000000000..b98e4a2db2d1f62ecc6558a4ee461562d3b4f7fc --- /dev/null +++ b/tlatools/test-model/symmetry/MC.tla @@ -0,0 +1,29 @@ +---- MODULE MC ---- +EXTENDS SymmetryLiveness3, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_143263724205431000 == +{a, b} +---- + +\* SYMMETRY definition +symm_143263724206532000 == +Permutations(const_143263724205431000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_143263724207533000 == +Spec2 +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_143263724208534000 == +Prop1 +---- +============================================================================= +\* Modification History +\* Created Tue May 26 12:47:22 CEST 2015 by markus diff --git a/tlatools/test-model/symmetry/MC_Graph.png b/tlatools/test-model/symmetry/MC_Graph.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d2ef0ac98d6f8c53f924427ff82ee246d4082d Binary files /dev/null and b/tlatools/test-model/symmetry/MC_Graph.png differ diff --git a/tlatools/test-model/symmetry/MCa.cfg b/tlatools/test-model/symmetry/MCa.cfg new file mode 100644 index 0000000000000000000000000000000000000000..ed287f880ec454f7d7e3019a83302a49af39c8fd --- /dev/null +++ b/tlatools/test-model/symmetry/MCa.cfg @@ -0,0 +1,16 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_143263724205431000 +\* SYMMETRY definition +SYMMETRY symm_143263724206532000 +\* SPECIFICATION definition +SPECIFICATION +spec_143263724207533000 +\* PROPERTY definition +PROPERTY +prop_143263724208534000 +\* Generated on Tue May 26 12:47:22 CEST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/MCa.tla b/tlatools/test-model/symmetry/MCa.tla new file mode 100644 index 0000000000000000000000000000000000000000..0520dcaadc07e35e01de4649b9a747e5efa81e83 --- /dev/null +++ b/tlatools/test-model/symmetry/MCa.tla @@ -0,0 +1,29 @@ +---- MODULE MCa ---- +EXTENDS SymmetryLiveness3, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_143263724205431000 == +{a, b} +---- + +\* SYMMETRY definition +symm_143263724206532000 == +Permutations(const_143263724205431000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_143263724207533000 == +Spec1 +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_143263724208534000 == +Prop1 +---- +============================================================================= +\* Modification History +\* Created Tue May 26 12:47:22 CEST 2015 by markus diff --git a/tlatools/test-model/symmetry/MCa_Graph.png b/tlatools/test-model/symmetry/MCa_Graph.png new file mode 100644 index 0000000000000000000000000000000000000000..072c9c3f70e2c33ebb3c53b353e22c176fe55fa3 Binary files /dev/null and b/tlatools/test-model/symmetry/MCa_Graph.png differ diff --git a/tlatools/test-model/symmetry/NQ/MC.cfg b/tlatools/test-model/symmetry/NQ/MC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..6c49e8e3722e6db56fb627cc0d6e35119a30e55a --- /dev/null +++ b/tlatools/test-model/symmetry/NQ/MC.cfg @@ -0,0 +1,49 @@ +\* MV CONSTANT declarations +CONSTANTS +e1 = e1 +e2 = e2 +\* MV CONSTANT declarations +CONSTANTS +i1 = i1 +i2 = i2 +\* MV CONSTANT declarations +CONSTANTS +d1 = d1 +\* MV CONSTANT declarations +CONSTANTS +v1 = v1 +v2 = v2 +\* MV CONSTANT definitions +CONSTANT +EnQers <- const_14467714432321108000 +\* MV CONSTANT definitions +CONSTANT +Ids <- const_14467714432421109000 +\* MV CONSTANT definitions +CONSTANT +DeQers <- const_14467714432521110000 +\* MV CONSTANT definitions +CONSTANT +Data <- const_14467714432621111000 +\* SYMMETRY definition +SYMMETRY symm_14467714432721112000 +\* CONSTANT definitions +CONSTANT +InitData <- const_14467714432821113000 +\* CONSTANT definition +CONSTANT +Done = Done +Busy = Busy +NotAnId = NotAnId +NotData = NotData +NotAnElement = NotAnElement +\* SPECIFICATION definition +SPECIFICATION +spec_14467714433421119000 +\* INVARIANT definition +INVARIANT +inv_14467714433521120000 +\* PROPERTY definition +PROPERTY +prop_14467714433621121000 +\* Generated on Thu Nov 05 16:57:23 PST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/NQ/MC.tla b/tlatools/test-model/symmetry/NQ/MC.tla new file mode 100644 index 0000000000000000000000000000000000000000..ef950829c17ba7720e55ee1e4a1da4d1d02ec906 --- /dev/null +++ b/tlatools/test-model/symmetry/NQ/MC.tla @@ -0,0 +1,68 @@ +---- MODULE MC ---- +EXTENDS NQSpec, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +e1, e2 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +i1, i2 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +d1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +v1, v2 +---- + +\* MV CONSTANT definitions EnQers +const_14467714432321108000 == +{e1, e2} +---- + +\* MV CONSTANT definitions Ids +const_14467714432421109000 == +{i1, i2} +---- + +\* MV CONSTANT definitions DeQers +const_14467714432521110000 == +{d1} +---- + +\* MV CONSTANT definitions Data +const_14467714432621111000 == +{v1, v2} +---- + +\* SYMMETRY definition +symm_14467714432721112000 == +Permutations(const_14467714432321108000) \union Permutations(const_14467714432521110000) +---- + +\* CONSTANT definitions @modelParameterConstants:4InitData +const_14467714432821113000 == +v1 +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_14467714433421119000 == +Spec +---- +\* INVARIANT definition @modelCorrectnessInvariants:0 +inv_14467714433521120000 == +TypeOK +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_14467714433621121000 == +(\E e \in elts : e.data = v2) ~> (\E d \in DeQers : deq[d] = v2) +---- +============================================================================= +\* Modification History +\* Created Thu Nov 05 16:57:23 PST 2015 by lamport diff --git a/tlatools/test-model/symmetry/NQ/MCa.cfg b/tlatools/test-model/symmetry/NQ/MCa.cfg new file mode 100644 index 0000000000000000000000000000000000000000..b27f9c68e77d42d8c8a70f4e8b5d7170c5ddf283 --- /dev/null +++ b/tlatools/test-model/symmetry/NQ/MCa.cfg @@ -0,0 +1,49 @@ +\* MV CONSTANT declarations +CONSTANTS +e1 = e1 +e2 = e2 +\* MV CONSTANT declarations +CONSTANTS +i1 = i1 +i2 = i2 +\* MV CONSTANT declarations +CONSTANTS +d1 = d1 +\* MV CONSTANT declarations +CONSTANTS +v1 = v1 +v2 = v2 +\* MV CONSTANT definitions +CONSTANT +EnQers <- const_14467710209401026000 +\* MV CONSTANT definitions +CONSTANT +Ids <- const_14467710209501027000 +\* MV CONSTANT definitions +CONSTANT +DeQers <- const_14467710209601028000 +\* MV CONSTANT definitions +CONSTANT +Data <- const_14467710209701029000 +\* SYMMETRY definition +SYMMETRY symm_14467710209801030000 +\* CONSTANT definitions +CONSTANT +InitData <- const_14467710209901031000 +\* CONSTANT definition +CONSTANT +Done = Done +Busy = Busy +NotAnId = NotAnId +NotData = NotData +NotAnElement = NotAnElement +\* SPECIFICATION definition +SPECIFICATION +spec_14467710210501037000 +\* INVARIANT definition +INVARIANT +inv_14467710210601038000 +\* PROPERTY definition +PROPERTY +prop_14467710210701039000 +\* Generated on Thu Nov 05 16:50:21 PST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/NQ/MCa.tla b/tlatools/test-model/symmetry/NQ/MCa.tla new file mode 100644 index 0000000000000000000000000000000000000000..ba6600dc97f8d85e69b2a4c0ff9c1fccb005939c --- /dev/null +++ b/tlatools/test-model/symmetry/NQ/MCa.tla @@ -0,0 +1,68 @@ +---- MODULE MCa ---- +EXTENDS NQSpec, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +e1, e2 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +i1, i2 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +d1 +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +v1, v2 +---- + +\* MV CONSTANT definitions EnQers +const_14467710209401026000 == +{e1, e2} +---- + +\* MV CONSTANT definitions Ids +const_14467710209501027000 == +{i1, i2} +---- + +\* MV CONSTANT definitions DeQers +const_14467710209601028000 == +{d1} +---- + +\* MV CONSTANT definitions Data +const_14467710209701029000 == +{v1, v2} +---- + +\* SYMMETRY definition +symm_14467710209801030000 == +Permutations(const_14467710209401026000) \union Permutations(const_14467710209601028000) +---- + +\* CONSTANT definitions @modelParameterConstants:4InitData +const_14467710209901031000 == +v1 +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_14467710210501037000 == +Spec +---- +\* INVARIANT definition @modelCorrectnessInvariants:0 +inv_14467710210601038000 == +TypeOK +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_14467710210701039000 == +(\E e \in EnQers : enq[e] = v2) ~> (\E d \in DeQers : deq[d] = v2) +---- +============================================================================= +\* Modification History +\* Created Thu Nov 05 16:50:21 PST 2015 by lamport diff --git a/tlatools/test-model/symmetry/NQ/NQSpec.tla b/tlatools/test-model/symmetry/NQ/NQSpec.tla new file mode 100644 index 0000000000000000000000000000000000000000..985e0634adbd5d0f7938685fbd0ba2d9ce3b829f --- /dev/null +++ b/tlatools/test-model/symmetry/NQ/NQSpec.tla @@ -0,0 +1,84 @@ +------------------------------- MODULE NQSpec ------------------------------- +EXTENDS Integers, Sequences + +CONSTANTS EnQers, DeQers, Data, InitData, Ids + +ASSUME InitData \in Data + +Done == CHOOSE D : D \notin Data +Busy == CHOOSE D : D \notin Data +NotAnId == CHOOSE i : i \notin Ids +NotData == CHOOSE D : D \notin Data +Elements == [data : Data, id : Ids] +NotAnElement == CHOOSE E : E \notin Elements + +VARIABLES enq, deq, elts, after, adding +vars == <<enq, deq, elts, after, adding>> + + + +TypeOK == /\ enq \in [EnQers -> Data \cup {Done}] + /\ deq \in [DeQers -> Data \cup {Busy}] + /\ elts \in SUBSET Elements + /\ after \in [elts -> SUBSET elts] + /\ adding \in [EnQers -> Elements \cup {NotAnElement}] + +Init == /\ enq = [e \in EnQers |-> Done] + /\ deq = [d \in DeQers |-> InitData] + /\ elts = {} + /\ after = << >> + /\ adding = [e \in EnQers |-> NotAnElement] +----------------------------------------------------------------------------- + +Assign(var, idx, val) == var' = [var EXCEPT ![idx] = val] + +UnusedIds == Ids \ {el.id : el \in elts} + +AddElt(el) == + /\ elts' = elts \cup {el} + /\ after' = [x \in elts' |-> + IF x = el THEN elts \ {adding[e] : e \in EnQers} + ELSE after[x] ] + +RemoveElt(el) == + /\ elts' = elts \ {el} + /\ after' = [x \in elts' |-> after[x] \ {el}] + +minimalElts == {el \in elts : after[el] = {}} + +BeginEnq(e) == /\ enq[e] = Done + /\ \E D \in Data, id \in UnusedIds : + LET el == [data |-> D, id |-> id] + IN /\ Assign(enq, e, D) + /\ AddElt(el) + /\ Assign(adding, e, el) + /\ UNCHANGED deq + +EndEnq(e) == /\ enq[e] # Done + /\ Assign(enq, e, Done) + /\ Assign(adding, e, NotAnElement) + /\ UNCHANGED <<deq, elts, after>> + +\* enq, deq, elts, after, adding +BeginDeq(d) == /\ deq[d] # Busy + /\ Assign(deq, d, Busy) + /\ UNCHANGED <<enq, elts, after, adding>> + +EndDeq(d) == /\ deq[d] = Busy + /\ \E el \in minimalElts : + /\ RemoveElt(el) + /\ Assign(deq, d, el.data) + /\ UNCHANGED <<enq, adding>> + +Next == \/ \E e \in EnQers : BeginEnq(e) \/ EndEnq(e) + \/ \E d \in DeQers : BeginDeq(d) \/ EndDeq(d) + +Liveness == /\ \A e \in EnQers : WF_vars(BeginEnq(e) \/ EndEnq(e)) + /\ \A d \in DeQers : WF_vars(BeginDeq(d) \/ EndDeq(d)) + +Spec == Init /\ [][Next]_vars /\ Liveness + +============================================================================= +\* Modification History +\* Last modified Thu Nov 05 16:16:15 PST 2015 by lamport +\* Created Thu Nov 05 15:07:25 PST 2015 by lamport diff --git a/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.cfg b/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..95c4dce4083eb2edfa412ba1459dfb5803ab183a --- /dev/null +++ b/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.cfg @@ -0,0 +1,31 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT declarations +CONSTANTS +c = c +d = d +\* MV CONSTANT declarations +CONSTANTS +e = e +f = f +\* MV CONSTANT definitions +CONSTANT +Val <- const_144172196716899000 +\* MV CONSTANT definitions +CONSTANT +Proc <- const_1441721967178100000 +\* MV CONSTANT definitions +CONSTANT +Adr <- const_1441721967189101000 +\* CONSTANT definition +CONSTANT +NoVal = NoVal +\* SPECIFICATION definition +SPECIFICATION +spec_1441721967209103000 +\* PROPERTY definition +PROPERTY +prop_1441721967219104000 +\* Generated on Tue Sep 08 16:19:27 CEST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.tla b/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..8427d29d68cd968144d758e9daa70911fa658344 --- /dev/null +++ b/tlatools/test-model/symmetry/NoSymmetryLivenessTableauMC.tla @@ -0,0 +1,42 @@ +---- MODULE NoSymmetryLivenessTableauMC ---- +EXTENDS SymmetryLivenessTableau, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +c, d +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +e, f +---- + +\* MV CONSTANT definitions Val +const_144172196716899000 == +{a, b} +---- + +\* MV CONSTANT definitions Proc +const_1441721967178100000 == +{c, d} +---- + +\* MV CONSTANT definitions Adr +const_1441721967189101000 == +{e, f} +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_1441721967209103000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_1441721967219104000 == +Liveness +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/OneBitMutex/OneBitMutex.tla b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutex.tla new file mode 100644 index 0000000000000000000000000000000000000000..242889c543ef70f26f7dbdffe82132c8983176dc --- /dev/null +++ b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutex.tla @@ -0,0 +1,141 @@ +---------------------------- MODULE OneBitMutex ---------------------------- +EXTENDS Integers +CONSTANT Procs + +(*************************************************************************** +--algorithm OneBitMutex + { + variable x = [i \in Procs |-> FALSE]; + fair process (p \in Procs) + variables unchecked = {}; + other \in Procs ; + { + ncs:- while (TRUE) + { + e1: x[self] := TRUE ; + unchecked := Procs \ {self}; + e2: while (unchecked # {}) + { + with (i \in unchecked) { other := i } ; + unchecked := unchecked \ {other}; + e3: if (x[other]) + { + if (TRUE) \* Changed from "self > other" to "TRUE" + { + e4: x[self] := FALSE; + e5: await ~x[other]; + goto e1; + } + else + { + e6: await ~x[other]; + } + }; + } ; + cs: skip; + f: x[self] := FALSE + } + } +} + ***************************************************************************) + +\* BEGIN TRANSLATION +VARIABLES x, pc, unchecked, other + +vars == << x, pc, unchecked, other >> + +ProcSet == (Procs) + +Init == (* Global variables *) + /\ x = [i \in Procs |-> FALSE] + (* Process p *) + /\ unchecked = [self \in Procs |-> {}] + /\ other \in [Procs -> Procs] + /\ pc = [self \in ProcSet |-> "ncs"] + +ncs(self) == /\ pc[self] = "ncs" + /\ pc' = [pc EXCEPT ![self] = "e1"] + /\ UNCHANGED << x, unchecked, other >> + +e1(self) == /\ pc[self] = "e1" + /\ x' = [x EXCEPT ![self] = TRUE] + /\ unchecked' = [unchecked EXCEPT ![self] = Procs \ {self}] + /\ pc' = [pc EXCEPT ![self] = "e2"] + /\ other' = other + +e2(self) == /\ pc[self] = "e2" + /\ IF unchecked[self] # {} + THEN /\ \E i \in unchecked[self]: + other' = [other EXCEPT ![self] = i] + /\ unchecked' = [unchecked EXCEPT ![self] = unchecked[self] \ {other'[self]}] + /\ pc' = [pc EXCEPT ![self] = "e3"] + ELSE /\ pc' = [pc EXCEPT ![self] = "cs"] + /\ UNCHANGED << unchecked, other >> + /\ x' = x + +e3(self) == /\ pc[self] = "e3" + /\ IF x[other[self]] + THEN /\ IF TRUE + THEN /\ pc' = [pc EXCEPT ![self] = "e4"] + ELSE /\ pc' = [pc EXCEPT ![self] = "e6"] + ELSE /\ pc' = [pc EXCEPT ![self] = "e2"] + /\ UNCHANGED << x, unchecked, other >> + +e4(self) == /\ pc[self] = "e4" + /\ x' = [x EXCEPT ![self] = FALSE] + /\ pc' = [pc EXCEPT ![self] = "e5"] + /\ UNCHANGED << unchecked, other >> + +e5(self) == /\ pc[self] = "e5" + /\ ~x[other[self]] + /\ pc' = [pc EXCEPT ![self] = "e1"] + /\ UNCHANGED << x, unchecked, other >> + +e6(self) == /\ pc[self] = "e6" + /\ ~x[other[self]] + /\ pc' = [pc EXCEPT ![self] = "e2"] + /\ UNCHANGED << x, unchecked, other >> + +cs(self) == /\ pc[self] = "cs" + /\ TRUE + /\ pc' = [pc EXCEPT ![self] = "f"] + /\ UNCHANGED << x, unchecked, other >> + +f(self) == /\ pc[self] = "f" + /\ x' = [x EXCEPT ![self] = FALSE] + /\ pc' = [pc EXCEPT ![self] = "ncs"] + /\ UNCHANGED << unchecked, other >> + +p(self) == ncs(self) \/ e1(self) \/ e2(self) \/ e3(self) \/ e4(self) + \/ e5(self) \/ e6(self) \/ cs(self) \/ f(self) + +Next == (\E self \in Procs: p(self)) + +Spec == /\ Init /\ [][Next]_vars + /\ \A self \in Procs : WF_vars((pc[self] # "ncs") /\ p(self)) + +\* END TRANSLATION + +----------------------------------------------------------------------------- +TypeOK == /\ pc \in [{0,1} -> {"r", "e1", "e2", "cs"}] + /\ x \in [{0,1} -> BOOLEAN] + +Past(i,j) == \/ ((pc[i] = "e2") /\ (j \notin unchecked[i])) + \/ /\ pc[i] \in {"e3", "e6"} + /\ j \notin unchecked[i] \cup {other[i]} + \/ pc[i] = "cs" + +Inv == /\ TypeOK + /\ \A i \in Procs : + /\ (pc[i] \in {"e2", "e3", "e6", "cs"}) => x[i] + /\ \A j \in Procs \ {i} : + Past(i, j) => ~Past(j, i) /\ x[i] + + +(*************************************************************************** + N-process One-Bit mutual exclusion is _not_ starvation free! + ***************************************************************************) +StarvationFreedom == \A e \in Procs : pc[e] = "e2" ~> pc[e] = "cs" + + +============================================================================= diff --git a/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.cfg b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..5b31cea12d04a107fda0619f30b270e06e33179b --- /dev/null +++ b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +A = A +B = B +\* MV CONSTANT definitions +CONSTANT +Procs <- const_144491423291817000 +\* SYMMETRY definition +SYMMETRY symm_144481269487927000 +\* SPECIFICATION definition +SPECIFICATION +spec_144491423292818000 +\* PROPERTY definition +PROPERTY +prop_144491423293819000 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.tla b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..e81b50c63ee43ca0d7bd806cfc0f49c0b16ca962 --- /dev/null +++ b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexMC.tla @@ -0,0 +1,27 @@ +---- MODULE OneBitMutexMC ---- +EXTENDS OneBitMutex, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +A, B +---- + +\* MV CONSTANT definitions Procs +const_144491423291817000 == +{A, B} +---- + +\* SYMMETRY definition +symm_144481269487927000 == +Permutations(const_144491423291817000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144491423292818000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144491423293819000 == +StarvationFreedom +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.cfg b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..aaaa39d3089a0410098ef62bec4ba63d040d030e --- /dev/null +++ b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.cfg @@ -0,0 +1,13 @@ +\* MV CONSTANT declarations +CONSTANTS +A = A +B = B +\* MV CONSTANT definitions +CONSTANT +Procs <- const_144491423291817000 +\* SPECIFICATION definition +SPECIFICATION +spec_144491423292818000 +\* PROPERTY definition +PROPERTY +prop_144491423293819000 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.tla b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..e5d1f68dd4e98e537e246aaca1089504e6b5750a --- /dev/null +++ b/tlatools/test-model/symmetry/OneBitMutex/OneBitMutexNoSymmetryMC.tla @@ -0,0 +1,22 @@ +---- MODULE OneBitMutexNoSymmetryMC ---- +EXTENDS OneBitMutex, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +A, B +---- + +\* MV CONSTANT definitions Procs +const_144491423291817000 == +{A, B} +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144491423292818000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144491423293819000 == +StarvationFreedom +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/SymmetryLiveness3.tla b/tlatools/test-model/symmetry/SymmetryLiveness3.tla new file mode 100644 index 0000000000000000000000000000000000000000..2e0cb3f2576ce119eccb5f0fab427ab1352e35b1 --- /dev/null +++ b/tlatools/test-model/symmetry/SymmetryLiveness3.tla @@ -0,0 +1,34 @@ +------------------------- MODULE SymmetryLiveness3 ------------------------- +EXTENDS Integers +CONSTANT S +Other(n) == CHOOSE x \in S \ {n} : TRUE + +VARIABLES x, y + +Init == x \in S /\ y = 0 + +\* MCa (SymmetryModelCheckerTest3a) +N1 == \/ /\ y = 0 + /\ y' = 1 + /\ x' = x + \/ /\ y = 1 + /\ y' = 0 + /\ x' = Other(x) +Spec1 == Init /\ [][N1]_<<x, y>> /\ WF_<<x,y>>(N1) \* Violates Prop1, but TLC produces bogus trace + +\* MC (SymmetryModelCheckerTest3) +\* Contrary to N1, x' either stays unchanged OR assumes Other(x) +N2 == \/ /\ y = 0 + /\ y' = 1 + /\ x' = x + \/ /\ y = 1 + /\ y' = 0 + /\ x' = x + \/ /\ y = 1 + /\ y' = 0 + /\ x' = Other(x) +Spec2 == Init /\ [][N2]_<<x, y>> /\ WF_<<x,y>>(N2) \* Fails to find error in Prop1 + + +Prop1 == <>[][x'=x]_<<x, y>> +============================================================================= diff --git a/tlatools/test-model/symmetry/SymmetryLivenessLong.tla b/tlatools/test-model/symmetry/SymmetryLivenessLong.tla new file mode 100644 index 0000000000000000000000000000000000000000..3606cf58c05ab61b9938dbbe9fc5818edb82dcc8 --- /dev/null +++ b/tlatools/test-model/symmetry/SymmetryLivenessLong.tla @@ -0,0 +1,54 @@ +------------------------- MODULE SymmetryLivenessLong ------------------------- +EXTENDS Integers +CONSTANT S +Other(n) == CHOOSE x \in S \ {n} : TRUE + +VARIABLES x, y + +Init == x \in S /\ y = 0 + +\* Contrary to SymmetryLiveness3, this produces a long trace which results in LiveWorker#bfsPostFix finding a longer path. + +\* MCa (SymmetryModelCheckerTestLonga) +N1 == \/ /\ y = 0 + /\ y' = 1 + /\ x' = x + \/ /\ y = 1 + /\ y' = 2 + /\ x' = x + \/ /\ y = 2 + /\ y' = 3 + /\ x' = x + \/ /\ y = 3 + /\ y' = 4 + /\ x' = x + \/ /\ y = 4 + /\ y' = 0 + /\ x' = Other(x) +Spec1 == Init /\ [][N1]_<<x, y>> /\ WF_<<x,y>>(N1) \* Violates Prop1, but TLC produces bogus trace + +\* MC (SymmetryModelCheckerTestLong) +\* Contrary to N1, x' either stays unchanged OR assumes Other(x) +N2 == \/ /\ y = 0 + /\ y' = 1 + /\ x' = x + \/ /\ y = 1 + /\ y' = 2 + /\ x' = x + \/ /\ y = 2 + /\ y' = 3 + /\ x' = x + \/ /\ y = 3 + /\ y' = 4 + /\ x' = x + \/ /\ y = 4 + /\ y' = 0 + /\ x' = x + \/ /\ y = 4 + /\ y' = 0 + /\ x' = Other(x) +Spec2 == Init /\ [][N2]_<<x, y>> /\ WF_<<x,y>>(N2) \* Fails to find error in Prop1 + + +Prop1 == <>[][x'=x]_<<x, y>> +============================================================================= diff --git a/tlatools/test-model/symmetry/SymmetryLivenessTableau.tla b/tlatools/test-model/symmetry/SymmetryLivenessTableau.tla new file mode 100644 index 0000000000000000000000000000000000000000..a1cc65b0d45bad2696ca299e10fe4b2a6f575bc9 --- /dev/null +++ b/tlatools/test-model/symmetry/SymmetryLivenessTableau.tla @@ -0,0 +1,66 @@ +---------------------- MODULE SymmetryLivenessTableau ---------------------- +VARIABLES mem, ctl, buf +VARIABLE memInt +CONSTANTS Proc, + Adr, + Val + +NoVal == CHOOSE x : x \notin Val +InitMemInt == {<<p, NoVal>> : p \in Proc} +Send(p, d, oldMemInt, newMemInt) == newMemInt = <<p, d>> +Reply(p, d, oldMemInt, newMemInt) == newMemInt = <<p, d>> +(***************************************************************************) +(* We comment out the assumption because TLC cannot handle unbounded *) +(* quantifiers. *) +(***************************************************************************) +\* ASSUME \A p, d, miOld, miNew : +\* /\ Send(p,d,miOld,miNew) \in BOOLEAN +\* /\ Reply(p,d,miOld,miNew) \in BOOLEAN + +----------------------------------------------------------------------------- +MReq == [op : {"Rd"}, adr: Adr] + \cup [op : {"Wr"}, adr: Adr, val : Val] + +-------------------------------------------------------------- +IInit == /\ mem \in [Adr->Val] + /\ ctl = [p \in Proc |-> "rdy"] + /\ buf = [p \in Proc |-> NoVal] + /\ memInt \in InitMemInt + +TypeInvariant == + /\ mem \in [Adr->Val] + /\ ctl \in [Proc -> {"rdy", "busy","done"}] + /\ buf \in [Proc -> MReq \cup Val \cup {NoVal}] + +Req(p) == /\ ctl[p] = "rdy" + /\ \E req \in MReq : + /\ Send(p, req, memInt, memInt') + /\ buf' = [buf EXCEPT ![p] = req] + /\ ctl' = [ctl EXCEPT ![p] = "busy"] + /\ UNCHANGED mem + +Do(p) == + /\ ctl[p] = "busy" + /\ mem' = IF buf[p].op = "Wr" + THEN [mem EXCEPT ![buf[p].adr] = buf[p].val] + ELSE mem + /\ buf' = [buf EXCEPT ![p] = IF buf[p].op = "Wr" + THEN NoVal + ELSE mem[buf[p].adr]] + /\ ctl' = [ctl EXCEPT ![p] = "done"] + /\ UNCHANGED memInt + +Rsp(p) == /\ ctl[p] = "done" + /\ Reply(p, buf[p], memInt, memInt') + /\ ctl' = [ctl EXCEPT ![p]= "rdy"] + /\ UNCHANGED <<mem, buf>> + +INext == \E p \in Proc: Req(p) \/ Do(p) \/ Rsp(p) + +Spec == IInit /\ [][INext]_<<memInt, mem, ctl, buf>> + /\ \A p \in Proc : WF_<<memInt, mem, ctl, buf>>(Do(p) \/ Rsp(p)) + +Liveness == \A p \in Proc : (ctl[p] = "busy") ~> (ctl[p] = "rdy") +-------------------------------------------------------------- +THEOREM Spec => []TypeInvariant +============================================================== \ No newline at end of file diff --git a/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.cfg b/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..a6a36db92f92499a81c85156f9f49481cf55c8c1 --- /dev/null +++ b/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.cfg @@ -0,0 +1,33 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT declarations +CONSTANTS +c = c +d = d +\* MV CONSTANT declarations +CONSTANTS +e = e +f = f +\* MV CONSTANT definitions +CONSTANT +Val <- const_144172195407986000 +\* MV CONSTANT definitions +CONSTANT +Proc <- const_144172195408987000 +\* MV CONSTANT definitions +CONSTANT +Adr <- const_144172195409988000 +\* SYMMETRY definition +SYMMETRY symm_144172195410989000 +\* CONSTANT definition +CONSTANT +NoVal = NoVal +\* SPECIFICATION definition +SPECIFICATION +spec_144172195413091000 +\* PROPERTY definition +PROPERTY +prop_144172195414092000 +\* Generated on Tue Sep 08 16:19:14 CEST 2015 \ No newline at end of file diff --git a/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.tla b/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..d3fb95c9829c6a4464620a2fabe3b4624756aef7 --- /dev/null +++ b/tlatools/test-model/symmetry/SymmetryLivenessTableauMC.tla @@ -0,0 +1,47 @@ +---- MODULE SymmetryLivenessTableauMC ---- +EXTENDS SymmetryLivenessTableau, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +c, d +---- + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +e, f +---- + +\* MV CONSTANT definitions Val +const_144172195407986000 == +{a, b} +---- + +\* MV CONSTANT definitions Proc +const_144172195408987000 == +{c, d} +---- + +\* MV CONSTANT definitions Adr +const_144172195409988000 == +{e, f} +---- + +\* SYMMETRY definition +symm_144172195410989000 == +Permutations(const_144172195407986000) \union Permutations(const_144172195408987000) \union Permutations(const_144172195409988000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144172195413091000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144172195414092000 == +Liveness +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/TableauSymmetry.png b/tlatools/test-model/symmetry/TableauSymmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..f8728c24966313e1c35a748d4087d2ddc5903fdc Binary files /dev/null and b/tlatools/test-model/symmetry/TableauSymmetry.png differ diff --git a/tlatools/test-model/symmetry/TableauSymmetry.tla b/tlatools/test-model/symmetry/TableauSymmetry.tla new file mode 100644 index 0000000000000000000000000000000000000000..829677122cabe09df53e3e21e4b139adfc303902 --- /dev/null +++ b/tlatools/test-model/symmetry/TableauSymmetry.tla @@ -0,0 +1,22 @@ +-------------------------- MODULE TableauSymmetry -------------------------- +CONSTANT Val +VARIABLE arr + +Init == arr = [v \in Val |-> "ready"] + +Ready(v) == /\ arr[v] = "ready" + /\ arr' = [arr EXCEPT ![v]= "busy"] + +Busy(v) == /\ arr[v] = "busy" + /\ arr' = [arr EXCEPT ![v]= "done"] + +Done(v) == /\ arr[v] = "done" + /\ arr' = [arr EXCEPT ![v]= "ready"] + +Next == \E v \in Val : Ready(v) \/ Busy(v) \/ Done(v) + +Spec == Init /\[][Next]_<<arr>> /\ WF_<<arr>>(Next) + +Liveness == \A v \in Val : (arr[v] = "busy") ~> (arr[v] = "ready") + +============================================================================= diff --git a/tlatools/test-model/symmetry/TableauSymmetryDisabled.png b/tlatools/test-model/symmetry/TableauSymmetryDisabled.png new file mode 100644 index 0000000000000000000000000000000000000000..3e0726a6ea541ac93c7f8d7711858c8404d26951 Binary files /dev/null and b/tlatools/test-model/symmetry/TableauSymmetryDisabled.png differ diff --git a/tlatools/test-model/symmetry/TableauSymmetryMC.cfg b/tlatools/test-model/symmetry/TableauSymmetryMC.cfg new file mode 100644 index 0000000000000000000000000000000000000000..823aee7b2c78dafe6e5cc779c7a2de726802bbba --- /dev/null +++ b/tlatools/test-model/symmetry/TableauSymmetryMC.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +Val <- const_144481269486926000 +\* SYMMETRY definition +SYMMETRY symm_144481269487927000 +\* SPECIFICATION definition +SPECIFICATION +spec_144481269488928000 +\* PROPERTY definition +PROPERTY +prop_144481269489929000 diff --git a/tlatools/test-model/symmetry/TableauSymmetryMC.tla b/tlatools/test-model/symmetry/TableauSymmetryMC.tla new file mode 100644 index 0000000000000000000000000000000000000000..e44b77ec5c7b59cffa99815735c739c58efd1180 --- /dev/null +++ b/tlatools/test-model/symmetry/TableauSymmetryMC.tla @@ -0,0 +1,27 @@ +---- MODULE TableauSymmetryMC ---- +EXTENDS TableauSymmetry, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions Val +const_144481269486926000 == +{a, b} +---- + +\* SYMMETRY definition +symm_144481269487927000 == +Permutations(const_144481269486926000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_144481269488928000 == +Spec +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_144481269489929000 == +Liveness +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/Unsymmetric.tla b/tlatools/test-model/symmetry/Unsymmetric.tla new file mode 100644 index 0000000000000000000000000000000000000000..26b1c5f8b3a63f1234160ef8f1119ab332d37376 --- /dev/null +++ b/tlatools/test-model/symmetry/Unsymmetric.tla @@ -0,0 +1,45 @@ +--------------------------- MODULE Unsymmetric --------------------------- +CONSTANT S +VARIABLE x + +Init == x \in S + +Back == \/ /\ x \in {1,2} + /\ x' \in S + +\* A: +\* +\* A honors S being symmetric. TLC finds and +\* prints the correct counter-example. + +tA == CHOOSE a \in S : TRUE + +NextA == \/ /\ x \in S + /\ IF x = tA THEN x' = 1 + ELSE x' = 2 +SpecA == Init /\ [][NextA \/ Back]_x /\ WF_x(NextA \/ Back) + +\* B: +\* +\* 'tb' breaks symmetry because it explicitly +\* requires an element in S that is different to +\* another S element. This is exactly opposite +\* to what symmetry represents. Hence, the spec +\* is _not_ symmetric and thus TLC fails to find a +\* violation/counter-example (TLC finds the +\* counter-example when S is not declared symmetric. + +tB == LET t1 == CHOOSE a \in S : TRUE + IN CHOOSE a \in S \ {t1} : TRUE + + +NextB == \/ /\ x \in S + /\ IF x = tB THEN x' = 1 + ELSE x' = 2 +SpecB == Init /\ [][NextB \/ Back]_x /\ WF_x(NextB \/ Back) + +\* Liveness + +Prop == []<>(x = 2) +============================================================================ + diff --git a/tlatools/test-model/symmetry/UnsymmetricAWithSymmetry.png b/tlatools/test-model/symmetry/UnsymmetricAWithSymmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..d6577f415881f559328f751ce0f4da4ebe491fb3 Binary files /dev/null and b/tlatools/test-model/symmetry/UnsymmetricAWithSymmetry.png differ diff --git a/tlatools/test-model/symmetry/UnsymmetricAWithoutSymmetry.png b/tlatools/test-model/symmetry/UnsymmetricAWithoutSymmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..f63daa7fbda24acd2fcfaf3594606669a08aea5d Binary files /dev/null and b/tlatools/test-model/symmetry/UnsymmetricAWithoutSymmetry.png differ diff --git a/tlatools/test-model/symmetry/UnsymmetricBWithSymmetry.png b/tlatools/test-model/symmetry/UnsymmetricBWithSymmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..8cba463e0c3a6065f1677c29f8e43450f5b39eaa Binary files /dev/null and b/tlatools/test-model/symmetry/UnsymmetricBWithSymmetry.png differ diff --git a/tlatools/test-model/symmetry/UnsymmetricBWithoutSymmetry.png b/tlatools/test-model/symmetry/UnsymmetricBWithoutSymmetry.png new file mode 100644 index 0000000000000000000000000000000000000000..360660991fb15ce163ca79d39b0fbddb192ffaaf Binary files /dev/null and b/tlatools/test-model/symmetry/UnsymmetricBWithoutSymmetry.png differ diff --git a/tlatools/test-model/symmetry/UnsymmetricMCA.cfg b/tlatools/test-model/symmetry/UnsymmetricMCA.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bec8fd9c0fe0a39d9571a24cc1d5070bb5373876 --- /dev/null +++ b/tlatools/test-model/symmetry/UnsymmetricMCA.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_1444366015085101000 +\* SYMMETRY definition +SYMMETRY symm_1444366015096102000 +\* SPECIFICATION definition +SPECIFICATION +spec_1444366015106103000 +\* PROPERTY definition +PROPERTY +prop_1444366015116104000 diff --git a/tlatools/test-model/symmetry/UnsymmetricMCA.tla b/tlatools/test-model/symmetry/UnsymmetricMCA.tla new file mode 100644 index 0000000000000000000000000000000000000000..fd772c596d3d202a98694d08942efc73968ed18c --- /dev/null +++ b/tlatools/test-model/symmetry/UnsymmetricMCA.tla @@ -0,0 +1,27 @@ +---- MODULE UnsymmetricMCA ---- +EXTENDS Unsymmetric, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_1444366015085101000 == +{a, b} +---- + +\* SYMMETRY definition +symm_1444366015096102000 == +Permutations(const_1444366015085101000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_1444366015106103000 == +SpecA +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_1444366015116104000 == +Prop +---- +============================================================================= diff --git a/tlatools/test-model/symmetry/UnsymmetricMCB.cfg b/tlatools/test-model/symmetry/UnsymmetricMCB.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bec8fd9c0fe0a39d9571a24cc1d5070bb5373876 --- /dev/null +++ b/tlatools/test-model/symmetry/UnsymmetricMCB.cfg @@ -0,0 +1,15 @@ +\* MV CONSTANT declarations +CONSTANTS +a = a +b = b +\* MV CONSTANT definitions +CONSTANT +S <- const_1444366015085101000 +\* SYMMETRY definition +SYMMETRY symm_1444366015096102000 +\* SPECIFICATION definition +SPECIFICATION +spec_1444366015106103000 +\* PROPERTY definition +PROPERTY +prop_1444366015116104000 diff --git a/tlatools/test-model/symmetry/UnsymmetricMCB.tla b/tlatools/test-model/symmetry/UnsymmetricMCB.tla new file mode 100644 index 0000000000000000000000000000000000000000..b6d8a0fec9d6d3926b50fa55db4ef68673c841a7 --- /dev/null +++ b/tlatools/test-model/symmetry/UnsymmetricMCB.tla @@ -0,0 +1,27 @@ +---- MODULE UnsymmetricMCB ---- +EXTENDS Unsymmetric, TLC + +\* MV CONSTANT declarations@modelParameterConstants +CONSTANTS +a, b +---- + +\* MV CONSTANT definitions S +const_1444366015085101000 == +{a, b} +---- + +\* SYMMETRY definition +symm_1444366015096102000 == +Permutations(const_1444366015085101000) +---- + +\* SPECIFICATION definition @modelBehaviorSpec:0 +spec_1444366015106103000 == +SpecB +---- +\* PROPERTY definition @modelCorrectnessProperties:0 +prop_1444366015116104000 == +Prop +---- +============================================================================= diff --git a/tlatools/test/tla2sany/drivers/Bug156TEStackOverflowTest.java b/tlatools/test/tla2sany/drivers/Bug156TEStackOverflowTest.java index 7b396d3817216425315992910904bbea80d3e2a8..6e3b687c232acb80a78b95ce4431bd961a326c81 100644 --- a/tlatools/test/tla2sany/drivers/Bug156TEStackOverflowTest.java +++ b/tlatools/test/tla2sany/drivers/Bug156TEStackOverflowTest.java @@ -1,6 +1,7 @@ package tla2sany.drivers; -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; import tla2sany.modanalyzer.SpecObj; import tla2sany.parser.ParseException; import util.SimpleFilenameToStream; @@ -9,13 +10,14 @@ import util.ToolIO; /** * @see http://bugzilla.tlaplus.net/show_bug.cgi?id=156 */ -public class Bug156TEStackOverflowTest extends TestCase { +public class Bug156TEStackOverflowTest { private SpecObj moduleSpec; /** * @throws java.lang.Exception */ + @Before public void setUp() throws Exception { // create a model and initialize moduleSpec = new SpecObj("test-model/Bug156/TE.tla", new SimpleFilenameToStream()); @@ -26,6 +28,7 @@ public class Bug156TEStackOverflowTest extends TestCase { * Test method for {@link tla2sany.drivers.SANY#frontEndParse(tla2sany.modanalyzer.SpecObj, java.io.PrintStream)}. * @throws ParseException */ + @Test public void testFrontEndParse() throws ParseException { // uncomment if bug 156 has been fixed // try { diff --git a/tlatools/test/tlc2/TLCTest.java b/tlatools/test/tlc2/TLCTest.java index ad8fc027cac3ccda08375a892a9d79e0d4358134..1db35c0900c7102f741e225036ccb0063d4e5bd2 100644 --- a/tlatools/test/tlc2/TLCTest.java +++ b/tlatools/test/tlc2/TLCTest.java @@ -1,20 +1,27 @@ package tlc2; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import util.TLCRuntime; -import junit.framework.TestCase; -public class TLCTest extends TestCase { +public class TLCTest { + @Test public void testHandleParametersAbsoluteInvalid() { final TLC tlc = new TLC(); assertFalse(tlc.handleParameters(new String[] {"-fpmem", "-1", "MC"})); } + @Test public void testHandleParametersAbsoluteValid() { final TLC tlc = new TLC(); assertTrue(tlc.handleParameters(new String[] {"-fpmem", "101", "MC"})); } + @Test public void testHandleParametersFractionInvalid() { final TLC tlc = new TLC(); assertFalse(tlc.handleParameters(new String[] {"-fpmem", "-0.5", "MC"})); @@ -23,6 +30,7 @@ public class TLCTest extends TestCase { /** * Allocating to little should result in min default */ + @Test public void testHandleParametersAllocateLowerBound() { final TLC tlc = new TLC(); assertTrue(tlc.handleParameters(new String[] {"-fpmem", "0", "MC"})); @@ -34,6 +42,7 @@ public class TLCTest extends TestCase { /** * Overallocating should result in max default */ + @Test public void testHandleParametersAllocateUpperBound() { final TLC tlc = new TLC(); assertTrue(tlc.handleParameters(new String[] {"-fpmem", Long.toString(Long.MAX_VALUE), "MC"})); @@ -45,6 +54,7 @@ public class TLCTest extends TestCase { /** * .5 is valid */ + @Test public void testHandleParametersAllocateHalf() { final TLC tlc = new TLC(); assertTrue(tlc.handleParameters(new String[] {"-fpmem", ".5", "MC"})); @@ -56,6 +66,7 @@ public class TLCTest extends TestCase { /** * .99 is valid */ + @Test public void testHandleParametersAllocate90() { final TLC tlc = new TLC(); assertTrue(tlc.handleParameters(new String[] {"-fpmem", ".99", "MC"})); @@ -67,6 +78,7 @@ public class TLCTest extends TestCase { /** * is valid */ + @Test public void testHandleParametersMaxSetSize() { final int progDefault = TLCGlobals.setBound; @@ -93,4 +105,14 @@ public class TLCTest extends TestCase { assertTrue(tlc.handleParameters(new String[] {"-maxSetSize", Integer.toString(Integer.MAX_VALUE), "MC"})); assertTrue(TLCGlobals.setBound == Integer.MAX_VALUE); } + + @Test + public void testRuntimeConversion() { + assertEquals("59s", TLC.convertRuntimeToHumanReadable(59000L)); + assertEquals("59min 59s", TLC.convertRuntimeToHumanReadable(3599000L)); + assertEquals("23h 59min", TLC.convertRuntimeToHumanReadable(86340000L)); + assertEquals("1d 23h", TLC.convertRuntimeToHumanReadable(169200000L)); + assertEquals("2d 23h", TLC.convertRuntimeToHumanReadable(255600000L)); + assertEquals("99d 23h", TLC.convertRuntimeToHumanReadable(8636400000L)); + } } diff --git a/tlatools/test/tlc2/TestDriver.java b/tlatools/test/tlc2/TestDriver.java index 2ec9ba768cc08a13572b099149c6ce7072d2f659..9bbd8bb4786c5d8a630d0b85e59f3c475e599993 100644 --- a/tlatools/test/tlc2/TestDriver.java +++ b/tlatools/test/tlc2/TestDriver.java @@ -8,7 +8,6 @@ public class TestDriver private static final int COUNT = 3; private static final long TIMEOUT = 1000 * 5; - private static final int STEP = 30; private static int reported; private static TLCThread tlcThread; diff --git a/tlatools/test/tlc2/TestDriver2.java b/tlatools/test/tlc2/TestDriver2.java index 6f28d608a860f4f80e0130089a7165c1f4732890..f5819464273da62f94398d4aa2e1f55117120c11 100644 --- a/tlatools/test/tlc2/TestDriver2.java +++ b/tlatools/test/tlc2/TestDriver2.java @@ -45,7 +45,7 @@ public class TestDriver2 /** * @param name */ - public TestDriver2(String rootModule, String cfgFile, String projectDir) + public TestDriver2(String rootModule, String cfgFile, String projectDir) { this.rootModule = rootModule; diff --git a/tlatools/test/tlc2/output/ErrorPrinterTest.java b/tlatools/test/tlc2/output/ErrorPrinterTest.java index 2b603cd30a70d420efad233268f50fa4a0caabcb..e857e1e3cb2e5a92856b2f6c6cf90200dcbeceb7 100644 --- a/tlatools/test/tlc2/output/ErrorPrinterTest.java +++ b/tlatools/test/tlc2/output/ErrorPrinterTest.java @@ -1,21 +1,24 @@ package tlc2.output; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; import util.ToolIO; /** * @author Simon Zambrovski * @version $Id$ */ -public class ErrorPrinterTest extends TestCase +public class ErrorPrinterTest { /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception + @Before + public void setUp() throws Exception { - super.setUp(); ToolIO.setMode(ToolIO.TOOL); ToolIO.reset(); } @@ -23,7 +26,8 @@ public class ErrorPrinterTest extends TestCase /** * Test method for {@link tlc2.output.MP#printError(int)}. */ - public void testPrintErrorInt() + @Test + public void testPrintErrorInt() { MP.printError(EC.UNIT_TEST); String[] allMessages = ToolIO.getAllMessages(); @@ -34,7 +38,8 @@ public class ErrorPrinterTest extends TestCase /** * Test method for {@link tlc2.output.MP#printError(int, java.lang.String)}. */ - public void testPrintErrorIntString() + @Test + public void testPrintErrorIntString() { String parameter = "EXPECTED"; MP.printError(EC.UNIT_TEST, parameter); @@ -46,7 +51,8 @@ public class ErrorPrinterTest extends TestCase /** * Test method for {@link tlc2.output.MP#printError(int, java.lang.String[])}. */ - public void testPrintErrorIntStringArray() + @Test + public void testPrintErrorIntStringArray() { String[] parameters = new String[] { "EXPECTED", "EXPECTED2", "TOO MANY" }; MP.printError(EC.UNIT_TEST, parameters); diff --git a/tlatools/test/tlc2/tool/ASTest.java b/tlatools/test/tlc2/tool/ASTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d00e3ae2a9eca4bb80e762c7644c0860570426cb --- /dev/null +++ b/tlatools/test/tlc2/tool/ASTest.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class ASTest extends ModelCheckerTestCase { + + public ASTest() { + super("AS", "AS"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_STATES_AND_NO_NEXT_ACTION)); + } +} diff --git a/tlatools/test/tlc2/tool/DoInitFunctorEvalExceptionTest.java b/tlatools/test/tlc2/tool/DoInitFunctorEvalExceptionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c6f3fb7874a5113b889c7d8527a4069a72786c1 --- /dev/null +++ b/tlatools/test/tlc2/tool/DoInitFunctorEvalExceptionTest.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class DoInitFunctorEvalExceptionTest extends ModelCheckerTestCase { + + public DoInitFunctorEvalExceptionTest() { + super("DoInitFunctorEvalException", "DoInitFunctor"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recorded(EC.TLC_STATS)); + assertFalse(recorder.recorded(EC.GENERAL)); + + assertTrue(recorder.recordedWithStringValues(EC.TLC_INITIAL_STATE, + "TLC expected a boolean value, but did not find one. line 15, col 15 to line 15, col 18 of module DoInitFunctorEvalException", + "x = 1\n")); + } +} diff --git a/tlatools/test/tlc2/tool/DoInitFunctorInvariantContinueTest.java b/tlatools/test/tlc2/tool/DoInitFunctorInvariantContinueTest.java new file mode 100644 index 0000000000000000000000000000000000000000..897a35ed19faeb2eb5042dbd13c95472e84c713e --- /dev/null +++ b/tlatools/test/tlc2/tool/DoInitFunctorInvariantContinueTest.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class DoInitFunctorInvariantContinueTest extends ModelCheckerTestCase { + + public DoInitFunctorInvariantContinueTest() { + super("DoInitFunctorInvariant", "DoInitFunctor", new String[] {"-continue"}); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "21", "11")); + assertFalse(recorder.recorded(EC.GENERAL)); + + assertTrue(recorder.recordedWithStringValues(EC.TLC_INVARIANT_VIOLATED_INITIAL, "NotNine", "x = 9\n")); + } +} diff --git a/tlatools/test/tlc2/tool/DoInitFunctorInvariantTest.java b/tlatools/test/tlc2/tool/DoInitFunctorInvariantTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7bfa4ecc5709e9164f35727ceae91f89636c7c98 --- /dev/null +++ b/tlatools/test/tlc2/tool/DoInitFunctorInvariantTest.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class DoInitFunctorInvariantTest extends ModelCheckerTestCase { + + public DoInitFunctorInvariantTest() { + super("DoInitFunctorInvariant", "DoInitFunctor"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.TLC_STATS)); + assertFalse(recorder.recorded(EC.GENERAL)); + + assertTrue(recorder.recordedWithStringValues(EC.TLC_INVARIANT_VIOLATED_INITIAL, "NotNine", "x = 9\n")); + } +} diff --git a/tlatools/test/tlc2/tool/DoInitFunctorPropertyTest.java b/tlatools/test/tlc2/tool/DoInitFunctorPropertyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e83801909c54e922287942639e0d68f7149d304e --- /dev/null +++ b/tlatools/test/tlc2/tool/DoInitFunctorPropertyTest.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class DoInitFunctorPropertyTest extends ModelCheckerTestCase { + + public DoInitFunctorPropertyTest() { + super("DoInitFunctorProperty", "DoInitFunctor"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.TLC_STATS)); + assertFalse(recorder.recorded(EC.GENERAL)); + + assertTrue(recorder.recordedWithStringValues(EC.TLC_PROPERTY_VIOLATED_INITIAL, "NotNine", "x = 9\n")); + } +} diff --git a/tlatools/test/tlc2/tool/EmptyTest.java b/tlatools/test/tlc2/tool/EmptyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..39edcf3473ff8ef1f5f959a7277f28f96eaa4730 --- /dev/null +++ b/tlatools/test/tlc2/tool/EmptyTest.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class EmptyTest extends ModelCheckerTestCase { + + public EmptyTest() { + super("Empty", "", new String[] {"-coverage", "1"}); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "0", "0", "0")); + } +} diff --git a/tlatools/test/tlc2/tool/IncompleteNextTest.java b/tlatools/test/tlc2/tool/IncompleteNextTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c3f52c03900846f733313622c6311147ece94f9c --- /dev/null +++ b/tlatools/test/tlc2/tool/IncompleteNextTest.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class IncompleteNextTest extends ModelCheckerTestCase { + + public IncompleteNextTest() { + super("IncompleteNext", ""); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "2", "1", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = 0\n/\\ y = 0"); + expectedTrace.add("/\\ x = 1\n/\\ y = null"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + } +} diff --git a/tlatools/test/tlc2/tool/PrintTraceRaceTest.java b/tlatools/test/tlc2/tool/PrintTraceRaceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..743f207b8a7c2182f4fe3cf4a910d492e70c0617 --- /dev/null +++ b/tlatools/test/tlc2/tool/PrintTraceRaceTest.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class PrintTraceRaceTest extends ModelCheckerTestCase { + + public PrintTraceRaceTest() { + super("MC", "PrintTraceRace"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "3", "2", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + assertTrue(recorder.recorded(EC.TLC_BEHAVIOR_UP_TO_THIS_POINT)); + + final List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); + + int i = 0; // State's position in records + Object[] objs = (Object[]) records.get(i++); + TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; + assertEquals("S = [q |-> <<>>, i |-> 1]", + stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace + assertEquals(i, objs[1]); + + objs = (Object[]) records.get(i++); + stateInfo = (TLCStateInfo) objs[0]; + assertEquals("S = [q |-> <<1>>, i |-> 2]", + stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace + assertEquals(i, objs[1]); + + assertEquals(2, objs.length); + } + + protected int getNumberOfThreads() { + // This bug only shows up with multiple threads. + return 4; + } +} diff --git a/tlatools/test/tlc2/tool/SetOfStatesTest.java b/tlatools/test/tlc2/tool/SetOfStatesTest.java new file mode 100644 index 0000000000000000000000000000000000000000..68171cc4627bc428aafc2fe26647d586423a1876 --- /dev/null +++ b/tlatools/test/tlc2/tool/SetOfStatesTest.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; +import tlc2.tool.queue.DummyTLCState; +import tlc2.util.SetOfStates; + +public class SetOfStatesTest extends TestCase { + + public void testSizeEmpty() { + final SetOfStates s = new SetOfStates(16); + + assertEquals(16, s.capacity()); + assertEquals(0, s.size()); + } + + public void testSize() { + final SetOfStates s = new SetOfStates(16); + s.put(new DummyTLCState(1L)); + + assertEquals(16, s.capacity()); + assertEquals(1, s.size()); + } + + public void testGrow() { + final SetOfStates s = new SetOfStates(1); + + for(int i = 0; i < 32; i++) { + s.put(new DummyTLCState(i)); + } + + assertTrue(s.capacity() > 32); + assertEquals(32, s.size()); + } + + public void testIterate() { + final SetOfStates s = new SetOfStates(1); + + for(int i = 1; i <= 32; i++) { + assertFalse(s.put(new DummyTLCState(i))); + } + assertEquals(32, s.size()); + + // successor is not equal to predecessor + TLCState predecessor = null; + for (int i = 0; i < s.size(); i++) { + TLCState state = s.next(); + assertNotSame(predecessor, state); + predecessor = state; + } + s.resetNext(); + + // The combined sum of elements is correct + long sum = 0L; + for (int i = 0; i < s.size(); i++) { + final TLCState elem = s.next(); + sum += elem.fingerPrint(); + } + assertEquals((32 / 2) * (1 + 32), sum); + } + + public void testDuplicates() { + final SetOfStates s = new SetOfStates(1); + + for(int i = 1; i <= 32; i++) { + assertFalse(s.put(new DummyTLCState(i))); + } + assertEquals(32, s.size()); + + // Adding the same elements again fails + final Set<TLCState> states = new HashSet<TLCState>(s.size()); + for (int i = 0; i < s.size(); i++) { + states.add(s.next()); + } + for (TLCState aState : states) { + assertTrue(s.put(aState)); + } + assertEquals(32, states.size()); + } + + public void testDuplicatesButNotEqual() { + // duplicates in terms of fingerprints, but not in terms of equality + // (symmetry). + final SetOfStates s = new SetOfStates(1); + + int id = 1; + for(int i = 1; i <= 32; i++) { + assertFalse(s.put(new EqualityDummyTLCState(i, id++))); + } + assertEquals(32, s.size()); + + // Add 32 more elements with identical fp but different ids + for(int i = 1; i <= 32; i++) { + assertFalse(s.put(new EqualityDummyTLCState(i, id++))); + } + assertEquals(64, s.size()); + + // Add 32 more elements with identical fp and ids + id = 1; + for(int i = 1; i <= 32; i++) { + assertTrue(s.put(new EqualityDummyTLCState(i, id++))); + } + assertEquals(64, s.size()); + } + + @SuppressWarnings("serial") + private static class EqualityDummyTLCState extends DummyTLCState { + + private final int id; + + public EqualityDummyTLCState(final int fp, final int id) { + super(fp); + this.id = id; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = (int) (prime * result + fingerPrint()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EqualityDummyTLCState other = (EqualityDummyTLCState) obj; + if (fingerPrint() != other.fingerPrint()) + return false; + if (id != other.id) + return false; + return true; + } + } +} diff --git a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntoPart.java b/tlatools/test/tlc2/tool/TSnapShotTest.java similarity index 68% rename from org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntoPart.java rename to tlatools/test/tlc2/tool/TSnapShotTest.java index 14d498f71d3897371eb697c8059fe0e227598fa9..077e3c2e93e89dba69c02f192a5d00e03d864cbb 100644 --- a/org.lamport.tla.toolbox.product.standalone/src/org/lamport/tla/toolbox/ui/intro/ToolboxIntoPart.java +++ b/tlatools/test/tlc2/tool/TSnapShotTest.java @@ -23,31 +23,33 @@ * Contributors: * Markus Alexander Kuppe - initial API and implementation ******************************************************************************/ -package org.lamport.tla.toolbox.ui.intro; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.intro.IIntroPart; -import org.eclipse.ui.part.IntroPart; -import org.lamport.tla.toolbox.ui.view.ToolboxWelcomeView; +package tlc2.tool; -public class ToolboxIntoPart extends IntroPart implements IIntroPart { +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; - private Composite container; +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; - /** - * @wbp.parser.entryPoint - */ - public void createPartControl(Composite container) { - this.container = container; - ToolboxWelcomeView.createControl(container); - } +public class TSnapShotTest extends ModelCheckerTestCase { - public void standbyStateChanged(boolean standby) { - // do nothing for now (don't expect users to - // send welcome to standy) + public TSnapShotTest() { + super("MC", "TSnapShot"); } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertFalse(recorder.recorded(EC.TLC_BUG)); - public void setFocus() { - container.setFocus(); + assertTrue(recorder.recorded(EC.TLC_BEHAVIOR_UP_TO_THIS_POINT)); + } + + protected int getNumberOfThreads() { + // This bug only shows up with multiple threads. + return 4; } } diff --git a/tlatools/test/tlc2/tool/distributed/TLCServerTestCase.java b/tlatools/test/tlc2/tool/distributed/TLCServerTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..ff9f53e1efd6bb239ca2e38330a261882b7ba7d4 --- /dev/null +++ b/tlatools/test/tlc2/tool/distributed/TLCServerTestCase.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.distributed; + +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.rmi.RemoteException; + +import org.junit.Before; + +import tlc2.TLCGlobals; +import tlc2.output.MP; +import tlc2.tool.fp.FPSetConfiguration; +import tlc2.tool.fp.MSBDiskFPSet; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public abstract class TLCServerTestCase extends ModelCheckerTestCase { + + public TLCServerTestCase(String spec) { + super(spec); + } + + public TLCServerTestCase(String spec, String path) { + super(spec, path); + } + + /* (non-Javadoc) + * @see tlc2.tool.liveness.ModelCheckerTestCase#setUp() + */ + @Before + public void setUp() { + try { + MP.setRecorder(recorder); + + // Never create checkpoints. They distort performance tests and are + // of no use anyway. + TLCGlobals.chkptDuration = 0; + + final String fqSpec = BASE_DIR + TEST_MODEL + path + File.separator + spec; + final FPSetConfiguration fpSetConfig = new DummyFPSetConfig(); + final TLCApp app = new TLCApp(fqSpec, fqSpec, false, null, fpSetConfig); + final TLCServer server = new TLCServer(app); + server.modelCheck(); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @SuppressWarnings("serial") + private class DummyFPSetConfig extends FPSetConfiguration { + + /* (non-Javadoc) + * @see tlc2.tool.fp.FPSetConfiguration#getImplementation() + */ + public String getImplementation() { + return DummyFPSet.class.getName(); + } + } + + @SuppressWarnings("serial") + protected static class DummyFPSet extends MSBDiskFPSet { + + public DummyFPSet(FPSetConfiguration fpSetConfig) throws RemoteException { + super(fpSetConfig); + } + + /* (non-Javadoc) + * @see tlc2.tool.fp.DiskFPSet#exit(boolean) + */ + public void exit(boolean cleanup) throws IOException { + //ignore because superclass calls System.exit(0) but we want to check our assertions first. + } + } +} diff --git a/tlatools/test/tlc2/tool/distributed/TLCSetTest.java b/tlatools/test/tlc2/tool/distributed/TLCSetTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1ad6bbdc1d1d862b9db915dae18882ee27da3426 --- /dev/null +++ b/tlatools/test/tlc2/tool/distributed/TLCSetTest.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.distributed; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import tlc2.output.EC; + +/** + * Tests support for TLCSet/TLCGet in TLCServer + */ +public class TLCSetTest extends TLCServerTestCase { + + public TLCSetTest() { + super("TLCSet"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_COMPUTING_INIT)); + assertTrue(recorder.recorded(EC.TLC_FEATURE_UNSUPPORTED)); + assertFalse(recorder.recorded(EC.GENERAL)); + } +} diff --git a/tlatools/test/tlc2/tool/distributed/TLCWorkerSmartProxyTest.java b/tlatools/test/tlc2/tool/distributed/TLCWorkerSmartProxyTest.java index a464be2ef48efe8dd477fce5220d6066d7d19ac7..5f5ead665bdb795b09bb5c3b7006798a66fed279 100644 --- a/tlatools/test/tlc2/tool/distributed/TLCWorkerSmartProxyTest.java +++ b/tlatools/test/tlc2/tool/distributed/TLCWorkerSmartProxyTest.java @@ -1,59 +1,70 @@ package tlc2.tool.distributed; -import java.rmi.RemoteException; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import java.rmi.RemoteException; +import org.junit.Test; import tlc2.tool.TLCState; import tlc2.tool.WorkerException; import tlc2.tool.distributed.selector.DummyTLCWorker; import tlc2.tool.queue.DummyTLCState; -import junit.framework.TestCase; -public class TLCWorkerSmartProxyTest extends TestCase { +public class TLCWorkerSmartProxyTest { private static final int ZERO = 0; private static final int ONE = 1; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE * (1/10); + @Test public void testGetNetworkOverheadMaxStateOne() throws RemoteException, WorkerException { long calculationDuration = Long.MAX_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[ONE]) > 0); } + @Test public void testGetNetworkOverheadMinStateOne() throws RemoteException, WorkerException { long calculationDuration = Long.MIN_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[ONE]) > 0); } + @Test public void testGetNetworkOverheadZeroStateOne() throws RemoteException, WorkerException { long calculationDuration = ZERO; assertTrue(doTest(calculationDuration, new DummyTLCState[ONE]) > 0); } + @Test public void testGetNetworkOverheadMaxStateZero() throws RemoteException, WorkerException { long calculationDuration = Long.MAX_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[ZERO]) > 0); } + @Test public void testGetNetworkOverheadMinStateZero() throws RemoteException, WorkerException { long calculationDuration = Long.MIN_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[ZERO]) > 0); } + @Test public void testGetNetworkOverheadZeroStateZero() throws RemoteException, WorkerException { long calculationDuration = ZERO; assertTrue(doTest(calculationDuration, new DummyTLCState[ZERO]) > 0); } + @Test public void testGetNetworkOverheadMinStateMax() throws RemoteException, WorkerException { long calculationDuration = Long.MIN_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[MAX_ARRAY_SIZE]) > 0); } + @Test public void testGetNetworkOverheadMaxStateMa() throws RemoteException, WorkerException { long calculationDuration = Long.MAX_VALUE; assertTrue(doTest(calculationDuration, new DummyTLCState[MAX_ARRAY_SIZE]) > 0); } + @Test public void testGetNetworkOverheadZeroStateMax() throws RemoteException, WorkerException { long calculationDuration = ZERO; assertTrue(doTest(calculationDuration, new DummyTLCState[MAX_ARRAY_SIZE]) > 0); diff --git a/tlatools/test/tlc2/tool/distributed/fp/DynamicFPSetManagerTest.java b/tlatools/test/tlc2/tool/distributed/fp/DynamicFPSetManagerTest.java index 3beb8e2a8eaa67b395b7694ecc38f0b8f0e521ba..a10b175dbf58e0be56d3d4cd039c0dd8d38cabc5 100644 --- a/tlatools/test/tlc2/tool/distributed/fp/DynamicFPSetManagerTest.java +++ b/tlatools/test/tlc2/tool/distributed/fp/DynamicFPSetManagerTest.java @@ -2,6 +2,11 @@ package tlc2.tool.distributed.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.rmi.RemoteException; import java.util.HashMap; @@ -9,18 +14,18 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - -import junit.framework.TestCase; +import org.junit.Test; import tlc2.tool.fp.FPSet; import tlc2.tool.fp.MemFPSet; import tlc2.util.BitVector; import tlc2.util.LongVec; -public class DynamicFPSetManagerTest extends TestCase { +public class DynamicFPSetManagerTest { /** * Test that the ctor rejects invalid values. */ + @Test public void testCtorInvalidZero() throws RemoteException { try { new DynamicFPSetManager(0); @@ -33,6 +38,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Test that the ctor rejects invalid values. */ + @Test public void testCtorInvalidMin1() throws RemoteException { try { new DynamicFPSetManager(-1); @@ -46,6 +52,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtor1() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(1); long mask = dynamicFPSetManager.getMask(); @@ -56,6 +63,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtor10() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(10); long mask = dynamicFPSetManager.getMask(); @@ -66,6 +74,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtor31() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(31); long mask = dynamicFPSetManager.getMask(); @@ -76,6 +85,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtor32() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(32); long mask = dynamicFPSetManager.getMask(); @@ -86,6 +96,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtor33() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(33); long mask = dynamicFPSetManager.getMask(); @@ -96,6 +107,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the ctor correctly calculates its mask used to index fpset * servers for valid values. */ + @Test public void testCtorMax() throws RemoteException { DynamicFPSetManager dynamicFPSetManager = new DynamicFPSetManager(Integer.MAX_VALUE); long mask = dynamicFPSetManager.getMask(); @@ -106,6 +118,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the {@link DynamicFPSetManager} correctly indexes into the * table of FPSet servers. */ + @Test public void testGetIndexSingleFPSet() throws RemoteException { // Simple case with a single FPSet server final Map<Long, Integer> pairs = new HashMap<Long, Integer>(); @@ -121,6 +134,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Test that the {@link DynamicFPSetManager} correctly indexes into the * table of FPSet servers. */ + @Test public void testGetIndex10FPSet() throws RemoteException { final Map<Long, Integer> pairs = new HashMap<Long, Integer>(); @@ -170,6 +184,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests that reassign doesn't accept invalid values */ + @Test public void testReassingInvalidMin1() throws RemoteException { int expectedNumOfServers = 1; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -190,6 +205,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests that reassign doesn't accept invalid values */ + @Test public void testReassingInvalid2() throws RemoteException { int expectedNumOfServers = 1; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -211,6 +227,7 @@ public class DynamicFPSetManagerTest extends TestCase { * Tests that reassign correctly terminates with -1 when reassignment to * next FPSet impossible (no FPSets left) */ + @Test public void testReassingTerminate() throws RemoteException { int expectedNumOfServers = 1; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -225,6 +242,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests that reassign correctly assigns to the next FPSet */ + @Test public void testReassing() throws RemoteException { int expectedNumOfServers = 10; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -268,6 +286,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests if the {@link FPSetManager} correctly fails over to the replacement {@link FPSet} */ + @Test public void testFailoverPut() throws RemoteException { int expectedNumOfServers = 2; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -292,6 +311,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests if the {@link FPSetManager} correctly fails over to the replacement {@link FPSet} */ + @Test public void testFailoverPutBlock() throws RemoteException { int expectedNumOfServers = 2; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -345,6 +365,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests if the {@link FPSetManager} correctly terminates if all nested FPSets fail */ + @Test public void testFailoverTerminationPutBlock() throws RemoteException { int expectedNumOfServers = 2; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -397,6 +418,7 @@ public class DynamicFPSetManagerTest extends TestCase { /** * Tests if the {@link FPSetManager} correctly terminates if all nested FPSets fail */ + @Test public void testFailoverTerminationPutBlockConcurrent() throws RemoteException { int expectedNumOfServers = 2; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); @@ -461,6 +483,7 @@ public class DynamicFPSetManagerTest extends TestCase { * {@link DynamicFPSetManager} is indeed faulty (pay attention to * intermittent test failures). */ + @Test public void testPutBlockConcurrentOrder() throws IOException { int expectedNumOfServers = 20; final DynamicFPSetManager dfm = new DynamicFPSetManager(expectedNumOfServers); diff --git a/tlatools/test/tlc2/tool/fp/AbstractFPSetTest.java b/tlatools/test/tlc2/tool/fp/AbstractFPSetTest.java index e0bc397d688e87de99573b1e70669470400ee877..dd7f8ca5f1f9ef72f5d4f0519ab69d55142eab70 100644 --- a/tlatools/test/tlc2/tool/fp/AbstractFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/AbstractFPSetTest.java @@ -6,10 +6,10 @@ import java.io.File; import java.io.IOException; import java.text.DecimalFormat; import java.util.Date; +import org.junit.After; +import org.junit.Before; -import junit.framework.TestCase; - -public abstract class AbstractFPSetTest extends TestCase { +public abstract class AbstractFPSetTest { protected static final long RNG_SEED = 15041980L; @@ -28,9 +28,8 @@ public abstract class AbstractFPSetTest extends TestCase { /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); - + @Before + public void setUp() throws Exception { // create temp folder dir = new File(tmpdir); dir.mkdirs(); @@ -44,6 +43,7 @@ public abstract class AbstractFPSetTest extends TestCase { /* (non-Javadoc) * @see junit.framework.TestCase#tearDown() */ + @After public void tearDown() { if (endTimeStamp == null) { endTimeStamp = new Date(); diff --git a/tlatools/test/tlc2/tool/fp/AbstractHeapBasedDiskFPSetTest.java b/tlatools/test/tlc2/tool/fp/AbstractHeapBasedDiskFPSetTest.java index 1e0c623ee65e4a0b1ed59677e4db2737356a8b86..657a01971b7f6a3ff36ce8ab09401c0fcbdf647f 100644 --- a/tlatools/test/tlc2/tool/fp/AbstractHeapBasedDiskFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/AbstractHeapBasedDiskFPSetTest.java @@ -1,62 +1,75 @@ // Copyright (c) 2012 Markus Alexander Kuppe. All rights reserved. package tlc2.tool.fp; -import java.rmi.RemoteException; +import static org.junit.Assert.assertTrue; -import junit.framework.TestCase; +import java.rmi.RemoteException; +import org.junit.Test; -public abstract class AbstractHeapBasedDiskFPSetTest extends TestCase { +public abstract class AbstractHeapBasedDiskFPSetTest { /* Test the lower limits */ + @Test public void testCtorLLMinus1() throws RemoteException { doTest(getLowerLimit() - 1); } + @Test public void testCtorLL() throws RemoteException { doTest(getLowerLimit()); } + @Test public void testCtorLLPlus1() throws RemoteException { doTest(getLowerLimit() + 1); } + @Test public void testCtorLLNextPow2Min1() throws RemoteException { doTest((getLowerLimit() << 1) - 1); } /* Test with a power far away from upper/lower limits */ + @Test public void testCtorPow16Minus1() throws RemoteException { doTest((1L << 16) - 1); } + @Test public void testCtorPow16() throws RemoteException { doTest(1L << 16); } + @Test public void testCtorPow16Plus1() throws RemoteException { doTest((1L << 16) + 1); } + @Test public void testCtorPow16NextPow2Min1() throws RemoteException { doTest(((1L << 16) << 1) - 1); } /* Test the upper limits */ + @Test public void testCtorULMinus1() throws RemoteException { doTest(getUpperLimit() - 1); } + @Test public void testCtorUL() throws RemoteException { doTest(getUpperLimit()); } + @Test public void testCtorULPlus1() throws RemoteException { doTest(getUpperLimit() + 1); } + @Test public void testCtorULNextPow2Min1() throws RemoteException { doTest((getUpperLimit() << 1) - 1); } diff --git a/tlatools/test/tlc2/tool/fp/Bug210DiskFPSetTest.java b/tlatools/test/tlc2/tool/fp/Bug210DiskFPSetTest.java index a7eb567f3300895d839b9d4d2e1529b9f2500ae5..1b065adfd57c3601b7d111ac0bd788a013c775cd 100644 --- a/tlatools/test/tlc2/tool/fp/Bug210DiskFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/Bug210DiskFPSetTest.java @@ -1,7 +1,11 @@ // Copyright (c) 2011 Microsoft Corporation. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; + import java.io.IOException; +import org.junit.Test; public class Bug210DiskFPSetTest extends AbstractFPSetTest { @@ -19,6 +23,7 @@ public class Bug210DiskFPSetTest extends AbstractFPSetTest { * @see http://bugzilla.tlaplus.net/show_bug.cgi?id=210 * @throws IOException */ + @Test public void testDiskLookupWithOverflow() throws IOException { // set up an index whose upper bound is beyond 1/1024 of // Integer.MAX_VALUE diff --git a/tlatools/test/tlc2/tool/fp/Bug242DiskFPSetTest.java b/tlatools/test/tlc2/tool/fp/Bug242DiskFPSetTest.java index 20e232d50824f4680dba97630313803bf30e992a..8e6f6882e50903fa31aad69ff0331f5ee041f907 100644 --- a/tlatools/test/tlc2/tool/fp/Bug242DiskFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/Bug242DiskFPSetTest.java @@ -1,8 +1,11 @@ // Copyright (c) 2011 Microsoft Corporation. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.fail; + import java.io.IOException; import java.rmi.RemoteException; +import org.junit.Test; /** * @@ -28,6 +31,7 @@ public class Bug242DiskFPSetTest extends AbstractFPSetTest { /** * @see http://bugzilla.tlaplus.net/show_bug.cgi?id=242 */ + @Test public void testDiskFPSetWithHighMem() throws RemoteException { try { getFPSet(2097153638); @@ -38,6 +42,7 @@ public class Bug242DiskFPSetTest extends AbstractFPSetTest { fail(e.getMessage()); } } + @Test public void testDiskFPSetIntMaxValue() throws RemoteException { try { getFPSet(Integer.MAX_VALUE); @@ -48,6 +53,7 @@ public class Bug242DiskFPSetTest extends AbstractFPSetTest { fail(e.getMessage()); } } + @Test public void testDiskFPSetIntMinValue() throws RemoteException { try { getFPSet(Integer.MIN_VALUE); @@ -57,6 +63,7 @@ public class Bug242DiskFPSetTest extends AbstractFPSetTest { } fail(); } + @Test public void testDiskFPSetZero() throws RemoteException { try { getFPSet(0); @@ -64,6 +71,7 @@ public class Bug242DiskFPSetTest extends AbstractFPSetTest { fail(e.getMessage()); } } + @Test public void testDiskFPSetOne() throws RemoteException { try { getFPSet(1); diff --git a/tlatools/test/tlc2/tool/fp/Bug246DiskFPSetTest.java b/tlatools/test/tlc2/tool/fp/Bug246DiskFPSetTest.java index c7c07483b9562ba3f7b8257e0cf34d447ba30cf3..92f221cf9ff078aec013b557c306e932f7c717a6 100644 --- a/tlatools/test/tlc2/tool/fp/Bug246DiskFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/Bug246DiskFPSetTest.java @@ -2,20 +2,24 @@ package tlc2.tool.fp; -import java.io.IOException; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import junit.framework.TestCase; +import java.io.IOException; +import org.junit.Test; import util.TLCRuntime; /** * @author Markus Alexander Kuppe */ -public class Bug246DiskFPSetTest extends TestCase { +public class Bug246DiskFPSetTest { /** * Tests if the DiskFPSet gets correctly flushed to disk (if the fp spaces is unevenly distributed) or causes an {@link OutOfMemoryError} * @throws IOException */ + @Test @SuppressWarnings("deprecation") public void testLinearFillup() throws IOException { final long vmMaxMemory = Runtime.getRuntime().maxMemory(); @@ -77,6 +81,7 @@ public class Bug246DiskFPSetTest extends TestCase { } } + @Test public void testFlushDiskFPSet() throws IOException { // // Dedicate 90% of VM memory to DiskFPSet diff --git a/tlatools/test/tlc2/tool/fp/FPSetFactoryTest.java b/tlatools/test/tlc2/tool/fp/FPSetFactoryTest.java index 00950765cd3ea83f4a5053ade30a6b985990e835..28cf1bd8fd81bb7071112bed96afdfca04300921 100644 --- a/tlatools/test/tlc2/tool/fp/FPSetFactoryTest.java +++ b/tlatools/test/tlc2/tool/fp/FPSetFactoryTest.java @@ -1,13 +1,15 @@ // Copyright (c) 2012 Markus Alexander Kuppe. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.rmi.NoSuchObjectException; import java.rmi.RemoteException; - -import junit.framework.TestCase; +import org.junit.Test; @SuppressWarnings("deprecation") -public class FPSetFactoryTest extends TestCase { +public class FPSetFactoryTest { // Has to be larger than util.TLCRuntime.MinFpMemSize. For off-heap/non-heap // tests it should not exceed what the VM allocates by default (usually // 64mb). 64mb is also the default used by util.TLCRuntime.getNonHeapPhysicalMemory(). @@ -15,6 +17,7 @@ public class FPSetFactoryTest extends TestCase { /* Test single FPSet with default memory */ + @Test public void testGetFPSetMSB() throws RemoteException { // Explicitly set MSBDiskFPSet to overwrite any previous setting (if any) System.setProperty(FPSetFactory.IMPL_PROPERTY, MSBDiskFPSet.class.getName()); @@ -22,12 +25,14 @@ public class FPSetFactoryTest extends TestCase { doTestGetFPSet(MSBDiskFPSet.class, fpSetConfiguration); } + @Test public void testGetFPSetLSB() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, LSBDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); doTestGetFPSet(LSBDiskFPSet.class, fpSetConfiguration); } + @Test public void testGetFPSetOffHeap() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, OffHeapDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -36,6 +41,7 @@ public class FPSetFactoryTest extends TestCase { /* Test single FPSet with explicit memory */ + @Test public void testGetFPSetMSBWithMem() throws RemoteException { // Explicitly set MSBDiskFPSet to overwrite any previous setting (if any) System.setProperty(FPSetFactory.IMPL_PROPERTY, MSBDiskFPSet.class.getName()); @@ -51,6 +57,7 @@ public class FPSetFactoryTest extends TestCase { assertTrue((MEMORY / FPSet.LongSize) > diskFPSet.getMaxTblCnt()); } + @Test public void testGetFPSetLSBWithMem() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, LSBDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -67,6 +74,7 @@ public class FPSetFactoryTest extends TestCase { assertTrue(((MEMORY / FPSet.LongSize) / 2) > diskFPSet.getMaxTblCnt()); } + @Test public void testGetFPSetOffHeapWithMem() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, OffHeapDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -83,6 +91,7 @@ public class FPSetFactoryTest extends TestCase { /* Test single FPSet with explicit memory and ratio */ + @Test public void testGetFPSetMSBWithMemAndRatio() throws RemoteException { // Explicitly set MSBDiskFPSet to overwrite any previous setting (if any) System.setProperty(FPSetFactory.IMPL_PROPERTY, MSBDiskFPSet.class.getName()); @@ -98,6 +107,7 @@ public class FPSetFactoryTest extends TestCase { assertTrue((MEMORY / FPSet.LongSize) > diskFPSet.getMaxTblCnt()); } + @Test public void testGetFPSetLSBWithMemAndRatio() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, LSBDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -114,6 +124,7 @@ public class FPSetFactoryTest extends TestCase { assertTrue((MEMORY / FPSet.LongSize) > diskFPSet.getMaxTblCnt()); } + @Test public void testGetFPSetOffHeapWithMemAndRatio() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, OffHeapDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -133,6 +144,7 @@ public class FPSetFactoryTest extends TestCase { /* Test MultiFPSet with default memory */ + @Test public void testGetFPSetMultiFPSet() throws RemoteException { // Explicitly set MSBDiskFPSet to overwrite any previous setting (if any) System.setProperty(FPSetFactory.IMPL_PROPERTY, MSBDiskFPSet.class.getName()); @@ -143,6 +155,7 @@ public class FPSetFactoryTest extends TestCase { doTestNested(MSBDiskFPSet.class, fpSetConfiguration, mFPSet); } + @Test public void testGetFPSetLSBMultiFPSet() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, LSBDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -152,6 +165,7 @@ public class FPSetFactoryTest extends TestCase { doTestNested(LSBDiskFPSet.class, fpSetConfiguration, mFPSet); } + @Test public void testGetFPSetOffHeapMultiFPSet() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, OffHeapDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -163,6 +177,7 @@ public class FPSetFactoryTest extends TestCase { /* Test MultiFPSet with explicit memory */ + @Test public void testGetFPSetMultiFPSetWithMem() throws RemoteException { // Explicitly set MSBDiskFPSet to overwrite any previous setting (if any) System.setProperty(FPSetFactory.IMPL_PROPERTY, MSBDiskFPSet.class.getName()); @@ -175,6 +190,7 @@ public class FPSetFactoryTest extends TestCase { doTestNested(MSBDiskFPSet.class, fpSetConfiguration, mFPSet); } + @Test public void testGetFPSetLSBMultiFPSetWithMem() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, LSBDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); @@ -186,6 +202,7 @@ public class FPSetFactoryTest extends TestCase { doTestNested(LSBDiskFPSet.class, fpSetConfiguration, mFPSet); } + @Test public void testGetFPSetOffHeapMultiFPSetWithMem() throws RemoteException { System.setProperty(FPSetFactory.IMPL_PROPERTY, OffHeapDiskFPSet.class.getName()); final FPSetConfiguration fpSetConfiguration = new FPSetConfiguration(); diff --git a/tlatools/test/tlc2/tool/fp/MSBDiskFPSetTest2.java b/tlatools/test/tlc2/tool/fp/MSBDiskFPSetTest2.java index 2a36cba69b3894472937e4079215aebe8fab451b..35f596756175f9e4e555aa9135379d900d7f5db6 100644 --- a/tlatools/test/tlc2/tool/fp/MSBDiskFPSetTest2.java +++ b/tlatools/test/tlc2/tool/fp/MSBDiskFPSetTest2.java @@ -1,10 +1,15 @@ // Copyright (c) 2012 Markus Alexander Kuppe. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.rmi.RemoteException; import java.util.NoSuchElementException; - +import org.junit.Test; import tlc2.tool.fp.MSBDiskFPSet.TLCIterator; public class MSBDiskFPSetTest2 extends AbstractHeapBasedDiskFPSetTest { @@ -30,6 +35,7 @@ public class MSBDiskFPSetTest2 extends AbstractHeapBasedDiskFPSetTest { return 1L << 31; } + @Test public void testGetLast() throws IOException { final MSBDiskFPSet msbDiskFPSet = getMSBDiskFPSet(); @@ -62,6 +68,7 @@ public class MSBDiskFPSetTest2 extends AbstractHeapBasedDiskFPSetTest { fail(); } + @Test public void testHighFingerprint1() throws RemoteException, IOException { final MSBDiskFPSet msbDiskFPSet = getMSBDiskFPSet(); assertFalse(msbDiskFPSet.put(9223368718049406096L)); @@ -71,6 +78,7 @@ public class MSBDiskFPSetTest2 extends AbstractHeapBasedDiskFPSetTest { assertTrue(msbDiskFPSet.put(9223368718049406096L)); } + @Test public void testHighFingerprint2() throws RemoteException, IOException { final MSBDiskFPSet msbDiskFPSet = getMSBDiskFPSet(); assertFalse(msbDiskFPSet.put(9223335424116589377L)); @@ -83,6 +91,7 @@ public class MSBDiskFPSetTest2 extends AbstractHeapBasedDiskFPSetTest { /* * Try to get the last element with no elements in the set. */ + @Test public void testGetLastNoBuckets() throws IOException { final MSBDiskFPSet msbDiskFPSet = getMSBDiskFPSet(); diff --git a/tlatools/test/tlc2/tool/fp/MultiFPSetTest.java b/tlatools/test/tlc2/tool/fp/MultiFPSetTest.java index 1ed2997cb7a6991c13d1d34f01ff8fefcf52680b..ef7788d15b3f254dc577c9a5ab17c08cf665e47b 100644 --- a/tlatools/test/tlc2/tool/fp/MultiFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/MultiFPSetTest.java @@ -1,26 +1,29 @@ // Copyright (c) 2011 Microsoft Corporation. All rights reserved. package tlc2.tool.fp; -import java.io.IOException; +import static org.junit.Assert.fail; -import junit.framework.TestCase; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; /** * @author Markus Alexander Kuppe */ -public class MultiFPSetTest extends TestCase { +public class MultiFPSetTest { /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { } /** * Test method for {@link tlc2.tool.fp.MultiFPSet#new}. * @throws IOException Not supposed to happen */ + @Test public void testCTorLowerMin() throws IOException { try { FPSetConfiguration conf = new FPSetConfiguration(); @@ -36,6 +39,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#new}. * @throws IOException Not supposed to happen */ + @Test public void testCTorMin() throws IOException { try { FPSetConfiguration conf = new FPSetConfiguration(); @@ -51,6 +55,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#new}. * @throws IOException Not supposed to happen */ + @Test public void testCTorMax() throws IOException { try { FPSetConfiguration conf = new FPSetConfiguration(); @@ -77,6 +82,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#new}. * @throws IOException Not supposed to happen */ + @Test public void testCTorHigherMax() throws IOException { try { FPSetConfiguration conf = new FPSetConfiguration(); @@ -92,6 +98,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#put(long)}. * @throws IOException Not supposed to happen */ + @Test public void testPutMax() throws IOException { FPSetConfiguration conf = new FPSetConfiguration(); conf.setFpBits(1); @@ -109,6 +116,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#put(long)}. * @throws IOException Not supposed to happen */ + @Test public void testPutMin() throws IOException { FPSetConfiguration conf = new FPSetConfiguration(); conf.setFpBits(1); @@ -126,6 +134,7 @@ public class MultiFPSetTest extends TestCase { * Test method for {@link tlc2.tool.fp.MultiFPSet#put(long)}. * @throws IOException Not supposed to happen */ + @Test public void testPutZero() throws IOException { FPSetConfiguration conf = new FPSetConfiguration(); conf.setFpBits(1); diff --git a/tlatools/test/tlc2/tool/fp/ShortDiskFPSetTest.java b/tlatools/test/tlc2/tool/fp/ShortDiskFPSetTest.java index 368810968d02fa695d04d7ade537e5a6b0bb4ee4..8be6387455fe2fbee918542c9906955c3f2a32b3 100644 --- a/tlatools/test/tlc2/tool/fp/ShortDiskFPSetTest.java +++ b/tlatools/test/tlc2/tool/fp/ShortDiskFPSetTest.java @@ -1,11 +1,16 @@ // Copyright (c) 2011 Microsoft Corporation. All rights reserved. package tlc2.tool.fp; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; - +import org.junit.Test; import tlc2.util.LongVec; public class ShortDiskFPSetTest extends AbstractFPSetTest { @@ -32,6 +37,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} returns true for zero fp * @throws IOException */ + @Test public void testWithoutZeroFP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse("Succeeded to look up 0 fp", fpSet.contains(0l)); @@ -41,6 +47,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} returns true for min fp * @throws IOException */ + @Test public void testWithoutMinFP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse("Succeeded to look up 0 fp", fpSet.contains(Long.MIN_VALUE)); @@ -50,6 +57,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} returns true for max fp * @throws IOException */ + @Test public void testWithoutMaxFP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse("Succeeded to look up 0 fp", fpSet.contains(Long.MAX_VALUE)); @@ -59,6 +67,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a 0 fp * @throws IOException */ + @Test public void testZeroFP() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -77,6 +86,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a min fp * @throws IOException */ + @Test public void testMinFP() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -96,6 +106,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a min - 1 fp * @throws IOException */ + @Test public void testMinMin1FP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); // zeroing the msb in DiskFPSet turns Long.Min_Value into 0 @@ -108,6 +119,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a -1 fp * @throws IOException */ + @Test public void testNeg1FP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse(fpSet.put(-1l)); @@ -118,6 +130,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a +1 fp * @throws IOException */ + @Test public void testPos1FP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse(fpSet.put(1l)); @@ -128,6 +141,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Tests if {@link DiskFPSet#diskLookup(long)} accepts a max fp * @throws IOException */ + @Test public void testMaxFP() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse(fpSet.put(Long.MAX_VALUE)); @@ -141,6 +155,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testValues() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); @@ -267,6 +282,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test @SuppressWarnings("deprecation") public void testDiskLookupWithFpOnLoPage() throws IOException { int freeMemory = 1000; // causes 16 in memory entries @@ -290,6 +306,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testMemLookupWithZeros() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -310,6 +327,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testMemLookupWithMin() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -330,6 +348,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testMemLookupWithMax() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration(.75d)); assertFalse(fpSet.memInsert(Long.MAX_VALUE)); @@ -342,6 +361,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithZeros() throws IOException { final long fp = 0L; @@ -376,6 +396,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithMin() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse(fpSet.memInsert(Long.MIN_VALUE & 0x7FFFFFFFFFFFFFFFL)); @@ -391,6 +412,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithMax() throws IOException { final DiskFPSet fpSet = (DiskFPSet) getFPSet(new FPSetConfiguration()); assertFalse(fpSet.memInsert(Long.MAX_VALUE)); @@ -405,6 +427,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithMaxOnPage() throws IOException { testDiskLookupOnPage(Long.MAX_VALUE); } @@ -414,6 +437,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithZerosOnPage() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -430,6 +454,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * * @throws IOException */ + @Test public void testDiskLookupWithLongMinValueOnPage() throws IOException { // skip known failures which aren't likely to be fixed anytime soon // @see https://bugzilla.tlaplus.net/show_bug.cgi?id=213 @@ -466,6 +491,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Test that both implementations - put(long) & putBlock(LongVec) - yield * the same results. */ + @Test public void testComparePutAndPutBlock() throws IOException { final FPSet putFpSet = (FPSet) getFPSetInitialized(); final FPSet putBlockFpSet = (FPSet) getFPSetInitialized(); @@ -483,6 +509,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { * Test that both implementations - contains(long) & containsBlock(LongVec) - yield * the same results. */ + @Test public void testCompareContainsAndContainsBlock() throws IOException { final FPSet containsFpSet = (FPSet) getFPSetInitialized(); final FPSet containsBlockFpSet = (FPSet) getFPSetInitialized(); @@ -496,6 +523,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { assertEquals(containsFpSet.contains(fp), containsBlockRes); } + @Test public void testContainsBlock() throws IOException { final FPSet fpSet = (FPSet) getFPSetInitialized(); @@ -512,6 +540,7 @@ public class ShortDiskFPSetTest extends AbstractFPSetTest { assertFalse(fpSet.containsBlock(fpv).get(0)); } + @Test public void testPutBlock() throws IOException { final FPSet fpSet = (FPSet) getFPSetInitialized(); diff --git a/tlatools/test/tlc2/tool/fp/iterator/TLCIteratorTest.java b/tlatools/test/tlc2/tool/fp/iterator/TLCIteratorTest.java index b15ac8788631cfe688dd4b14041b13412409e4d7..388222b1a5d444427bd29272e92cafb0b9e4b6d7 100644 --- a/tlatools/test/tlc2/tool/fp/iterator/TLCIteratorTest.java +++ b/tlatools/test/tlc2/tool/fp/iterator/TLCIteratorTest.java @@ -2,21 +2,25 @@ package tlc2.tool.fp.iterator; -import java.util.NoSuchElementException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.NoSuchElementException; +import org.junit.Before; +import org.junit.Test; import tlc2.tool.fp.MSBDiskFPSet; -import junit.framework.TestCase; - -public class TLCIteratorTest extends TestCase { +public class TLCIteratorTest { private MSBDiskFPSet.TLCIterator itr; /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { itr = new MSBDiskFPSet.TLCIterator(getBuffer()); } @@ -52,6 +56,7 @@ public class TLCIteratorTest extends TestCase { /** * Test method for {@link tlc2.tool.fp.TLCIterator#next()}. */ + @Test public void testNext() { long predecessor = -1l; @@ -72,6 +77,7 @@ public class TLCIteratorTest extends TestCase { /** * Test method for {@link tlc2.tool.fp.TLCIterator#next()}. */ + @Test public void testNoNext() { // read up all real elements while (itr.hasNext()) { @@ -91,6 +97,7 @@ public class TLCIteratorTest extends TestCase { /** * Test method for {@link tlc2.tool.fp.TLCIterator#getLast()}. */ + @Test public void testGetLast() { assertEquals(getLast(), itr.getLast()); } diff --git a/tlatools/test/tlc2/tool/liveness/AbstractDiskGraph.JPG b/tlatools/test/tlc2/tool/liveness/AbstractDiskGraph.JPG new file mode 100644 index 0000000000000000000000000000000000000000..f3e3c82c44e40dfe061993d47d498799596bb3c5 Binary files /dev/null and b/tlatools/test/tlc2/tool/liveness/AbstractDiskGraph.JPG differ diff --git a/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTest.java b/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d5c9e70b984438faa40d150fa4a287a9e4c5d119 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTest.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class ChooseTableauSymmetryTest extends ModelCheckerTestCase { + + public ChooseTableauSymmetryTest() { + super("ChooseTableauSymmetryMC", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "13", "6", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(7); + // Trace prefix + expectedTrace.add("arr = (a :> \"ready\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"busy\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"busy\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"done\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"ready\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(4, "<Action line 7, col 13 to line 8, col 47 of module ChooseTableauSymmetry>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTestA.java b/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTestA.java new file mode 100644 index 0000000000000000000000000000000000000000..048abf054cb1087b7e5bce0745d091680ba93572 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/ChooseTableauSymmetryTestA.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; + +public class ChooseTableauSymmetryTestA extends ModelCheckerTestCase { + + public ChooseTableauSymmetryTestA() { + super("ChooseTableauSymmetryMCa", "symmetry"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "13", "6", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(7); + // Trace prefix + expectedTrace.add("arr = (a :> \"ready\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"busy\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"busy\" @@ b :> \"busy\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"busy\")"); + expectedTrace.add("arr = (a :> \"ready\" @@ b :> \"busy\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(3, "<Action line 7, col 13 to line 8, col 47 of module ChooseTableauSymmetry>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08AgentRingTest.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08AgentRingTest.java index 3db7fa50cee308b072b1d8244c215d7f7f3e7f6e..bcf6e3d272753f95334669356e929b24453c813c 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08AgentRingTest.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08AgentRingTest.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,75 +43,68 @@ public class CodePlexBug08AgentRingTest extends ModelCheckerTestCase { super("AgentRingMC", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "361", "120")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(8496L, 2880L); // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); - - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 0, LastLoad |-> 0, ReadyToMove |-> TRUE, Task |-> 0]\n" + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 0, ReadyToMove |-> TRUE, Task |-> 0]\n" + "/\\ CanCreate = TRUE\n" - + "/\\ Nodes = (0 :> [Load |-> 0] @@ 1 :> [Load |-> 0])", - stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + + "/\\ Nodes = (0 :> [Load |-> 0] @@ 1 :> [Load |-> 0])"); + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 0, ReadyToMove |-> TRUE, Task |-> 0]\n" + "/\\ CanCreate = TRUE\n" - + "/\\ Nodes = (0 :> [Load |-> 0] @@ 1 :> [Load |-> 0])", - stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 0])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + "/\\ CanCreate = TRUE\n" - + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 0])", - stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 0])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + "/\\ CanCreate = TRUE\n" - + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])", - stateInfo.toString().trim()); - assertEquals(i, objs[1]); - + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); // The two states below violate the liveness property [](~CanCreate /\ // (\A i,j \in NodeRange : Nodes[i].Load = Nodes[j].Load) => // [](Agent.Task = 0)). State 5 has CanCreate = FALSE and Task=0 and // state six changes Task back to 1. - - // state 5 - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 0, ReadyToMove |-> FALSE, Task |-> 0]\n" + "/\\ CanCreate = FALSE\n" - + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])", - stateInfo.toString().trim()); - assertEquals(i, objs[1]); + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 1, ReadyToMove |-> TRUE, Task |-> 1]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 1])"); - // state 6 - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ Agent = [Loc |-> 1, LastLoad |-> 1, ReadyToMove |-> TRUE, Task |-> 1]\n" + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 1, ReadyToMove |-> FALSE, Task |-> 1]\n" + "/\\ CanCreate = FALSE\n" - + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 1])", - stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - // ...more states to follow + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 1])"); + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 2, ReadyToMove |-> TRUE, Task |-> 1]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 1])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 2, ReadyToMove |-> FALSE, Task |-> 1]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 1])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 2, ReadyToMove |-> TRUE, Task |-> 0]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 2, ReadyToMove |-> FALSE, Task |-> 0]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); + expectedTrace.add("/\\ Agent = [Loc |-> 0, LastLoad |-> 2, ReadyToMove |-> TRUE, Task |-> 0]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); + expectedTrace.add("/\\ Agent = [Loc |-> 1, LastLoad |-> 2, ReadyToMove |-> FALSE, Task |-> 0]\n" + + "/\\ CanCreate = FALSE\n" + + "/\\ Nodes = (0 :> [Load |-> 2] @@ 1 :> [Load |-> 2])"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(10); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL1Test.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL1Test.java index 066f3d233231e041f737e85090b72e6abb869c45..8d4a2ed62ed84c90ab3f37087c8f87298079ca8f 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL1Test.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL1Test.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,37 +43,64 @@ public class CodePlexBug08EWD840FL1Test extends ModelCheckerTestCase { super("EWD840MC1", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(7560068L, 279616L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); + final List<String> expectedTrace = new ArrayList<String>(); + expectedTrace.add("/\\ tpos = 0\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> TRUE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> TRUE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"black\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ tpos = 0\n" - + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" - + "/\\ tcolor = \"black\"\n" - + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")", - stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - // Omitted check of 8 in-between states, just make sure four more states exist - assertEquals("Expect five states prior to stuttering", 8, records.size()); - - // state six is back to state 1 - assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); - List<Object> stutter = recorder.getRecords(EC.TLC_BACK_TO_STATE); - assertTrue(stutter.size() > 0); - Object[] object = (Object[]) stutter.get(0); - assertEquals("1", object[0]); + assertBackToState(1); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL2Test.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL2Test.java index f38a278c40c879b6d4c8ebf4a5d4f9a2ca343ee3..2a57e3311e27088f4e8f209a9b413916d920854e 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL2Test.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL2Test.java @@ -26,10 +26,12 @@ package tlc2.tool.liveness; -import java.io.File; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import tlc2.TLCGlobals; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; /** @@ -41,35 +43,65 @@ public class CodePlexBug08EWD840FL2Test extends ModelCheckerTestCase { super("EWD840MC2", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566")); - + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566","0")); + assertFalse(recorder.recorded(EC.GENERAL)); + // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(54037212L, 831296L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(); + expectedTrace.add("/\\ tpos = 0\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> TRUE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> TRUE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"black\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); // last state points back to state 1 - assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); - List<Object> stutter = recorder.getRecords(EC.TLC_BACK_TO_STATE); - assertTrue(stutter.size() > 0); - Object[] object = (Object[]) stutter.get(0); - assertEquals("1", object[0]); - - // Check the file size of the AbstractDiskGraph files to check if the - // expected amount of ptrs and nodes (outgoing arcs) have been written - // to disk. - final String metadir = TLCGlobals.mainChecker.metadir; - assertNotNull(metadir); - final File nodes = new File(metadir + File.separator + "nodes_0"); - final File ptrs = new File(metadir + File.separator + "ptrs_0"); - assertTrue(nodes.exists()); - assertTrue(ptrs.exists()); - assertEquals(53958732L, nodes.length()); - assertEquals(831296L, ptrs.length()); + assertBackToState(1); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL3Test.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL3Test.java index 571312fb4417d3aa612999fcafd06ea1eae94057..36ef06dac38a083beb81ad9a6784f1853341ea96 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL3Test.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL3Test.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,37 +43,57 @@ public class CodePlexBug08EWD840FL3Test extends ModelCheckerTestCase { super("EWD840MC3", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(143808L, 25040L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); - - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ tpos = 0\n" - + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" - + "/\\ tcolor = \"black\"\n" - + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")", - stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - // Omitted check of 8 in-between states, just make sure four more states exist - assertEquals("Expect five states prior to stuttering", 8, records.size()); + final List<String> expectedTrace = new ArrayList<String>(); + expectedTrace.add("/\\ tpos = 0\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> FALSE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 2\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"white\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"black\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 1\n" + + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + + "/\\ tcolor = \"black\"\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); - // state eight points back to state 1 - assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); - List<Object> stutter = recorder.getRecords(EC.TLC_BACK_TO_STATE); - assertTrue(stutter.size() > 0); - Object[] object = (Object[]) stutter.get(0); - assertEquals("1", object[0]); + // last state loops back to state 1 + assertBackToState(1); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL4Test.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL4Test.java index 22074f3783d44c6e3488c5e77eb086905fa069d9..a011674c4f28875887dca87acfdcf6ea355ed046 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL4Test.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08EWD840FL4Test.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,42 +43,33 @@ public class CodePlexBug08EWD840FL4Test extends ModelCheckerTestCase { super("EWD840MC4", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "15986", "1566", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(135540L, 23456L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); - - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ tpos = 0\n" + final List<String> expectedTrace = new ArrayList<String>(); + expectedTrace.add("/\\ tpos = 0\n" + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + "/\\ tcolor = \"black\"\n" - + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")", - stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ tpos = 3\n" + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + expectedTrace.add("/\\ tpos = 3\n" + "/\\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE @@ 3 :> TRUE)\n" + "/\\ tcolor = \"white\"\n" - + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")", - stateInfo.toString().trim()); + + "/\\ color = (0 :> \"white\" @@ 1 :> \"white\" @@ 2 :> \"white\" @@ 3 :> \"white\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); // state 3 is stuttering - assertTrue(recorder.recorded(EC.TLC_STATE_PRINT3)); - List<Object> stutter = recorder.getRecords(EC.TLC_STATE_PRINT3); - assertTrue(stutter.size() > 0); - Object[] object = (Object[]) stutter.get(0); - assertEquals(3, object[1]); + assertStuttering(3); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08Test.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08Test.java index 3009401350f143c2068b1dee1b5c9e4ebbde03f4..4a34df33f3d03bf26e1efa8edea6f7beaa1a3cae 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08Test.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08Test.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,54 +43,30 @@ public class CodePlexBug08Test extends ModelCheckerTestCase { super("MC", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "18", "11")); - + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "18", "11", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(744L, 320L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 2"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 3"); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 3"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 4"); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 4"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 5"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 2", stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 3", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 3", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 4", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 4", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 5", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - // Assert the error trace contains a stuttering step at position 5 - assertTrue(recorder.recorded(EC.TLC_STATE_PRINT3)); - records = recorder.getRecords(EC.TLC_STATE_PRINT3); - objs = (Object[]) records.get(0); - assertEquals(++i, objs[1]); + assertStuttering(7); } } diff --git a/tlatools/test/tlc2/tool/liveness/CodePlexBug08aTest.java b/tlatools/test/tlc2/tool/liveness/CodePlexBug08aTest.java index e08f7c820af45c0992fbde3f271c88a20e66fa80..930a2f7d4dfd8bafa62f0a4394451ef002958cfe 100644 --- a/tlatools/test/tlc2/tool/liveness/CodePlexBug08aTest.java +++ b/tlatools/test/tlc2/tool/liveness/CodePlexBug08aTest.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; /** * see http://tlaplus.codeplex.com/workitem/8 @@ -40,64 +43,33 @@ public class CodePlexBug08aTest extends ModelCheckerTestCase { super("MCa", "CodePlexBug08"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "18", "11")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "18", "11", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertNodeAndPtrSizes(816L, 352L); + // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); - - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 1", stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 2", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 2", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 3", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 3", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 4", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = FALSE\n/\\ x = 4", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("/\\ b = TRUE\n/\\ x = 5", stateInfo.toString().trim()); - assertEquals(i, objs[1]); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 1"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 2"); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 2"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 3"); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 3"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 4"); + expectedTrace.add("/\\ b = FALSE\n/\\ x = 4"); + expectedTrace.add("/\\ b = TRUE\n/\\ x = 5"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); // Assert the error trace contains a stuttering step at position 5 - assertTrue(recorder.recorded(EC.TLC_STATE_PRINT3)); - records = recorder.getRecords(EC.TLC_STATE_PRINT3); - objs = (Object[]) records.get(0); - assertEquals(++i, objs[1]); + assertStuttering(9); } } diff --git a/tlatools/test/tlc2/tool/liveness/DiskGraphTest.java b/tlatools/test/tlc2/tool/liveness/DiskGraphTest.java index 8150f3bc59f1acdbc73de6c5b81fe84f649b319b..075943ff473499d5e7a7c68ed6b3259c11cd4c53 100644 --- a/tlatools/test/tlc2/tool/liveness/DiskGraphTest.java +++ b/tlatools/test/tlc2/tool/liveness/DiskGraphTest.java @@ -26,16 +26,21 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.File; import java.io.IOException; - -import junit.framework.TestCase; +import org.junit.Test; import tlc2.util.BitVector; import tlc2.util.LongVec; import tlc2.util.statistics.BucketStatistics; import tlc2.util.statistics.IBucketStatistics; -public class DiskGraphTest extends TestCase { +public class DiskGraphTest { private static final IBucketStatistics GRAPH_STATS = new BucketStatistics("Test Dummy", 16); private static final int NUMBER_OF_SOLUTIONS = 1; @@ -62,6 +67,7 @@ public class DiskGraphTest extends TestCase { } // No init node makes DiskGraph#getPath never break from the while loop + @Test public void testGetPathWithoutInitNoTableau() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); dg.addNode(new GraphNode(1L, NO_TABLEAU)); @@ -76,6 +82,7 @@ public class DiskGraphTest extends TestCase { // Create a linear minimal graph (2 nodes) and check if the graph is // returned by getPath afterwards. + @Test public void testGetMinimalPathWithoutTableau() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -121,6 +128,7 @@ public class DiskGraphTest extends TestCase { * * @see https://bugzilla.tlaplus.net/show_bug.cgi?id=293 */ + @Test public void testPathWithTwoInitNodes() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -168,6 +176,7 @@ public class DiskGraphTest extends TestCase { /* * Make sure the same logical node isn't counted twice. */ + @Test public void testAddSameGraphNodeTwice() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); dg.addNode(new GraphNode(1L, 1)); @@ -179,6 +188,7 @@ public class DiskGraphTest extends TestCase { /* * Test that it is possible to "update" a GraphNode's outgoing transitions. */ + @Test public void testLookupExistingNode() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -226,6 +236,7 @@ public class DiskGraphTest extends TestCase { * Test that adding a GraphNode twice (same fingerprint & tableau idx) but * with different successors afterwards yields the union of the successors. */ + @Test public void testAddSameGraphNodeTwiceCorrectSuccessors() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -266,6 +277,7 @@ public class DiskGraphTest extends TestCase { * checking runs periodically on an incomplete state/behavior graph, a * liveness violation is found and the path of the error trace gets explored. */ + @Test public void testGetPathPartialGraph() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); diff --git a/tlatools/test/tlc2/tool/liveness/ErrorTraceConstructionTest.java b/tlatools/test/tlc2/tool/liveness/ErrorTraceConstructionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5bd535ed4abb8153a033286bcfd92b02975c68a2 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/ErrorTraceConstructionTest.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; + +public class ErrorTraceConstructionTest extends ModelCheckerTestCase { + + public ErrorTraceConstructionTest() { + super("ErrorTraceConstructionMC", "symmetry"); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "9", "8", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(288L, 128L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = 0\n/\\ y = 0"); + expectedTrace.add("/\\ x = 0\n/\\ y = 1"); + expectedTrace.add("/\\ x = 0\n/\\ y = 2"); + expectedTrace.add("/\\ x = 0\n/\\ y = 3"); + expectedTrace.add("/\\ x = 0\n/\\ y = 4"); + expectedTrace.add("/\\ x = 1\n/\\ y = 5"); + expectedTrace.add("/\\ x = 0\n/\\ y = 6"); + expectedTrace.add("/\\ x = 0\n/\\ y = 7"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(4, "<Action line 32, col 7 to line 34, col 19 of module ErrorTraceConstruction>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/GraphNodeTest.java b/tlatools/test/tlc2/tool/liveness/GraphNodeTest.java index ac2592cf1a156f0bc61cec09c1f7823c4960dc1a..697d4208ddb488258c734e556be1a95f9e8d0d3d 100644 --- a/tlatools/test/tlc2/tool/liveness/GraphNodeTest.java +++ b/tlatools/test/tlc2/tool/liveness/GraphNodeTest.java @@ -26,14 +26,17 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.util.ArrayList; import java.util.List; import java.util.Random; +import org.junit.Test; -import junit.framework.TestCase; - -public class GraphNodeTest extends TestCase { +public class GraphNodeTest { + @Test public void testAllocateRealign() { // Create a random graph node (fingerprint/tableau don't matter) final GraphNode node = new GraphNode(0, 0); @@ -48,13 +51,14 @@ public class GraphNodeTest extends TestCase { int overallocated = node.realign(); assertTrue("Allocation overallocated", overallocated == 0); - assertTrue("Lost a transition during this allocation business", node.transExists(1, -1)); - assertTrue("Lost a transition during this allocation business", node.transExists(2, -1)); - assertTrue("Lost a transition during this allocation business", node.transExists(3, -1)); - assertTrue("Lost a transition during this allocation business", node.transExists(4, -1)); - assertTrue("Lost a transition during this allocation business", node.transExists(5, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(1, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(2, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(3, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(4, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(5, -1)); } + @Test public void testRealign() { // Create a random graph node (fingerprint/tableau don't matter) final GraphNode node = new GraphNode(0, 0); @@ -67,9 +71,10 @@ public class GraphNodeTest extends TestCase { overallocated = node.realign(); assertTrue("Allocation overallocated", overallocated == 0); - assertTrue("Lost a transition during this allocation business", node.transExists(1, -1)); + assertTrue("Lost a transition during the allocation business", node.transExists(1, -1)); } + @Test public void testAllocateNested() { // Create a random graph node (fingerprint/tableau don't matter) final GraphNode node = new GraphNode(0, 0); @@ -91,6 +96,7 @@ public class GraphNodeTest extends TestCase { } } + @Test public void testAllocateNestedRandom() { // Create a random graph node (fingerprint/tableau don't matter) final GraphNode node = new GraphNode(0, 0); @@ -117,6 +123,7 @@ public class GraphNodeTest extends TestCase { } } + @Test public void testAllocateNegative() { // Create a random graph node (fingerprint/tableau don't matter) final GraphNode node = new GraphNode(0, 0); @@ -124,6 +131,7 @@ public class GraphNodeTest extends TestCase { assertTrue("overallocated", node.realign() == 0); } + @Test public void testAllocateAndSuccessorSize() { // Hint to allocate 100 transitions and make sure the actual number of // transitions is 1. diff --git a/tlatools/test/tlc2/tool/liveness/LoopTest.java b/tlatools/test/tlc2/tool/liveness/LoopTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c7f47ce344067c8e74cfb4e6389832525404be49 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/LoopTest.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; + +/** + * System LOOP as described by Manna & Pneuli on page 423ff + */ +public class LoopTest extends ModelCheckerTestCase { + + public LoopTest() { + super("SystemLoop", "Loop"); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "4", "0")); + assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(136L, 64L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("x = 0"); + expectedTrace.add("x = 1"); + expectedTrace.add("x = 2"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + // Without any fairness defined, state 4 is stuttering instead of moving + // on to state x=3. + assertStuttering(4); + + //TODO This error trace is not the shortest one. The shortest one would + // be stuttering after the initial state x=0 and not after x=2 with x=3 + // as the last successor in the behavior. However, the SCC search + // implemented in LiveWorker#checkSccs checks the path end to start and + // not start to end. + // If liveness is (forcefully) triggered after the initial state, stuttering + // after the initial state is correctly detected. + } +} diff --git a/tlatools/test/tlc2/tool/liveness/LoopTestForcedPartial.java b/tlatools/test/tlc2/tool/liveness/LoopTestForcedPartial.java new file mode 100644 index 0000000000000000000000000000000000000000..394b6d461de7bb017d1b7d76294ee85fe883386b --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/LoopTestForcedPartial.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; +import tlc2.tool.AbstractChecker; + +/** + * Identical to {@link LoopTest}, except that liveness checking uses + * {@link AddAndCheckLiveCheck}. This way, TLC correctly produces the shortest + * possible counterexample. + */ +public class LoopTestForcedPartial extends ModelCheckerTestCase { + + static { + AbstractChecker.LIVENESS_TESTING_IMPLEMENTATION = true; + } + + public LoopTestForcedPartial() { + super("SystemLoop", "Loop"); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "4", "0")); + assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("x = 0"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + // Stuttering after the init state. + assertStuttering(2); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/LoopTestWeakFair.java b/tlatools/test/tlc2/tool/liveness/LoopTestWeakFair.java new file mode 100644 index 0000000000000000000000000000000000000000..ee4551a195af734164f556d768dd267e3ec05e30 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/LoopTestWeakFair.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; + +/** + * System LOOP as described by Manna & Pneuli on page 423ff + */ +public class LoopTestWeakFair extends ModelCheckerTestCase { + + public LoopTestWeakFair() { + super("SystemLoop", "Loop", new String[] { "-config", "SystemLoopWeakFair" }); + } + + @Test + public void testSpec() { + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "4", "0")); + assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); + assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "4")); + + assertTrue(recorder.recordedWithStringValues(EC.TLC_CHECKING_TEMPORAL_PROPS, "complete", "4")); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/ModelCheckerTestCase.java b/tlatools/test/tlc2/tool/liveness/ModelCheckerTestCase.java index ba50d0b6e2c57183fc956e6939d662b7abb8edeb..f2c1beaa873b916e9068e97d88371a2ac18dca22 100644 --- a/tlatools/test/tlc2/tool/liveness/ModelCheckerTestCase.java +++ b/tlatools/test/tlc2/tool/liveness/ModelCheckerTestCase.java @@ -25,26 +25,36 @@ ******************************************************************************/ package tlc2.tool.liveness; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; - -import junit.framework.TestCase; +import org.junit.Before; import tlc2.TLC; +import tlc2.TLCGlobals; import tlc2.TestMPRecorder; +import tlc2.output.EC; import tlc2.output.MP; +import tlc2.output.MPRecorder; +import tlc2.tool.TLCStateInfo; +import tlc2.util.BitVector; +import tlc2.util.BufferedRandomAccessFile; import util.ToolIO; -public abstract class ModelCheckerTestCase extends TestCase { +public abstract class ModelCheckerTestCase { - private static final String BASE_DIR = System.getProperty("basedir", ""); - private static final String TEST_MODEL = "test-model" + File.separator; + protected static final String BASE_DIR = System.getProperty("basedir", ""); + protected static final String TEST_MODEL = "test-model" + File.separator; - private String path = ""; - private final String spec; + protected String path = ""; + protected final String spec; protected final TestMPRecorder recorder = new TestMPRecorder(); - private String[] extraArguments = new String[0]; + protected String[] extraArguments = new String[0]; public ModelCheckerTestCase(String spec) { @@ -61,6 +71,10 @@ public abstract class ModelCheckerTestCase extends TestCase { this.extraArguments = extraArguments; } + /* (non-Javadoc) + * @see junit.framework.TestCase#setUp() + */ + @Before public void setUp() { try { // TEST_MODEL is where TLC should look for user defined .tla files @@ -68,6 +82,13 @@ public abstract class ModelCheckerTestCase extends TestCase { MP.setRecorder(recorder); + // Increase the liveness checking threshold to prevent liveness + // checking of an incomplete graph. Most tests check that the + // state queue is empty and fail if not. This is only given + // when liveness checking is executed when all states have been + // generated. + TLCGlobals.livenessThreshold = Double.MAX_VALUE; + final TLC tlc = new TLC(); // * We want *no* deadlock checking to find the violation of the // temporal formula @@ -103,7 +124,119 @@ public abstract class ModelCheckerTestCase extends TestCase { } } + /** + * @return The number of worker threads TLC should use. + */ protected int getNumberOfThreads() { return 1; } + + /** + * Asserts that the actual trace and the expected error trace are equal. + * + * @param actual + * The actual trace as recorded by {@link MPRecorder}. + * @param expectedTrace + * The expected trace. + */ + protected void assertTraceWith(final List<Object> actual, final List<String> expectedTrace) { + assertEquals(expectedTrace.size(), actual.size()); + for (int i = 0; i < expectedTrace.size(); i++) { + final Object[] objs = (Object[]) actual.get(i); + final TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; + final String info = (String) stateInfo.info; + if (i == 0) { + // The first state has to be an initial state. + "<Initial predicate>".equals(info); + } else { + // ... all others are reachable via an action. + info.startsWith("<Action"); + } + assertEquals(expectedTrace.get(i), + stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace + assertEquals(i+1, objs[1]); + } + } + + /** + * Asserts that the error trace ends in stuttering at the given number. + * + * @param stateNum + * The number of the stuttering state + */ + protected void assertStuttering(int stateNum) { + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT3)); + List<Object> stutter = recorder.getRecords(EC.TLC_STATE_PRINT3); + assertTrue(stutter.size() > 0); + Object[] object = (Object[]) stutter.get(0); + assertEquals(stateNum, object[1]); + } + + /** + * Asserts that the error trace loops back to the state with the given + * number. + * + * @param i The loop back state number. + */ + protected void assertBackToState(int stateNum) { + assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); + List<Object> loop = recorder.getRecords(EC.TLC_BACK_TO_STATE); + assertTrue(loop.size() > 0); + Object[] object = (Object[]) loop.get(0); + assertEquals(Integer.toString(stateNum), object[0]); + } + + /** + * Asserts that the error trace loops back to the state with the given + * number. + * + * @param i The loop back state number. + * @param action The action label associated with the loop back marker + */ + protected void assertBackToState(int stateNum, final String action) { + assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); + List<Object> loop = recorder.getRecords(EC.TLC_BACK_TO_STATE); + assertTrue(loop.size() > 0); + Object[] object = (Object[]) loop.get(0); + assertTrue(object.length > 1); + assertEquals(Integer.toString(stateNum), object[0]); + assertEquals(action, object[1]); + } + + /** + * Check the file size of the AbstractDiskGraph files to assert that the + * expected amount of ptrs and nodes (outgoing arcs) have been written to + * disk. + * <p> + * CAUTION: The order in which the transitions are inserted into the + * {@link GraphNode} determines the size of the {@link BitVector}. I.e. if + * the truth values of the first N nodes inserted are true, and the + * remainder is false, the BitVector's size will correspond to N. However, + * if the first N truth values are false, followed by M trues, the + * BitVector's size is N + M. + * <p> + * See {@link GraphNode}'s constructor: it initializes {@link BitVector} + * with capacity zero and subsequently grows BV when bits are set to true. + * <p> + * + * @see BitVector#read(BufferedRandomAccessFile) + * @see BitVector#write(BufferedRandomAccessFile) + * @see GraphNode#read(BufferedRandomAccessFile) + * @see GraphNode#write(BufferedRandomAccessFile) + * + * @param nodesSize + * @param ptrsSize + */ + protected void assertNodeAndPtrSizes(final long nodesSize, final long ptrsSize) { + final String metadir = TLCGlobals.mainChecker.metadir; + assertNotNull(metadir); + + final File nodes = new File(metadir + File.separator + "nodes_0"); + assertTrue(nodes.exists()); + assertEquals(nodesSize, nodes.length()); + + final File ptrs = new File(metadir + File.separator + "ptrs_0"); + assertTrue(ptrs.exists()); + assertEquals(ptrsSize, ptrs.length()); + } } diff --git a/tlatools/test/tlc2/tool/liveness/NQTest.java b/tlatools/test/tlc2/tool/liveness/NQTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3aedf8cf1d6f973e685bc3f5dd830518ea327183 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/NQTest.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Ignore; +import org.junit.Test; + +import tlc2.output.EC; + +public class NQTest extends ModelCheckerTestCase { + + public NQTest() { + super("MC", "symmetry" + File.separator + "NQ"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "1049", "363", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has *not* found a temporal violation and a counter example + assertFalse(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertFalse(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // Assert *no* error trace + assertFalse(recorder.recorded(EC.TLC_STATE_PRINT2)); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/NQaTest.java b/tlatools/test/tlc2/tool/liveness/NQaTest.java new file mode 100644 index 0000000000000000000000000000000000000000..739c1226fd9597a1faa9a5492c87d856062744f2 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/NQaTest.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import tlc2.output.EC; + +public class NQaTest extends ModelCheckerTestCase { + + public NQaTest() { + super("MCa", "symmetry" + File.separator + "NQ"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "1049", "363", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // Assert an error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + + fail("Check actual error trace and its completeness"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/NoSymmetryTableauModelCheckerTest.java b/tlatools/test/tlc2/tool/liveness/NoSymmetryTableauModelCheckerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b28c4cad270e16fceca6cf4c22c2537013ec8d52 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/NoSymmetryTableauModelCheckerTest.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; + +public class NoSymmetryTableauModelCheckerTest extends ModelCheckerTestCase { + + public NoSymmetryTableauModelCheckerTest() { + super("NoSymmetryLivenessTableauMC", "symmetry"); + } + + @Test + public void testSpec() { + // ModelChecker intends to check liveness + assertTrue(recorder.recordedWithStringValues(EC.TLC_LIVE_IMPLIED, "2")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_INIT_GENERATED1, "8", "s")); + + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5492", "1272", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has not found a temporal violation nor a counter example + assertFalse(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertFalse(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + assertFalse(recorder.recorded(EC.TLC_STATE_PRINT2)); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/OneBitMutexNoSymmetryTest.java b/tlatools/test/tlc2/tool/liveness/OneBitMutexNoSymmetryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a441bfd8f925cee1dad70207b17f1366393c63f2 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/OneBitMutexNoSymmetryTest.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; + +public class OneBitMutexNoSymmetryTest extends ModelCheckerTestCase { + + public OneBitMutexNoSymmetryTest() { + super("OneBitMutexNoSymmetryMC", "symmetry" + File.separator + "OneBitMutex"); + } + + @Test + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "244", "127", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(11700L, 3728L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(17); + //1 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"ncs\" @@ B :> \"ncs\")"); + //2 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"ncs\")"); + //3 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e1\")"); + //4 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {A})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e2\")"); + //5 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e3\")"); + //6 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e2\")"); + //7 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"cs\")"); + //8 (Loops back to) + expectedTrace.add("/\\ unchecked = (A :> {B} @@ B :> {})\n" + + "/\\ other = (A :> A @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e2\" @@ B :> \"cs\")"); + //9 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"cs\")"); + //10 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"f\")"); + //11 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e4\" @@ B :> \"f\")"); + //12 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e4\" @@ B :> \"ncs\")"); + //13 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e4\" @@ B :> \"e1\")"); + //14 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e5\" @@ B :> \"e1\")"); + //15 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e1\")"); + //16 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {A})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e2\")"); + //17 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e3\")"); + //18 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e2\")"); + //19 + expectedTrace.add("/\\ unchecked = (A :> {B} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e2\" @@ B :> \"e2\")"); + //20 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"e2\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(9, "<Action line 66, col 13 to line 74, col 21 of module OneBitMutex>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/OneBitMutexTest.java b/tlatools/test/tlc2/tool/liveness/OneBitMutexTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c4a415d850628aee2dc1145d8e0f637a79884254 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/OneBitMutexTest.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class OneBitMutexTest extends ModelCheckerTestCase { + + public OneBitMutexTest() { + super("OneBitMutexMC", "symmetry" + File.separator + "OneBitMutex"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "133", "68", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(7380L, 2368L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(17); + //1 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"ncs\" @@ B :> \"ncs\")"); + //2 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"ncs\")"); + //3 + expectedTrace.add("/\\ unchecked = (A :> {B} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e2\" @@ B :> \"ncs\")"); + //4 + expectedTrace.add("/\\ unchecked = (A :> {B} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e2\" @@ B :> \"e1\")"); + //5 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"e1\")"); + //6 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {A})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"e2\")"); + //7 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e3\" @@ B :> \"e3\")"); + //8 (Loops back to) + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e4\" @@ B :> \"e3\")"); + //9 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> TRUE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e4\" @@ B :> \"e4\")"); + //10 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> TRUE)\n" + + "/\\ pc = (A :> \"e5\" @@ B :> \"e4\")"); + //11 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e5\" @@ B :> \"e5\")"); + //12 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e5\")"); + //13 + expectedTrace.add("/\\ unchecked = (A :> {} @@ B :> {})\n" + + "/\\ other = (A :> B @@ B :> A)\n" + + "/\\ x = (A :> FALSE @@ B :> FALSE)\n" + + "/\\ pc = (A :> \"e1\" @@ B :> \"e1\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(4, "<Action line 60, col 13 to line 64, col 29 of module OneBitMutex>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3.java b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3.java new file mode 100644 index 0000000000000000000000000000000000000000..37fc9bc8de07c79abe2adb8578bac5983448f5e8 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class SymmetryModelCheckerTest3 extends ModelCheckerTestCase { + + public SymmetryModelCheckerTest3() { + super("MC", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + // Violates Prop1 with Spec2. TLC finds no violation. + // + // The reason why TLC does not find the violation is due to the fact that + // the action predicate from state s to t is not being taken into + // consideration during the liveness graph checks. It is not + // being considered because it is a redundant and thus omitted arc in + // the liveness graph, but its label (which corresponds to the action + // property evaluation) is different. + // + // Without symmetry, the liveness graph consists of four distinct nodes + // {(x=a, y=0), (x=a, y=1), (x=b, y=0), (x=b, y=1)} with transitions + // (ignoring self-loops) from {1:2, 2:1, 2:3, 3:4, 4:3, 4:1}. The arc + // that violates Prop1 (<>[][x'=x] "x does not change") is the trans. + // from node 2. (x=a, y=1) to node 3. (x=b, y=0). TLC correctly finds + // a violation and produces the correct counterexample which is asserted + // below. + // + // See test-model/symmetry/MC_Graph.png for a visualization. + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "2", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(84L, 32L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = a\n/\\ y = 0"); + expectedTrace.add("/\\ x = a\n/\\ y = 1"); // <= x changes after this state + expectedTrace.add("/\\ x = b\n/\\ y = 0"); + expectedTrace.add("/\\ x = b\n/\\ y = 1"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(1, "<Action line 27, col 12 to line 29, col 27 of module SymmetryLiveness3>"); + + // With symmetry defined, the state/liveness graph is collapsed into + // just two states: {(x=a, y=0), (x=b, y=1)} and transitions going + // from {1:2, 2:1} (self-loops omitted). Thus, TLC never checks the + // transition from (x=a, y=1) -> (x=b, y=0) which violates Prop1. + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3a.java b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3a.java new file mode 100644 index 0000000000000000000000000000000000000000..d414072e49a74cdf6405d624287d8802ae92a5c4 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTest3a.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class SymmetryModelCheckerTest3a extends ModelCheckerTestCase { + + public SymmetryModelCheckerTest3a() { + super("MCa", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + // Violates Prop1 of Spec1 which TLC correctly detects. However, it + // produced a bogus counterexample. It does not use the symmetric state + // that is actually supposed to be on the error trace. It uses the + // smallest state under symmetry (the one all symmetric states get + // mapped to). + // + // Spec1 and its action N1 more precisely can never make Prop1 eventually + // _globally_ true. The second disjunct flips x's state forever. This + // violation is correctly detected by TLC. However, due to the fact that + // the x=b state is symmetry reduced and thus discarded from the + // liveness graph causes the counter-example to incorrectly show a + // violating state with x=a (where x=a logically does not violate + // Prop1). + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "4", "2", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(72L, 32L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = a\n/\\ y = 0"); + expectedTrace.add("/\\ x = a\n/\\ y = 1"); // <= x changes after this state + expectedTrace.add("/\\ x = b\n/\\ y = 0"); + expectedTrace.add("/\\ x = b\n/\\ y = 1"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(1); + + // Surprisingly enough, it turns out that LiveWorker#printTrace actually + // generates a partially valid trace but simply fails to correctly print + // it. It omits the final/last state with x=b from being printed. The + // reason why printTrace(..) correctly generates the states is due to + // the way the error trace gets generated. Starting from an init state, + // it uses the predecessor and the successors fingerprint to generate + // the successor state. For this model this means the error trace + // contains the states <<(x=a, y=0), (x=a, y=1), (x=b, y=0)>>. The state + // (x=b, y=0) is generated from its fingerprint f and its predecessor + // state (x=a, y=1) AND the action N1. Since N1 mandates that x flips, + // tool generates a state with x=b despite the declared symmetry. + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLong.java b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLong.java new file mode 100644 index 0000000000000000000000000000000000000000..d51282b3c8fd20899c11ed906ea75fe439d5b7f9 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLong.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class SymmetryModelCheckerTestLong extends ModelCheckerTestCase { + + public SymmetryModelCheckerTestLong() { + super("LongMC", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "8", "5", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(192L, 80L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = a\n/\\ y = 0"); + expectedTrace.add("/\\ x = a\n/\\ y = 1"); + expectedTrace.add("/\\ x = a\n/\\ y = 2"); + expectedTrace.add("/\\ x = a\n/\\ y = 3"); + expectedTrace.add("/\\ x = a\n/\\ y = 4"); // <= x changes after this state + expectedTrace.add("/\\ x = b\n/\\ y = 0"); + expectedTrace.add("/\\ x = b\n/\\ y = 1"); + expectedTrace.add("/\\ x = b\n/\\ y = 2"); + expectedTrace.add("/\\ x = b\n/\\ y = 3"); + expectedTrace.add("/\\ x = b\n/\\ y = 4"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(1, "<Action line 47, col 12 to line 49, col 27 of module SymmetryLivenessLong>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLonga.java b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLonga.java new file mode 100644 index 0000000000000000000000000000000000000000..817e4334851ee6f9c836b87a6a393ed8689624b6 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryModelCheckerTestLonga.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class SymmetryModelCheckerTestLonga extends ModelCheckerTestCase { + + public SymmetryModelCheckerTestLonga() { + super("LongMCa", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "7", "5", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(180L, 80L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("/\\ x = a\n/\\ y = 0"); + expectedTrace.add("/\\ x = a\n/\\ y = 1"); + expectedTrace.add("/\\ x = a\n/\\ y = 2"); + expectedTrace.add("/\\ x = a\n/\\ y = 3"); + expectedTrace.add("/\\ x = a\n/\\ y = 4"); // <= x changes after this state + expectedTrace.add("/\\ x = b\n/\\ y = 0"); + expectedTrace.add("/\\ x = b\n/\\ y = 1"); + expectedTrace.add("/\\ x = b\n/\\ y = 2"); + expectedTrace.add("/\\ x = b\n/\\ y = 3"); + expectedTrace.add("/\\ x = b\n/\\ y = 4"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(1, "<Action line 25, col 12 to line 27, col 27 of module SymmetryLivenessLong>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryTableauLiveCheckTest.java b/tlatools/test/tlc2/tool/liveness/SymmetryTableauLiveCheckTest.java new file mode 100644 index 0000000000000000000000000000000000000000..662f1a3a3da9f1d3d78c65e6bc35af48eac26cb5 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryTableauLiveCheckTest.java @@ -0,0 +1,373 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Set; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import tlc2.tool.Action; +import tlc2.tool.StateVec; +import tlc2.tool.TLCState; +import tlc2.tool.Tool; +import tlc2.tool.liveness.GraphNode.Transition; +import tlc2.tool.queue.DummyTLCState; +import tlc2.util.BitVector; +import tlc2.util.SetOfStates; +import tlc2.util.statistics.DummyBucketStatistics; + +public class SymmetryTableauLiveCheckTest { + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testTableau() throws IOException { + final ILiveCheck lc = getLiveCheckWithTwoNodeTableau(); + + final SetOfStates nexts = new SetOfStates(1); + final AbstractDiskGraph diskGraph = lc.getChecker(0).getDiskGraph(); + + // Add init state v + final TLCState v = new DummyTLCState(100L); + lc.addInitState(v, v.fingerPrint()); + + // one init node (two elements in LongVec) + assertEquals(1, diskGraph.getInitNodes().size() / 2); + + // Add v > s + final TLCState s = new DummyTLCState(200L); + nexts.put(s); + lc.addNextState(v, v.fingerPrint(), nexts); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); // only tidx0 is an init node + + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 1).succSize()); + + // Add s > t + nexts.clear(); + final TLCState t = new DummyTLCState(300L); + nexts.put(t); + lc.addNextState(s, s.fingerPrint(), nexts); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); + + assertEquals(2, diskGraph.getNode(s.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 1).succSize()); + + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 1).succSize()); + + // add s > u + nexts.clear(); + final TLCState u = new DummyTLCState(400L); + nexts.put(u); + lc.addNextState(s, s.fingerPrint(), nexts); + + Assert.fail("finish incomplete test! Assertions below are partially bogus."); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); + + assertEquals(4, diskGraph.getNode(s.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 1).succSize()); + + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 1).succSize()); + + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 1).succSize()); + + assertTrue(diskGraph.checkInvariants(0, 0)); + } + + private ILiveCheck getLiveCheckWithTwoNodeTableau() throws IOException { + final TBGraphNode node1 = EasyMock.createNiceMock(TBGraphNode.class); + EasyMock.expect(node1.isConsistent((TLCState) EasyMock.anyObject(), (Tool) EasyMock.anyObject())) + .andReturn(true).anyTimes(); + EasyMock.expect(node1.nextSize()).andReturn(0).anyTimes(); + EasyMock.expect(node1.getIndex()).andReturn(1).anyTimes(); + EasyMock.replay(node1); + + final TBGraphNode node0 = EasyMock.createMock(TBGraphNode.class); + EasyMock.expect(node0.isConsistent((TLCState) EasyMock.anyObject(), (Tool) EasyMock.anyObject())) + .andReturn(true).anyTimes(); + EasyMock.expect(node0.nextSize()).andReturn(2).anyTimes(); + EasyMock.expect(node0.nextAt(0)).andReturn(node0).anyTimes(); + EasyMock.expect(node0.nextAt(1)).andReturn(node1).anyTimes(); + EasyMock.expect(node0.getIndex()).andReturn(0).anyTimes(); + EasyMock.replay(node0); + + final TBGraph tbGraph = new TBGraph(null); + tbGraph.addElement(node0); + tbGraph.addElement(node1); + tbGraph.setInitCnt(1); + + // Configure OOS mock to react to the subsequent invocation. This is a + // essentially the list of calls being made on OOS during + // LiveCheck#addInitState and LiveCheck#addNextState + final OrderOfSolution oos = EasyMock.createNiceMock(OrderOfSolution.class); + EasyMock.expect(oos.hasTableau()).andReturn(true); + EasyMock.expect(oos.getTableau()).andReturn(tbGraph).anyTimes(); + EasyMock.expect(oos.getCheckAction()).andReturn(new LiveExprNode[0]).anyTimes(); + EasyMock.expect(oos.getCheckState()).andReturn(new LiveExprNode[0]).anyTimes(); + EasyMock.expect(oos.checkState((TLCState) EasyMock.anyObject())).andReturn(new boolean[0]).anyTimes(); + EasyMock.replay(oos); + + return new LiveCheck(EasyMock.createNiceMock(Tool.class), new Action[0], + new OrderOfSolution[] { oos }, System.getProperty("java.io.tmpdir"), new DummyBucketStatistics()); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSymmetry() throws IOException { + final TLCState s = new DummyTLCState(200L); + final TLCState s1 = new DummyTLCState(s.fingerPrint()); // symmetric sibling of s + final TLCState t = new DummyTLCState(300L); + + final ILiveCheck lc = getLiveCheckWithTwoNodeTableauSymmetry(s, s1, t); + + final SetOfStates nexts = new SetOfStates(1); + final AbstractDiskGraph diskGraph = lc.getChecker(0).getDiskGraph(); + + // Add init state v + final TLCState v = new DummyTLCState(100L); + lc.addInitState(v, v.fingerPrint()); + + // one init node (two elements in LongVec) + assertEquals(1, diskGraph.getInitNodes().size() / 2); + + // Add v > s + nexts.put(s); + lc.addNextState(v, v.fingerPrint(), nexts); + + GraphNode vgn = diskGraph.getNode(v.fingerPrint(), 0); + assertEquals(2, vgn.succSize()); // s is consistent with tidx0 and tidx1, but not with tidx2 + final Set<Transition> tvgn = vgn.getTransition(); + assertTrue(tvgn.contains(new Transition(s.fingerPrint(), 0, new BitVector(0)))); + assertTrue(tvgn.contains(new Transition(s.fingerPrint(), 1, new BitVector(0)))); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); // only tidx0 is an init node + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 2).succSize()); // only tidx0 is an init node + + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 2).succSize()); + + // Add s > t + nexts.clear(); + nexts.put(t); + lc.addNextState(s, s.fingerPrint(), nexts); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 2).succSize()); + + // Even though t is consistent with all three tableau nodes (0,1,2), + // (fingerprint X tableau idx) node <<200.0>> checks only its direct + // succeccors tidx0, tidx1. Not tidx2; + assertEquals(2, diskGraph.getNode(s.fingerPrint(), 0).succSize()); // 300.0, 300.1 + // <<200,1>> checks tidx1 and tidx2 + assertEquals(2, diskGraph.getNode(s.fingerPrint(), 1).succSize()); // 300.1, 300.2 + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 2).succSize()); + + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 2).succSize()); + + // Add additional init state u + final TLCState u = new DummyTLCState(400L); + lc.addInitState(u, u.fingerPrint()); + + // two init nodes now (four elements in LongVec) + assertEquals(2, diskGraph.getInitNodes().size() / 2); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 2).succSize()); + + assertEquals(2, diskGraph.getNode(s.fingerPrint(), 0).succSize()); + assertEquals(2, diskGraph.getNode(s.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(s.fingerPrint(), 2).succSize()); + + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 2).succSize()); + + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 2).succSize()); + + // add a symmetric s1 (same fingerprint as s) + nexts.clear(); + nexts.put(s1); + lc.addNextState(u, u.fingerPrint(), nexts); + + assertEquals(2, diskGraph.getNode(v.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(v.fingerPrint(), 2).succSize()); + + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(t.fingerPrint(), 2).succSize()); + + assertEquals(1, diskGraph.getNode(u.fingerPrint(), 0).succSize()); + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 1).succSize()); + assertEquals(0, diskGraph.getNode(u.fingerPrint(), 2).succSize()); + + Assert.fail("finish incomplete test! Assertions below are partially bogus."); + + //TODO GN are equals in terms of <<state, tidx>> but not necessarily transitions + assertEquals(diskGraph.getNode(s1.fingerPrint(), 0), diskGraph.getNode(s.fingerPrint(), 0)); + assertEquals(diskGraph.getNode(s1.fingerPrint(), 1), diskGraph.getNode(s.fingerPrint(), 1)); + assertEquals(diskGraph.getNode(s1.fingerPrint(), 2), diskGraph.getNode(s.fingerPrint(), 2)); + + // s1 and s are represented by the same GraphNode + final GraphNode node200_0 = diskGraph.getNode(s.fingerPrint(), 0); + assertEquals(2, node200_0.succSize()); + final Set<Transition> transitions200_0 = node200_0.getTransition(); + assertTrue(transitions200_0.contains(new Transition(t.fingerPrint(), 0, new BitVector(0)))); + assertTrue(transitions200_0.contains(new Transition(t.fingerPrint(), 1, new BitVector(0)))); + + final GraphNode node200_1 = diskGraph.getNode(s.fingerPrint(), 1); + assertEquals(2, node200_1.succSize()); + final Set<Transition> transitions200_1 = node200_1.getTransition(); + assertTrue(transitions200_1.contains(new Transition(t.fingerPrint(), 1, new BitVector(0)))); + assertTrue(transitions200_1.contains(new Transition(t.fingerPrint(), 2, new BitVector(0)))); + + final GraphNode node200_2 = diskGraph.getNode(s.fingerPrint(), 2); + assertEquals(0, node200_2.succSize()); + + assertTrue(diskGraph.checkInvariants(0, 0)); + } + + /** + * @param s The smallest state under symmetry + * @param sSymmetric A symmetric state to s + */ + @SuppressWarnings("deprecation") + private ILiveCheck getLiveCheckWithTwoNodeTableauSymmetry(final TLCState s, final TLCState sSymmetric, final TLCState t) throws IOException { + final TBGraphNode node2 = EasyMock.createMock(TBGraphNode.class); + // consistency + final Capture<TLCState> capture = new Capture<TLCState>(); + EasyMock.expect(node2.isConsistent(EasyMock.capture(capture), (Tool) EasyMock.anyObject())).andAnswer(new IAnswer<Boolean>() { + public Boolean answer() throws Throwable { + final TLCState value = capture.getValue(); + if (value == s) { + return false; + } + return true; + } + }).anyTimes(); + // index + EasyMock.expect(node2.getIndex()).andReturn(2).anyTimes(); + // nextSize + EasyMock.expect(node2.nextSize()).andReturn(1).anyTimes(); + // nextAt + EasyMock.expect(node2.nextAt(0)).andReturn(node2).anyTimes(); + EasyMock.replay(node2); + + final TBGraphNode node1 = EasyMock.createMock(TBGraphNode.class); + // consistency + final Capture<TLCState> capture1 = new Capture<TLCState>(); + EasyMock.expect(node1.isConsistent(EasyMock.capture(capture1), (Tool) EasyMock.anyObject())).andAnswer(new IAnswer<Boolean>() { + public Boolean answer() throws Throwable { + final TLCState value = capture1.getValue(); + if (value == sSymmetric) { + return false; + } + return true; + } + }).anyTimes(); + // index + EasyMock.expect(node1.getIndex()).andReturn(1).anyTimes(); + // nextSize + EasyMock.expect(node1.nextSize()).andReturn(2).anyTimes(); + // nextAt + EasyMock.expect(node1.nextAt(0)).andReturn(node1).anyTimes(); + EasyMock.expect(node1.nextAt(1)).andReturn(node2).anyTimes(); + EasyMock.replay(node1); + + final TBGraphNode node0 = EasyMock.createMock(TBGraphNode.class); + // consistency (simpler to node1 and node2) + EasyMock.expect(node0.isConsistent((TLCState) EasyMock.anyObject(), (Tool) EasyMock.anyObject())).andReturn(true).anyTimes(); + // index + EasyMock.expect(node0.getIndex()).andReturn(0).anyTimes(); + // nextSize + EasyMock.expect(node0.nextSize()).andReturn(2).anyTimes(); + // nextAt + EasyMock.expect(node0.nextAt(0)).andReturn(node0).anyTimes(); + EasyMock.expect(node0.nextAt(1)).andReturn(node1).anyTimes(); + EasyMock.replay(node0); + + final TBGraph tbGraph = new TBGraph(null); + tbGraph.addElement(node0); + tbGraph.addElement(node1); + tbGraph.addElement(node2); + tbGraph.setInitCnt(1); + + // Configure OOS mock to react to the subsequent invocation. This is a + // essentially the list of calls being made on OOS during + // LiveCheck#addInitState and LiveCheck#addNextState + final OrderOfSolution oos = EasyMock.createNiceMock(OrderOfSolution.class); + EasyMock.expect(oos.hasTableau()).andReturn(true); + EasyMock.expect(oos.getTableau()).andReturn(tbGraph).anyTimes(); + EasyMock.expect(oos.getCheckAction()).andReturn(new LiveExprNode[0]).anyTimes(); + EasyMock.expect(oos.getCheckState()).andReturn(new LiveExprNode[0]).anyTimes(); + EasyMock.expect(oos.checkState((TLCState) EasyMock.anyObject())).andReturn(new boolean[0]).anyTimes(); + EasyMock.replay(oos); + + final Tool tool = EasyMock.createNiceMock(Tool.class); + EasyMock.expect(tool.hasSymmetry()).andReturn(true); + final Capture<TLCState> nextStates = new Capture<TLCState>(); + EasyMock.expect(tool.getNextStates((Action) EasyMock.anyObject(), EasyMock.capture(nextStates))).andAnswer(new IAnswer<StateVec>() { + public StateVec answer() throws Throwable { + final StateVec nss = new StateVec(0); + // s > t for sSymmetric + TLCState state = nextStates.getValue(); + if (state == sSymmetric) { + nss.addElement(t); + } + return nss; + } + }); + EasyMock.expect(tool.isInModel((TLCState) EasyMock.anyObject())).andReturn(true).anyTimes(); + EasyMock.expect(tool.isInActions((TLCState) EasyMock.anyObject(), (TLCState) EasyMock.anyObject())).andReturn(true).anyTimes(); + EasyMock.replay(tool); + return new LiveCheck(tool, new Action[1], + new OrderOfSolution[] { oos }, "states", new DummyBucketStatistics()); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/SymmetryTableauModelCheckerTest.java b/tlatools/test/tlc2/tool/liveness/SymmetryTableauModelCheckerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9dd871594013378af673351e96b21a8272e3b1c0 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/SymmetryTableauModelCheckerTest.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertTrue; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class SymmetryTableauModelCheckerTest extends ModelCheckerTestCase { + + public SymmetryTableauModelCheckerTest() { + super("SymmetryLivenessTableauMC", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + // ModelChecker intends to check liveness + assertTrue(recorder.recordedWithStringValues(EC.TLC_LIVE_IMPLIED, "2")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_INIT_GENERATED2, "8", "s", "2")); + + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "779", "168", "0")); + + // Assert it has found a temporal violation and a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + // The spec's 'NoVal' value is what violates symmetry. + assertTrue(recorder.recordedWithStringValue(EC.GENERAL, + "TLC threw an unexpected exception.\n" + + "This was probably caused by an error in the spec or model.\n" + + "The error occurred when TLC was checking liveness.\n" + + "The exception was a tlc2.tool.EvalException\n" + + ": Failed to recover the next state from its fingerprint during\n" + + "liveness error trace re-construction. This indicates that the\n" + + "spec is in fact not symmetric (Please report a TLC bug if the\n" + + "spec is known to be symmetric).")); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/TableauDiskGraphTest.java b/tlatools/test/tlc2/tool/liveness/TableauDiskGraphTest.java index 9eaa09015f05e1e71f0aafb6573c3f3401cf861d..d2aba42ca272e23affbc0028f387d5d4f8079cb2 100644 --- a/tlatools/test/tlc2/tool/liveness/TableauDiskGraphTest.java +++ b/tlatools/test/tlc2/tool/liveness/TableauDiskGraphTest.java @@ -26,8 +26,13 @@ package tlc2.tool.liveness; -import java.io.IOException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.io.IOException; +import org.junit.Test; import tlc2.util.BitVector; import tlc2.util.LongVec; import tlc2.util.statistics.BucketStatistics; @@ -70,6 +75,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * +------+ * */ + @Test public void testGetShortestPath() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -157,6 +163,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * * (Drawn with http://asciiflow.com/) */ + @Test public void testUnifyingNodeInPath() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -243,6 +250,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * | | * +---------+ */ + @Test public void testUnifyingNodeShortestPath() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -334,6 +342,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * * @see https://bugzilla.tlaplus.net/show_bug.cgi?id=293 */ + @Test public void testPathWithTwoInitNodesWithTableau() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -382,6 +391,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * Test that getPath returns the correct init state if the graph contains * multiple initial states with the same fingerprint but different tableau idxs. */ + @Test public void testGetPathWithTwoInits() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -414,6 +424,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { /* * Test how an initial node transitions through the done state */ + @Test public void testNodeSetDone() throws IOException { final TableauDiskGraph dg = (TableauDiskGraph) getDiskGraph(); final long fingerprint = 1L; @@ -436,6 +447,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { * Tests a path where both states have an identical fingerprint and only * differ in the tableau idx. */ + @Test public void testGetPathWithTwoNodesWithSameFingerprint() throws IOException { final AbstractDiskGraph dg = getDiskGraph(); @@ -465,6 +477,7 @@ public class TableauDiskGraphTest extends DiskGraphTest { /* * Test that it is possible to "update" a GraphNode's outgoing transitions. */ + @Test public void testLookupExistingNodeWithTidx() throws IOException { final TableauDiskGraph dg = (TableauDiskGraph) getDiskGraph(); diff --git a/tlatools/test/tlc2/tool/liveness/TableauNodePtrTableTest.java b/tlatools/test/tlc2/tool/liveness/TableauNodePtrTableTest.java index 6c9dfcfabf00fe687ef6147880554e9ac9de9145..b7b7c647d486d7b00d02666517864440b9697d0e 100644 --- a/tlatools/test/tlc2/tool/liveness/TableauNodePtrTableTest.java +++ b/tlatools/test/tlc2/tool/liveness/TableauNodePtrTableTest.java @@ -26,12 +26,16 @@ package tlc2.tool.liveness; -import java.util.Arrays; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; -import junit.framework.TestCase; +import java.util.Arrays; +import org.junit.Test; -public class TableauNodePtrTableTest extends TestCase { +public class TableauNodePtrTableTest { + @Test public void testSetDone() { final TableauNodePtrTable tbl = new TableauNodePtrTable(0); // init with 0 so that grow is tested @@ -56,6 +60,7 @@ public class TableauNodePtrTableTest extends TestCase { } // Test various methods which apparently all yield pretty much the same result + @Test public void testRedundantMethodYieldSameResult() { final TableauNodePtrTable tbl = new TableauNodePtrTable(0); // init with 0 so that grow is tested diff --git a/tlatools/test/tlc2/tool/liveness/TableauSymmetryTest.java b/tlatools/test/tlc2/tool/liveness/TableauSymmetryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..649cac5b4f72bb3a5c7ac90e5110c9e492cdbd76 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/TableauSymmetryTest.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import tlc2.output.EC; + +public class TableauSymmetryTest extends ModelCheckerTestCase { + + public TableauSymmetryTest() { + super("TableauSymmetryMC", "symmetry"); + } + + @Test + @Ignore("Ignored for as long as symmetry is incorrectly handled by TLC with liveness checking.") + public void testSpec() { + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "13", "6", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); + + // Assert it has found the temporal violation and also a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertNodeAndPtrSizes(624L, 224L); + + // Assert the error trace + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(7); + // Trace prefix + expectedTrace.add("arr = (a :> \"ready\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"busy\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"ready\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"busy\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"done\")"); + expectedTrace.add("arr = (a :> \"done\" @@ b :> \"ready\")"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(4, "<Action line 7, col 13 to line 8, col 47 of module TableauSymmetry>"); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/Test052.java b/tlatools/test/tlc2/tool/liveness/Test052.java index 8ed0bcf4251bb6f9d5985d00b808c1a80955e626..6c767175035288dc79f03104fcdc4284c669605a 100644 --- a/tlatools/test/tlc2/tool/liveness/Test052.java +++ b/tlatools/test/tlc2/tool/liveness/Test052.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test052 extends ModelCheckerTestCase { @@ -34,10 +38,12 @@ public class Test052 extends ModelCheckerTestCase { super("test52"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_MODE_MC)); assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recorded(EC.TLC_COMPUTING_INIT)); assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "2")); assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); diff --git a/tlatools/test/tlc2/tool/liveness/Test055.java b/tlatools/test/tlc2/tool/liveness/Test055.java index 4a35ec0c1b282e0a8d30cc14a02b830cc6c134ec..9a8329e315079091f8ee8828023fc3c643c71390 100644 --- a/tlatools/test/tlc2/tool/liveness/Test055.java +++ b/tlatools/test/tlc2/tool/liveness/Test055.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test055 extends ModelCheckerTestCase { @@ -34,10 +38,12 @@ public class Test055 extends ModelCheckerTestCase { super("test55"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_MODE_MC)); assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recorded(EC.TLC_COMPUTING_INIT)); assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "2")); assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); diff --git a/tlatools/test/tlc2/tool/liveness/Test056.java b/tlatools/test/tlc2/tool/liveness/Test056.java index f54c2737f82b4c44fae92447563a631a09e05726..3484d273d73766aa2487f52dd25995c0f96ef4ab 100644 --- a/tlatools/test/tlc2/tool/liveness/Test056.java +++ b/tlatools/test/tlc2/tool/liveness/Test056.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test056 extends ModelCheckerTestCase { @@ -34,10 +38,12 @@ public class Test056 extends ModelCheckerTestCase { super("test56"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_MODE_MC)); assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recorded(EC.TLC_COMPUTING_INIT)); assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "2")); assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "3")); diff --git a/tlatools/test/tlc2/tool/liveness/Test057.java b/tlatools/test/tlc2/tool/liveness/Test057.java index bb269600687a95fd40cb06bdfaf67822d987c69e..2c63d5022c5fde620e4823261281b5fc42b04416 100644 --- a/tlatools/test/tlc2/tool/liveness/Test057.java +++ b/tlatools/test/tlc2/tool/liveness/Test057.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test057 extends ModelCheckerTestCase { @@ -34,10 +38,12 @@ public class Test057 extends ModelCheckerTestCase { super("test57"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_MODE_MC)); assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recorded(EC.TLC_COMPUTING_INIT)); assertTrue(recorder.recordedWithStringValues(EC.TLC_INVARIANT_VIOLATED_INITIAL, "Invariant1", "x = 1")); } diff --git a/tlatools/test/tlc2/tool/liveness/Test059.java b/tlatools/test/tlc2/tool/liveness/Test059.java index dc9f162db9e30a5d6cf43ce352309d4f3c6b501f..6d8fb81bd3da3ae6879a7c45e6775f7a0be73066 100644 --- a/tlatools/test/tlc2/tool/liveness/Test059.java +++ b/tlatools/test/tlc2/tool/liveness/Test059.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test059 extends ModelCheckerTestCase { @@ -34,9 +38,11 @@ public class Test059 extends ModelCheckerTestCase { super("test59"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "5")); assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "1")); assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "6", "5", "0")); diff --git a/tlatools/test/tlc2/tool/liveness/Test063.java b/tlatools/test/tlc2/tool/liveness/Test063.java index f08e0ef08c4358511c84d14e79274f6d1db76c80..4a34dc101240d0014150372ede7260d44fcf010d 100644 --- a/tlatools/test/tlc2/tool/liveness/Test063.java +++ b/tlatools/test/tlc2/tool/liveness/Test063.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; public class Test063 extends ModelCheckerTestCase { @@ -34,9 +38,11 @@ public class Test063 extends ModelCheckerTestCase { super("test63"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); assertTrue(recorder.recordedWithStringValue(EC.TLC_SEARCH_DEPTH, "2")); assertTrue(recorder.recordedWithStringValue(EC.TLC_INIT_GENERATED1, "72")); assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "696", "216", "0")); diff --git a/tlatools/test/tlc2/tool/liveness/Test3.java b/tlatools/test/tlc2/tool/liveness/Test3.java index 6067d34831692d2a3d27809834b009fd105e2780..38728b3fafb13cc182f282306d48078b8ef94cb7 100644 --- a/tlatools/test/tlc2/tool/liveness/Test3.java +++ b/tlatools/test/tlc2/tool/liveness/Test3.java @@ -26,10 +26,13 @@ package tlc2.tool.liveness; -import java.util.List; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; -import tlc2.tool.TLCStateInfo; public class Test3 extends ModelCheckerTestCase { @@ -37,10 +40,12 @@ public class Test3 extends ModelCheckerTestCase { super("Test3"); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); - assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "3")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "3", "0")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); @@ -48,33 +53,13 @@ public class Test3 extends ModelCheckerTestCase { // Assert the error trace assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); - List<Object> records = recorder.getRecords(EC.TLC_STATE_PRINT2); - - int i = 0; // State's position in records - Object[] objs = (Object[]) records.get(i++); - TLCStateInfo stateInfo = (TLCStateInfo) objs[0]; - assertEquals("x = 0", stateInfo.toString().trim()); // trimmed to remove any newlines or whitespace - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("x = 1", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("x = 0", stateInfo.toString().trim()); - assertEquals(i, objs[1]); - - objs = (Object[]) records.get(i++); - stateInfo = (TLCStateInfo) objs[0]; - assertEquals("x = 2", stateInfo.toString().trim()); - assertEquals(i, objs[1]); + final List<String> expectedTrace = new ArrayList<String>(4); + expectedTrace.add("x = 0"); + expectedTrace.add("x = 1"); + expectedTrace.add("x = 0"); + expectedTrace.add("x = 2"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); - // Assert the error trace ends with "Back to state 1" - assertTrue(recorder.recorded(EC.TLC_BACK_TO_STATE)); - records = recorder.getRecords(EC.TLC_BACK_TO_STATE); - objs = (Object[]) records.get(0); - assertEquals("1", objs[0]); + assertBackToState(1); } } diff --git a/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestA.java b/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestA.java new file mode 100644 index 0000000000000000000000000000000000000000..aceaa5e2fffda9b421ed280e18ff69ac5a65651d --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestA.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import tlc2.output.EC; + +public class UnsymmetricModelCheckerTestA extends ModelCheckerTestCase { + + public UnsymmetricModelCheckerTestA() { + super("UnsymmetricMCA", "symmetry"); + } + + @Test + public void testSpec() { + // ModelChecker intends to check liveness + assertTrue(recorder.recordedWithStringValues(EC.TLC_LIVE_IMPLIED, "1")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_INIT_GENERATED2, "2", "s", "1")); + + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "2", "0")); + + // Assert it has found a temporal violation and a counter example + assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); + assertTrue(recorder.recorded(EC.TLC_COUNTER_EXAMPLE)); + + assertTrue(recorder.recorded(EC.TLC_STATE_PRINT2)); + final List<String> expectedTrace = new ArrayList<String>(2); + expectedTrace.add("x = a"); + expectedTrace.add("x = 1"); + assertTraceWith(recorder.getRecords(EC.TLC_STATE_PRINT2), expectedTrace); + + assertBackToState(1); + } +} diff --git a/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestB.java b/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestB.java new file mode 100644 index 0000000000000000000000000000000000000000..edf127fd3d4e3bc29fc709b6ae6e8f70b16aba76 --- /dev/null +++ b/tlatools/test/tlc2/tool/liveness/UnsymmetricModelCheckerTestB.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.liveness; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import tlc2.output.EC; + +public class UnsymmetricModelCheckerTestB extends ModelCheckerTestCase { + + public UnsymmetricModelCheckerTestB() { + super("UnsymmetricMCB", "symmetry"); + } + + @Test + public void testSpec() { + // ModelChecker intends to check liveness + assertTrue(recorder.recordedWithStringValues(EC.TLC_LIVE_IMPLIED, "1")); + assertTrue(recorder.recordedWithStringValues(EC.TLC_INIT_GENERATED2, "2", "s", "1")); + + // ModelChecker has finished and generated the expected amount of states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + assertTrue(recorder.recordedWithStringValues(EC.TLC_STATS, "5", "2", "0")); + + // Contrary to UMCTA, B doesn't find a counter-example. This is due to + // the CHOOSE on S and the selected initial state. + } +} diff --git a/tlatools/test/tlc2/tool/liveness/simulation/AbstractExampleTestCase.java b/tlatools/test/tlc2/tool/liveness/simulation/AbstractExampleTestCase.java index 4d8398abe178ab98538a93540f6592fe644ec02f..4ffaecc8805d9fa7c341a33c0ff9bd0d1f0def6a 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/AbstractExampleTestCase.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/AbstractExampleTestCase.java @@ -26,8 +26,12 @@ package tlc2.tool.liveness.simulation; -import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; import tlc2.tool.TLCStateInfo; import tlc2.tool.liveness.ModelCheckerTestCase; @@ -40,10 +44,12 @@ public abstract class AbstractExampleTestCase extends ModelCheckerTestCase { super(cfg, "simulation", new String[] {"-simulate", "-depth", "11"}); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); assertTrue(recorder.recordedWithStringValue(EC.TLC_STATS_SIMU, "12")); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); diff --git a/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckSimulationTest2a.java b/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckSimulationTest2a.java index c18e2b3b3d72f78cc7f1c6a9f8301eb868b0a575..b0e2e976d106c822516548d5f2fa077fb2df4948 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckSimulationTest2a.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckSimulationTest2a.java @@ -26,8 +26,12 @@ package tlc2.tool.liveness.simulation; -import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.List; +import org.junit.Test; import tlc2.TLC; import tlc2.output.EC; import tlc2.tool.Simulator; @@ -50,10 +54,12 @@ public class LiveCheckSimulationTest2a extends ModelCheckerTestCase { TLC.traceNum = 100; } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); assertTrue(recorder.recorded(EC.TLC_STATS_SIMU)); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); diff --git a/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckTest.java b/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckTest.java index 9c353715ef591a824bacfc9fcf603cb9baaf6df1..fb3b64f8848464d17cf607ed1020686bfa8d7837 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckTest.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/LiveCheckTest.java @@ -26,14 +26,14 @@ package tlc2.tool.liveness.simulation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.io.IOException; -import java.util.Enumeration; import org.easymock.EasyMock; - -import junit.framework.TestCase; +import org.junit.Test; import tlc2.tool.Action; -import tlc2.tool.StateVec; import tlc2.tool.TLCState; import tlc2.tool.Tool; import tlc2.tool.liveness.AbstractDiskGraph; @@ -44,10 +44,10 @@ import tlc2.tool.liveness.OrderOfSolution; import tlc2.tool.liveness.TBGraph; import tlc2.tool.liveness.TBGraphNode; import tlc2.tool.queue.DummyTLCState; -import tlc2.util.LongVec; +import tlc2.util.SetOfStates; import tlc2.util.statistics.DummyBucketStatistics; -public class LiveCheckTest extends TestCase { +public class LiveCheckTest { // Add identical state twice, which can happen in simulation mode when the // trace is: <<100L, 200L, ..., 100L, 200L>> (as in a cycle that is @@ -56,10 +56,12 @@ public class LiveCheckTest extends TestCase { // change. // Note that adding/updating the state would result in a larger // on-disk file, but doesn't seem to invalidate the simulation validity. + @Test public void testAddIdenticalNodeTwiceNoTableau() throws IOException { addIdenticalNodeTwice(false, -1); } + @Test public void testAddIdenticalNodeTwiceWithTableau() throws IOException { addIdenticalNodeTwice(true, 0); } @@ -79,20 +81,17 @@ public class LiveCheckTest extends TestCase { final TLCState state = new DummyTLCState(); liveCheck.addInitState(state, 100L); - final StateVec stateVec = new StateVec(1); - stateVec.addElement(new DummyTLCState()); - - final LongVec longVec = new LongVec(1); - longVec.addElement(200L); + final SetOfStates setOfStates = new SetOfStates(1); + setOfStates.put(200L, new DummyTLCState(200L)); // Add state 100L the first time, then add its successor - liveCheck.addNextState(state, 100L, stateVec, longVec); - liveCheck.addNextState(state, 200L, stateVec, longVec); + liveCheck.addNextState(state, 100L, setOfStates); + liveCheck.addNextState(state, 200L, setOfStates); assertEquals(0, diskGraph.getPtr(100L, tableauId)); // Add state 100L again and check that it does *not* // end up in disk graph. - liveCheck.addNextState(state, 100L, stateVec, longVec); + liveCheck.addNextState(state, 100L, setOfStates); assertEquals(0, diskGraph.getPtr(100L, tableauId)); } @@ -109,29 +108,19 @@ public class LiveCheckTest extends TestCase { return new LiveCheck(EasyMock.createNiceMock(Tool.class), new Action[0], new OrderOfSolution[] { oos }, System.getProperty("java.io.tmpdir"), new DummyBucketStatistics()); } - + private ILiveCheck getLiveCheckWithTableau() throws IOException { final TBGraphNode node = EasyMock.createMock(TBGraphNode.class); EasyMock.expect(node.isConsistent((TLCState) EasyMock.anyObject(), (Tool) EasyMock.anyObject())).andReturn(true) .anyTimes(); EasyMock.expect(node.nextSize()).andReturn(1).anyTimes(); EasyMock.expect(node.nextAt(0)).andReturn(node).anyTimes(); + EasyMock.expect(node.getIndex()).andReturn(0).anyTimes(); EasyMock.replay(node); - final TBGraph tbGraph = EasyMock.createNiceMock(TBGraph.class); - EasyMock.expect(tbGraph.elements()).andReturn(new Enumeration<TBGraphNode>() { - boolean has = true; - public boolean hasMoreElements() { - return has; - } - public TBGraphNode nextElement() { - has = false; - return node; - } - }).anyTimes(); - EasyMock.expect(tbGraph.getInitCnt()).andReturn(1).anyTimes(); - EasyMock.expect(tbGraph.getNode(EasyMock.anyInt())).andReturn(node).anyTimes(); - EasyMock.replay(tbGraph); + final TBGraph tbGraph = new TBGraph(null); + tbGraph.addElement(node); + tbGraph.setInitCnt(1); // Configure OOS mock to react to the subsequent invocation. This is a // essentially the list of calls being made on OOS during diff --git a/tlatools/test/tlc2/tool/liveness/simulation/SimulationTest2a.java b/tlatools/test/tlc2/tool/liveness/simulation/SimulationTest2a.java index 22ddaa47664d0b012006718ff467d16d8a074b90..c1ad559e6de984ee7cc8d7b403ced4b57dcf3c62 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/SimulationTest2a.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/SimulationTest2a.java @@ -26,8 +26,12 @@ package tlc2.tool.liveness.simulation; -import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.List; +import org.junit.Test; import tlc2.output.EC; import tlc2.tool.TLCStateInfo; import tlc2.tool.liveness.ModelCheckerTestCase; @@ -41,10 +45,12 @@ public class SimulationTest2a extends ModelCheckerTestCase { super("Test2a", "/", new String[] {"-simulate", "-depth", "6"}); } + @Test public void testSpec() { // ModelChecker has finished and generated the expected amount of states assertTrue(recorder.recorded(EC.TLC_FINISHED)); assertTrue(recorder.recorded(EC.TLC_STATS_SIMU)); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); diff --git a/tlatools/test/tlc2/tool/liveness/simulation/StutteringTest.java b/tlatools/test/tlc2/tool/liveness/simulation/StutteringTest.java index 66d3c3b40b8d0abbdc0b53d91f8530bb94cc4e87..fcffab9731778f6bea89c2256ca3f8a5bb35262f 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/StutteringTest.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/StutteringTest.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness.simulation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; import tlc2.tool.liveness.ModelCheckerTestCase; @@ -35,10 +39,12 @@ public class StutteringTest extends ModelCheckerTestCase { super("MC", "CodePlexBug08", new String[] { "-simulate" }); } + @Test public void testSpec() { // Simulation has finished and generated states assertTrue(recorder.recorded(EC.TLC_FINISHED)); assertTrue(recorder.recorded(EC.TLC_STATS_SIMU)); + assertFalse(recorder.recorded(EC.GENERAL)); // Assert it has found the temporal violation and also a counter example assertTrue(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); diff --git a/tlatools/test/tlc2/tool/liveness/simulation/SuccessfulSimulationTestCase.java b/tlatools/test/tlc2/tool/liveness/simulation/SuccessfulSimulationTestCase.java index 35e7b77443fe372d31b073554a885efb609a783d..66ce18651a82041bcf4aa544e90ae275521f9109 100644 --- a/tlatools/test/tlc2/tool/liveness/simulation/SuccessfulSimulationTestCase.java +++ b/tlatools/test/tlc2/tool/liveness/simulation/SuccessfulSimulationTestCase.java @@ -26,6 +26,10 @@ package tlc2.tool.liveness.simulation; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; import tlc2.output.EC; import tlc2.tool.liveness.ModelCheckerTestCase; @@ -43,6 +47,7 @@ public abstract class SuccessfulSimulationTestCase extends ModelCheckerTestCase super(spec, path, extraArguments); } + @Test public void testSpec() { // Simulation must *NOT* show a counterexample. Regular model-checking // shows that the liveness property holds. @@ -53,6 +58,7 @@ public abstract class SuccessfulSimulationTestCase extends ModelCheckerTestCase // Finished... assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); // No temporal violation assertFalse(recorder.recorded(EC.TLC_TEMPORAL_PROPERTY_VIOLATED)); // No counterexample diff --git a/tlatools/test/tlc2/tool/other/CheckFP.java b/tlatools/test/tlc2/tool/other/CheckFP.java index ae8025227726e77ca5737874ce65324fbbb6f675..2ad6f3227304dda705998a3e13c35ec6d85ad1b5 100644 --- a/tlatools/test/tlc2/tool/other/CheckFP.java +++ b/tlatools/test/tlc2/tool/other/CheckFP.java @@ -19,7 +19,8 @@ import tlc2.util.BufferedRandomAccessFile; public class CheckFP { public static void main(String args[]) { try { - BufferedRandomAccessFile raf = new BufferedRandomAccessFile(args[0], "r"); + @SuppressWarnings("resource") + BufferedRandomAccessFile raf = new BufferedRandomAccessFile(args[0], "r"); long fileLen = raf.length(); long dis = Long.MAX_VALUE; int cnt = 0; diff --git a/tlatools/test/tlc2/tool/other/FileClassLoader.java b/tlatools/test/tlc2/tool/other/FileClassLoader.java index c65f4b08e9a1a470b84c1ce7c7ab824382e5563c..41f30d21a50e9fda82cd30daf01bfc152ef7a849 100644 --- a/tlatools/test/tlc2/tool/other/FileClassLoader.java +++ b/tlatools/test/tlc2/tool/other/FileClassLoader.java @@ -14,6 +14,7 @@ import java.io.IOException; * it seems to be a helper utility used during the development * @deprecated according to the paths it is not used (SZ February 19, 2009) */ +@SuppressWarnings({ "rawtypes", "unchecked" }) public class FileClassLoader extends ClassLoader { /* Load a class from a file. */ private String dir; diff --git a/tlatools/test/tlc2/tool/queue/DummyTLCState.java b/tlatools/test/tlc2/tool/queue/DummyTLCState.java index 7120cd6de9544f1bd653f9535087805fc1cea9ae..d5f7ef86677e8f6bebcec710016f440b467fc4df 100644 --- a/tlatools/test/tlc2/tool/queue/DummyTLCState.java +++ b/tlatools/test/tlc2/tool/queue/DummyTLCState.java @@ -10,11 +10,18 @@ import util.UniqueString; @SuppressWarnings("serial") public class DummyTLCState extends TLCState { + private final long fp; + public DummyTLCState() { uid = 0; TLCState.Empty = this; + this.fp = 0L; } + public DummyTLCState(long fp) { + this.fp = fp; + } + /* (non-Javadoc) * @see tlc2.tool.TLCState#bind(util.UniqueString, tlc2.value.Value, tla2sany.semantic.SemanticNode) */ @@ -82,7 +89,7 @@ public class DummyTLCState extends TLCState { * @see tlc2.tool.TLCState#fingerPrint() */ public long fingerPrint() { - return 0; + return this.fp; } /* (non-Javadoc) @@ -103,7 +110,7 @@ public class DummyTLCState extends TLCState { * @see tlc2.tool.TLCState#toString() */ public String toString() { - return "Dummy#" + uid; + return "Dummy#" + uid + ":" + fp; } /* (non-Javadoc) diff --git a/tlatools/test/tlc2/tool/queue/StateQueueTest.java b/tlatools/test/tlc2/tool/queue/StateQueueTest.java index f86316116e316ccf2a5c39d9b7026ae5bf437e64..058912f0408808d3fdd429a18b05ed4cadc39ed1 100644 --- a/tlatools/test/tlc2/tool/queue/StateQueueTest.java +++ b/tlatools/test/tlc2/tool/queue/StateQueueTest.java @@ -1,21 +1,28 @@ package tlc2.tool.queue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; import tlc2.tool.TLCState; -import junit.framework.TestCase; -public class StateQueueTest extends TestCase { +public class StateQueueTest { protected IStateQueue sQueue; /* (non-Javadoc) * @see junit.framework.TestCase#setUp() */ - protected void setUp() throws Exception { - super.setUp(); + @Before + public void setUp() throws Exception { sQueue = new MemStateQueue(""); } // add and remove a single state + @Test public void testEnqueue() { final TLCState expected = new DummyTLCState(); sQueue.enqueue(expected); @@ -24,18 +31,21 @@ public class StateQueueTest extends TestCase { } // dequeue from empty + @Test public void testsDequeueEmpty() { TLCState state = sQueue.sDequeue(); assertNull(state); } // dequeue from empty + @Test public void testDequeueEmpty() { TLCState state = sQueue.dequeue(); assertNull(state); } // dequeue from not empty + @Test public void testsDequeueNotEmpty() { DummyTLCState expected = new DummyTLCState(); sQueue.sEnqueue(expected); @@ -46,6 +56,7 @@ public class StateQueueTest extends TestCase { } // dequeue from not empty + @Test public void testDequeueNotEmpty() { DummyTLCState expected = new DummyTLCState(); sQueue.enqueue(expected); @@ -56,6 +67,7 @@ public class StateQueueTest extends TestCase { } // add 10 states and check size + @Test public void testEnqueueAddNotSame() { final int j = 10; for (int i = 0; i < j; i++) { @@ -65,6 +77,7 @@ public class StateQueueTest extends TestCase { } // add same states 10 times and check size + @Test public void testEnqueueAddSame() { final DummyTLCState state = new DummyTLCState(); final int j = 10; @@ -75,6 +88,7 @@ public class StateQueueTest extends TestCase { } // uncommon input with empty queue sDequeue + @Test public void testsDequeueAbuseEmpty() { expectRuntimeException(sQueue, 0); expectRuntimeException(sQueue, -1); @@ -84,6 +98,7 @@ public class StateQueueTest extends TestCase { // uncommon input with non-empty queue // unfortunately sDequeue behaves differently depending what's its internal state + @Test public void testsDequeueAbuseNonEmpty() { sQueue.sEnqueue(new DummyTLCState()); // make sure isAvail = true diff --git a/tlatools/test/tlc2/tool/simulation/NQSpecTest.java b/tlatools/test/tlc2/tool/simulation/NQSpecTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ee145ae1c797c3df25a0e84c5feae2f2a76e420b --- /dev/null +++ b/tlatools/test/tlc2/tool/simulation/NQSpecTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.tool.simulation; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; + +import org.junit.Test; + +import tlc2.TLC; +import tlc2.output.EC; +import tlc2.tool.liveness.ModelCheckerTestCase; + +public class NQSpecTest extends ModelCheckerTestCase { + + public NQSpecTest() { + super("MC", "simulation" + File.separator + "NQSpec", new String[] { "-simulate" }); + TLC.traceNum = 100; + } + + @Test + public void testSpec() { + // Simulation has finished and generated states + assertTrue(recorder.recorded(EC.TLC_FINISHED)); + assertFalse(recorder.recorded(EC.GENERAL)); + } +} diff --git a/tlatools/test/tlc2/util/ByteUtilsTest.java b/tlatools/test/tlc2/util/ByteUtilsTest.java index c5509c98de5e0cf3dad6f0e415cdd831ede0acf4..94e0d57c494498ba4cb15305a259813791e2bad4 100644 --- a/tlatools/test/tlc2/util/ByteUtilsTest.java +++ b/tlatools/test/tlc2/util/ByteUtilsTest.java @@ -7,8 +7,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.util.Random; - -import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; import util.ToolIO; /** @@ -16,7 +16,7 @@ import util.ToolIO; * @author Simon Zambrovski * @version $Id$ */ -public class ByteUtilsTest extends TestCase +public class ByteUtilsTest { public static final int ARRAYSIZE = 10000; public static final int BITS = 1000; @@ -32,9 +32,9 @@ public class ByteUtilsTest extends TestCase long t1; long t2; - protected void setUp() throws Exception + @Before + public void setUp() throws Exception { - super.setUp(); Arr = new BigInteger[ARRAYSIZE]; Arr2 = new BigInteger[ARRAYSIZE]; Arr3 = new BigInteger[ARRAYSIZE]; @@ -54,7 +54,8 @@ public class ByteUtilsTest extends TestCase } - public void test1() + @Test + public void test1() { t1 = System.currentTimeMillis(); mainTestinttoByte(); @@ -62,7 +63,8 @@ public class ByteUtilsTest extends TestCase ToolIO.out.println("Testing IntToByteArray took " + (t2 - t1) + "ms"); } - public void test2() throws FileNotFoundException, IOException + @Test + public void test2() throws FileNotFoundException, IOException { t1 = System.currentTimeMillis(); mainTestWriteIntReadInt(); @@ -70,7 +72,8 @@ public class ByteUtilsTest extends TestCase ToolIO.out.println("Testing WriteInt, ReadInt took " + (t2 - t1) + "ms"); } - public void test3() + @Test + public void test3() { t1 = System.currentTimeMillis(); mainTestlongtoByte(); @@ -78,7 +81,8 @@ public class ByteUtilsTest extends TestCase ToolIO.out.println("Testing longToByteArray took " + (t2 - t1) + "ms"); } - public void test4() throws FileNotFoundException, IOException + @Test + public void test4() throws FileNotFoundException, IOException { t1 = System.currentTimeMillis(); mainTestWriteLongReadLong(); @@ -86,7 +90,8 @@ public class ByteUtilsTest extends TestCase ToolIO.out.println("Testing WriteLong, ReadLong took " + (t2 - t1) + "ms"); } - public void test5() throws FileNotFoundException, IOException + @Test + public void test5() throws FileNotFoundException, IOException { t1 = System.currentTimeMillis(); mainTestWriteReadSizeByteArray(); @@ -94,7 +99,8 @@ public class ByteUtilsTest extends TestCase ToolIO.out.println("Testing Write, Read took " + (t2 - t1) + "ms"); } - public void test6() throws FileNotFoundException, IOException + @Test + public void test6() throws FileNotFoundException, IOException { t1 = System.currentTimeMillis(); mainTestAppend(); diff --git a/tlatools/test/tlc2/util/ContextTest.java b/tlatools/test/tlc2/util/ContextTest.java index f916ed2f7313f25b3dd1b7c32d7598618ab1e28e..b726cf890a8182e568eaeb79fd6b1c8fb8837ddc 100644 --- a/tlatools/test/tlc2/util/ContextTest.java +++ b/tlatools/test/tlc2/util/ContextTest.java @@ -26,8 +26,10 @@ package tlc2.util; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -39,8 +41,9 @@ import tla2sany.semantic.SymbolNode; import tla2sany.xml.SymbolContext; import util.UniqueString; -public class ContextTest extends TestCase { +public class ContextTest { + @Test public void testLookupEmpty() { assertNull(Context.Empty.lookup(new DummySymbolNode())); assertNull(Context.Empty.lookup(new DummySymbolNode(), true)); @@ -50,6 +53,7 @@ public class ContextTest extends TestCase { assertNull(Context.Empty.lookup(null, false)); } + @Test public void testLookupBranch() { // BranchCtx -> Empty final Context ctx = Context.branch(Context.Empty); @@ -61,11 +65,13 @@ public class ContextTest extends TestCase { assertNull(ctx.lookup(null, true)); } + @Test public void testLookupSymbolNodeNull() { final Context ctx = Context.branch(Context.Empty); assertNull(ctx.lookup(null)); } + @Test public void testLookup() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -79,6 +85,7 @@ public class ContextTest extends TestCase { assertEquals(value, ctx3.lookup(name)); } + @Test public void testLookupCutOffFalse() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -93,6 +100,7 @@ public class ContextTest extends TestCase { } // Cutoff causes lookup to stop at branching context + @Test public void testLookupCutOffTrue() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -106,6 +114,7 @@ public class ContextTest extends TestCase { assertNull(ctx3.lookup(name, true)); } + @Test public void testLookupWithAtBranching() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -117,6 +126,7 @@ public class ContextTest extends TestCase { assertEquals(value, branch.lookup(name)); } + @Test public void testLookupWithCutOffFalseAtBranching() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -128,6 +138,7 @@ public class ContextTest extends TestCase { assertEquals(value, branch.lookup(name, false)); } + @Test public void testLookupWithCutOffTrueAtBranching() { final DummySymbolNode name = new DummySymbolNode("ctx1"); final Object value = "value1"; @@ -142,6 +153,7 @@ public class ContextTest extends TestCase { /** * Test method for {@link tlc2.util.Context#lookup(tla2sany.semantic.SymbolNode)}. */ + @Test public void testLookupSymbolNode() { final DummySymbolNode name = new DummySymbolNode(); final Object value = new Object(); diff --git a/tlatools/test/tlc2/util/GrowingLongVecTest.java b/tlatools/test/tlc2/util/GrowingLongVecTest.java index 5d9d914ce22f531904c0189fa2da87fa8683702e..fb2f913401a430915efb09365e584e1907333939 100644 --- a/tlatools/test/tlc2/util/GrowingLongVecTest.java +++ b/tlatools/test/tlc2/util/GrowingLongVecTest.java @@ -26,6 +26,10 @@ package tlc2.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; public class GrowingLongVecTest extends LongVecTest { @@ -34,6 +38,7 @@ public class GrowingLongVecTest extends LongVecTest { return new LongVec(0); } + @Test public void testGrowAndShrink() { // Zero capacity, LongVec has to grow final LongVec vec = new LongVec(0); diff --git a/tlatools/test/tlc2/util/LongVecTest.java b/tlatools/test/tlc2/util/LongVecTest.java index 2af02a8d0fb928fec0b8a2bf690527680bad503a..4b6903cf213e6b750a546a9a779c13ebf3209321 100644 --- a/tlatools/test/tlc2/util/LongVecTest.java +++ b/tlatools/test/tlc2/util/LongVecTest.java @@ -26,15 +26,19 @@ package tlc2.util; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; -public class LongVecTest extends TestCase { +import org.junit.Test; + +public class LongVecTest { protected LongVec getLongVec() { // default capacity return new LongVec(); } + @Test public void testReadBeyondCapacity() { final LongVec vec = getLongVec(); try { @@ -45,6 +49,7 @@ public class LongVecTest extends TestCase { fail("Read beyond capacity"); } + @Test public void testAddAndReadBeyondCapacity() { final LongVec vec = getLongVec(); vec.addElement(1L); @@ -57,6 +62,7 @@ public class LongVecTest extends TestCase { fail("Read beyond capacity"); } + @Test public void testRemoveBeyondCapacity() { final LongVec vec = new LongVec(10); for (int i = -1; i <= 10; i++) { @@ -69,6 +75,7 @@ public class LongVecTest extends TestCase { } } + @Test public void testAddRemoveBeyondCapacity() { final LongVec vec = new LongVec(10); vec.addElement(1L); @@ -81,6 +88,7 @@ public class LongVecTest extends TestCase { fail("Read beyond capacity"); } + @Test public void testRemoveAndGet() { final LongVec vec = getLongVec(); vec.addElement(1L); @@ -103,6 +111,7 @@ public class LongVecTest extends TestCase { fail("A new elements magically appeared in LongVec"); } + @Test public void testRemoveWrongOrder() { final LongVec vec = getLongVec(); vec.addElement(1L); @@ -120,6 +129,7 @@ public class LongVecTest extends TestCase { fail("Removed non-existing element"); } + @Test public void testGetNegative() { final LongVec vec = getLongVec(); try { @@ -130,6 +140,7 @@ public class LongVecTest extends TestCase { fail("Read negative"); } + @Test public void testRemoveNegative() { final LongVec vec = getLongVec(); try { diff --git a/tlatools/test/tlc2/util/MemIntQueueTest.java b/tlatools/test/tlc2/util/MemIntQueueTest.java index cd645d2d93db3911471f102793666b9ce25f182f..e82fe4f173e33abb7cb72cfc7e9e64fd62250722 100644 --- a/tlatools/test/tlc2/util/MemIntQueueTest.java +++ b/tlatools/test/tlc2/util/MemIntQueueTest.java @@ -26,12 +26,15 @@ package tlc2.util; -import java.util.NoSuchElementException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; -import junit.framework.TestCase; +import java.util.NoSuchElementException; +import org.junit.Test; -public class MemIntQueueTest extends TestCase { +public class MemIntQueueTest { + @Test public void testDequeuePastLastElement() { final MemIntQueue queue = new MemIntQueue("irrelevant", "irrelevant"); queue.enqueueInt(1); @@ -47,6 +50,7 @@ public class MemIntQueueTest extends TestCase { // Add 0 three times and make sure it's only returned this many times. 0 // is MemIntQueue's internal default for an empty slot. + @Test public void testEnqueueZeros() { final MemIntQueue queue = new MemIntQueue("irrelevant", "irrelevant"); queue.enqueueInt(0); @@ -65,6 +69,7 @@ public class MemIntQueueTest extends TestCase { fail("Returned element where there should be none."); } + @Test public void testEnqueueLong() { final MemIntQueue queue = new MemIntQueue("irrelevant", "irrelevant"); queue.enqueueLong(0); @@ -80,6 +85,7 @@ public class MemIntQueueTest extends TestCase { fail("Returned element where there should be none."); } + @Test public void testEnqueueDequeueLong() { final MemIntQueue queue = new MemIntQueue("irrelevant", "irrelevant"); queue.enqueueLong(0); @@ -93,4 +99,36 @@ public class MemIntQueueTest extends TestCase { } fail("Returned element where there should be none."); } + + @Test + public void testGrow() { + final MemIntQueue queue = new MemIntQueue("irrelevant", "irrelevant", 4); + queue.enqueueInt(0); + queue.enqueueInt(1); + queue.enqueueInt(2); + queue.enqueueInt(3); + assertEquals(4, queue.size()); + + queue.dequeueInt(); + queue.dequeueInt(); + queue.dequeueInt(); + queue.dequeueInt(); + assertEquals(0, queue.size()); + + queue.enqueueInt(4); + queue.enqueueInt(5); + queue.enqueueInt(6); + queue.enqueueInt(7); + assertEquals(4, queue.size()); + + queue.dequeueInt(); + + // This should make the queue grow internally. + queue.enqueueInt(8); + queue.enqueueInt(9); + for(int i = 5; i < 10; i++) { + assertEquals(i, queue.dequeueInt()); + } + assertEquals(0, queue.size()); + } } diff --git a/tlatools/test/tlc2/util/MemIntStackTest.java b/tlatools/test/tlc2/util/MemIntStackTest.java new file mode 100644 index 0000000000000000000000000000000000000000..90ed6d70d64512ce1806f93347a137916d403c0e --- /dev/null +++ b/tlatools/test/tlc2/util/MemIntStackTest.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class MemIntStackTest { + + @Test + public void testPeak() { + final MemIntStack memIntStack = new MemIntStack("", ""); + + memIntStack.pushLong(4711L); + memIntStack.pushLong(2323L); + memIntStack.pushInt(1); + memIntStack.pushLong(77L); + + assertEquals(4711L, memIntStack.peakLong(0)); + assertEquals(2323L, memIntStack.peakLong(2)); + assertEquals(1, memIntStack.peakInt(4)); + assertEquals(77L, memIntStack.peakLong(5)); + } +} diff --git a/tlatools/test/tlc2/util/SynchronousDiskIntStackTest.java b/tlatools/test/tlc2/util/SynchronousDiskIntStackTest.java new file mode 100644 index 0000000000000000000000000000000000000000..96d650c7ec2dc18796aaba975ac89d8b9afc8a3d --- /dev/null +++ b/tlatools/test/tlc2/util/SynchronousDiskIntStackTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.util; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import org.junit.Test; + +public class SynchronousDiskIntStackTest { + + @Test + public void testPushIntNoWrite() { + final String diskdir = System.getProperty("java.io.tmpdir") + File.separator + "SynchronousDiskIntStackTest_" + + System.currentTimeMillis(); + + final int size = 8; + + final IntStack diskIntStack = new SynchronousDiskIntStack(diskdir, "SynchronousDiskIntStackTest", size); + + // Fill stack + for (int i = 0; i < size; i++) { + diskIntStack.pushInt(i); + } + + // Check all elements still on stack + for (int i = size - 1; i >= 0; i--) { + assertEquals(i, diskIntStack.popInt()); + } + } + + @Test + public void testPushIntWrite() { + final String diskdir = System.getProperty("java.io.tmpdir") + File.separator + "SynchronousDiskIntStackTest_" + + System.currentTimeMillis(); + new File(diskdir).mkdirs(); + + final int size = 8; + + final IntStack diskIntStack = new SynchronousDiskIntStack(diskdir, "SynchronousDiskIntStackTest", size); + + // Fill stack trice + for (int i = 0; i < (size * 3); i++) { + diskIntStack.pushInt(i); + } + + // Check all elements still on stack + for (int i = (3*size) - 1; i >= 0; i--) { + assertEquals(i, diskIntStack.popInt()); + } + + assertEquals(0, diskIntStack.size()); + } +} diff --git a/tlatools/test/tlc2/util/statistics/BucketStatisticsTest.java b/tlatools/test/tlc2/util/statistics/BucketStatisticsTest.java index 390bc25ad3d2b207c89eb5022561ff2720635fc0..7f418e902dbdc01b94ef3053afec8502ae094918 100644 --- a/tlatools/test/tlc2/util/statistics/BucketStatisticsTest.java +++ b/tlatools/test/tlc2/util/statistics/BucketStatisticsTest.java @@ -26,11 +26,15 @@ package tlc2.util.statistics; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.junit.Test; import tlc2.util.statistics.BucketStatistics; -import junit.framework.TestCase; -public class BucketStatisticsTest extends TestCase { +public class BucketStatisticsTest { + @Test public void testInvalidArgument() { try { final IBucketStatistics gs = new BucketStatistics(); @@ -41,38 +45,41 @@ public class BucketStatisticsTest extends TestCase { fail(); } + @Test public void testMean() { final IBucketStatistics gs = new BucketStatistics(); - assertEquals(-1.0d, gs.getMean()); + assertEquals(-1.0d, gs.getMean(), 0); gs.addSample(0); - assertEquals(0.0d, gs.getMean()); + assertEquals(0.0d, gs.getMean(), 0); gs.addSample(1); gs.addSample(2); - assertEquals(1.0d, gs.getMean()); + assertEquals(1.0d, gs.getMean(), 0); gs.addSample(2); gs.addSample(2); - assertEquals(1.4d, gs.getMean()); + assertEquals(1.4d, gs.getMean(), 0); } + @Test public void testMedian() { final IBucketStatistics gs = new BucketStatistics(); - assertEquals(-1, gs.getMedian()); + assertEquals(-1, gs.getMedian(), 0); gs.addSample(0); - assertEquals(0, gs.getMedian()); + assertEquals(0, gs.getMedian(), 0); gs.addSample(1); gs.addSample(2); - assertEquals(1, gs.getMedian()); + assertEquals(1, gs.getMedian(), 0); gs.addSample(2); gs.addSample(2); - assertEquals(2, gs.getMedian()); + assertEquals(2, gs.getMedian(), 0); } + @Test public void testMin() { final IBucketStatistics gs = new BucketStatistics(); assertEquals(-1, gs.getMin()); @@ -88,6 +95,7 @@ public class BucketStatisticsTest extends TestCase { assertEquals(0, gs.getMin()); } + @Test public void testMax() { final IBucketStatistics gs = new BucketStatistics(); assertEquals(-1, gs.getMax()); @@ -105,9 +113,10 @@ public class BucketStatisticsTest extends TestCase { assertEquals(3, gs.getMax()); } + @Test public void testStandardDeviation() { final IBucketStatistics gs = new BucketStatistics(); - assertEquals(-1.0, gs.getStdDev()); + assertEquals(-1.0, gs.getStdDev(), 0); gs.addSample(0); gs.addSample(0); @@ -119,12 +128,13 @@ public class BucketStatisticsTest extends TestCase { gs.addSample(2); gs.addSample(2); gs.addSample(3); - assertEquals(1.005d, (Math.round(gs.getStdDev() * 10000d) / 10000d)); + assertEquals(1.005d, (Math.round(gs.getStdDev() * 10000d) / 10000d), 0); } + @Test public void testGetPercentile() { final IBucketStatistics gs = new BucketStatistics(); - assertEquals(-1.0, gs.getPercentile(1)); + assertEquals(-1.0, gs.getPercentile(1), 0); try { gs.addSample(1); // <- first element @@ -140,11 +150,12 @@ public class BucketStatisticsTest extends TestCase { gs.addSample(2); gs.addSample(2); gs.addSample(3); - assertEquals(2.0d, gs.getPercentile(0.5d)); - assertEquals(2.0d, gs.getPercentile(0.75d)); - assertEquals(3.0d, gs.getPercentile(0.999d)); + assertEquals(2.0d, gs.getPercentile(0.5d), 0); + assertEquals(2.0d, gs.getPercentile(0.75d), 0); + assertEquals(3.0d, gs.getPercentile(0.999d), 0); } // NaN test + @Test public void testGetPercentileNaN() { try { final IBucketStatistics gs = new BucketStatistics(); @@ -155,6 +166,7 @@ public class BucketStatisticsTest extends TestCase { fail("Parameter not a number"); } + @Test public void testToString() { try { //just invoke to check for exceptions @@ -177,6 +189,7 @@ public class BucketStatisticsTest extends TestCase { } } + @Test public void testMaximum() { final IBucketStatistics gs = new BucketStatistics("test title", 8); gs.addSample(16); diff --git a/tlatools/test/tlc2/value/RecordValueTest.java b/tlatools/test/tlc2/value/RecordValueTest.java new file mode 100644 index 0000000000000000000000000000000000000000..57f44dcb7c9d45bc1b5e165c18752d6bd396b6b8 --- /dev/null +++ b/tlatools/test/tlc2/value/RecordValueTest.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Microsoft Research. All rights reserved. + * + * The MIT License (MIT) + * + * 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. + * + * Contributors: + * Markus Alexander Kuppe - initial API and implementation + ******************************************************************************/ + +package tlc2.value; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import util.InternTable; +import util.UniqueString; + +public class RecordValueTest { + + /** + * Test method for {@link tlc2.value.RecordValue#deepCopy()}. + * + * This test reveals a bug in the implementation of RecordValue#deepCopy() + * when the original instance from which the deep copy has been created is + * normalized afterwards and the normalization incorrectly ripples through + * to the copy. + */ + @Test + public void testDeepCopy() { + final InternTable internTable = new InternTable(2); + final UniqueString a = internTable.put("a"); + final UniqueString b = internTable.put("b"); + + final Value aVal = new StringValue("aVal"); + final Value bVal = new StringValue("bVal"); + + // Create the source to create a deep copy of + final RecordValue orig = new RecordValue(new UniqueString[] {b, a}, new Value[] {bVal, aVal}, false); + + // Verify the mappings in RecordValue are correct + assertTrue(orig.names[0].equals(b)); + assertTrue(orig.names[1].equals(a)); + assertTrue(orig.values[0].equals(bVal)); + assertTrue(orig.values[1].equals(aVal)); + + // Make a deep copy of te origina record value + final RecordValue deepCopy = (RecordValue) orig.deepCopy(); + + // Normalize the original record value and check its mappings have been + // re-organized + orig.deepNormalize(); + assertTrue(orig.names[0].equals(a)); + assertTrue(orig.names[1].equals(b)); + assertTrue(orig.values[0].equals(aVal)); + assertTrue(orig.values[1].equals(bVal)); + + // Check that the mappings in the deep copy didn't change. + assertTrue(deepCopy.names[0].equals(b)); + assertTrue(deepCopy.names[1].equals(a)); + assertTrue(deepCopy.values[0].equals(bVal)); + assertTrue(deepCopy.values[1].equals(aVal)); + } +} diff --git a/tlatools/test/util/SimpleFilenameToStreamTest.java b/tlatools/test/util/SimpleFilenameToStreamTest.java index 3082129fc822977b8ea928694efa82b0785b3164..2ae4c61e0bd61b167918bce89c1e4584ca42cf20 100644 --- a/tlatools/test/util/SimpleFilenameToStreamTest.java +++ b/tlatools/test/util/SimpleFilenameToStreamTest.java @@ -1,14 +1,17 @@ package util; -import java.io.File; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; -import junit.framework.TestCase; +import java.io.File; +import org.junit.Test; -public class SimpleFilenameToStreamTest extends TestCase { +public class SimpleFilenameToStreamTest { /** * Try to load a standard module */ + @Test public void testResolveStandardModule() { final SimpleFilenameToStream sfts = new SimpleFilenameToStream(); final File file = sfts.resolve("TLC.tla", true);