diff --git a/org.eventb.texteditor.feature/.project b/org.eventb.texteditor.feature/.project new file mode 100644 index 0000000000000000000000000000000000000000..397bf70082bb74a5c6494a551f1510adbced1bfb --- /dev/null +++ b/org.eventb.texteditor.feature/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eventb.texteditor.feature</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.pde.FeatureBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.FeatureNature</nature> + </natures> +</projectDescription> diff --git a/org.eventb.texteditor.feature/build.properties b/org.eventb.texteditor.feature/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..64f93a9f0b7328eb563aa5ad6cec7f828020e124 --- /dev/null +++ b/org.eventb.texteditor.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/org.eventb.texteditor.feature/compile.org.eventb.texteditor.feature.xml b/org.eventb.texteditor.feature/compile.org.eventb.texteditor.feature.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff68285d8f19c3fad2dbaa2fbeb69cff6341240e --- /dev/null +++ b/org.eventb.texteditor.feature/compile.org.eventb.texteditor.feature.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project name="Compile org.eventb.texteditor.feature" default="main"> + <target name="main"> + <ant antfile="build.xml" dir="../org.eventb.texttools" target="build.jars"/> + <ant antfile="build.xml" dir="../org.eventb.texteditor.ui" target="build.jars"/> + </target> +</project> diff --git a/org.eventb.texteditor.feature/feature.xml b/org.eventb.texteditor.feature/feature.xml new file mode 100644 index 0000000000000000000000000000000000000000..3fb82a3cdec61fd6a390105dac9d4f6ffe76c328 --- /dev/null +++ b/org.eventb.texteditor.feature/feature.xml @@ -0,0 +1,175 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feature + id="org.eventb.texteditor.feature" + label="Camille TextEditor" + version="1.1.2.beta" + provider-name="Heinrich-Heine University Dusseldorf" + plugin="org.eventb.texteditor.ui"> + + <description> + A text editor for the Rodin platform to edit Event-B models +----------------------------------------------------------- +Release History: +1.0.2 - Rodin 1.1 release +1.0.1 - Branding, Relaxed version restrictions +1.0.0 - Initial relase + </description> + + <copyright> + Copyright (c) 2009 Heinrich-Heine University Dusseldorf. +All rights reserved. + </copyright> + + <license> + RODIN SOFTWARE USER AGREEMENT +June 1, 2006 +Usage Of Content +THE RODIN PROJECT MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION +AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS (COLLECTIVELY +"CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND +CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS +OF +LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. +BY USING +THE CONTENT, YOU AGREE THAT YOUR USE OF THE CONTENT IS GOVERNED +BY +THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE +LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. +IF YOU +DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND +THE +TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR +NOTICES +INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT. +Applicable Licenses +Unless otherwise indicated, all Content made available by the +Rodin +Project is provided to you under the terms and conditions of +the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL +is +provided with this Content and is also available at +http://www.eclipse.org/legal/epl-v10.html. For purposes of the +EPL, +"Program" will mean the Content. +Content includes, but is not limited to, source code, object +code, +documentation and other files maintained in the Rodin SourceForge +CVS +repository ("Repository") in CVS modules ("Modules") and made +available as downloadable archives ("Downloads"). +- Content may be structured and packaged into modules to facilitate +delivering, extending, and upgrading the Content. Typical modules +may include plug-ins ("Plug-ins"), plug-in fragments +("Fragments"), and features ("Features"). +- Each Plug-in or Fragment may be packaged as a sub-directory +or JAR +(Java(TM) ARchive) in a directory named "plugins". +- A Feature is a bundle of one or more Plug-ins and/or Fragments +and +associated material. Each Feature may be packaged as a +sub-directory in a directory named "features". Within a Feature, +files named "feature.xml" may contain a list of the names and +version numbers of the Plug-ins and/or Fragments associated with +that Feature. +- Features may also include other Features ("Included +Features"). Within a Feature, files named "feature.xml" may +contain a list of the names and version numbers of Included +Features. +The terms and conditions governing Plug-ins and Fragments should +be +contained in files named "about.html" ("Abouts"). The terms and +conditions governing Features and Included Features should be +contained in files named "license.html" ("Feature Licenses"). +Abouts +and Feature Licenses may be located in any directory of a Download +or +Module including, but not limited to the following locations: +- The top-level (root) directory +- Plug-in and Fragment directories +- Inside Plug-ins and Fragments packaged as JARs +- Sub-directories of the directory named "src" of certain Plug-ins +- Feature directories +Note: if a Feature made available by the Rodin Project is installed +using the Eclipse Update Manager, you must agree to a license +("Feature Update License") during the installation process. +If the +Feature contains Included Features, the Feature Update License +should +either provide you with the terms and conditions governing the +Included Features or inform you where you can locate them. Feature +Update Licenses may be found in the "license" property of files +named +"feature.properties" found within a Feature. Such Abouts, Feature +Licenses, and Feature Update Licenses contain the terms and conditions +(or references to such terms and conditions) that govern your +use of +the associated Content in that directory. +THE ABOUTS, FEATURE LICENSES, AND FEATURE UPDATE LICENSES MAY +REFER TO +THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. +SOME OF THESE OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT +LIMITED TO): +- Common Public License Version 1.0 (available at http://www.eclipse.org/legal/cpl-v10.html) +- Apache Software License 1.1 (available at http://www.apache.org/licenses/LICENSE) +- Apache Software License 2.0 (available at http://www.apache.org/licenses/LICENSE-2.0) +- IBM Public License 1.0 (available at http://oss.software.ibm.com/developerworks/opensource/license10.html) +- Metro Link Public License 1.00 (available at http://www.opengroup.org/openmotif/supporters/metrolink/license.html) +- Mozilla Public License Version 1.1 (available at http://www.mozilla.org/MPL/MPL-1.1.html) +IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS +PRIOR TO USE OF THE CONTENT. If no About, Feature License, or +Feature +Update License is provided, please contact the Rodin Project +to +determine what terms and conditions govern that particular +Content. +Cryptography +Content may contain encryption software. The country in which +you are +currently may have restrictions on the import, possession, and +use, +and/or re-export to another country, of encryption software. +BEFORE +using any encryption software, please check the country's laws, +regulations and policies concerning the import, possession, or +use, +and re-export of encryption software, to see if this is permitted. +Java and all Java-based trademarks are trademarks of Sun Microsystems, +Inc. in the United States, other countries, or both. + </license> + + <url> + <update label="Heinrich-Heine University Düsseldorf" url="http://www.stups.uni-duesseldorf.de/update/"/> + <discovery label="Rodin Update Site" url="http://rodin-b-sharp.sourceforge.net/updates"/> + </url> + + <requires> + <import plugin="org.eventb.ui" version="1.2.0" match="equivalent"/> + <import plugin="org.eclipse.ui.editors" version="3.5.0" match="equivalent"/> + <import plugin="org.eclipse.jface.text" version="3.5.0" match="equivalent"/> + <import plugin="org.eclipse.emf.edit.ui" version="2.5.0" match="equivalent"/> + <import plugin="org.eventb.eventBKeyboard" version="2.9.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="3.5.0" match="equivalent"/> + <import plugin="org.eventb.core.ast" version="1.2.0" match="equivalent"/> + <import plugin="org.rodinp.core" version="1.2.0" match="equivalent"/> + <import plugin="org.eclipse.emf.compare" version="1.0.0" match="compatible"/> + <import plugin="org.eclipse.emf.compare.diff" version="1.0.0" match="compatible"/> + <import plugin="org.eclipse.emf.compare.match" version="1.0.0" match="compatible"/> + <import feature="org.eventb.emf.feature" version="1.2.1" match="compatible"/> + </requires> + + <plugin + id="org.eventb.texteditor.ui" + download-size="0" + install-size="0" + version="1.1.1" + unpack="false"/> + + <plugin + id="org.eventb.texttools" + download-size="0" + install-size="0" + version="1.1.2" + unpack="false"/> + +</feature> diff --git a/org.eventb.texteditor.ui/.classpath b/org.eventb.texteditor.ui/.classpath new file mode 100644 index 0000000000000000000000000000000000000000..cdf77e53eb0c88a3e3b0dba86a5a4f67137572a6 --- /dev/null +++ b/org.eventb.texteditor.ui/.classpath @@ -0,0 +1,8 @@ +<?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.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="lib" path="lib/commons-lang-2.4.jar"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eventb.texteditor.ui/.project b/org.eventb.texteditor.ui/.project new file mode 100644 index 0000000000000000000000000000000000000000..f4c75c1f6b72a7280918a29c18c2623a122b7153 --- /dev/null +++ b/org.eventb.texteditor.ui/.project @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eventb.texteditor.ui</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature> + </natures> +</projectDescription> diff --git a/org.eventb.texteditor.ui/.settings/org.eclipse.jdt.core.prefs b/org.eventb.texteditor.ui/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000000000000000000000000000000000..bb1930094c1824baf52a0d8ba07d7ba68c17472f --- /dev/null +++ b/org.eventb.texteditor.ui/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +#Thu Mar 26 12:38:05 CET 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/org.eventb.texteditor.ui/META-INF/MANIFEST.MF b/org.eventb.texteditor.ui/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..ea8df4bfef9fb9cadb01f8e5a0aaa6779fed7e56 --- /dev/null +++ b/org.eventb.texteditor.ui/META-INF/MANIFEST.MF @@ -0,0 +1,22 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Camille Texteditor +Bundle-SymbolicName: org.eventb.texteditor.ui;singleton:=true +Bundle-Version: 1.1.1 +Bundle-Localization: plugin +Bundle-Activator: org.eventb.texteditor.ui.TextEditorPlugin$Implementation +Require-Bundle: org.eventb.texttools;bundle-version="[1.1.0,1.2.0)";visibility:=reexport, + org.eventb.emf.formulas;bundle-version="[1.0.0,1.1.0)";visibility:=reexport, + org.eventb.emf.persistence;bundle-version="[1.1.1,1.2.0)";visibility:=reexport, + org.eventb.ui;bundle-version="[1.2.0,1.3.0)";visibility:=reexport, + org.eclipse.ui.editors;bundle-version="[3.5.0,3.6.0)";visibility:=reexport, + org.eclipse.jface.text;bundle-version="[3.5.0,3.6.0)";visibility:=reexport, + org.eclipse.emf.edit.ui;bundle-version="[2.5.0,2.6.0)";visibility:=reexport, + org.eventb.eventBKeyboard;bundle-version="[2.9.0,3.0.0)" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Bundle-ClassPath: lib/commons-lang-2.4.jar, + . +Bundle-Vendor: Heinrich-Heine University Dusseldorf +Export-Package: org.eventb.texteditor.ui, + org.eventb.texteditor.ui.editor diff --git a/org.eventb.texteditor.ui/about.ini b/org.eventb.texteditor.ui/about.ini new file mode 100644 index 0000000000000000000000000000000000000000..b0f2a9ba8dfb89d90a2a3eb5ddfc7abf30e341a3 --- /dev/null +++ b/org.eventb.texteditor.ui/about.ini @@ -0,0 +1 @@ +featureImage=icons/camille32.gif \ No newline at end of file diff --git a/org.eventb.texteditor.ui/build.properties b/org.eventb.texteditor.ui/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..2d2dfd6f245d8d6bc422769491b488641cc063d3 --- /dev/null +++ b/org.eventb.texteditor.ui/build.properties @@ -0,0 +1,20 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + lib/commons-lang-2.4.jar,\ + plugin.properties,\ + templates.xml,\ + icons/ +src.includes = templates.xml,\ + src/,\ + plugin.xml,\ + plugin.properties,\ + build.properties,\ + META-INF/,\ + .project,\ + .classpath,\ + .settings/,\ + lib/,\ + icons/ diff --git a/org.eventb.texteditor.ui/icons/camille16.gif b/org.eventb.texteditor.ui/icons/camille16.gif new file mode 100644 index 0000000000000000000000000000000000000000..aa4659863cf52ec7742b3afc93b1cdefb4236c9f Binary files /dev/null and b/org.eventb.texteditor.ui/icons/camille16.gif differ diff --git a/org.eventb.texteditor.ui/icons/camille32.gif b/org.eventb.texteditor.ui/icons/camille32.gif new file mode 100644 index 0000000000000000000000000000000000000000..3d6ec1dd8af370c87dcebb265fb46652d35b44c6 Binary files /dev/null and b/org.eventb.texteditor.ui/icons/camille32.gif differ diff --git a/org.eventb.texteditor.ui/icons/camille64.gif b/org.eventb.texteditor.ui/icons/camille64.gif new file mode 100644 index 0000000000000000000000000000000000000000..7807bd63c49d363ff13ef2b1901295d484286c07 Binary files /dev/null and b/org.eventb.texteditor.ui/icons/camille64.gif differ diff --git a/org.eventb.texteditor.ui/icons/template_obj.gif b/org.eventb.texteditor.ui/icons/template_obj.gif new file mode 100644 index 0000000000000000000000000000000000000000..fdde5fbb95e50851757a749194ce6a3431bb1afb Binary files /dev/null and b/org.eventb.texteditor.ui/icons/template_obj.gif differ diff --git a/org.eventb.texteditor.ui/plugin.properties b/org.eventb.texteditor.ui/plugin.properties new file mode 100644 index 0000000000000000000000000000000000000000..6da1d5fb6f25c8562e01d5e63e4f5dfb5b06820d --- /dev/null +++ b/org.eventb.texteditor.ui/plugin.properties @@ -0,0 +1,60 @@ + +# <copyright> +# </copyright> +# +# $Id$ + +pluginName = Camille Texteditor + +_UI_CoreEditor_menu = &Core Editor +_UI_MachineEditor_menu = &Machine Editor +_UI_ContextEditor_menu = &Context Editor + +_UI_CreateChild_menu_item = &New Child +_UI_CreateSibling_menu_item = N&ew Sibling + +_UI_ShowPropertiesView_menu_item = Show &Properties View +_UI_RefreshViewer_menu_item = &Refresh + +_UI_SelectionPage_label = Selection +_UI_ParentPage_label = Parent +_UI_ListPage_label = List +_UI_TreePage_label = Tree +_UI_TablePage_label = Table +_UI_TreeWithColumnsPage_label = Tree with Columns +_UI_ObjectColumn_label = Object +_UI_SelfColumn_label = Self + +_UI_NoObjectSelected = Selected Nothing +_UI_SingleObjectSelected = Selected Object: {0} +_UI_MultiObjectSelected = Selected {0} Objects + +_UI_OpenEditorError_label = Open Editor + +_UI_Wizard_category = Example EMF Model Creation Wizards + +_UI_CreateModelError_message = Problems encountered in file "{0}" + +_UI_MachineModelWizard_label = Machine Model +_UI_MachineModelWizard_description = Create a new Machine model +_UI_MachineEditorFilenameDefaultBase = Machine +_UI_MachineEditorFilenameExtensions = bum + +_UI_MachineEditor_label = Camille Texteditor + +_UI_Wizard_label = New + +_WARN_FilenameExtension = The file name must end in ''.{0}'' +_WARN_FilenameExtensions = The file name must have one of the following extensions: {0} + +_UI_ModelObject = &Model Object +_UI_XMLEncoding = &XML Encoding +_UI_XMLEncodingChoices = UTF-8 ASCII UTF-16 UTF-16BE UTF-16LE ISO-8859-1 +_UI_Wizard_initial_object_description = Select a model object to create + +_UI_FileConflict_label = File Conflict +_WARN_FileConflict = There are unsaved changes that conflict with changes made outside the editor. Do you wish to discard this editor's changes? + +ContentAssistProposal.label=Content assist +ContentAssistProposal.tooltip=Content assist +ContentAssistProposal.description=Provides Content Assistance diff --git a/org.eventb.texteditor.ui/plugin.xml b/org.eventb.texteditor.ui/plugin.xml new file mode 100644 index 0000000000000000000000000000000000000000..8b34c6c4e2e2fad0572c3fe7038a14ecee178dc8 --- /dev/null +++ b/org.eventb.texteditor.ui/plugin.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.2"?> +<plugin> + <extension + id="eventb_texteditor" + name="EventB Texteditor" + point="org.eclipse.ui.editors"> + <editor + class="org.eventb.texteditor.ui.editor.EventBTextEditor" + default="true" + id="org.eventb.texteditor.ui.texteditor" + name="Camille Text Editor"> + <contentTypeBinding + contentTypeId="org.eventb.core.machineFile"> + </contentTypeBinding> + <contentTypeBinding + contentTypeId="org.eventb.core.contextFile"> + </contentTypeBinding> + </editor> + </extension> + <extension + id="syntaxerror" + name="Syntax Error" + point="org.eclipse.core.resources.markers"> + <super + type="org.eclipse.core.resources.problemmarker"> + </super> + </extension> + <extension + id="eventb.templates" + name="EventB TextEditor Templates" + point="org.eclipse.ui.editors.templates"> + <contextType + class="org.eclipse.jface.text.templates.TemplateContextType" + id="org.eventb.texteditor.machine" + name="Machine Context"> + </contextType> + <contextType + class="org.eclipse.jface.text.templates.TemplateContextType" + id="org.eventb.texteditor.events" + name="Events Context"> + </contextType> + <contextType + class="org.eclipse.jface.text.templates.TemplateContextType" + id="org.eventb.texteditor.context" + name="Context Context"> + </contextType> + <contextType + class="org.eclipse.jface.text.templates.TemplateContextType" + id="org.eventb.texteditor.formula" + name="Formula Context"> + </contextType> + <contextType + class="org.eclipse.jface.text.templates.TemplateContextType" + id="org.eventb.texteditor.anywhere" + name="Anywhere Context"> + </contextType> + <include + file="templates.xml"> + </include> + </extension> + <extension + point="org.eclipse.ui.preferencePages"> + <page + category="org.eventb.texteditor.ui.preferences" + class="org.eventb.texteditor.ui.preferences.HighlightingPreferencePage" + id="org.eventb.texteditor.ui.preferences.HighlightingPreferencePage" + name="Syntax Highlighting"> + </page> + <page + category="org.eventb.ui.preferences.eventB" + class="org.eventb.texteditor.ui.preferences.TextEditorPreferencePage" + id="org.eventb.texteditor.ui.preferences" + name="Camille TextEditor"> + </page> + <page + category="org.eventb.texteditor.ui.preferences" + class="org.eventb.texteditor.ui.preferences.TemplatePrefPage" + id="org.eventb.texteditor.ui.preferences.TemplatePreferences" + name="Templates"> + </page> + </extension> + <extension + point="org.eclipse.core.runtime.preferences"> + <initializer + class="org.eventb.texteditor.ui.preferences.PreferenceInitializer"> + </initializer> + </extension> + <extension + point="org.eclipse.ui.commands"> + <command + defaultHandler="org.eventb.texteditor.ui.editor.actions.FormatHandler" + description="Formats the Event-B text representation" + id="org.eventb.texteditor.command.format" + name="Format"> + </command> + </extension> + <extension point="org.eclipse.ui.menus"> + <menuContribution locationURI="popup:#texteditor.context.menu?after=additions"> + <command + commandId="org.eventb.texteditor.command.format" + label="Format" + mnemonic="F" + tooltip="Reformats the Event-B text representation with the default formatting"> + </command> + </menuContribution> + </extension> + <extension + point="org.eclipse.ui.bindings"> + <key + commandId="org.eventb.texteditor.command.format" + schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" + sequence="M1+M2+F"> + </key> + </extension> + <extension point="org.eclipse.ui.handlers"> + <handler + class="org.eventb.texteditor.ui.editor.actions.InsertHandler" + commandId="org.eventb.ui.edit.insert"> + <activeWhen> + <with + variable="activeEditorId"> + <equals + value="org.eventb.texteditor.ui.texteditor"> + </equals> + </with> + </activeWhen> + </handler> + </extension> + +</plugin> diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/Images.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/Images.java new file mode 100644 index 0000000000000000000000000000000000000000..4d0f77d354a28ae1322cd421b8e2a4c01b59272f --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/Images.java @@ -0,0 +1,125 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eventb.ui.EventBUIPlugin; + +public class Images { + public static final String IMG_MACHINE = "Machine"; + public static final String IMG_CONTEXT = "Context"; + public static final String IMG_REFINES = "Refines"; + public static final String IMG_INVARIANT = "Invariant"; + public static final String IMG_THEOREM = "Theorem"; + public static final String IMG_VARIABLE = "Variable"; + public static final String IMG_EVENT = "Event"; + public static final String IMG_GUARD = "Guard"; + public static final String IMG_ACTION = "Action"; + public static final String IMG_AXIOM = "Axiom"; + public static final String IMG_CONSTANT = "Constant"; + public static final String IMG_CARRIER_SET = "Carrier Set"; + public static final String IMG_REFINE_OVERLAY = IMG_REFINES + "Overlay"; + public static final String IMG_COMMENT_OVERLAY = "CommentOverlay"; + public static final String IMG_WARNING_OVERLAY = "WarningOverlay"; + public static final String IMG_ERROR_OVERLAY = "ErrorOverlay"; + + private static final String IMG_MACHINE_PATH = "icons/full/obj16/mch_obj.gif"; + private static final String IMG_CONTEXT_PATH = "icons/full/obj16/ctx_obj.gif"; + private static final String IMG_REFINES_PATH = "icons/full/ctool16/refines.gif"; + private static final String IMG_INVARIANT_PATH = "icons/full/obj16/inv_obj.gif"; + private static final String IMG_THEOREM_PATH = "icons/full/obj16/thm_obj.gif"; + private static final String IMG_VARIABLE_PATH = "icons/full/obj16/var_obj.gif"; + private static final String IMG_EVENT_PATH = "icons/full/obj16/evt_obj.gif"; + private static final String IMG_GUARD_PATH = "icons/full/obj16/grd_obj.gif"; + // private static final String IMG_WITNESS_PATH = + // "icons/full/obj16/evt_obj.gif"; + private static final String IMG_ACTION_PATH = "icons/full/obj16/act_obj.gif"; + private static final String IMG_AXIOM_PATH = "icons/full/obj16/axm_obj.gif"; + private static final String IMG_CONSTANT_PATH = "icons/full/obj16/cst_obj.gif"; + private static final String IMG_CARRIER_SET_PATH = "icons/full/obj16/set_obj.gif"; + private static final String IMG_REFINE_OVERLAY_PATH = "icons/full/ovr16/ref_ovr.gif"; + private static final String IMG_COMMENT_OVERLAY_PATH = "icons/full/ovr16/cmt_ovr.gif"; + private static final String IMG_WARNING_OVERLAY_PATH = "icons/full/ovr16/warning_ovr.gif"; + private static final String IMG_ERROR_OVERLAY_PATH = "icons/full/ovr16/error_ovr.gif"; + + private static ImageRegistry imageRegistry = TextEditorPlugin.getPlugin() + .getImageRegistry(); + private static Map<String, String> rodinIcons = new HashMap<String, String>(); + + static { + rodinIcons.put(IMG_MACHINE, IMG_MACHINE_PATH); + rodinIcons.put(IMG_CONTEXT, IMG_CONTEXT_PATH); + rodinIcons.put(IMG_REFINES, IMG_REFINES_PATH); + rodinIcons.put(IMG_INVARIANT, IMG_INVARIANT_PATH); + rodinIcons.put(IMG_THEOREM, IMG_THEOREM_PATH); + rodinIcons.put(IMG_VARIABLE, IMG_VARIABLE_PATH); + rodinIcons.put(IMG_EVENT, IMG_EVENT_PATH); + rodinIcons.put(IMG_GUARD, IMG_GUARD_PATH); + rodinIcons.put(IMG_ACTION, IMG_ACTION_PATH); + rodinIcons.put(IMG_AXIOM, IMG_AXIOM_PATH); + rodinIcons.put(IMG_CONSTANT, IMG_CONSTANT_PATH); + rodinIcons.put(IMG_CARRIER_SET, IMG_CARRIER_SET_PATH); + + rodinIcons.put(IMG_REFINE_OVERLAY, IMG_REFINE_OVERLAY_PATH); + rodinIcons.put(IMG_COMMENT_OVERLAY, IMG_COMMENT_OVERLAY_PATH); + rodinIcons.put(IMG_WARNING_OVERLAY, IMG_WARNING_OVERLAY_PATH); + rodinIcons.put(IMG_ERROR_OVERLAY, IMG_ERROR_OVERLAY_PATH); + } + + public static final String IMG_TEMPLATE = "Template"; + public static final String IMG_TEMPLATE_PATH = "icons/template_obj.gif"; + + public static Image getImage(final String key) { + final Image image = imageRegistry.get(key); + + if (image == null && rodinIcons.containsKey(key)) { + return getImage(key, rodinIcons.get(key)); + } else { + return image; + } + } + + public static Image getImage(final String key, final String path) { + Image image = imageRegistry.get(key); + + if (image == null) { + registerImage(key, path); + image = imageRegistry.get(key); + } + + return image; + } + + public static void registerImage(final String key, final String path) { + if (rodinIcons.containsKey(key)) { + registerImage(EventBUIPlugin.PLUGIN_ID, key, path); + } else { + registerImage(TextEditorPlugin.PLUGIN_ID, key, path); + } + } + + private static void registerImage(final String pluginID, final String key, + final String path) { + final ImageDescriptor desc = AbstractUIPlugin + .imageDescriptorFromPlugin(pluginID, path); + imageRegistry.put(key, desc); + } + + public static void dispose() { + // imageRegistry.remove(key); + } + + public static ImageRegistry getRegistry() { + return imageRegistry; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/MarkerHelper.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/MarkerHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..49eaae3075e039493e519affc44ee2a3087fda67 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/MarkerHelper.java @@ -0,0 +1,81 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui; + +import java.util.Map; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.edit.ui.EMFEditUIPlugin; +import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper; +import org.eclipse.ui.texteditor.MarkerUtilities; +import org.eventb.texteditor.ui.build.ast.IParseProblemWrapper; + +public class MarkerHelper extends EditUIMarkerHelper { + + private final String markerId; + + public MarkerHelper(final String markerId) { + this.markerId = markerId; + + } + + @Override + protected String getMarkerID() { + return markerId; + } + + @SuppressWarnings("unchecked") + @Override + protected boolean adjustMarker(final IMarker marker, + final Diagnostic diagnostic) throws CoreException { + if (diagnostic.getData() != null) { + for (final Object element : diagnostic.getData()) { + if (element instanceof IParseProblemWrapper) { + final IParseProblemWrapper parseProblem = (IParseProblemWrapper) element; + final int offset = parseProblem.getOffset(); + final int line = parseProblem.getLine(); + final int column = parseProblem.getColumn(); + + // Setting attributes for marker + final Map attributes = marker.getAttributes(); + + MarkerUtilities.setCharStart(attributes, offset); + MarkerUtilities.setCharEnd(attributes, offset + + parseProblem.getTokenLength()); + MarkerUtilities.setMessage(attributes, parseProblem + .getMessage()); + MarkerUtilities.setLineNumber(attributes, line); + + attributes.put(IMarker.LOCATION, EMFEditUIPlugin + .getPlugin().getString( + "_UI_MarkerLocation", + new String[] { Integer.toString(line + 1), + Integer.toString(column + 1) })); + + marker.setAttributes(attributes); + return true; + } + } + } + + return super.adjustMarker(marker, diagnostic); + } + + public void deleteMarkers(final Resource resource, final String markerType, + final boolean includeSubtypes, final int depth) { + final IFile file = getFile(resource); + try { + file.deleteMarkers(markerType, includeSubtypes, depth); + } catch (final CoreException e) { + // IGNORE + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextDecoration.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextDecoration.java new file mode 100644 index 0000000000000000000000000000000000000000..ad35648038e0c2993eb19779a1b391533c799975 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextDecoration.java @@ -0,0 +1,74 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; + +public class TextDecoration { + + public enum ESyntaxElement { + Comment("Comment"), Label("Label"), Keyword("Keyword"), MathKeyword( + "Mathematical Keyword"), GlobalVariable("Global Variable"), Parameter( + "Event Parameter"), BoundedVariable("Bound Variable"), Constant( + "Constant"), Set("Carrier Set"); + + private final String label; + private final String colorKey; + private final String styleKey; + private final Token token = new Token(null); + + private ESyntaxElement(final String label) { + this.label = label; + + colorKey = toString() + "." + "colorPreference"; + styleKey = toString() + "." + "stylePreference"; + } + + public String getLabel() { + return label; + } + + public String getColorKey() { + return colorKey; + } + + public String getStyleKey() { + return styleKey; + } + + public synchronized Token getToken() { + if (token.getData() == null) { + token.setData(getAttribute(this)); + } + + return token; + } + + public synchronized void resetToken() { + token.setData(getAttribute(this)); + } + }; + + private final static IPreferenceStore store = TextEditorPlugin.getPlugin() + .getPreferenceStore(); + + private static TextAttribute getAttribute(final ESyntaxElement element) { + final RGB color = PreferenceConverter.getColor(store, element + .getColorKey()); + final int style = store.getInt(element.getStyleKey()); + + final TextAttribute attribute = new TextAttribute(new Color(Display + .getDefault(), color), null, style); + return attribute; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextEditorPlugin.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextEditorPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..9c40c04988d1acfb191f2ac2c69a96f593fd5241 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/TextEditorPlugin.java @@ -0,0 +1,165 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texteditor.ui; + +import java.io.IOException; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.EMFPlugin; +import org.eclipse.emf.common.ui.EclipseUIPlugin; +import org.eclipse.emf.common.util.ResourceLocator; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.editors.text.templates.ContributionContextTypeRegistry; +import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; +import org.eventb.texteditor.ui.build.dom.DomManager; +import org.eventb.texteditor.ui.editor.codecompletion.DefaultContentAssist.ContextType; + +public final class TextEditorPlugin extends EMFPlugin { + /** + * Plug-in ID + */ + public static final String PLUGIN_ID = "org.eventb.texteditor.ui"; + + /** + * ID to identify the texteditor + */ + public static final String TEXTEDITOR_ID = PLUGIN_ID + ".texteditor"; + + /** + * ID for text editor context menu + */ + public static final String TEXTEDITOR_CONTEXTMENU_ID = "#texteditor.context.menu"; + + /** + * ID for problem markers of this plugin + */ + public static final String SYNTAXERROR_MARKER_ID = PLUGIN_ID + ".syntaxerror"; + + /** + * ID the 'format' command + */ + public static final String FORMAT_COMMAND_ID = "org.eventb.texteditor.command.format"; + + /** + * Singleton instance of EMFPlugin + */ + public static final TextEditorPlugin INSTANCE = new TextEditorPlugin(); + + /** + * Singleton instance of EclipseUIPlugin + */ + private static Implementation plugin; + + private static final DomManager domManager = new DomManager(); + + public TextEditorPlugin() { + super(new ResourceLocator[] {}); + } + + @Override + public ResourceLocator getPluginResourceLocator() { + return plugin; + } + + /** + * Returns the singleton instance of the {@link EclipseUIPlugin} plugin. + * + * @return the singleton instance. + */ + public static Implementation getPlugin() { + return plugin; + } + + public static DomManager getDomManager() { + return domManager; + } + + /** + * This looks up a string in plugin.properties, making a substitution. + */ + public static String getString(final String key, final Object s1) { + return INSTANCE.getString(key, new Object[] { s1 }); + } + + /** + * The actual implementation of the Eclipse <b>Plugin</b> ( + * {@link EclipseUIPlugin}). + */ + public static class Implementation extends EclipseUIPlugin { + private ResourceBundle resourceBundle; + private ContributionTemplateStore templateStore; + private ContributionContextTypeRegistry contextTypeRegistry; + + public Implementation() { + super(); + plugin = this; + + try { + resourceBundle = ResourceBundle.getBundle("plugin"); + } catch (final MissingResourceException e) { + log(new Status(IStatus.ERROR, PLUGIN_ID, + "Cannot load resource bundle", e)); + resourceBundle = null; + } + } + + public ResourceBundle getResourceBundle() { + return resourceBundle; + } + + public IWorkbenchPage getActivePage() { + final IWorkbenchWindow window = getWorkbench() + .getActiveWorkbenchWindow(); + if (window == null) { + return null; + } + return window.getActivePage(); + } + + public ContributionContextTypeRegistry getContextTypeRegistry() { + if (contextTypeRegistry == null) { + contextTypeRegistry = new ContributionContextTypeRegistry(); + contextTypeRegistry.addContextType(ContextType.Anywhere.key); + contextTypeRegistry.addContextType(ContextType.Machine.key); + contextTypeRegistry.addContextType(ContextType.Events.key); + contextTypeRegistry.addContextType(ContextType.Context.key); + } + + return contextTypeRegistry; + } + + public ContributionTemplateStore getTemplateStore() { + if (templateStore == null) { + final IPreferenceStore preferenceStore = TextEditorPlugin + .getPlugin().getPreferenceStore(); + templateStore = new ContributionTemplateStore( + getContextTypeRegistry(), preferenceStore, "templates"); + try { + templateStore.load(); + } catch (final IOException e) { + TextEditorPlugin.getPlugin().getLog().log( + new Status(IStatus.ERROR, + TextEditorPlugin.PLUGIN_ID, + "Cannot load templates", e)); + } + } + + return templateStore; + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/Builder.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/Builder.java new file mode 100644 index 0000000000000000000000000000000000000000..ae2b262e98871b0b17514b1f10860576763d4ce6 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/Builder.java @@ -0,0 +1,54 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IDocument; +import org.eventb.texteditor.ui.build.ast.AstBuilder; +import org.eventb.texteditor.ui.build.dom.DomBuilder; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class Builder { + private final List<IBuildPhase> buildPhases = new ArrayList<IBuildPhase>(); + private boolean successful; + + public Builder() { + buildPhases.add(new AstBuilder()); + buildPhases.add(new DomBuilder()); + } + + public void run(final EventBTextEditor editor, final IDocument document, + final IProgressMonitor monitor) { + final Resource resource = editor.getResource(); + + final SubMonitor subMonitor = SubMonitor.convert(monitor, "Building '" + + resource + "'...", buildPhases.size()); + successful = true; + + for (final IBuildPhase phase : buildPhases) { + if (subMonitor.isCanceled()) { + return; + } + + phase.run(editor, resource, document, subMonitor.newChild(1)); + + if (phase.canFail() && !phase.wasSuccessful()) { + successful = false; + return; + } + } + } + + public boolean wasSuccessful() { + return successful; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/IBuildPhase.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/IBuildPhase.java new file mode 100644 index 0000000000000000000000000000000000000000..3dea07136b41b5fa9e5fb3bd1100af93ca4f2d78 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/IBuildPhase.java @@ -0,0 +1,50 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IDocument; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public interface IBuildPhase { + /** + * Returns whether this phase involves UI related work. + * + * @return + */ + public boolean isUIPhase(); + + /** + * Can this build phase fail? If <code>true</code> the method + * {@link #wasSuccessful()} might be called after running + * {@link #run(IProgressMonitor)}. + * + * @see #wasSuccessful() + * @return + */ + public boolean canFail(); + + /** + * Has the call of {@link #run(IProgressMonitor)}, i.e. the run of this + * build phase been successful? It only makes sense to return + * <code>false</code> if {@link #canFail()} returned <code>true</code>, + * otherwise the result will be ignored anyway. + * + * @see #canFail() + * @return + */ + public boolean wasSuccessful(); + + /** + * Run this build phase. + * + * @param monitor + */ + public void run(final EventBTextEditor editor, final Resource resource, + final IDocument document, final IProgressMonitor monitor); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ReconcilingStrategy.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ReconcilingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..e375ab36a97fea7587d0795adb826ff17388421e --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ReconcilingStrategy.java @@ -0,0 +1,59 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.reconciler.DirtyRegion; +import org.eclipse.jface.text.reconciler.IReconcilingStrategy; +import org.eclipse.jface.text.reconciler.IReconcilingStrategyExtension; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class ReconcilingStrategy implements IReconcilingStrategy, + IReconcilingStrategyExtension { + + private IDocument document; + private IProgressMonitor monitor; + private final EventBTextEditor editor; + + public ReconcilingStrategy(final EventBTextEditor editor) { + this.editor = editor; + } + + public void initialReconcile() { + monitor.beginTask("Reconciling '" + editor.getEditorInput().getName() + + "'", 3); + + if (document != null && !editor.isSaving() && !editor.isInLinkedMode()) { + new Builder().run(editor, document, monitor); + } + + monitor.done(); + } + + public void reconcile(final IRegion partition) { + initialReconcile(); + } + + public void reconcile(final DirtyRegion dirtyRegion, final IRegion subRegion) { + initialReconcile(); + } + + public void setDocument(final IDocument document) { + this.document = document; + } + + public void setProgressMonitor(final IProgressMonitor monitor) { + if (monitor != null) { + this.monitor = monitor; + } else { + this.monitor = new NullProgressMonitor(); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/AstBuilder.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/AstBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..69d1a8505e6a9f318b2648e41e2bed4f18d19eed --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/AstBuilder.java @@ -0,0 +1,143 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eclipse.core.resources.IResource; +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.emf.common.util.BasicDiagnostic; +import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IDocument; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.texteditor.ui.MarkerHelper; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.IBuildPhase; +import org.eventb.texteditor.ui.editor.EventBTextEditor; +import org.eventb.texttools.ParseException; +import org.eventb.texttools.Parser; +import org.eventb.texttools.PersistenceHelper; + +public class AstBuilder implements IBuildPhase { + + private final FormulaTranslator translator = new FormulaTranslator(); + private final Parser textParser = new Parser(); + + private boolean successful; + + public boolean isUIPhase() { + return false; + } + + public boolean canFail() { + return true; + } + + public boolean wasSuccessful() { + return successful; + } + +public void run(final EventBTextEditor editor, final Resource resource, + final IDocument document, final IProgressMonitor monitor) { + successful = false; + + final EventBNamedCommentedComponentElement astRoot = build(resource, document, true, editor, + monitor); + + if (astRoot != null) { + final String docContent = document.get(); + TextEditorPlugin.getDomManager().storeParseResult( + editor.getEditorInput(), docContent, astRoot); + successful = true; + } else { + successful = false; + } + } + + private EventBNamedCommentedComponentElement build(final Resource resource, + final IDocument document, final boolean markFormulaErrors, + final EventBTextEditor editor, IProgressMonitor monitor) { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + + final EventBNamedCommentedComponentElement astRoot = createAST(resource, document); + monitor.worked(1); + + if (monitor.isCanceled()) { + return astRoot; + } + + if (astRoot != null) { + /* + * Translate formula input from ASCII to mathmatical language + */ + monitor.subTask("Translating formulas"); + translator.translateFormulas(astRoot, document, editor); + monitor.worked(1); + + if (monitor.isCanceled()) { + return null; + } + + /* + * Resolver can work even if some formulas could not be translated. + * Other formulas might be ok. Anyway we want to highlight errors. + */ + monitor.subTask("Resolving formulas"); + FormulasResolver.resolveFormulas(resource, astRoot, document, + markFormulaErrors); + monitor.worked(1); + + /* + * Add text representation as annotation to resource's model + */ + PersistenceHelper.addTextAnnotation(astRoot, document.get(), System + .currentTimeMillis()); + } + + return astRoot; + } + + private EventBNamedCommentedComponentElement createAST(final Resource resource, + final IDocument document) { + /* + * Clean up all markers which belong to our tool (including subtypes) + * 'cause we don't know whether following tools will be able to run, + * i.e., whether parsing will be successful. + */ + final MarkerHelper helper = new MarkerHelper(TextEditorPlugin.SYNTAXERROR_MARKER_ID); + helper.deleteMarkers(resource, true, IResource.DEPTH_INFINITE); + + EventBNamedCommentedComponentElement component = null; + + try { + component = textParser.parse(document); + } catch (final ParseException e) { + final IParseProblemWrapper wrappedException = new ParseExceptionWrapperDiagnostic( + document, e); + + final BasicDiagnostic diagnostic = new BasicDiagnostic( + Diagnostic.ERROR, TextEditorPlugin.PLUGIN_ID, 0, e + .getLocalizedMessage(), new Object[] { resource, + wrappedException }); + + try { + helper.createMarkers(diagnostic); + } catch (final CoreException ex) { + TextEditorPlugin.getPlugin().getLog().log( + new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, + "Could not create markers", ex)); + } + } + + return component; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ContextTranslateSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ContextTranslateSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..d45ddb165656dce802477059704463a27bddbc45 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ContextTranslateSwitch.java @@ -0,0 +1,25 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.context.util.ContextSwitch; + +public class ContextTranslateSwitch extends ContextSwitch<Boolean> { + + private final FormulaTranslator translator; + + public ContextTranslateSwitch(final FormulaTranslator formulaTranslator) { + translator = formulaTranslator; + } + + @Override + public Boolean caseEventBNamedCommentedPredicateElement(final EventBNamedCommentedPredicateElement object) { + translator.replace(object); + return true; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaExceptionWrapperDiagnostic.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaExceptionWrapperDiagnostic.java new file mode 100644 index 0000000000000000000000000000000000000000..905df6e5e4a2988eaf89fdea0daaae958e151162 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaExceptionWrapperDiagnostic.java @@ -0,0 +1,104 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eventb.core.ast.ASTProblem; +import org.eventb.core.ast.SourceLocation; + +public class FormulaExceptionWrapperDiagnostic implements IParseProblemWrapper { + + final ASTProblem problem; + private final IDocument document; + private final int startOffset; + + public FormulaExceptionWrapperDiagnostic(final ASTProblem problem, + final int formulaOffset, final IDocument document) { + this.problem = problem; + startOffset = formulaOffset; + this.document = document; + } + + /* + * (non-Javadoc) + * + * @see + * org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getColumn() + */ + public int getColumn() { + try { + final int offset = getOffset(); + return offset + - document.getLineOffset(document.getLineOfOffset(offset)); + } catch (final BadLocationException e) { + // IGNORE + return 0; + } + } + + /* + * (non-Javadoc) + * + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getLine() + */ + public int getLine() { + try { + return document.getLineOfOffset(getOffset()); + } catch (final BadLocationException e) { + // IGNORE + return 0; + } + } + + /* + * (non-Javadoc) + * + * @see + * org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getOffset() + */ + public int getOffset() { + return startOffset + problem.getSourceLocation().getStart(); + } + + /* + * (non-Javadoc) + * + * @see + * org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getTokenLength + * () + */ + public int getTokenLength() { + final SourceLocation location = problem.getSourceLocation(); + return location.getEnd() - location.getStart(); + } + + /* + * (non-Javadoc) + * + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getToken() + */ + public String getToken() { + try { + return document.get(getOffset(), getTokenLength()); + } catch (final BadLocationException e) { + // IGNORE and return fallback value + return ""; + } + } + + /* + * (non-Javadoc) + * + * @see + * org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getMessage() + */ + public String getMessage() { + return String + .format(problem.getMessage().toString(), problem.getArgs()); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaTranslator.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaTranslator.java new file mode 100644 index 0000000000000000000000000000000000000000..ad61ba630c53520dd6e9aa26e089aa17b0ce275e --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulaTranslator.java @@ -0,0 +1,189 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.swt.widgets.Display; +import org.eventb.emf.core.EventBCommentedExpressionElement; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Machine; +import org.eventb.eventBKeyboard.Text2EventBMathTranslator; +import org.eventb.texteditor.ui.editor.EventBTextEditor; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; + +public class FormulaTranslator { + private IDocument document; + private EventBTextEditor editor; + + public void translateFormulas( + final EventBNamedCommentedComponentElement astRoot, + final IDocument document, final EventBTextEditor editor) { + this.document = document; + this.editor = editor; + + // traverse tree using an iterator + final TreeIterator<EObject> iterator = EcoreUtil.getAllContents( + astRoot, false); + + if (astRoot instanceof Machine) { + final MachineTranslateSwitch switcher = new MachineTranslateSwitch( + this); + while (iterator.hasNext()) { + // visit node + switcher.doSwitch(iterator.next()); + } + } else if (astRoot instanceof Context) { + final ContextTranslateSwitch switcher = new ContextTranslateSwitch( + this); + while (iterator.hasNext()) { + // visit node + switcher.doSwitch(iterator.next()); + } + } + } + + public void replace(final EventBNamedCommentedPredicateElement predicate) { + final String input = predicate.getPredicate(); + + if (input != null) { + final String translatedInput = Text2EventBMathTranslator + .translate(input); + + if (!input.equals(translatedInput)) { + // replace in emf node + predicate.setPredicate(translatedInput); + final TextRange range = updatePosition(predicate, input, + translatedInput); + + updateDocument(input, translatedInput, range); + } + } + } + + public void replace(final EventBCommentedExpressionElement expression) { + final String input = expression.getExpression(); + + if (input != null) { + final String translatedInput = Text2EventBMathTranslator + .translate(input); + + if (!input.equals(translatedInput)) { + // replace in emf node + expression.setExpression(translatedInput); + final TextRange range = updatePosition(expression, input, + translatedInput); + + updateDocument(input, translatedInput, range); + } + } + } + + public void replace(final Action action) { + final String input = action.getAction(); + + if (input != null) { + final String translatedInput = Text2EventBMathTranslator + .translate(input); + + if (!input.equals(translatedInput)) { + // replace in emf node + action.setAction(translatedInput); + final TextRange range = updatePosition(action, input, + translatedInput); + + updateDocument(input, translatedInput, range); + } + } + } + + private void updateDocument(final String input, + final String translatedInput, final TextRange range) { + /* + * Check length and fill output string with whitespaces to length of + * input. So we avoid position changes for the following text elements. + * Output should always be <= input. + */ + final int lengthDiff = input.length() - translatedInput.length(); + Assert + .isTrue(lengthDiff >= 0, + "Expecting length of translated formula to be less or equal to original length"); + final String filledString = fillOutput(translatedInput, lengthDiff); + + Display.getDefault().asyncExec(new Runnable() { + public void run() { + try { + int cursorOffset = -1; + if (editor != null) { + cursorOffset = editor.getCursorOffset(); + } + + /* + * Replace text in document. Important: Don't use length + * from TextRange because it's only set to the short new + * version without the trailing whitespaces. + */ + final int startOffset = range.getOffset(); + document.replace(startOffset, filledString.length(), + filledString); + + // correct cursor position afterwards + if (editor != null && cursorOffset >= 0) { + correctCursorPosition(cursorOffset, range, lengthDiff); + } + } catch (final BadLocationException e) { + // IGNORE, cannot fix it anyway + } + } + }); + } + + private void correctCursorPosition(final int oldOffset, + final TextRange range, final int lengthCorrection) { + // was cursor in changed region? + if (oldOffset >= range.getOffset() + && oldOffset <= range.getOffset() + range.getLength() + + lengthCorrection) { + // then move cursor to front a bit + editor.setCurserOffset(oldOffset - lengthCorrection); + } + } + + private String fillOutput(final String output, final int count) { + if (count > 0) { + final StringBuilder buffer = new StringBuilder(output); + for (int i = 0; i < count; i++) { + buffer.append(' '); + } + + return buffer.toString(); + } else { + return output; + } + } + + private TextRange updatePosition(final EventBElement parent, + final String oldContent, final String newContent) { + final TextRange range = TextPositionUtil.getInternalPosition(parent, + oldContent); + + range.setLength(newContent.length()); + TextPositionUtil.replaceInternalPosition(parent, oldContent, + newContent, range); + + return range; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulasResolver.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulasResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..1c72589cdf728788d1da76c4759f9a0f00e92dbc --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/FormulasResolver.java @@ -0,0 +1,94 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import java.util.List; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.emf.common.util.BasicDiagnostic; +import org.eclipse.emf.common.util.Diagnostic; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IDocument; +import org.eventb.core.ast.ASTProblem; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.texteditor.ui.MarkerHelper; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.formulas.FormulaParseException; +import org.eventb.texttools.formulas.FormulaResolver; +import org.eventb.texttools.model.texttools.TextRange; +import org.rodinp.core.RodinMarkerUtil; + +public class FormulasResolver { + + private static final MarkerHelper markerHelper = new MarkerHelper( + TextEditorPlugin.SYNTAXERROR_MARKER_ID); + + public static void resolveFormulas(final Resource resource, + final EventBNamedCommentedComponentElement astRoot, final IDocument document, + final boolean markErrors) { + + final List<FormulaParseException> exceptions = FormulaResolver + .resolveAllFormulas(astRoot); + + if (markErrors && exceptions.size() > 0) { + markerHelper.deleteMarkers(resource, + RodinMarkerUtil.RODIN_PROBLEM_MARKER, true, + IResource.DEPTH_ZERO); + markErrors(exceptions, resource, document); + } + } + + private static void markErrors( + final List<FormulaParseException> exceptions, + final Resource resource, final IDocument document) { + // TODO Do we really want to add a marker/diagnostic to the resource? + // When the model is saved the RodinDB will create another marker for + // it. Maybe having a marker in the texteditor is enough. + + final BasicDiagnostic diagnostic = new BasicDiagnostic( + Diagnostic.ERROR, TextEditorPlugin.PLUGIN_ID, 0, + "Error occured when parsing the formulas", + new Object[] { resource }); + + for (final FormulaParseException ex : exceptions) { + final String inputFormula = ex.getFormula(); + final TextRange range = TextPositionUtil.getInternalPosition(ex + .getEmfObject(), inputFormula); + final int offset = range.getOffset(); + + final List<ASTProblem> problems = ex.getAstProblems(); + for (final ASTProblem problem : problems) { + diagnostic.add(createChildDiagnostic(problem, offset, + inputFormula, document, resource)); + } + } + + try { + markerHelper.createMarkers(diagnostic); + } catch (final CoreException e) { + TextEditorPlugin.INSTANCE.log(e); + } + + } + + private static Diagnostic createChildDiagnostic(final ASTProblem problem, + final int offset, final String inputFormula, + final IDocument document, final Resource resource) { + + final IParseProblemWrapper wrappedException = new FormulaExceptionWrapperDiagnostic( + problem, offset, document); + + final BasicDiagnostic diagnostic = new BasicDiagnostic( + Diagnostic.ERROR, TextEditorPlugin.PLUGIN_ID, 0, + wrappedException.getMessage(), new Object[] { resource, + wrappedException }); + + return diagnostic; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/IParseProblemWrapper.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/IParseProblemWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..295564c86aea08d9a906b7809cb767fd05e964bc --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/IParseProblemWrapper.java @@ -0,0 +1,23 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +public interface IParseProblemWrapper { + + public abstract int getColumn(); + + public abstract int getLine(); + + public abstract int getOffset(); + + public abstract int getTokenLength(); + + public abstract String getToken(); + + public abstract String getMessage(); + +} \ No newline at end of file diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/MachineTranslateSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/MachineTranslateSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..b8d860b5efa1c78e9b513de279bb06c9e26aa7ce --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/MachineTranslateSwitch.java @@ -0,0 +1,39 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eventb.emf.core.EventBCommentedExpressionElement; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.util.MachineSwitch; + +public class MachineTranslateSwitch extends MachineSwitch<Boolean> { + + private final FormulaTranslator translator; + + public MachineTranslateSwitch(final FormulaTranslator formulaTranslator) { + translator = formulaTranslator; + } + + @Override + public Boolean caseEventBNamedCommentedPredicateElement(final EventBNamedCommentedPredicateElement object) { + translator.replace(object); + return true; + } + + @Override + public Boolean caseEventBCommentedExpressionElement(final EventBCommentedExpressionElement object) { + translator.replace(object); + return true; + } + + @Override + public Boolean caseAction(final Action object) { + translator.replace(object); + return true; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ParseExceptionWrapperDiagnostic.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ParseExceptionWrapperDiagnostic.java new file mode 100644 index 0000000000000000000000000000000000000000..7fd7305aeb8d2c37a335e98b7d4fca024b0cf135 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/ast/ParseExceptionWrapperDiagnostic.java @@ -0,0 +1,75 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.ast; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eventb.texttools.ParseException; + +public class ParseExceptionWrapperDiagnostic implements IParseProblemWrapper { + + private final ParseException exception; + private final IDocument document; + + public ParseExceptionWrapperDiagnostic(final IDocument document, + final ParseException e) { + this.document = document; + exception = e; + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getColumn() + */ + public int getColumn() { + return exception.getPosition(); + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getLine() + */ + public int getLine() { + return exception.getLine(); + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getOffset() + */ + public int getOffset() { + try { + return document.getLineOffset(getLine()) + getColumn(); + } catch (final BadLocationException e) { + // IGNORE and return fallback value + return 0; + } + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getTokenLength() + */ + public int getTokenLength() { + return exception.getTokenLength(); + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getToken() + */ + public String getToken() { + try { + return document.get(getOffset(), getTokenLength()); + } catch (final BadLocationException e) { + // IGNORE and return fallback value + return ""; + } + } + + /* (non-Javadoc) + * @see org.eventb.emf.texteditor2.build.ast.IParseProblemWrapper#getMessage() + */ + public String getMessage() { + return exception.getLocalizedMessage(); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractComponentDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractComponentDom.java new file mode 100644 index 0000000000000000000000000000000000000000..ff7cf685633f297210ef37590071093c6b0ccce7 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractComponentDom.java @@ -0,0 +1,69 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; + +public abstract class AbstractComponentDom extends AbstractDom implements + IComponentDom { + + protected boolean initialized = false; + private boolean initializing = false; + private final Resource resource; + + protected AbstractComponentDom(final Type type, final IDom parent, + final Resource resource) { + super(type, parent); + this.resource = resource; + } + + @Override + public synchronized void reset() { + if (initialized) { + super.reset(); + doReset(); + initialized = false; + } + } + + public synchronized void resetAndinit() { + reset(); + init(); + } + + public Resource getResource() { + return resource; + } + + protected void checkInitialization() { + if (!isInitialized()) { + init(); + } + } + + private synchronized void init() { + if (!initialized && !initializing) { + initializing = true; + doInitialize(); + initialized = true; + initializing = false; + } + } + + private synchronized boolean isInitialized() { + return initialized; + } + + protected void doInitialize() { + DomBuilder.initializeDOM(this, + (EventBNamedCommentedComponentElement) getEventBElement(), + resource); + } + + protected abstract void doReset(); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractDom.java new file mode 100644 index 0000000000000000000000000000000000000000..18a6a1180b5e625ceac4b8bf4aed813b6ebef59f --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/AbstractDom.java @@ -0,0 +1,105 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; + +public abstract class AbstractDom implements IDom { + + private final Type type; + private final IDom parent; + private final List<IDom> children = new LinkedList<IDom>(); + + protected AbstractDom(final Type type, final IDom parent) { + this.type = type; + this.parent = parent; + } + + public Type getType() { + return type; + } + + public IDom getParent() { + return parent; + } + + public synchronized void reset() { + children.clear(); + } + + /** + * Returns a {@link List} with all child DOMs of this DOM. <b>Attention:</b> + * The returned list is unmodifiable and will throw + * {@link UnsupportedOperationException}s for modification method calls. + */ + public synchronized List<IDom> getChildren() { + return Collections.unmodifiableList(children); + } + + public synchronized void addChild(final IDom child) { + children.add(child); + } + + public synchronized IDom getScopingDom(final int offset) { + if (!containsOffset(TextPositionUtil.getTextRange(getEventBElement()), + offset)) { + return null; + } + + for (final IDom child : children) { + final IDom scope = child.getScopingDom(offset); + if (scope != null) { + return scope; + } + } + + return this; + } + + public Set<String> getIdentifiers() { + final Set<String> result = new HashSet<String>(); + + if (parent != null) { + result.addAll(parent.getIdentifiers()); + } + + result.addAll(doGetIdentifiers()); + + return result; + } + + public IdentifierType getIdentifierType(final String identifier) { + IDom currentDom = this; + IdentifierType type = null; + + // search identifier upwarts in hierarchy + while (currentDom != null + && (type = ((AbstractDom) currentDom) + .doGetIdentifierType(identifier)) == null) { + currentDom = currentDom.getParent(); + } + + return type; + } + + private boolean containsOffset(final TextRange range, final int offset) { + return range != null && range.getOffset() <= offset + && range.getOffset() + range.getLength() >= offset; + } + + protected abstract Set<String> doGetIdentifiers(); + + protected abstract IdentifierType doGetIdentifierType( + final String identifier); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/CollectingHelper.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/CollectingHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..2faa9f6675bce6f695debe1a72ef12bba54f0974 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/CollectingHelper.java @@ -0,0 +1,65 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.URIConverter; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.machine.Machine; + +public class CollectingHelper { + public static final String SUFFIX_MACHINE = "bum"; + public static final String SUFFIX_CONTEXT = "buc"; + + protected static Resource resolveComponentsResource( + final EventBNamedCommentedComponentElement component, final Resource referencingResource) { + Resource resource = null; + + if (component.eIsProxy()) { + // resolve context in resource's ResourceSet + final EventBNamedCommentedComponentElement resolvedComponent = (EventBNamedCommentedComponentElement) EcoreUtil + .resolve(component, referencingResource); + + /* + * Workaround when automatic resolving didn't work. + */ + if (resolvedComponent.eIsProxy()) { + final URI uri = referencingResource.getURI() + .trimFileExtension().trimSegments(1).appendSegment( + ((EventBNamed) component).getName()) + .appendFileExtension(getFileExtension(component)); + /* + * Only try to resolve if file already exists. We don't want new + * files to be created on the fly. + */ + if (URIConverter.INSTANCE.exists(uri, null)) { + resource = referencingResource.getResourceSet() + .getResource(uri, true); + } else { + resource = resolvedComponent.eResource(); + } + } else { + resource = resolvedComponent.eResource(); + } + } else { + resource = component.eResource(); + } + + return resource; + } + + private static String getFileExtension(final EventBNamedCommentedComponentElement component) { + if (component instanceof Machine) { + return SUFFIX_MACHINE; + } else { + return SUFFIX_CONTEXT; + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextCollectingSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextCollectingSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..020e9e74edd3f7ae8c097f4c5a246d4f638e2d36 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextCollectingSwitch.java @@ -0,0 +1,92 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Axiom; +import org.eventb.emf.core.context.CarrierSet; +import org.eventb.emf.core.context.Constant; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.util.ContextSwitch; +import org.eventb.emf.formulas.BFormula; +import org.eventb.texttools.formulas.ExtensionHelper; + +public class ContextCollectingSwitch extends ContextSwitch<Boolean> { + private final ContextDom contextDom; + private final Resource contextResource; + + protected ContextCollectingSwitch(final ContextDom dom, + final Resource contextResource) { + contextDom = dom; + this.contextResource = contextResource; + } + + protected AbstractDom getDom() { + return contextDom; + } + + public IDom getCurrentParentDom() { + return contextDom; + } + + @Override + public Boolean caseEventBObject(final EventBObject object) { + // default to avoid NPEs + return true; + } + + @Override + public Boolean caseContext(final Context object) { + contextDom.setContext(object); + + final EList<Context> extendedContexts = object.getExtends(); + for (final Context context : extendedContexts) { + final IComponentDom dom = DomBuilder.getReferencedDom(context, + contextResource); + + if (dom != null) { + Assert.isTrue(dom instanceof ContextDom); + contextDom.addExtendedContext((ContextDom) dom); + } + + /* + * If no DOM is found the reference must be to a not-existing + * machine. + */ + } + + return true; + } + + @Override + public Boolean caseAxiom(final Axiom object) { + final BFormula formula = ExtensionHelper.getFormula(object + .getExtensions()); + + if (formula != null) { + final FormulaDom formDom = new FormulaDom(formula, contextDom); + contextDom.addChild(formDom); + } + + return false; + } + + @Override + public Boolean caseConstant(final Constant object) { + contextDom.addConstant(object); + return false; + } + + @Override + public Boolean caseCarrierSet(final CarrierSet object) { + contextDom.addSet(object); + return false; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextDom.java new file mode 100644 index 0000000000000000000000000000000000000000..c539721d568a72f82e218db2f73bc5362dffb587 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/ContextDom.java @@ -0,0 +1,140 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.CarrierSet; +import org.eventb.emf.core.context.Constant; +import org.eventb.emf.core.context.Context; + +public class ContextDom extends AbstractComponentDom { + private Context context; + private final Map<String, Constant> constants = new HashMap<String, Constant>(); + private final Map<String, CarrierSet> sets = new HashMap<String, CarrierSet>(); + + private final List<ContextDom> extendedContexts = new LinkedList<ContextDom>(); + + ContextDom(final Resource resource, final Context context) { + super(Type.Context, null, resource); + this.context = context; + } + + @Override + protected synchronized void doReset() { + constants.clear(); + sets.clear(); + extendedContexts.clear(); + } + + @Override + protected synchronized Set<String> doGetIdentifiers() { + final Set<String> result = new HashSet<String>(); + + result.addAll(getConstants(true).keySet()); + result.addAll(getSets(true).keySet()); + + return result; + } + + @Override + protected synchronized IdentifierType doGetIdentifierType( + final String identifier) { + if (getConstants(true).containsKey(identifier)) { + return IdentifierType.Constant; + } + + if (getSets(true).containsKey(identifier)) { + return IdentifierType.Set; + } + + return null; + } + + public Set<IComponentDom> getReferencedDoms(final boolean transitive) { + final List<ContextDom> contexts = getExtendedContexts(); + final HashSet<IComponentDom> result = new HashSet<IComponentDom>( + contexts); + + if (transitive) { + for (final IComponentDom referencedDom : contexts) { + result.addAll(referencedDom.getReferencedDoms(true)); + } + } + + return result; + } + + public synchronized List<ContextDom> getExtendedContexts() { + checkInitialization(); + return extendedContexts; + } + + public synchronized Map<String, Constant> getConstants( + final boolean includeInherited) { + checkInitialization(); + + final Map<String, Constant> result = new HashMap<String, Constant>(); + + if (includeInherited) { + // add referenced context's constants + for (final ContextDom dom : getExtendedContexts()) { + result.putAll(dom.getConstants(includeInherited)); + } + } + // finally the local constants + result.putAll(constants); + + return result; + } + + public synchronized Map<String, CarrierSet> getSets( + final boolean includeInherited) { + checkInitialization(); + + final Map<String, CarrierSet> result = new HashMap<String, CarrierSet>(); + + if (includeInherited) { + // add referenced context's constants + for (final ContextDom dom : getExtendedContexts()) { + result.putAll(dom.getSets(includeInherited)); + } + } + + // finally the local sets + result.putAll(sets); + + return result; + } + + synchronized void addExtendedContext(final ContextDom dom) { + extendedContexts.add(dom); + } + + synchronized void addConstant(final Constant constant) { + constants.put(constant.getName(), constant); + } + + synchronized void addSet(final CarrierSet set) { + sets.put(set.getName(), set); + } + + public synchronized EventBObject getEventBElement() { + return context; + } + + public void setContext(final Context context) { + this.context = context; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomBuilder.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..9c54eaf0dab35e0b9e9d0dd46187eae71ce3db07 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomBuilder.java @@ -0,0 +1,204 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorInput; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.ContextPackage; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.MachinePackage; +import org.eventb.emf.formulas.FormulasPackage; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.IBuildPhase; +import org.eventb.texteditor.ui.build.dom.DomManager.ParseResult; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class DomBuilder implements IBuildPhase { + + public boolean isUIPhase() { + return false; + } + + public boolean canFail() { + return false; + } + + public boolean wasSuccessful() { + return true; + } + + public void run(final EventBTextEditor editor, final Resource resource, + final IDocument document, final IProgressMonitor monitor) { + final DomManager domManager = TextEditorPlugin.getDomManager(); + final IEditorInput editorInput = editor.getEditorInput(); + final ParseResult parseResult = domManager + .getLastParseResult(editorInput); + + if (parseResult != null) { + final EventBNamedCommentedComponentElement astRoot = (EventBNamedCommentedComponentElement) parseResult.astRoot; + final IComponentDom dom = domManager.getDom(resource, astRoot); + + if (astRoot instanceof Machine) { + ((MachineDom) dom).setMachine((Machine) astRoot); + } else if (astRoot instanceof Context) { + ((ContextDom) dom).setContext((Context) astRoot); + } + + dom.resetAndinit(); + } + } + + /** + * Find the {@link IComponentDom} for the given {@link EventBNamedCommentedComponentElement}. The + * given {@link Resource} is used as a base when resolving the resource for + * the referenced component. + * + * @param component + * @param referencingResource + * @return + */ + public static IComponentDom getReferencedDom( + final EventBNamedCommentedComponentElement component, final Resource referencingResource) { + final Resource resource = CollectingHelper.resolveComponentsResource( + component, referencingResource); + + if (resource != null) { + return TextEditorPlugin.getDomManager().getDom(resource, component); + } + + return null; + } + + /** + * Initializes the given {@link IComponentDom} with the given + * {@link EventBNamedCommentedComponentElement}. For all referenced {@link EventBNamedCommentedComponentElement} the + * given {@link Resource} is used as the base from where to search the + * referenced {@link Resource}. + * + * @param dom + * @param component + * @param resource + * @return + */ + public static void initializeDOM(final IComponentDom dom, + final EventBNamedCommentedComponentElement component, final Resource resource) { + if (component instanceof Machine) { + initializeDOM((MachineDom) dom, (Machine) component, resource); + } else if (component instanceof Context) { + initializeDOM((ContextDom) dom, (Context) component, resource); + } + + // notify listeners in dom manager about change + TextEditorPlugin.getDomManager().notifyDomChangeListeners(dom); + } + + /** + * Initializes the given {@link MachineDom} with the given {@link Machine}. + * For all referenced {@link EventBNamedCommentedComponentElement} the given {@link Resource} is + * used as the base from where to search the referenced {@link Resource}. + * + * @param dom + * @param component + * @param resource + * @return + */ + private static void initializeDOM(final MachineDom dom, + final Machine component, final Resource resource) { + dom.reset(); + + final MachineCollectingSwitch machineSwitch = new MachineCollectingSwitch( + dom, resource); + machineSwitch.doSwitch(component); + final FormulaCollectingSwitch formulaSwitch = new FormulaCollectingSwitch(); + + // traverse tree using an iterator + final TreeIterator<EObject> iterator = getContentIterator(component, + resource); + + while (iterator.hasNext()) { + final EObject next = iterator.next(); + final String nsURI = next.eClass().getEPackage().getNsURI(); + Boolean visitChildren = true; + + if (MachinePackage.eNS_URI.equals(nsURI)) { + visitChildren = machineSwitch.doSwitch(next); + } else if (FormulasPackage.eNS_URI.equals(nsURI)) { + formulaSwitch.setCurrentParentDom(machineSwitch + .getCurrentParentDom()); + formulaSwitch.doSwitch(next); + } + + // visit node + if (!visitChildren) { + // skip children if visited node returne false + iterator.prune(); + } + } + } + + /** + * Initializes the given {@link ContextDom} with the given {@link Context}. + * For all referenced {@link EventBNamedCommentedComponentElement} the given {@link Resource} is + * used as the base from where to search the referenced {@link Resource}. + * + * @param dom + * @param component + * @param resource + * @return + */ + private static void initializeDOM(final ContextDom dom, + final Context component, final Resource resource) { + dom.reset(); + + final ContextCollectingSwitch contextSwitch = new ContextCollectingSwitch( + dom, resource); + contextSwitch.doSwitch(component); + final FormulaCollectingSwitch formulaSwitch = new FormulaCollectingSwitch(); + + // traverse tree using an iterator + final TreeIterator<EObject> iterator = getContentIterator(component, + resource); + + while (iterator.hasNext()) { + final EObject next = iterator.next(); + final String nsURI = next.eClass().getEPackage().getNsURI(); + Boolean visitChildren = true; + + if (ContextPackage.eNS_URI.equals(nsURI)) { + visitChildren = contextSwitch.doSwitch(next); + } else if (FormulasPackage.eNS_URI.equals(nsURI)) { + formulaSwitch.setCurrentParentDom(contextSwitch + .getCurrentParentDom()); + formulaSwitch.doSwitch(next); + } + + // visit node + if (!visitChildren) { + // skip children if visited node returne false + iterator.prune(); + } + } + } + + private static TreeIterator<EObject> getContentIterator( + final EventBNamedCommentedComponentElement component, final Resource resource) { + if (component.eIsProxy()) { + // the component has not been resolved yet, use resource to do so + return EcoreUtil.getAllContents(resource, true); + } else { + // we can use the contents of the component itself + return EcoreUtil.getAllContents(component, false); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomManager.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomManager.java new file mode 100644 index 0000000000000000000000000000000000000000..7050e032fe20594df1035ae97384fb22406b6225 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/DomManager.java @@ -0,0 +1,210 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.ui.IEditorInput; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Machine; + +public class DomManager { + + private final Map<Resource, IComponentDom> lastDom = new HashMap<Resource, IComponentDom>(); + private final Map<IEditorInput, ParseResult> lastParseResults = new HashMap<IEditorInput, ParseResult>(); + + private final Set<IDomChangeListener> domListeners = new HashSet<IDomChangeListener>(); + private final Map<IEditorInput, List<IParseResultListener>> parseResultListeners = new HashMap<IEditorInput, List<IParseResultListener>>(); + + public void storeDom(final IComponentDom dom) { + if (dom != null) { + synchronized (lastDom) { + lastDom.put(dom.getResource(), dom); + } + } + } + + public void removeDom(final Resource resource) { + synchronized (lastDom) { + lastDom.remove(resource); + } + } + + /** + * Returns an {@link IComponentDom} for the given {@link Resource} or + * <code>null</code> if none is available. See + * {@link #getDom(Resource, EventBNamedCommentedComponentElement)} if you want lookup and + * on-demand creation. + * + * @see #getDom(Resource, EventBNamedCommentedComponentElement) + * @param resource + * @return + */ + public IComponentDom getDom(final Resource resource) { + synchronized (lastDom) { + return lastDom.get(resource); + } + } + + /** + * Returns an {@link IComponentDom} object for the given {@link Resource}. + * If no DOM is available so far a new and uninitialized one is created for + * the resource and the given {@link EventBNamedCommentedComponentElement}. + * + * @param resource + * @param object + * @return + */ + public IComponentDom getDom(final Resource resource, + final EventBNamedCommentedComponentElement component) { + synchronized (lastDom) { + IComponentDom dom = lastDom.get(resource); + + if (dom == null && component != null) { + dom = createNewDom(resource, component); + storeDom(dom); + } + + return dom; + } + } + + /** + * Returns the {@link IComponentDom} object for the given + * {@link EventBObject}'s resource. If no DOM is available so far a new and + * uninitialized one is created. + * + * @see #getDom(Resource) + * @param object + * @return + */ + public IComponentDom getDom(final EventBNamedCommentedComponentElement component) { + final Resource resource = component.eResource(); + + if (resource != null) { + synchronized (lastDom) { + return getDom(resource, component); + } + } + + return null; + } + + /** + * Creates a new and uninitialized {@link IComponentDom} for the given + * {@link EventBNamedCommentedComponentElement}. + * + * @param component + * @return + */ + private IComponentDom createNewDom(final Resource resource, + final EventBNamedCommentedComponentElement component) { + IComponentDom dom; + if (component instanceof Machine) { + dom = new MachineDom(resource, (Machine) component); + } else { + dom = new ContextDom(resource, (Context) component); + } + + return dom; + } + + public void storeParseResult(final IEditorInput editorInput, + final String textInput, final EventBNamedCommentedComponentElement astRoot) { + final ParseResult parseResult = new ParseResult(astRoot, textInput); + lastParseResults.put(editorInput, parseResult); + + notifyParseResultListeners(editorInput, parseResult); + } + + /** + * <p> + * Returns the last successful parse result, i.e., the last successfully + * parsed text and its AST that was produced. + * </p> + * <p> + * Attention: The result may be old and not consistent with the current + * content of the editor. + * </p> + * + * @param editorInput + * @return + */ + public ParseResult getLastParseResult(final IEditorInput editorInput) { + return lastParseResults.get(editorInput); + } + + public void addParseResultListener(final IEditorInput editorInput, + final IParseResultListener listener) { + if (!parseResultListeners.containsKey(editorInput)) { + final ArrayList<IParseResultListener> list = new ArrayList<IParseResultListener>(); + list.add(listener); + parseResultListeners.put(editorInput, list); + } else { + parseResultListeners.get(editorInput).add(listener); + } + } + + public void removeParseResultListener(final IEditorInput editorInput, + final IParseResultListener listener) { + if (parseResultListeners.containsKey(editorInput)) { + final List<IParseResultListener> list = parseResultListeners + .get(editorInput); + list.remove(listener); + + if (list.isEmpty()) { + parseResultListeners.remove(editorInput); + } + } + } + + private void notifyParseResultListeners(final IEditorInput editorInput, + final ParseResult parseResult) { + if (parseResultListeners.containsKey(editorInput)) { + for (final IParseResultListener listener : parseResultListeners + .get(editorInput)) { + listener.parseResultChanged(parseResult); + } + } + } + + public void addDomChangeListener(final IDomChangeListener listener) { + if (listener != null && !domListeners.contains(listener)) { + domListeners.add(listener); + } + } + + public void removeDomChangeListener(final IDomChangeListener listener) { + if (listener != null) { + domListeners.remove(listener); + } + } + + public void notifyDomChangeListeners(final IComponentDom dom) { + for (final IDomChangeListener listener : domListeners) { + listener.domChanged(dom); + } + } + + public class ParseResult { + public final EventBObject astRoot; + public final String textInput; + + public ParseResult(final EventBObject astRoot, final String textInput) { + this.astRoot = astRoot; + this.textInput = textInput; + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/EventDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/EventDom.java new file mode 100644 index 0000000000000000000000000000000000000000..82d051f4e5dd6b120d2ab8529ead8ce85eab9230 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/EventDom.java @@ -0,0 +1,71 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Parameter; + +public class EventDom extends AbstractDom { + private final Event event; + + private final Map<String, Event> refinedEvents = new HashMap<String, Event>(); + private final Map<String, Parameter> parameters = new HashMap<String, Parameter>(); + + public EventDom(final Event event, final MachineDom parent) { + super(Type.Event, parent); + this.event = event; + } + + @Override + protected synchronized Set<String> doGetIdentifiers() { + final Set<String> result = new HashSet<String>(); + + result.addAll(getParameters().keySet()); + + return result; + } + + @Override + protected IdentifierType doGetIdentifierType(final String identifier) { + if (parameters.containsKey(identifier)) { + return IdentifierType.Parameter; + } + + return null; + } + + public EventBObject getEventBElement() { + return event; + } + + public Map<String, Parameter> getParameters() { + return parameters; + } + + public void addAllParameters(final List<Parameter> params) { + for (final Parameter parameter : params) { + parameters.put(parameter.getName(), parameter); + } + } + + public Map<String, Event> getRefinedEvents() { + return refinedEvents; + } + + public void addAllRefinedEvent(final List<Event> events) { + for (final Event evt : events) { + refinedEvents.put(evt.getName(), evt); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaCollectingSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaCollectingSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..09c795bf7a2a45e1fe72608e8323baf22f4f6ed7 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaCollectingSwitch.java @@ -0,0 +1,52 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.core.runtime.Assert; +import org.eventb.emf.formulas.BFormula; +import org.eventb.emf.formulas.BoundIdentifierExpression; +import org.eventb.emf.formulas.IdentifierExpression; +import org.eventb.emf.formulas.util.FormulasSwitch; + +public class FormulaCollectingSwitch extends FormulasSwitch<Boolean> { + + private FormulaDom formulaDom; + private IDom currentParentDom; + + public void setCurrentParentDom(final IDom currentParentDom) { + Assert.isNotNull(currentParentDom); + this.currentParentDom = currentParentDom; + } + + @Override + public Boolean caseBFormula(final BFormula object) { + formulaDom = new FormulaDom(object, currentParentDom); + currentParentDom.addChild(formulaDom); + return true; + } + + @Override + public Boolean caseIdentifierExpression(final IdentifierExpression object) { + if (formulaDom == null) { + caseBFormula(object); + } + + formulaDom.addFreeIdentifier(object); + return false; + } + + @Override + public Boolean caseBoundIdentifierExpression( + final BoundIdentifierExpression object) { + if (formulaDom == null) { + caseBFormula(object); + } + + formulaDom.addBoundIdentifier(object); + return false; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaDom.java new file mode 100644 index 0000000000000000000000000000000000000000..4328b5ec101458ffcbe494da941f273241e1ad9a --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/FormulaDom.java @@ -0,0 +1,67 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.formulas.BFormula; +import org.eventb.emf.formulas.BoundIdentifierExpression; +import org.eventb.emf.formulas.IdentifierExpression; + +public class FormulaDom extends AbstractDom { + private final BFormula formula; + private final Map<String, IdentifierExpression> freeIdentifiers = new HashMap<String, IdentifierExpression>(); + private final Map<String, BoundIdentifierExpression> boundIdentifiers = new HashMap<String, BoundIdentifierExpression>(); + + public FormulaDom(final BFormula formula, final IDom parent) { + super(Type.Event, parent); + this.formula = formula; + } + + public EventBObject getEventBElement() { + return formula; + } + + @Override + protected synchronized Set<String> doGetIdentifiers() { + final Set<String> result = new HashSet<String>(); + + result.addAll(getBoundIdentifiers().keySet()); + result.addAll(getFreeIdentifiers().keySet()); + + return result; + } + + @Override + protected IdentifierType doGetIdentifierType(final String identifier) { + if (boundIdentifiers.containsKey(identifier)) { + return IdentifierType.LocalVariable; + } + + return null; + } + + public Map<String, IdentifierExpression> getFreeIdentifiers() { + return freeIdentifiers; + } + + public void addFreeIdentifier(final IdentifierExpression expression) { + freeIdentifiers.put(expression.getName(), expression); + } + + public Map<String, BoundIdentifierExpression> getBoundIdentifiers() { + return boundIdentifiers; + } + + public void addBoundIdentifier(final BoundIdentifierExpression expression) { + boundIdentifiers.put(expression.getName(), expression); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IComponentDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IComponentDom.java new file mode 100644 index 0000000000000000000000000000000000000000..c3340b2aebe181d7cc3b602e8361c4b0f57ac757 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IComponentDom.java @@ -0,0 +1,22 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; + +public interface IComponentDom extends IDom { + + public void reset(); + + public void resetAndinit(); + + public Resource getResource(); + + public Set<IComponentDom> getReferencedDoms(final boolean transitive); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDom.java new file mode 100644 index 0000000000000000000000000000000000000000..65828f8998e55190b46a38a0ff1b982c2732f189 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDom.java @@ -0,0 +1,38 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.List; +import java.util.Set; + +import org.eventb.emf.core.EventBObject; + +public interface IDom { + public enum Type { + Machine, Context, Event, Formula, Unknown + }; + + public enum IdentifierType { + GlobalVariable, Parameter, Constant, Set, LocalVariable + }; + + public Type getType(); + + public EventBObject getEventBElement(); + + public IDom getParent(); + + public void addChild(final IDom child); + + public List<IDom> getChildren(); + + public IDom getScopingDom(final int offset); + + public Set<String> getIdentifiers(); + + public IdentifierType getIdentifierType(String identifier); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDomChangeListener.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDomChangeListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4fccbbbb54e35bbc6613f78248e622747f6107c9 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IDomChangeListener.java @@ -0,0 +1,19 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +public interface IDomChangeListener { + /** + * Method will be called when a new {@link IDom} is stored in the + * {@link DomManager} for a certain AST. Implementing listeners are asked to + * update according to the new result. + * + * @param dom + * The new {@link IDom} or <code>null</code>. + */ + public void domChanged(final IComponentDom changedDom); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IParseResultListener.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IParseResultListener.java new file mode 100644 index 0000000000000000000000000000000000000000..76c32dbd30e35dc5d554a2e9ff9ff56ede3ffee9 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/IParseResultListener.java @@ -0,0 +1,22 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.ui.IEditorInput; +import org.eventb.texteditor.ui.build.dom.DomManager.ParseResult; + +public interface IParseResultListener { + /** + * Method will be called when a new {@link ParseResult} is stored in the + * {@link DomManager} for a certain {@link IEditorInput}. Implementing + * listeners are asked to update according to the new result. + * + * @param parseResult + * The new {@link ParseResult} or <code>null</code>. + */ + public void parseResultChanged(ParseResult parseResult); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineCollectingSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineCollectingSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..a6c275e62db24dc18882d405e7bd2fe59f189b75 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineCollectingSwitch.java @@ -0,0 +1,101 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variable; +import org.eventb.emf.core.machine.util.MachineSwitch; + +public class MachineCollectingSwitch extends MachineSwitch<Boolean> { + private final MachineDom machineDom; + private IDom currentParent = null; + private final Resource machineResource; + + protected MachineCollectingSwitch(final MachineDom dom, + final Resource machineResource) { + machineDom = dom; + this.machineResource = machineResource; + } + + protected AbstractDom getDom() { + return machineDom; + } + + public IDom getCurrentParentDom() { + return currentParent; + } + + @Override + public Boolean caseEventBObject(final EventBObject object) { + return true; + } + + @Override + public Boolean caseMachine(final Machine object) { + machineDom.setMachine(object); + + final EList<Machine> refines = object.getRefines(); + for (final Machine machine : refines) { + final IComponentDom dom = DomBuilder.getReferencedDom(machine, + machineResource); + + if (dom != null) { + Assert.isTrue(dom instanceof MachineDom); + machineDom.addRefinedMachine((MachineDom) dom); + } + + /* + * If no DOM is found the reference must be to a not-existing + * machine. + */ + } + + final EList<Context> sees = object.getSees(); + for (final Context context : sees) { + final IComponentDom dom = DomBuilder.getReferencedDom(context, + machineResource); + + if (dom != null) { + Assert.isTrue(dom instanceof ContextDom); + machineDom.addSeenContext((ContextDom) dom); + } + + /* + * If no DOM is found the reference must be to a not-existing + * machine. + */ + } + + currentParent = machineDom; + + return true; + } + + @Override + public Boolean caseVariable(final Variable object) { + machineDom.addVariable(object); + return false; + } + + @Override + public Boolean caseEvent(final Event object) { + final EventDom eventDom = new EventDom(object, machineDom); + eventDom.addAllRefinedEvent(object.getRefines()); + eventDom.addAllParameters(object.getParameters()); + machineDom.addChild(eventDom); + + currentParent = eventDom; + + return true; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineDom.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineDom.java new file mode 100644 index 0000000000000000000000000000000000000000..041ac774e17b54dc861fc1834e42b126f7d66b32 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/build/dom/MachineDom.java @@ -0,0 +1,169 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.build.dom; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.CarrierSet; +import org.eventb.emf.core.context.Constant; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variable; + +public class MachineDom extends AbstractComponentDom { + private Machine machine; + private final Map<String, Variable> variables = new HashMap<String, Variable>(); + + private final List<MachineDom> refinedMachines = new LinkedList<MachineDom>(); + private final List<ContextDom> seenContexts = new LinkedList<ContextDom>(); + + MachineDom(final Resource resource, final Machine machine) { + super(Type.Machine, null, resource); + this.machine = machine; + } + + @Override + protected synchronized void doReset() { + refinedMachines.clear(); + seenContexts.clear(); + variables.clear(); + } + + @Override + protected synchronized Set<String> doGetIdentifiers() { + final Set<String> result = new HashSet<String>(); + + result.addAll(getVariables(true).keySet()); + result.addAll(getConstants().keySet()); + result.addAll(getSets().keySet()); + + return result; + } + + @Override + protected synchronized IdentifierType doGetIdentifierType( + final String identifier) { + if (variables.containsKey(identifier)) { + return IdentifierType.GlobalVariable; + } + + if (getConstants().containsKey(identifier)) { + return IdentifierType.Constant; + } + + if (getSets().containsKey(identifier)) { + return IdentifierType.Set; + } + + return null; + } + + public Set<IComponentDom> getReferencedDoms(final boolean transitive) { + final List<ContextDom> contexts = getSeenContexts(); + final List<MachineDom> machines = getRefinedMachines(); + final HashSet<IComponentDom> result = new HashSet<IComponentDom>( + contexts); + result.addAll(machines); + + if (transitive) { + for (final IComponentDom referencedDom : contexts) { + result.addAll(referencedDom.getReferencedDoms(transitive)); + } + + for (final IComponentDom referencedDom : machines) { + result.addAll(referencedDom.getReferencedDoms(false)); + } + } + + return result; + } + + public synchronized Map<String, Variable> getVariables( + final boolean includeInherited) { + checkInitialization(); + + final Map<String, Variable> result = new HashMap<String, Variable>(); + + if (includeInherited) { + // add referenced machine's variables + for (final MachineDom dom : getRefinedMachines()) { + /* + * Only add directly referenced machine's variables, but not + * transitively. Variables need to be redeclared then. + */ + result.putAll(dom.getVariables(false)); + } + } + + // finally the local variables + result.putAll(variables); + + return result; + } + + public synchronized Map<String, Constant> getConstants() { + checkInitialization(); + + final Map<String, Constant> result = new HashMap<String, Constant>(); + + // add referenced context's constants + for (final ContextDom dom : getSeenContexts()) { + result.putAll(dom.getConstants(true)); + } + + return result; + } + + public synchronized Map<String, CarrierSet> getSets() { + checkInitialization(); + + final Map<String, CarrierSet> result = new HashMap<String, CarrierSet>(); + + // add referenced context's constants + for (final ContextDom dom : getSeenContexts()) { + result.putAll(dom.getSets(true)); + } + + return result; + } + + public synchronized List<MachineDom> getRefinedMachines() { + checkInitialization(); + return refinedMachines; + } + + public synchronized List<ContextDom> getSeenContexts() { + checkInitialization(); + return seenContexts; + } + + synchronized void addVariable(final Variable var) { + variables.put(var.getName(), var); + } + + synchronized void addRefinedMachine(final MachineDom dom) { + refinedMachines.add(dom); + } + + synchronized void addSeenContext(final ContextDom dom) { + seenContexts.add(dom); + } + + public synchronized EventBObject getEventBElement() { + return machine; + } + + public void setMachine(final Machine machine) { + this.machine = machine; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/AnnotationModel.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/AnnotationModel.java new file mode 100644 index 0000000000000000000000000000000000000000..e89878fbeebfb429a0bcb8185c14537b74245cef --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/AnnotationModel.java @@ -0,0 +1,225 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel; +import org.eventb.core.IExtendsContext; +import org.eventb.core.IRefinesEvent; +import org.eventb.core.IRefinesMachine; +import org.eventb.core.ISeesContext; +import org.eventb.emf.core.EventBExpression; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.EventBPredicate; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Parameter; +import org.eventb.emf.core.machine.Variable; +import org.eventb.emf.persistence.factory.RodinResource; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; +import org.rodinp.core.IInternalElement; +import org.rodinp.core.RodinDBException; +import org.rodinp.core.RodinMarkerUtil; + +public class AnnotationModel extends ResourceMarkerAnnotationModel { + private static final String LABEL_ATTRIBUTE_ID = "org.eventb.core.label"; + private final RodinResource rodinResource; + + public AnnotationModel(final IResource fileResource, + final Resource emfResource) { + super(fileResource); + + if (emfResource instanceof RodinResource) { + rodinResource = (RodinResource) emfResource; + } else { + rodinResource = null; + } + } + + @Override + protected Position createPositionFromMarker(final IMarker marker) { + try { + if (RodinMarkerUtil.RODIN_PROBLEM_MARKER.equals(marker.getType())) { + return createPosition(marker); + } + } catch (final CoreException e) { + TextEditorPlugin.getPlugin().getLog().log( + new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, + "Error analyzing marker", e)); + } + + return super.createPositionFromMarker(marker); + } + + private Position createPosition(final IMarker marker) { + final IRegion region = getBestRegion(marker); + + if (region != null) { + return new Position(region.getOffset(), region.getLength()); + } else { + return null; + } + } + + private IRegion getBestRegion(final IMarker marker) { + final int charStart = RodinMarkerUtil.getCharStart(marker); + final int charEnd = RodinMarkerUtil.getCharEnd(marker); + + final IInternalElement rodinElement = RodinMarkerUtil + .getInternalElement(marker); + + // special case for references (sees, refines, ...) + if (isReferenceElement(rodinElement)) { + return handleReferenceElement(rodinElement); + } + + final EventBObject eventBObject = rodinResource.getMap().get( + rodinElement); + + if (charStart >= 0 && charEnd >= 0) { + /* + * If start and end char is set, it must be a problem inside a + * formula. + */ + return getRegionInFormula(eventBObject, charStart, charEnd); + + } else if (onlyLabelIsRelevant(marker, eventBObject)) { + final TextRange range = TextPositionUtil.getInternalPosition( + eventBObject, ((EventBNamed) eventBObject).getName()); + + if (range != null) { + return new Region(range.getOffset(), range.getLength()); + } + } else { + /* + * Asuming the whole element has a problem as a fallback + */ + final TextRange range = TextPositionUtil.getTextRange(eventBObject); + + if (range != null) { + return new Region(range.getOffset(), range.getLength()); + } + } + + return new Region(0, 0); + } + + private boolean isReferenceElement(final IInternalElement rodinElement) { + return rodinElement instanceof IRefinesEvent + || rodinElement instanceof ISeesContext + || rodinElement instanceof IRefinesMachine + || rodinElement instanceof IExtendsContext; + } + + private IRegion handleReferenceElement(final IInternalElement rodinElement) { + final EventBObject parent = rodinResource.getMap().get( + rodinElement.getParent()); + + if (parent != null) { + String name = null; + + try { + if (rodinElement instanceof IRefinesEvent) { + name = ((IRefinesEvent) rodinElement) + .getAbstractEventLabel(); + } else if (rodinElement instanceof ISeesContext) { + name = ((ISeesContext) rodinElement).getSeenContextName(); + } else if (rodinElement instanceof IRefinesMachine) { + name = ((IRefinesMachine) rodinElement) + .getAbstractMachineName(); + } else if (rodinElement instanceof IExtendsContext) { + name = ((IExtendsContext) rodinElement) + .getAbstractContextName(); + } + + final TextRange range = TextPositionUtil.getInternalPosition( + parent, name); + if (range != null) { + return new Region(range.getOffset(), range.getLength()); + } + } catch (final RodinDBException e) { + // IGNORE + } + } + + return new Region(0, 0); + } + + private boolean onlyLabelIsRelevant(final IMarker marker, + final EventBObject eventBObject) { + /* + * The attribute means there's a problem with the label of this element. + * Of course this needs to be an EventBNamed element too. + */ + final String attributeId = marker.getAttribute( + RodinMarkerUtil.ATTRIBUTE_ID, null); + if (LABEL_ATTRIBUTE_ID.equals(attributeId) + && eventBObject instanceof EventBNamed) { + return true; + } + + /* + * If the surrounding element is an event we don't want to highlight the + * whole event. This could be very distracting to the user. So we just + * choose the event's name. + */ + if (eventBObject instanceof Event) { + return true; + } + + /* + * When the problem is in a variable or parameter declaration we want + * only the identifier to be highlighted but not optional comments. + */ + if (eventBObject instanceof Variable + || eventBObject instanceof Parameter) { + return true; + } + + return false; + } + + private IRegion getRegionInFormula(final EventBObject eventBObject, + final int charStart, final int charEnd) { + TextRange range = null; + + if (eventBObject instanceof EventBPredicate) { + range = TextPositionUtil.getInternalPosition(eventBObject, + ((EventBPredicate) eventBObject).getPredicate()); + } else if (eventBObject instanceof Action) { + range = TextPositionUtil.getInternalPosition(eventBObject, + ((Action) eventBObject).getAction()); + } else if (eventBObject instanceof EventBExpression) { + range = TextPositionUtil.getInternalPosition(eventBObject, + ((EventBExpression) eventBObject).getExpression()); + } + + if (range != null) { + return new Region(range.getOffset() + charStart, charEnd + - charStart); + } else { + // fall back and mark the complete formula + range = TextPositionUtil.getTextRange(eventBObject); + if (range != null) { + return new Region(range.getOffset(), range.getLength()); + } + } + + return null; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/DocumentProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/DocumentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..744ed749b5bc66a4a92ca328120fc6008f8d2322 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/DocumentProvider.java @@ -0,0 +1,273 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.core.resources.ResourcesPlugin; +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.SubMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.edit.ui.util.EditUIUtil; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IDocumentPartitioner; +import org.eclipse.jface.text.source.IAnnotationModel; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.editors.text.FileDocumentProvider; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.Builder; +import org.eventb.texteditor.ui.build.dom.DomManager.ParseResult; +import org.eventb.texteditor.ui.reconciler.partitioning.PartitionScanner; +import org.eventb.texteditor.ui.reconciler.partitioning.Partitioner; +import org.eventb.texttools.PersistenceHelper; + +public class DocumentProvider extends FileDocumentProvider { + + private final EventBTextEditor editor; + + public DocumentProvider(final EventBTextEditor editor) { + this.editor = editor; + } + + @Override + protected IAnnotationModel createAnnotationModel(final Object element) + throws CoreException { + if (element instanceof IFileEditorInput) { + final IFileEditorInput input = (IFileEditorInput) element; + return new AnnotationModel(input.getFile(), + getResourceForElement(input)); + } + + return super.createAnnotationModel(element); + } + + @Override + protected void setupDocument(final Object element, final IDocument document) { + // remove old partitioner if set + final IDocumentPartitioner oldPartitioner = document + .getDocumentPartitioner(); + if (oldPartitioner != null) { + oldPartitioner.disconnect(); + } + + final Partitioner partitioner = new Partitioner(new PartitionScanner(), + PartitionScanner.CONTENT_TYPES); + document.setDocumentPartitioner(partitioner); + partitioner.connect(document, true); + } + + @Override + protected ElementInfo createElementInfo(final Object element) + throws CoreException { + final ElementInfo info = super.createElementInfo(element); + + if (info != null && element instanceof IEditorInput) { + final IDocument document = info.fDocument; + + document.addDocumentListener(new IDocumentListener() { + public void documentChanged(final DocumentEvent event) { + final boolean newState = isDirty(info, + (IEditorInput) element); + + if (info.fCanBeSaved != newState) { + info.fCanBeSaved = newState; + editor.updateIsDirty(); + } + } + + public void documentAboutToBeChanged(final DocumentEvent event) { + // IGNORE + } + }); + } + return info; + } + + private boolean isDirty(final ElementInfo info, final IEditorInput element) { + final String currentInput = info.fDocument.get(); + final String savedInput = PersistenceHelper + .getTextAnnotation(getResourceForElement(element)); + + return currentInput != null && !currentInput.equals(savedInput); + } + + @Override + protected boolean setDocumentContent(final IDocument document, + final IEditorInput editorInput, final String encoding) + throws CoreException { + /* + * We intercept loading from file and replace it with loading from + * RodinDB plus conversion to text (if necessary). + */ + final Resource resource = getResourceForElement(editorInput); + + // load content as stream + InputStream stream; + try { + stream = createContentStream(resource, encoding, document + .getLegalLineDelimiters()[0]); + } catch (final IOException e) { + // IGNORE + return false; + } + + try { + setDocumentContent(document, stream, encoding); + + /* + * After setting content in new document initially parse and merge + * result into original model to create text positions in the + * original model. + */ + reconcileContent(resource, document, null); + return true; + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (final IOException ex) { + // IGNORE, no solution available here + } + } + } + + @Override + protected ISchedulingRule getSaveRule(final Object element) { + /* + * We need to set the scheduling rule to the workspace root. Save + * operations of the editor run in a runnable and its scope is + * determined with the help of this method. The EMF persistence layer + * wrappes a save into another runnable which is handled by the RodinDB. + * The DB uses the workspace root as scope. If we set a smaller scope + * this collides with the larger scope. + */ + if (element instanceof IFileEditorInput) { + return ResourcesPlugin.getWorkspace().getRoot(); + } + + return null; + } + + @Override + protected void doSaveDocument(final IProgressMonitor monitor, + final Object element, final IDocument document, + final boolean overwrite) throws CoreException { + + if (element instanceof IEditorInput) { + final SubMonitor subMonitor = SubMonitor.convert(monitor, "Saving", + 4); + + editor.setIsSaving(true); + final Resource resource = getResourceForElement((IEditorInput) element); + + // trim lines to remove trailing whitespaces + final int newCursorOffset = trimLines(document, subMonitor + .newChild(1)); + + // parse and translate formulas + reconcileContent(resource, document, subMonitor.newChild(1)); + subMonitor.worked(1); + + PersistenceHelper.saveText(resource, overwrite, subMonitor + .newChild(1)); + + editor.setIsSaving(false); + editor.setCurserOffset(newCursorOffset); + editor.selectAndReveal(newCursorOffset, 0); + } else { + throw new CoreException(new Status(IStatus.ERROR, + TextEditorPlugin.PLUGIN_ID, "Unsupported target element: " + + element)); + } + } + + private void reconcileContent(final Resource resource, + final IDocument document, final IProgressMonitor m) { + final SubMonitor monitor = SubMonitor.convert(m, 2); + EventBNamedCommentedComponentElement astRoot = null; + + final Builder builder = new Builder(); + builder.run(editor, document, monitor.newChild(1)); + + if (builder.wasSuccessful()) { + final ParseResult lastParseResult = TextEditorPlugin + .getDomManager() + .getLastParseResult(editor.getEditorInput()); + + if (lastParseResult != null) { + astRoot = (EventBNamedCommentedComponentElement) lastParseResult.astRoot; + } + } + + if (astRoot != null) { + PersistenceHelper.mergeRootElement(resource, astRoot, monitor + .newChild(1)); + } else { + // we need to store the current text because it is not parsable + try { + PersistenceHelper.addTextAnnotation(resource, document.get(), + System.currentTimeMillis()); + } catch (final CoreException e) { + TextEditorPlugin + .getPlugin() + .getLog() + .log( + new Status( + IStatus.ERROR, + TextEditorPlugin.PLUGIN_ID, + "Cannot store text representation into Resource", + e)); + } + + monitor.worked(1); + } + } + + private int trimLines(final IDocument document, final SubMonitor monitor) { + final SubMonitor subMonitor = SubMonitor.convert(monitor, 1); + int cursorOffset = editor.getCursorOffset(); + + try { + final int removedChars = TrimLinesAction.trimLines(document, 0, + document.getNumberOfLines() - 1, subMonitor.newChild(1)); + cursorOffset -= removedChars; + + return cursorOffset < document.getLength() ? cursorOffset + : document.getLength() - 1; + } catch (final BadLocationException e) { + // IGNORE + return cursorOffset; + } + } + + private Resource getResourceForElement(final IEditorInput editorInput) { + final URI inputUri = EditUIUtil.getURI(editorInput); + final Resource resource = editor.getEditingDomain().getResourceSet() + .getResource(inputUri, true); + return resource; + } + + private InputStream createContentStream(final Resource resource, + final String encoding, final String lineBreak) + throws CoreException, IOException { + return new ByteArrayInputStream(PersistenceHelper.loadText(resource, + lineBreak).getBytes(encoding)); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/EventBTextEditor.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/EventBTextEditor.java new file mode 100644 index 0000000000000000000000000000000000000000..86834d3fdbfc45d8ad8516c9e48932b9aafd1a34 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/EventBTextEditor.java @@ -0,0 +1,407 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; +import org.eclipse.emf.edit.ui.util.EditUIUtil; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.link.LinkedModeUI; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.LineChangeHover; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.editors.text.TextEditor; +import org.eclipse.ui.ide.IGotoMarker; +import org.eclipse.ui.texteditor.ContentAssistAction; +import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; +import org.eventb.emf.core.context.ContextPackage; +import org.eventb.emf.core.machine.MachinePackage; +import org.eventb.texteditor.ui.Images; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.outline.OutlinePage; +import org.eventb.texteditor.ui.outline.RevealSelectionListener; +import org.eventb.texteditor.ui.reconciler.EventBPresentationReconciler; +import org.eventb.texttools.PersistenceHelper; +import org.eventb.texttools.TextToolsPlugin; + +public class EventBTextEditor extends TextEditor implements IGotoMarker { + + public static final String CONTENT_FORMAT = "ContentFormat"; + public static final String CONTENT_ASSIST_PROPOSAL = "ContentAssistProposal"; + + private GotoMarker gotoMarkerAdapter; + private IContentOutlinePage contentOutlinePage; + private Resource resource; + private ModelChangeListener modelChangeListener; + private AdapterFactoryEditingDomain editingDomain; + private boolean isCurrentlySaving = false; + private EventBPresentationReconciler presentationReconciler; + private final IPartListener activeListener; + + private boolean resourceChangedWhileDirty; + + private boolean inLinkedMode = false; + + public EventBTextEditor() { + activeListener = new ActivateListener(); + } + + @Override + public void init(final IEditorSite site, final IEditorInput input) + throws PartInitException { + super.init(site, input); + site.getPage().addPartListener(activeListener); + } + + @Override + protected void initializeEditor() { + super.initializeEditor(); + + setDocumentProvider(new DocumentProvider(this)); + final SourceViewerConfiguration viewerConfiguration = new SourceViewerConfiguration( + this, getPreferenceStore()); + presentationReconciler = (EventBPresentationReconciler) viewerConfiguration + .getPresentationReconciler(getSourceViewer()); + setSourceViewerConfiguration(viewerConfiguration); + + // not supported for our editor at the moment + showChangeInformation(false); + + setEditorContextMenuId(TextEditorPlugin.TEXTEDITOR_CONTEXTMENU_ID); + } + + @Override + protected void createActions() { + super.createActions(); + + /* + * Add content assist + */ + final Action action = new ContentAssistAction(TextEditorPlugin + .getPlugin().getResourceBundle(), "ContentAssistProposal.", + this); + action + .setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); + setAction(CONTENT_ASSIST_PROPOSAL, action); + markAsStateDependentAction(CONTENT_ASSIST_PROPOSAL, true); + } + + @Override + protected void doSetInput(final IEditorInput input) throws CoreException { + final IEditorInput oldInput = getEditorInput(); + + super.doSetInput(input); + resource = null; + + registerChangeListener(oldInput, getEditorInput()); + presentationReconciler.setInputResource(getResource()); + + adjustLabels(); + } + + private void registerChangeListener(final IEditorInput oldInput, + final IEditorInput editorInput) { + if (modelChangeListener != null && oldInput instanceof IFileEditorInput) { + ((IFileEditorInput) oldInput).getFile().getWorkspace() + .removeResourceChangeListener(modelChangeListener); + } + + if (editorInput instanceof IFileEditorInput) { + modelChangeListener = new ModelChangeListener(this); + ((IFileEditorInput) editorInput).getFile().getWorkspace() + .addResourceChangeListener(modelChangeListener); + } + } + + public AdapterFactoryEditingDomain getEditingDomain() { + if (editingDomain == null) { + final IFileEditorInput fileInput = (IFileEditorInput) getEditorInput(); + final IProject project = fileInput.getFile().getProject(); + + editingDomain = TextToolsPlugin.getDefault().getResourceManager() + .getEditingDomain(project); + } + + return editingDomain; + } + + @Override + protected String[] collectContextMenuPreferencePages() { + final List<String> result = new ArrayList<String>(Arrays.asList(super + .collectContextMenuPreferencePages())); + result.add("org.eventb.texteditor.ui.preferences"); + result + .add("org.eventb.texteditor.ui.preferences.HighlightingPreferencePage"); + result.add("org.eventb.texteditor.ui.preferences.TemplatePreferences"); + + return result.toArray(new String[result.size()]); + } + + @Override + protected LineChangeHover createChangeHover() { + // disabling change hover because we don't support it (yet) + return null; + } + + @Override + public boolean isSaveAsAllowed() { + return false; + } + + @Override + public void doSaveAs() { + throw new UnsupportedOperationException( + "'SaveAs' is not allowed for the Event-B text editor."); + } + + @Override + public void dispose() { + getSite().getPage().removePartListener(activeListener); + registerChangeListener(getEditorInput(), null); + super.dispose(); + } + + public Resource getResource() { + if (resource == null) { + final IEditorInput editorInput = getEditorInput(); + if (editorInput != null) { + final AdapterFactoryEditingDomain editingDomain = getEditingDomain(); + final URI inputUri = EditUIUtil.getURI(editorInput); + resource = editingDomain.getResourceSet().getResource(inputUri, + true); + } + } + + return resource; + } + + public StyledText getTextWidget() { + return getSourceViewer().getTextWidget(); + } + + public void insert(final String symbol, final boolean correctCursorPosition) { + if (symbol != null && symbol.length() > 0) { + getSite().getShell().getDisplay().asyncExec(new Runnable() { + public void run() { + final IDocument document = getDocumentProvider() + .getDocument(getEditorInput()); + final int offset = getCursorOffset(); + + try { + document.replace(offset, 0, symbol); + + if (correctCursorPosition) { + setCurserOffset(offset + symbol.length()); + } + } catch (final BadLocationException e) { + TextEditorPlugin + .getPlugin() + .getLog() + .log( + new Status( + IStatus.WARNING, + TextEditorPlugin.PLUGIN_ID, + "Could not insert text into Event-B text editor.", + e)); + } + } + }); + } + } + + public int getCursorOffset() { + final ISourceViewer sourceViewer = getSourceViewer(); + if (sourceViewer != null) { + return widgetOffset2ModelOffset(sourceViewer, sourceViewer + .getTextWidget().getCaretOffset()); + } + + return 0; + } + + public void setCurserOffset(final int offsetInDocument) { + final ISourceViewer sourceViewer = getSourceViewer(); + if (sourceViewer != null) { + final int widgetOffset = modelOffset2WidgetOffset(sourceViewer, + offsetInDocument); + final StyledText textWidget = sourceViewer.getTextWidget(); + textWidget.setCaretOffset(widgetOffset); + } + } + + @SuppressWarnings("unchecked") + @Override + public Object getAdapter(final Class adapter) { + if (IGotoMarker.class.equals(adapter)) { + return getGotoMarker(); + } + + if (adapter.equals(IContentOutlinePage.class)) { + return getContentOutlinePage(); + } + + return super.getAdapter(adapter); + } + + private Object getGotoMarker() { + if (gotoMarkerAdapter == null) { + gotoMarkerAdapter = new GotoMarker(this); + } + + return gotoMarkerAdapter; + } + + private IContentOutlinePage getContentOutlinePage() { + if (contentOutlinePage == null) { + contentOutlinePage = new OutlinePage(this); + contentOutlinePage + .addSelectionChangedListener(new RevealSelectionListener( + this)); + } + + return contentOutlinePage; + } + + public void updateIsDirty() { + firePropertyChange(PROP_DIRTY); + } + + public void setIsSaving(final boolean state) { + isCurrentlySaving = state; + } + + public boolean isSaving() { + return isCurrentlySaving; + } + + public final int widgetOffset2ModelOffset(final int widgetOffset) { + return widgetOffset2ModelOffset(getSourceViewer(), widgetOffset); + } + + public final int modelOffset2WidgetOffset(final int modelOffset) { + return modelOffset2WidgetOffset(getSourceViewer(), modelOffset); + } + + public EventBPresentationReconciler getPresentationReconciler() { + return presentationReconciler; + } + + public void changeWhileDirty() { + resourceChangedWhileDirty = true; + } + + /** + * Change whether the editor is currently in 'linked mode'. + * + * @param linkedMode + */ + public void setInLinkedMode(final boolean linkedMode) { + inLinkedMode = linkedMode; + } + + /** + * Returns whether the editor is currently in 'linked mode', i.e., when a + * template has just been inserted. + * + * @see LinkedModeUI + * @return + */ + public boolean isInLinkedMode() { + return inLinkedMode; + } + + private void adjustLabels() { + final IEditorInput input = getEditorInput(); + + if (input != null) { + final URI inputUri = EditUIUtil.getURI(input); + final Resource resource = getEditingDomain().getResourceSet() + .getResource(inputUri, true); + + changeImage(resource); + + if (input instanceof IFileEditorInput) { + final IFile file = ((IFileEditorInput) input).getFile(); + String name = file.getName(); + name = name.substring(0, name.length() + - file.getFileExtension().length() - 1); + setPartName(name); + } + } + } + + private void changeImage(final Resource resource) { + final EClass componentType = PersistenceHelper + .getComponentType(resource); + if (componentType != null) { + final String packageNsURI = componentType.getEPackage().getNsURI(); + if (packageNsURI.equals(MachinePackage.eNS_URI)) { + setTitleImage(Images.getImage(Images.IMG_MACHINE)); + } else if (packageNsURI.equals(ContextPackage.eNS_URI)) { + setTitleImage(Images.getImage(Images.IMG_CONTEXT)); + } + } + } + + private void handleActive() { + if (resourceChangedWhileDirty) { + try { + handleEditorInputChanged(); + } finally { + resourceChangedWhileDirty = false; + } + } + + // only update presentation + presentationReconciler.reconcilePresentation(); + } + + class ActivateListener implements IPartListener { + + public void partActivated(final IWorkbenchPart part) { + if (part == EventBTextEditor.this) { + handleActive(); + } + } + + public void partBroughtToTop(final IWorkbenchPart part) { + // IGNORE + } + + public void partClosed(final IWorkbenchPart part) { + // IGNORE + } + + public void partDeactivated(final IWorkbenchPart part) { + // IGNORE + } + + public void partOpened(final IWorkbenchPart part) { + // IGNORE + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/GotoMarker.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/GotoMarker.java new file mode 100644 index 0000000000000000000000000000000000000000..fad681f99575f2f41917c7e8785240c9bc383387 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/GotoMarker.java @@ -0,0 +1,47 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.ui.ide.IGotoMarker; +import org.eclipse.ui.texteditor.MarkerUtilities; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.rodinp.core.RodinMarkerUtil; + +public class GotoMarker implements IGotoMarker { + + private final EventBTextEditor editor; + + public GotoMarker(final EventBTextEditor editor) { + this.editor = editor; + } + + public void gotoMarker(final IMarker marker) { + /* + * Unfortunately the marker can already be gone, i.e., not exiting by + * the time we are called. This happens, for example, when the + * corresponding editor needs to opened before and starts an initial + * reconcile which replaces the markers. + */ + if (marker.exists()) { + try { + if (TextEditorPlugin.SYNTAXERROR_MARKER_ID.equals(marker.getType())) { + final int charStart = MarkerUtilities.getCharStart(marker); + final int charEnd = MarkerUtilities.getCharEnd(marker); + + editor.selectAndReveal(charStart, charEnd - charStart); + } else if (RodinMarkerUtil.RODIN_PROBLEM_MARKER.equals(marker + .getType())) { + // TODO find a way to locate the problem in our text + } + } catch (final CoreException exception) { + TextEditorPlugin.INSTANCE.log(exception); + } + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/ModelChangeListener.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/ModelChangeListener.java new file mode 100644 index 0000000000000000000000000000000000000000..2b2b4fbc504aa300a86c45f8cb1bf5491230039f --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/ModelChangeListener.java @@ -0,0 +1,105 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; + +public class ModelChangeListener implements IResourceChangeListener, + IResourceDeltaVisitor { + + private final EventBTextEditor editor; + + public ModelChangeListener(final EventBTextEditor editor) { + this.editor = editor; + } + + /* + * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) + */ + public void resourceChanged(final IResourceChangeEvent e) { + final IResourceDelta delta = e.getDelta(); + try { + if (delta != null) { + delta.accept(this); + } + } catch (final CoreException x) { + // TODO log exception + } + } + + /* + * @see + * IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta) + */ + public boolean visit(IResourceDelta delta) throws CoreException { + if (delta == null) { + return false; + } + + final IFile file = getFile(); + if (file == null) { + return false; + } + + delta = delta.findMember(file.getFullPath()); + + if (delta == null) { + return false; + } + + if (delta.getKind() == IResourceDelta.CHANGED) { + // is change affecting content? + if ((IResourceDelta.CONTENT & delta.getFlags()) != 0) { + // has editor unsaved changes? + if (editor.isDirty()) { + // notify editor about change + editor.changeWhileDirty(); + } + } + } + + return false; + } + + /** + * Computes the initial modification stamp for the given resource. + * + * @param resource + * the resource + * @return the modification stamp + */ + protected long computeModificationStamp(final IResource resource) { + long modificationStamp = resource.getModificationStamp(); + + final IPath path = resource.getLocation(); + if (path == null) { + return modificationStamp; + } + + modificationStamp = path.toFile().lastModified(); + return modificationStamp; + } + + private IFile getFile() { + final IEditorInput editorInput = editor.getEditorInput(); + + if (editorInput instanceof IFileEditorInput) { + return ((IFileEditorInput) editorInput).getFile(); + } + + return null; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/SourceViewerConfiguration.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/SourceViewerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..29ba76bbd50375bc02e5c4bf469bc7cc38872d87 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/SourceViewerConfiguration.java @@ -0,0 +1,88 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ContentAssistant; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; +import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.reconciler.IReconciler; +import org.eclipse.jface.text.reconciler.MonoReconciler; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.editors.text.TextSourceViewerConfiguration; +import org.eventb.texteditor.ui.build.ReconcilingStrategy; +import org.eventb.texteditor.ui.editor.codecompletion.DefaultContentAssist; +import org.eventb.texteditor.ui.reconciler.EventBPresentationReconciler; +import org.eventb.texteditor.ui.reconciler.partitioning.PartitionScanner; + +public class SourceViewerConfiguration extends TextSourceViewerConfiguration { + + private final EventBTextEditor editor; + private EventBPresentationReconciler presentationReconciler; + + public SourceViewerConfiguration(final EventBTextEditor editor, + final IPreferenceStore preferenceStore) { + super(preferenceStore); + this.editor = editor; + } + + @Override + public IContentAssistant getContentAssistant( + final ISourceViewer sourceViewer) { + final ContentAssistant assistant = new ContentAssistant(); + final IContentAssistProcessor processor = new DefaultContentAssist( + editor); + + assistant.setContentAssistProcessor(processor, + IDocument.DEFAULT_CONTENT_TYPE); + assistant.setContentAssistProcessor(processor, + PartitionScanner.CONTENT_TYPE_STRUCTURAL_KEYWORD); + assistant.setContentAssistProcessor(processor, + PartitionScanner.CONTENT_TYPE_FORMULA_KEYWORD); + + assistant + .setInformationControlCreator(getInformationControlCreator(sourceViewer)); + assistant.enableAutoActivation(true); + assistant.setAutoActivationDelay(500); + + return assistant; + } + + @Override + public IPresentationReconciler getPresentationReconciler( + final ISourceViewer sourceViewer) { + if (presentationReconciler == null) { + presentationReconciler = new EventBPresentationReconciler(); + presentationReconciler + .setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer)); + } + + return presentationReconciler; + } + + @Override + public final IReconciler getReconciler(final ISourceViewer sourceViewer) { + final ReconcilingStrategy reconcileStrategy = new ReconcilingStrategy( + editor); + + final MonoReconciler reconciler = new MonoReconciler(reconcileStrategy, + false); + reconciler.setIsAllowedToModifyDocument(true); + reconciler.setProgressMonitor(new NullProgressMonitor()); + reconciler.setDelay(500); + + return reconciler; + } + + @Override + public String[] getConfiguredContentTypes(final ISourceViewer sourceViewer) { + return PartitionScanner.CONTENT_TYPES; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/TrimLinesAction.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/TrimLinesAction.java new file mode 100644 index 0000000000000000000000000000000000000000..3f9e0e55121e9196a1cac3e756a55326fd1b7276 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/TrimLinesAction.java @@ -0,0 +1,185 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor; + +import java.util.ResourceBundle; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.TextEditorAction; + +public class TrimLinesAction extends TextEditorAction { + + public TrimLinesAction(final ResourceBundle bundle, final String prefix, + final ITextEditor editor) { + super(bundle, prefix, editor); + update(); + } + + @Override + public void run() { + final ITextEditor editor = getTextEditor(); + if (editor == null) { + return; + } + + if (!validateEditorInputState()) { + return; + } + + final IDocument document = getDocument(editor); + if (document == null) { + return; + } + + int startLine = 0; + int endLine = document.getNumberOfLines() - 1; + + final ITextSelection selection = getSelection(editor); + if (selection != null) { + startLine = selection.getStartLine(); + endLine = selection.getEndLine(); + } + + try { + trimLines(document, startLine, endLine, SubMonitor.convert(null, + "Trimming lines", 1)); + + if (selection != null) { + final int startOffset = document.getLineOffset(selection + .getStartLine()); + editor.selectAndReveal(startOffset, 0); + } + } catch (final BadLocationException e) { + // should not happen + } + } + + /** + * Returns the editor's document. + * + * @param editor + * the editor + * @return the editor's document + */ + private static IDocument getDocument(final ITextEditor editor) { + final IDocumentProvider documentProvider = editor.getDocumentProvider(); + if (documentProvider == null) { + return null; + } + + final IDocument document = documentProvider.getDocument(editor + .getEditorInput()); + if (document == null) { + return null; + } + + return document; + } + + /** + * Returns the editor's selection. + * + * @param editor + * the editor + * @return the editor's selection + */ + private static ITextSelection getSelection(final ITextEditor editor) { + final ISelectionProvider selectionProvider = editor + .getSelectionProvider(); + if (selectionProvider == null) { + return null; + } + + final ISelection selection = selectionProvider.getSelection(); + if (!(selection instanceof ITextSelection)) { + return null; + } + + return (ITextSelection) selection; + } + + @Override + public void update() { + super.update(); + if (!isEnabled()) { + return; + } + + if (!canModifyEditor()) { + setEnabled(false); + return; + } + + final ITextEditor editor = getTextEditor(); + setEnabled(editor.isEditable()); + } + + public static int trimLines(final IDocument document, final int startLine, + final int endLine, final IProgressMonitor monitor) + throws BadLocationException { + final SubMonitor subMonitor = SubMonitor.convert(monitor); + + if (startLine >= document.getNumberOfLines() || startLine > endLine) { + return 0; + } + + int removeChars = 0; + final StringBuffer buffer = new StringBuffer(); + subMonitor.setWorkRemaining(endLine - startLine + 1); + + for (int line = startLine; line <= endLine; line++) { + final String trimmedLine = trim(document, line); + buffer.append(trimmedLine); + removeChars += document.getLineLength(line) - trimmedLine.length(); + + subMonitor.worked(1); + } + + final int startLineOffset = document.getLineOffset(startLine); + final int endLineOffset = document.getLineOffset(endLine) + + document.getLineLength(endLine); + final String replaceString = buffer.toString(); + + document.replace(startLineOffset, endLineOffset - startLineOffset, + replaceString); + subMonitor.worked(1); + + return removeChars; + } + + private static String trim(final IDocument document, final int line) + throws BadLocationException { + final int lineOffset = document.getLineOffset(line); + final int lineDelimiterLength = getLineDelimiterLength(document, line); + int lineLength = document.getLineLength(line) - lineDelimiterLength; + + // correct new line length until no trailing whitespace left + while (lineLength > 0 + && Character.isWhitespace(document.getChar(lineOffset + + lineLength - 1))) { + lineLength--; + } + + final String lineDelimiter = lineDelimiterLength > 0 ? document + .getLineDelimiter(line) : ""; + return document.get(lineOffset, lineLength) + lineDelimiter; + } + + private static int getLineDelimiterLength(final IDocument document, + final int line) throws BadLocationException { + final String lineDelimiter = document.getLineDelimiter(line); + return lineDelimiter != null ? lineDelimiter.length() : 0; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/FormatHandler.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/FormatHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..fc5f4a15f509f35671281f91c4c3f2e3cd377150 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/FormatHandler.java @@ -0,0 +1,92 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.actions; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.dom.DomManager.ParseResult; +import org.eventb.texteditor.ui.editor.EventBTextEditor; +import org.eventb.texttools.prettyprint.PrettyPrinter; + +public class FormatHandler extends AbstractHandler { + public FormatHandler() { + super(); + setBaseEnabled(true); + } + + public Object execute(final ExecutionEvent event) throws ExecutionException { + final IEditorPart activeEditor = HandlerUtil.getActiveEditor(event); + + if (activeEditor != null && activeEditor instanceof EventBTextEditor) { + try { + formatContent((EventBTextEditor) activeEditor); + } catch (final CoreException e) { + throw new ExecutionException("Content formatting failed", e); + } + } + + return null; + } + + private void formatContent(final EventBTextEditor editor) + throws CoreException { + if (!hasSyntaxErrors(editor)) { + final int cursorOffset = editor.getCursorOffset(); + + final IEditorInput editorInput = editor.getEditorInput(); + final ParseResult parseResult = TextEditorPlugin.getDomManager() + .getLastParseResult(editorInput); + + if (parseResult != null) { + final IDocument document = editor.getDocumentProvider() + .getDocument(editor.getEditorInput()); + + if (document != null) { + final String remberedContent = document.get(); + + final StringBuilder buffer = new StringBuilder(); + new PrettyPrinter(buffer, + document.getLegalLineDelimiters()[0], null) + .prettyPrint(parseResult.astRoot); + + final String newContent = buffer.toString(); + + if (!newContent.equals(remberedContent)) { + document.set(newContent); + editor.setCurserOffset(cursorOffset); + } + } + } + } + } + + private boolean hasSyntaxErrors(final EventBTextEditor editor) + throws CoreException { + final IEditorInput editorInput = editor.getEditorInput(); + + if (editorInput instanceof IFileEditorInput) { + final IFileEditorInput fileInput = (IFileEditorInput) editorInput; + final IMarker[] markers = fileInput.getFile().findMarkers( + TextEditorPlugin.SYNTAXERROR_MARKER_ID, true, + IResource.DEPTH_INFINITE); + + return markers.length > 0; + } + + return true; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/InsertHandler.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/InsertHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..7994eef7c4aa8f7a7b63a5e91b1a0299dd0e4529 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/actions/InsertHandler.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2009 Systerel 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: + * Systerel - initial API and implementation + *******************************************************************************/ +package org.eventb.texteditor.ui.editor.actions; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.handlers.HandlerUtil; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +/** + * Handles insertion command (id="org.eventb.ui.edit.insert"). + * + * @author "Nicolas Beauger" + * + */ +public class InsertHandler extends AbstractHandler { + + public Object execute(ExecutionEvent event) throws ExecutionException { + final IEditorPart activeEditor = HandlerUtil.getActiveEditor(event); + + if (!(activeEditor instanceof EventBTextEditor)) { + return "Unexpected editor"; + } + + final String insertText = event + .getParameter("org.eventb.ui.edit.insert.text"); + if (insertText == null) { + return "Unable to retrieve the text to insert"; + } + + ((EventBTextEditor) activeEditor).insert(insertText, true); + return null; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/DefaultContentAssist.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/DefaultContentAssist.java new file mode 100644 index 0000000000000000000000000000000000000000..623d96f2b4b9e046e57e0d8e5fb28468c7297db5 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/DefaultContentAssist.java @@ -0,0 +1,425 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.templates.Template; +import org.eclipse.jface.text.templates.TemplateCompletionProcessor; +import org.eclipse.jface.text.templates.TemplateContext; +import org.eclipse.jface.text.templates.TemplateContextType; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.editors.text.templates.ContributionTemplateStore; +import org.eventb.texteditor.ui.Images; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.dom.DomManager; +import org.eventb.texteditor.ui.build.dom.IComponentDom; +import org.eventb.texteditor.ui.build.dom.IDom; +import org.eventb.texteditor.ui.build.dom.MachineDom; +import org.eventb.texteditor.ui.build.dom.IDom.IdentifierType; +import org.eventb.texteditor.ui.build.dom.IDom.Type; +import org.eventb.texteditor.ui.editor.EventBTextEditor; +import org.eventb.texttools.Constants; + +/** + * Computes the content assist (code completion) proposals, i.e., for + * identifiers, keywords and templates. + * + * It uses the following scheme to measure the proposals' relevance: + * <ul> + * <li>Identifiers get 100 points (to be always at the top of the list)</li> + * <li>Templates can reach 90 points</li> + * <ul> + * <li><code>Anywhere</code> templates get 90 points</li> + * <li>Others get 50 points for the correct compontent</li> + * <li><code>Events</code> templates can get 40 more points if the current + * offset is within the events section.</li> + * <li>All other get these 40 points per default</li> + * </ul> + * <li>Keywords can reach 80 points</li> + * <ul> + * <li>All templates get 50 points for the correct compontent</li> + * <li>The remaining 30 points are given depending on the template type and the + * current offset</li> + * </ul> + * </ul> + * + */ +public class DefaultContentAssist extends TemplateCompletionProcessor { + + public enum ContextType { + Unkown(PREFIX + "unknown"), Anywhere(PREFIX + "anywhere"), Machine( + PREFIX + "machine"), Context(PREFIX + "context"), Events(PREFIX + + "events"); + + public final String key; + + ContextType(final String key) { + this.key = key; + } + }; + + private static final ICompletionProposal[] NO_PROPOSALS = new ICompletionProposal[0]; + private static final String PREFIX = "org.eventb.texteditor."; + + private ContributionTemplateStore templateStore; + private final DomManager domManager = TextEditorPlugin.getDomManager(); + private final EventBTextEditor editor; + + private String errorMessage; + private String contentType; + private Region region; + private String prefix; + private IComponentDom dom; + private IDom scopingDom; + private Type componentType; + private IDocument document; + private ITextViewer viewer; + private boolean offsetInEvents; + + public DefaultContentAssist(final EventBTextEditor editor) { + this.editor = editor; + } + + @Override + public ICompletionProposal[] computeCompletionProposals( + final ITextViewer viewer, final int offset) { + errorMessage = null; + + try { + setupContextInfo(viewer, offset); + + final List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); + computeIdentifierProposals(proposals); + computeKeywordProposals(proposals); + computeTemplateProposals(proposals); + + return sort(proposals); + } catch (final BadLocationException e) { + errorMessage = "Error while calculating content type"; + } + + return NO_PROPOSALS; + } + + @Override + protected ICompletionProposal createProposal(final Template template, + final TemplateContext context, final IRegion region, + final int relevance) { + return new EventBTemplateProposal(template, context, region, + getImage(template), relevance); + } + + private void setupContextInfo(final ITextViewer viewer, int offset) + throws BadLocationException { + this.viewer = viewer; + offset = adjustOffset(viewer, offset); + + document = viewer.getDocument(); + contentType = document.getContentType(offset); + prefix = extractPrefix(viewer, offset); + region = new Region(offset - prefix.length(), prefix.length()); + + final int eventsOffset = document.get().indexOf(Constants.EVENTS); + offsetInEvents = offset >= eventsOffset; + + dom = domManager.getDom(editor.getResource()); + + if (dom != null) { + scopingDom = dom.getScopingDom(offset); + componentType = dom instanceof MachineDom ? Type.Machine + : Type.Context; + } + } + + private ICompletionProposal[] sort(final List<ICompletionProposal> proposals) { + // sort by relevance and lexically + Collections.sort(proposals, new ProposalComparator()); + return proposals.toArray(new ICompletionProposal[proposals.size()]); + } + + private void computeTemplateProposals( + final List<ICompletionProposal> proposals) { + final Template[] templates = getTemplates(); + final TemplateContext context = createContext(viewer, region); + + for (final Template template : templates) { + if (template.getName().startsWith(prefix)) { + final EventBTemplateProposal proposal = new EventBTemplateProposal( + template, context, region, getImage(template), + getRelevance(template, prefix)); + + proposals.add(proposal); + } + } + } + + private void computeKeywordProposals( + final List<ICompletionProposal> proposals) { + if (!IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) { + return; + } + + final Map<String, ICompletionProposal> result = new HashMap<String, ICompletionProposal>(); + + int componentRelevance = 50; + int scopeRelevance = 30; + addKeywords(result, Arrays.asList(Constants.formula_keywords), + componentRelevance, scopeRelevance); + + componentRelevance = componentType == Type.Machine ? 50 : 0; + scopeRelevance = offsetInEvents ? 30 : 0; + addKeywords(result, Arrays.asList(Constants.event_keywords), + componentRelevance, scopeRelevance); + + componentRelevance = componentType == Type.Machine ? 50 : 0; + scopeRelevance = offsetInEvents ? 20 : 30; + addKeywords(result, Arrays.asList(Constants.machine_keywords), + componentRelevance, scopeRelevance); + + componentRelevance = componentType == Type.Context ? 50 : 0; + scopeRelevance = offsetInEvents ? 0 : 30; + addKeywords(result, Arrays.asList(Constants.context_keywords), + componentRelevance, scopeRelevance); + + proposals.addAll(result.values()); + } + + private void addKeywords(final Map<String, ICompletionProposal> result, + final List<String> keywords, final int componentRelevance, + final int scopeRelevance) { + for (final String keyword : keywords) { + if (keyword.startsWith(prefix)) { + final ICompletionProposal oldProposal = result.get(keyword); + final int relevance = componentRelevance + scopeRelevance; + + // adjust relevance if already present + if (oldProposal != null) { + if (oldProposal instanceof IRelevantProposal) { + final EventBCompletionProposal evtBProp = (EventBCompletionProposal) oldProposal; + final int oldRelevance = evtBProp.getRelevance(); + + if (oldRelevance < relevance) { + evtBProp.changeRelevance(relevance - oldRelevance); + } + } + } else { + final EventBCompletionProposal proposal = new EventBCompletionProposal( + keyword, region.getOffset(), prefix.length(), + keyword.length(), null, keyword, null, "keyword '" + + keyword + "'"); + proposal.changeRelevance(relevance); + + result.put(keyword, proposal); + } + } + } + } + + private void computeIdentifierProposals( + final List<ICompletionProposal> proposals) { + if (dom == null || scopingDom == null + || !IDocument.DEFAULT_CONTENT_TYPE.equals(contentType)) { + return; + } + + final Set<String> identifiers = scopingDom.getIdentifiers(); + for (final String ident : identifiers) { + if (ident.startsWith(prefix)) { + final IdentifierType type = scopingDom.getIdentifierType(ident); + + final String description = createDescription(ident, type); + final Image image = getImage(type); + + final EventBCompletionProposal proposal = new EventBCompletionProposal( + ident, region.getOffset(), prefix.length(), ident + .length(), image, ident, null, description); + proposals.add(proposal); + proposal.changeRelevance(100); + } + } + } + + private String createDescription(final String ident, + final IdentifierType type) { + final StringBuffer description = new StringBuffer(ident); + + if (type != null) { + switch (type) { + case GlobalVariable: + description.append(" : global variable"); + break; + case LocalVariable: + description.append(" : local variable"); + break; + case Parameter: + description.append(" : event parameter"); + break; + case Constant: + description.append(" : constant"); + break; + case Set: + description.append(" : carrier set"); + break; + default: + return null; + } + } + + return description.toString(); + } + + private Image getImage(final IdentifierType type) { + if (type != null) { + switch (type) { + case GlobalVariable: + case LocalVariable: + case Parameter: + return Images.getImage(Images.IMG_VARIABLE); + case Constant: + return Images.getImage(Images.IMG_CONSTANT); + case Set: + return Images.getImage(Images.IMG_CARRIER_SET); + } + } + + return null; + } + + private int adjustOffset(final ITextViewer viewer, int offset) { + // adjust offset to end of normalized selection + final ITextSelection selection = (ITextSelection) viewer + .getSelectionProvider().getSelection(); + if (selection.getOffset() == offset) { + offset = selection.getOffset() + selection.getLength(); + } + return offset; + } + + @Override + protected TemplateContextType getContextType(final ITextViewer viewer, + final IRegion region) { + // we don't care about context types here + return new TemplateContextType(ContextType.Unkown.key); + } + + @Override + protected Image getImage(final Template template) { + return Images.getImage(Images.IMG_TEMPLATE, Images.IMG_TEMPLATE_PATH); + } + + private Template[] getTemplates() { + if (templateStore == null) { + templateStore = TextEditorPlugin.getPlugin().getTemplateStore(); + } + + /* + * We don't filter by context types here. They are consider when + * calculating the relevance. + */ + return templateStore.getTemplates(null); + } + + @Override + protected Template[] getTemplates(final String contextTypeId) { + return getTemplates(); + } + + @Override + protected int getRelevance(final Template template, final String prefix) { + final String id = template.getContextTypeId(); + + // These templates are relevant everywhere + if (ContextType.Anywhere.key.equals(id)) { + return 90; + } + + int relevance = 0; + + if (componentType != null) { + // 50 points for matching component + switch (componentType) { + case Machine: + if (ContextType.Machine.key.equals(id) + || ContextType.Events.key.equals(id)) { + relevance += 50; + } + break; + case Context: + if (ContextType.Context.key.equals(id)) { + relevance += 50; + } + break; + } + } else { + // fallback: do not distinguish + relevance += 50; + } + + /* + * Templates for the events section get their points when the current + * offset is in this section. + */ + if (ContextType.Events.key.equals(id)) { + if (offsetInEvents) { + relevance += 40; + } + } + // other templates get these points in all cases + else { + relevance += 40; + } + + return relevance; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } + + private final class ProposalComparator implements + Comparator<ICompletionProposal> { + public int compare(final ICompletionProposal o1, + final ICompletionProposal o2) { + final int rel1 = getRelevance(o1); + final int rel2 = getRelevance(o2); + final int diff = rel2 - rel1; + + if (diff != 0 && rel1 >= 0 && rel2 >= 0) { + return diff; + } + + // fall back to alpha-numerical comparison + return o1.getDisplayString().toLowerCase().compareTo( + o2.getDisplayString().toLowerCase()); + } + + private int getRelevance(final ICompletionProposal o) { + + if (o instanceof IRelevantProposal) { + return ((IRelevantProposal) o).getRelevance(); + } + + return -1; + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EditorSynchronizer.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EditorSynchronizer.java new file mode 100644 index 0000000000000000000000000000000000000000..6826211ebb38753c63e124ae35fcd6e7a1866041 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EditorSynchronizer.java @@ -0,0 +1,33 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import org.eclipse.jface.text.link.ILinkedModeListener; +import org.eclipse.jface.text.link.LinkedModeModel; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class EditorSynchronizer implements ILinkedModeListener { + + private final EventBTextEditor editor; + + public EditorSynchronizer(final EventBTextEditor editor) { + this.editor = editor; + editor.setInLinkedMode(true); + } + + public void left(final LinkedModeModel model, final int flags) { + editor.setInLinkedMode(false); + } + + public void resume(final LinkedModeModel model, final int flags) { + // IGNORE + } + + public void suspend(final LinkedModeModel model) { + // IGNORE + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBCompletionProposal.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBCompletionProposal.java new file mode 100644 index 0000000000000000000000000000000000000000..b42b93ec5a30f7c5d6efb6cec2257443d7ba3e95 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBCompletionProposal.java @@ -0,0 +1,161 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +public class EventBCompletionProposal implements IRelevantProposal, + ICompletionProposal { + + /** The string to be displayed in the completion proposal popup. */ + private final String fDisplayString; + /** The replacement string. */ + private final String fReplacementString; + /** The replacement offset. */ + private final int fReplacementOffset; + /** The replacement length. */ + private final int fReplacementLength; + /** The cursor position after this proposal has been applied. */ + private final int fCursorPosition; + /** The image to be displayed in the completion proposal popup. */ + private final Image fImage; + /** The context information of this proposal. */ + private final IContextInformation fContextInformation; + /** The additional info of this proposal. */ + private final String fAdditionalProposalInfo; + + private int relevance; + + /** + * Creates a new completion proposal based on the provided information. The + * replacement string is considered being the display string too. All + * remaining fields are set to <code>null</code>. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementOffset + * the offset of the text to be replaced + * @param replacementLength + * the length of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + */ + public EventBCompletionProposal(final String replacementString, + final int replacementOffset, final int replacementLength, + final int cursorPosition) { + this(replacementString, replacementOffset, replacementLength, + cursorPosition, null, null, null, null); + } + + /** + * Creates a new completion proposal. All fields are initialized based on + * the provided information. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementOffset + * the offset of the text to be replaced + * @param replacementLength + * the length of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + * @param image + * the image to display for this proposal + * @param displayString + * the string to be displayed for the proposal + * @param contextInformation + * the context information associated with this proposal + * @param additionalProposalInfo + * the additional information associated with this proposal + */ + public EventBCompletionProposal(final String replacementString, + final int replacementOffset, final int replacementLength, + final int cursorPosition, final Image image, + final String displayString, + final IContextInformation contextInformation, + final String additionalProposalInfo) { + Assert.isNotNull(replacementString); + Assert.isTrue(replacementOffset >= 0); + Assert.isTrue(replacementLength >= 0); + Assert.isTrue(cursorPosition >= 0); + + fReplacementString = replacementString; + fReplacementOffset = replacementOffset; + fReplacementLength = replacementLength; + fCursorPosition = cursorPosition; + fImage = image; + fDisplayString = displayString; + fContextInformation = contextInformation; + fAdditionalProposalInfo = additionalProposalInfo; + } + + /* + * @see ICompletionProposal#apply(IDocument) + */ + public void apply(final IDocument document) { + try { + document.replace(fReplacementOffset, fReplacementLength, + fReplacementString); + } catch (final BadLocationException x) { + // ignore + } + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + public Point getSelection(final IDocument document) { + return new Point(fReplacementOffset + fCursorPosition, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return fContextInformation; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return fImage; + } + + /* + * @see ICompletionProposal#getDisplayString() + */ + public String getDisplayString() { + if (fDisplayString != null) { + return fDisplayString; + } + return fReplacementString; + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } + + public int getRelevance() { + return relevance; + } + + public void changeRelevance(final int byValue) { + relevance += byValue; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBTemplateProposal.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBTemplateProposal.java new file mode 100644 index 0000000000000000000000000000000000000000..01d19604db6571bb832b567820ff4e9dbda83b74 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/EventBTemplateProposal.java @@ -0,0 +1,264 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3; +import org.eclipse.jface.text.link.ILinkedModeListener; +import org.eclipse.jface.text.link.InclusivePositionUpdater; +import org.eclipse.jface.text.link.LinkedModeModel; +import org.eclipse.jface.text.link.LinkedModeUI; +import org.eclipse.jface.text.link.LinkedPosition; +import org.eclipse.jface.text.link.LinkedPositionGroup; +import org.eclipse.jface.text.link.ProposalPosition; +import org.eclipse.jface.text.templates.GlobalTemplateVariables; +import org.eclipse.jface.text.templates.Template; +import org.eclipse.jface.text.templates.TemplateBuffer; +import org.eclipse.jface.text.templates.TemplateContext; +import org.eclipse.jface.text.templates.TemplateException; +import org.eclipse.jface.text.templates.TemplateProposal; +import org.eclipse.jface.text.templates.TemplateVariable; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class EventBTemplateProposal extends TemplateProposal implements + IRelevantProposal, ICompletionProposal, ICompletionProposalExtension, + ICompletionProposalExtension2, ICompletionProposalExtension3 { + + private final IRegion fRegion; + + private IRegion fSelectedRegion; // initialized by apply() + private InclusivePositionUpdater fUpdater; + + public EventBTemplateProposal(final Template template, + final TemplateContext context, final IRegion region, + final Image image, final int relevance) { + super(template, context, region, image, relevance); + + fRegion = region; + } + + /** + * Inserts the template offered by this proposal into the viewer's document + * and sets up a <code>LinkedModeUI</code> on the viewer to edit any of the + * template's unresolved variables. + * + * @param viewer + * {@inheritDoc} + * @param trigger + * {@inheritDoc} + * @param stateMask + * {@inheritDoc} + * @param offset + * {@inheritDoc} + */ + @Override + public void apply(final ITextViewer viewer, final char trigger, + final int stateMask, final int offset) { + + final IDocument document = viewer.getDocument(); + try { + final TemplateContext fContext = getContext(); + fContext.setReadOnly(false); + int start; + TemplateBuffer templateBuffer; + { + final int oldReplaceOffset = getReplaceOffset(); + try { + // this may already modify the document (e.g. add imports) + templateBuffer = fContext.evaluate(getTemplate()); + } catch (final TemplateException e1) { + fSelectedRegion = fRegion; + return; + } + + start = getReplaceOffset(); + final int shift = start - oldReplaceOffset; + final int end = Math.max(getReplaceEndOffset(), offset + shift); + + // insert template string + final String templateString = templateBuffer.getString(); + document.replace(start, end - start, templateString); + } + + // translate positions + final LinkedModeModel model = new LinkedModeModel(); + final TemplateVariable[] variables = templateBuffer.getVariables(); + boolean hasPositions = false; + for (int i = 0; i != variables.length; i++) { + final TemplateVariable variable = variables[i]; + + if (variable.isUnambiguous()) { + continue; + } + + final LinkedPositionGroup group = new LinkedPositionGroup(); + + final int[] offsets = variable.getOffsets(); + final int length = variable.getLength(); + + LinkedPosition first; + { + final String[] values = variable.getValues(); + final ICompletionProposal[] proposals = new ICompletionProposal[values.length]; + for (int j = 0; j < values.length; j++) { + ensurePositionCategoryInstalled(document, model); + final Position pos = new Position(offsets[0] + start, + length); + document.addPosition(getCategory(), pos); + proposals[j] = new PositionBasedCompletionProposal( + values[j], pos, length); + } + + if (proposals.length > 1) { + first = new ProposalPosition(document, offsets[0] + + start, length, proposals); + } else { + first = new LinkedPosition(document, + offsets[0] + start, length); + } + } + + for (int j = 0; j != offsets.length; j++) { + if (j == 0) { + group.addPosition(first); + } else { + group.addPosition(new LinkedPosition(document, + offsets[j] + start, length)); + } + } + + model.addGroup(group); + hasPositions = true; + } + + if (hasPositions) { + model.forceInstall(); + + final EventBTextEditor editor = getEventBEditor(); + if (editor != null) { + model.addLinkingListener(new EditorSynchronizer(editor)); + } + + final LinkedModeUI ui = new EditorLinkedModeUI(model, viewer); + ui.setExitPosition(viewer, getCaretOffset(templateBuffer) + + start, 0, Integer.MAX_VALUE); + ui.enter(); + + fSelectedRegion = ui.getSelectedRegion(); + } else { + ensurePositionCategoryRemoved(document); + fSelectedRegion = new Region(getCaretOffset(templateBuffer) + + start, 0); + } + } catch (final BadLocationException e) { + openErrorDialog(viewer.getTextWidget().getShell(), e); + ensurePositionCategoryRemoved(document); + fSelectedRegion = fRegion; + } catch (final BadPositionCategoryException e) { + openErrorDialog(viewer.getTextWidget().getShell(), e); + fSelectedRegion = fRegion; + } + + } + + private EventBTextEditor getEventBEditor() { + final IEditorPart part = TextEditorPlugin.getPlugin().getActivePage() + .getActiveEditor(); + if (part instanceof EventBTextEditor) { + return (EventBTextEditor) part; + } else { + return null; + } + } + + private void ensurePositionCategoryInstalled(final IDocument document, + final LinkedModeModel model) { + if (!document.containsPositionCategory(getCategory())) { + document.addPositionCategory(getCategory()); + fUpdater = new InclusivePositionUpdater(getCategory()); + document.addPositionUpdater(fUpdater); + + model.addLinkingListener(new ILinkedModeListener() { + + /* + * @see + * org.eclipse.jface.text.link.ILinkedModeListener#left(org. + * eclipse.jface.text.link.LinkedModeModel, int) + */ + public void left(final LinkedModeModel environment, + final int flags) { + ensurePositionCategoryRemoved(document); + } + + public void suspend(final LinkedModeModel environment) { + } + + public void resume(final LinkedModeModel environment, + final int flags) { + } + }); + } + } + + private void ensurePositionCategoryRemoved(final IDocument document) { + if (document.containsPositionCategory(getCategory())) { + try { + document.removePositionCategory(getCategory()); + } catch (final BadPositionCategoryException e) { + // ignore + } + document.removePositionUpdater(fUpdater); + } + } + + private String getCategory() { + return "TemplateProposalCategory_" + toString(); //$NON-NLS-1$ + } + + private int getCaretOffset(final TemplateBuffer buffer) { + + final TemplateVariable[] variables = buffer.getVariables(); + for (int i = 0; i != variables.length; i++) { + final TemplateVariable variable = variables[i]; + if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME)) { + return variable.getOffsets()[0]; + } + } + + return buffer.getString().length(); + } + + private void openErrorDialog(final Shell shell, final Exception e) { + MessageDialog.openError(shell, "Template Evaluation Error", e + .getMessage()); + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + @Override + public Point getSelection(final IDocument document) { + return new Point(fSelectedRegion.getOffset(), fSelectedRegion + .getLength()); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/IRelevantProposal.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/IRelevantProposal.java new file mode 100644 index 0000000000000000000000000000000000000000..69ce7f32998292cf4229b66eaa53f490645e5f69 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/IRelevantProposal.java @@ -0,0 +1,13 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import org.eclipse.jface.text.contentassist.ICompletionProposal; + +public interface IRelevantProposal extends ICompletionProposal { + public int getRelevance(); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/PositionBasedCompletionProposal.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/PositionBasedCompletionProposal.java new file mode 100644 index 0000000000000000000000000000000000000000..ce484f68a03264fef842ff1be21218678d5a2daa --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/editor/codecompletion/PositionBasedCompletionProposal.java @@ -0,0 +1,198 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.editor.codecompletion; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +/** + * A position based completion proposal. + * + * @since 3.0 + */ +final class PositionBasedCompletionProposal implements ICompletionProposal, + ICompletionProposalExtension2 { + + /** The string to be displayed in the completion proposal popup */ + private final String fDisplayString; + /** The replacement string */ + private final String fReplacementString; + /** The replacement position. */ + private final Position fReplacementPosition; + /** The cursor position after this proposal has been applied */ + private final int fCursorPosition; + /** The image to be displayed in the completion proposal popup */ + private final Image fImage; + /** The context information of this proposal */ + private final IContextInformation fContextInformation; + /** The additional info of this proposal */ + private final String fAdditionalProposalInfo; + + /** + * Creates a new completion proposal based on the provided information. The + * replacement string is considered being the display string too. All + * remaining fields are set to <code>null</code>. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementPosition + * the position of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + */ + public PositionBasedCompletionProposal(final String replacementString, + final Position replacementPosition, final int cursorPosition) { + this(replacementString, replacementPosition, cursorPosition, null, + null, null, null); + } + + /** + * Creates a new completion proposal. All fields are initialized based on + * the provided information. + * + * @param replacementString + * the actual string to be inserted into the document + * @param replacementPosition + * the position of the text to be replaced + * @param cursorPosition + * the position of the cursor following the insert relative to + * replacementOffset + * @param image + * the image to display for this proposal + * @param displayString + * the string to be displayed for the proposal + * @param contextInformation + * the context information associated with this proposal + * @param additionalProposalInfo + * the additional information associated with this proposal + */ + public PositionBasedCompletionProposal(final String replacementString, + final Position replacementPosition, final int cursorPosition, + final Image image, final String displayString, + final IContextInformation contextInformation, + final String additionalProposalInfo) { + Assert.isNotNull(replacementString); + Assert.isTrue(replacementPosition != null); + + fReplacementString = replacementString; + fReplacementPosition = replacementPosition; + fCursorPosition = cursorPosition; + fImage = image; + fDisplayString = displayString; + fContextInformation = contextInformation; + fAdditionalProposalInfo = additionalProposalInfo; + } + + /* + * @see ICompletionProposal#apply(IDocument) + */ + public void apply(final IDocument document) { + try { + document.replace(fReplacementPosition.getOffset(), + fReplacementPosition.getLength(), fReplacementString); + } catch (final BadLocationException x) { + // ignore + } + } + + /* + * @see ICompletionProposal#getSelection(IDocument) + */ + public Point getSelection(final IDocument document) { + return new Point(fReplacementPosition.getOffset() + fCursorPosition, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return fContextInformation; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return fImage; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposal#getDisplayString + * () + */ + public String getDisplayString() { + if (fDisplayString != null) { + return fDisplayString; + } + return fReplacementString; + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply + * (org.eclipse.jface.text.ITextViewer, char, int, int) + */ + public void apply(final ITextViewer viewer, final char trigger, + final int stateMask, final int offset) { + apply(viewer.getDocument()); + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected + * (org.eclipse.jface.text.ITextViewer, boolean) + */ + public void selected(final ITextViewer viewer, final boolean smartToggle) { + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected + * (org.eclipse.jface.text.ITextViewer) + */ + public void unselected(final ITextViewer viewer) { + } + + /* + * @see + * org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate + * (org.eclipse.jface.text.IDocument, int, + * org.eclipse.jface.text.DocumentEvent) + */ + public boolean validate(final IDocument document, final int offset, + final DocumentEvent event) { + try { + final String content = document.get(fReplacementPosition + .getOffset(), offset - fReplacementPosition.getOffset()); + if (fReplacementString.startsWith(content)) { + return true; + } + } catch (final BadLocationException e) { + // ignore concurrently modified document + } + return false; + } + +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContentProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c62af5447617971245e1f0aef8268018c27a7dae --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContentProvider.java @@ -0,0 +1,232 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variant; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.dom.DomManager; +import org.eventb.texteditor.ui.build.dom.IParseResultListener; +import org.eventb.texteditor.ui.build.dom.DomManager.ParseResult; + +/** + * {@link ITreeContentProvider} which can handle {@link IEditorInput}s as input. + */ +public class ContentProvider implements ITreeContentProvider, + IParseResultListener { + + private final DomManager domManager = TextEditorPlugin.getDomManager(); + + private static final Object[] NO_ELEMENTS = new Object[] {}; + + /** + * Cache for children of a machine + */ + private Object[] machineChildren; + + /** + * Cache for children of a context + */ + private Object[] contextChildren; + + /** + * Caches for children of events + */ + private final Map<Event, Object[]> eventChildren = new HashMap<Event, Object[]>(); + + private TreeViewer viewer; + + private Object currentInput; + + synchronized public Object[] getElements(final Object inputElement) { + if (inputElement instanceof IEditorInput) { + // show root element based on last successful parse result + final ParseResult lastParseResult = domManager + .getLastParseResult((IEditorInput) inputElement); + + if (lastParseResult != null) { + return new Object[] { lastParseResult.astRoot }; + } else if (inputElement instanceof IFileEditorInput) { + final IFile inputFile = ((IFileEditorInput) inputElement) + .getFile(); + return createFallbackStructure(inputFile); + } + } + + return NO_ELEMENTS; + } + + synchronized public Object[] getChildren(final Object parentElement) { + if (parentElement instanceof Machine) { + return internalGetChildren((Machine) parentElement); + } + + if (parentElement instanceof Context) { + return internalGetChildren((Context) parentElement); + } + + if (parentElement instanceof Event) { + return internalGetChildren((Event) parentElement); + } + + // no children for unknown parents + return NO_ELEMENTS; + } + + synchronized public Object getParent(final Object element) { + // node is one of our emf classes + if (element instanceof EventBObject) { + final EventBObject emfObject = (EventBObject) element; + return emfObject.eContainer(); + } + + // we cannot compute the parent + return null; + } + + synchronized public boolean hasChildren(final Object element) { + int count = 0; + + if (element instanceof Machine) { + count = internalGetChildren((Machine) element).length; + } + + if (element instanceof Context) { + count = internalGetChildren((Context) element).length; + } + + if (element instanceof Event) { + count = internalGetChildren((Event) element).length; + } + + return count > 0; + } + + synchronized public void inputChanged(final Viewer viewer, + final Object oldInput, final Object newInput) { + registerLister(newInput); + + if (viewer instanceof TreeViewer) { + this.viewer = (TreeViewer) viewer; + } + + clearCache(); + currentInput = newInput; + } + + synchronized public void dispose() { + registerLister(null); + viewer = null; + clearCache(); + } + + private void clearCache() { + machineChildren = null; + contextChildren = null; + eventChildren.clear(); + } + + private void registerLister(final Object newInput) { + if (currentInput != null && currentInput instanceof IEditorInput) { + domManager.removeParseResultListener((IEditorInput) currentInput, + this); + } + + if (newInput != null && newInput instanceof IEditorInput) { + domManager.addParseResultListener((IEditorInput) newInput, this); + } + } + + private Object[] createFallbackStructure(final IFile inputFile) { + /* + * TODO Use fallback strategy if no AST available, for example scan text + * for some keywords and use them to offer quick navigation by clicking + */ + return NO_ELEMENTS; + } + + private Object[] internalGetChildren(final Machine machine) { + if (machineChildren == null) { + final ArrayList<EventBObject> children = new ArrayList<EventBObject>(); + children.addAll(machine.getVariables()); + children.addAll(machine.getInvariants()); + + final Variant variant = machine.getVariant(); + if (variant != null) { + children.add(variant); + } + + children.addAll(machine.getEvents()); + + machineChildren = children.toArray(); + } + + return machineChildren; + } + + private Object[] internalGetChildren(final Context context) { + if (contextChildren == null) { + final ArrayList<EventBObject> children = new ArrayList<EventBObject>(); + children.addAll(context.getConstants()); + children.addAll(context.getAxioms()); + children.addAll(context.getSets()); + + contextChildren = children.toArray(); + } + + return contextChildren; + } + + private Object[] internalGetChildren(final Event event) { + if (!eventChildren.containsKey(event)) { + final ArrayList<EventBObject> children = new ArrayList<EventBObject>(); + children.addAll(event.getWitnesses()); + children.addAll(event.getGuards()); + children.addAll(event.getActions()); + + eventChildren.put(event, children.toArray()); + } + + return eventChildren.get(event); + } + + private void refresh() { + if (viewer != null && viewer.getControl() != null + && !viewer.getControl().isDisposed()) { + viewer.refresh(); + viewer.expandAll(); + } + } + + public void parseResultChanged(final ParseResult parseResult) { + if (viewer != null && viewer.getControl() != null + && !viewer.getControl().isDisposed()) { + // remove our now old cache so it can be rebuild + clearCache(); + + // refresh the viewer async because we might not be in the UI thread + viewer.getControl().getDisplay().asyncExec(new Runnable() { + public void run() { + refresh(); + } + }); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextImageSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextImageSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..5673250188247b5467d6185ff9c783dc19b844c3 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextImageSwitch.java @@ -0,0 +1,45 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.swt.graphics.Image; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Axiom; +import org.eventb.emf.core.context.CarrierSet; +import org.eventb.emf.core.context.Constant; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.util.ContextSwitch; +import org.eventb.texteditor.ui.Images; + +public class ContextImageSwitch extends ContextSwitch<Image> { + + @Override + public Image caseEventBObject(final EventBObject object) { + // TODO return generic image? + return null; + } + + @Override + public Image caseContext(final Context object) { + return Images.getImage(Images.IMG_CONTEXT); + } + + @Override + public Image caseConstant(final Constant object) { + return Images.getImage(Images.IMG_CONSTANT); + } + + @Override + public Image caseCarrierSet(final CarrierSet object) { + return Images.getImage(Images.IMG_CARRIER_SET); + } + + @Override + public Image caseAxiom(final Axiom object) { + return Images.getImage(Images.IMG_AXIOM); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextLabelSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextLabelSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..ea8030f1c5dccd7945ffbb4d91675730a86c267d --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextLabelSwitch.java @@ -0,0 +1,53 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.viewers.StyledString; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.context.Axiom; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.util.ContextSwitch; + +public class ContextLabelSwitch extends ContextSwitch<StyledString> { + + @Override + public StyledString caseEventBNamed(final EventBNamed object) { + /* + * Default method for all EventBNamedElements that are not handled + * seperately in their own methods. + */ + return LabelHelper.getStyledName(object.getName()); + } + + @Override + public StyledString caseContext(final Context object) { + final StyledString result = LabelHelper.getStyledName(object.getName()); + + final EList<String> extendsNames = object.getExtendsNames(); + if (extendsNames.size() > 0) { + LabelHelper.appendAttrDelim(result); + result.append("extends ", LabelHelper.ATTRIBUTE_STYLER); + result.append(LabelHelper.joinEList(extendsNames), + LabelHelper.ATTRIBUTE_STYLER); + } + + return result; + } + + @Override + public StyledString caseAxiom(final Axiom object) { + final StyledString result = LabelHelper.getStyledName(object.getName()); + + if (object.isTheorem()) { + LabelHelper.appendAttrDelim(result); + result.append("theorem", LabelHelper.ATTRIBUTE_STYLER); + } + + return result; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextNavigationProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextNavigationProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..229fe40f05df8b0555ccc52dc15eafb40e80aed8 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/ContextNavigationProvider.java @@ -0,0 +1,66 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eventb.emf.core.EventBCommented; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.util.ContextSwitch; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; + +public class ContextNavigationProvider extends ContextSwitch<TextRange> + implements INavigationProvider { + + public TextRange getHighlightRange(final EventBObject element) { + return doSwitch(element); + } + + @Override + public TextRange caseEventBObject(final EventBObject object) { + /* + * Default for all elments that are not explicitely handled. We create a + * new copy here so it's save to change the range. + */ + return TextPositionUtil.createTextRange(object); + } + + @Override + public TextRange caseEventBElement(final EventBElement object) { + final TextRange range = caseEventBObject(object); + + /* + * Correct offset if there is a comment as we don't want to highlight + * the comment. + */ + if (object instanceof EventBCommented) { + final String comment = ((EventBCommented) object).getComment(); + if (comment != null && comment.length() > 0) { + TextPositionUtil.correctStartOffset(range, comment.length()); + } + } + + return range; + } + + @Override + public TextRange caseEventBNamed(final EventBNamed object) { + TextRange range = TextPositionUtil.getInternalPosition( + (EventBElement) object, object.getName()); + + if (range == null) { + range = caseEventBElement((EventBElement) object); + + if (range != null) { + range.setLength(1); + } + } + + return range; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/INavigationProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/INavigationProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..3d46edb2110c2b0cb0c5dd9325b70f1de9496c7b --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/INavigationProvider.java @@ -0,0 +1,14 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eventb.emf.core.EventBObject; +import org.eventb.texttools.model.texttools.TextRange; + +public interface INavigationProvider { + public TextRange getHighlightRange(final EventBObject element); +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/LabelHelper.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/LabelHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..68e735ec33909bc0c41d0145ab43473fe8641d31 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/LabelHelper.java @@ -0,0 +1,52 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.StyledString.Styler; +import org.eventb.emf.core.EventBNamed; + +public class LabelHelper { + + public static final Styler ATTRIBUTE_STYLER = StyledString.QUALIFIER_STYLER; + public static final String ATTRIBUTE_DELIMITER = " : "; + + public static StyledString getPlainStyledText(final String contents) { + return new StyledString(contents); + } + + public static StyledString getStyledName(final String name) { + return getPlainStyledText(name); + } + + public static <T> String joinEList(final EList<T> parameters) { + return StringUtils.join(getAsStringList(parameters), ", "); + } + + public static void appendAttrDelim(final StyledString result) { + result.append(ATTRIBUTE_DELIMITER, ATTRIBUTE_STYLER); + } + + private static <T> List<String> getAsStringList(final EList<T> parameters) { + final List<String> result = new ArrayList<String>(); + + for (final T element : parameters) { + if (element instanceof EventBNamed) { + result.add(((EventBNamed) element).getName()); + } else { + result.add(element.toString()); + } + } + + return result; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineImageSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineImageSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..3d1400386faf917db306b2ae1df63ba94f48caae --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineImageSwitch.java @@ -0,0 +1,67 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.swt.graphics.Image; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Guard; +import org.eventb.emf.core.machine.Invariant; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variable; +import org.eventb.emf.core.machine.util.MachineSwitch; +import org.eventb.texteditor.ui.Images; + +public class MachineImageSwitch extends MachineSwitch<Image> { + + @Override + public Image caseEventBObject(final EventBObject object) { + // TODO return generic image? + return null; + } + + @Override + public Image caseMachine(final Machine object) { + return Images.getImage(Images.IMG_MACHINE); + } + + @Override + public Image caseVariable(final Variable object) { + return Images.getImage(Images.IMG_VARIABLE); + } + + @Override + public Image caseInvariant(final Invariant object) { + if (object.isTheorem()) { + return Images.getImage(Images.IMG_INVARIANT); + } else { + return Images.getImage(Images.IMG_THEOREM); + } + } + + @Override + public Image caseEvent(final Event object) { + return Images.getImage(Images.IMG_EVENT); + } + + @Override + public Image caseGuard(final Guard object) { + return Images.getImage(Images.IMG_GUARD); + } + + @Override + public Image caseAction(final Action object) { + return Images.getImage(Images.IMG_ACTION); + } + + // TODO find images for these elements + // @Override + // public Image caseWitness(Witness object) { + // return Images.getImage(Images.IMG_wi); + // } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineLabelSwitch.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineLabelSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..4049ed73848a14df007d4e8eb6ab23698a502f85 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineLabelSwitch.java @@ -0,0 +1,102 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.viewers.StyledString; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Invariant; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variant; +import org.eventb.emf.core.machine.util.MachineSwitch; + +public class MachineLabelSwitch extends MachineSwitch<StyledString> { + + @Override + public StyledString caseEventBNamed(final EventBNamed object) { + /* + * Default method for all EventBNamedElements that are not handled + * seperately in their own methods. + */ + return LabelHelper.getStyledName(object.getName()); + } + + @Override + public StyledString caseMachine(final Machine object) { + final StyledString result = LabelHelper.getStyledName(object.getName()); + + final EList<String> refinesNames = object.getRefinesNames(); + if (refinesNames.size() > 0) { + LabelHelper.appendAttrDelim(result); + result.append("refines ", LabelHelper.ATTRIBUTE_STYLER); + result.append(LabelHelper.joinEList(refinesNames), + LabelHelper.ATTRIBUTE_STYLER); + } + + final EList<String> seesNames = object.getSeesNames(); + if (seesNames.size() > 0) { + LabelHelper.appendAttrDelim(result); + result.append("sees ", LabelHelper.ATTRIBUTE_STYLER); + result.append(LabelHelper.joinEList(seesNames), + LabelHelper.ATTRIBUTE_STYLER); + } + + return result; + } + + @Override + public StyledString caseInvariant(final Invariant object) { + final StyledString result = LabelHelper.getStyledName(object.getName()); + + if (object.isTheorem()) { + LabelHelper.appendAttrDelim(result); + result.append("theorem", LabelHelper.ATTRIBUTE_STYLER); + } + + return result; + } + + @Override + public StyledString caseVariant(final Variant object) { + return LabelHelper.getStyledName("Variant"); + } + + @Override + public StyledString caseEvent(final Event object) { + final StyledString result = LabelHelper.getStyledName(object.getName()); + + if (object.getParameters().size() > 0) { + final String paramString = LabelHelper.joinEList(object + .getParameters()); + result.append(" (" + paramString + ")"); + } + + final EList<String> refinesNames = object.getRefinesNames(); + if (object.isExtended()) { + LabelHelper.appendAttrDelim(result); + result.append("extends ", LabelHelper.ATTRIBUTE_STYLER); + + if (refinesNames.size() > 0) { + result + .append(refinesNames.get(0), + LabelHelper.ATTRIBUTE_STYLER); + } else { + result.append("?", LabelHelper.ATTRIBUTE_STYLER); + } + } else { + if (refinesNames.size() > 0) { + LabelHelper.appendAttrDelim(result); + result.append("refines ", LabelHelper.ATTRIBUTE_STYLER); + result.append(LabelHelper.joinEList(refinesNames), + LabelHelper.ATTRIBUTE_STYLER); + } + } + + return result; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineNavigationProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineNavigationProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..f9c18b8c1881134a91dcec8301c086c405c81457 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/MachineNavigationProvider.java @@ -0,0 +1,81 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eventb.emf.core.EventBCommented; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.machine.Variant; +import org.eventb.emf.core.machine.util.MachineSwitch; +import org.eventb.texttools.Constants; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; + +public class MachineNavigationProvider extends MachineSwitch<TextRange> + implements INavigationProvider { + + public TextRange getHighlightRange(final EventBObject element) { + return doSwitch(element); + } + + @Override + public TextRange caseEventBObject(final EventBObject object) { + /* + * Default for all elments that are not explicitely handled. We create a + * new copy here so it's save to change the range. + */ + return TextPositionUtil.createTextRange(object); + } + + @Override + public TextRange caseEventBElement(final EventBElement object) { + final TextRange range = caseEventBObject(object); + + /* + * Correct offset if there is a comment as we don't want to highlight + * the comment. + */ + if (object instanceof EventBCommented) { + final String comment = ((EventBCommented) object).getComment(); + + if (comment != null && comment.length() > 0) { + TextPositionUtil.correctStartOffset(range, comment.length()); + } + } + + return range; + } + + @Override + public TextRange caseVariant(final Variant object) { + final TextRange range = caseEventBElement(object); + + if (range != null) { + TextPositionUtil.correctStartOffset(range, Constants.VARIANT + .length()); + } + + return range; + } + + @Override + public TextRange caseEventBNamed(final EventBNamed object) { + TextRange range = TextPositionUtil.getInternalPosition( + (EventBElement) object, object.getName()); + + if (range == null) { + range = caseEventBElement((EventBElement) object); + + if (range != null) { + range.setLength(1); + } + } + + return range; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlineLabelProvider.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlineLabelProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..05a23280dfa2dd9ea0fde224990841f9b73024d5 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlineLabelProvider.java @@ -0,0 +1,95 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.ContextPackage; +import org.eventb.emf.core.machine.MachinePackage; + +public class OutlineLabelProvider extends LabelProvider implements + IStyledLabelProvider { + + private MachineLabelSwitch machineLabelSwitch; + private ContextLabelSwitch contextLabelSwitch; + private MachineImageSwitch machineImageSwitch; + private ContextImageSwitch contextImageSwitch; + + @Override + public String getText(final Object element) { + return getStyledText(element).getString(); + } + + public StyledString getStyledText(final Object element) { + if (element instanceof EventBObject) { + final EventBObject emfObject = (EventBObject) element; + final String packageNsURI = emfObject.eClass().getEPackage() + .getNsURI(); + + if (packageNsURI.equals(MachinePackage.eNS_URI)) { + return getTextsForMachine(emfObject); + } else if (packageNsURI.equals(ContextPackage.eNS_URI)) { + return getTextsForContext(emfObject); + } + } + + // we can only handle EventBObjects here, use default otherwise + return new StyledString(super.getText(element)); + } + + @Override + public Image getImage(final Object element) { + if (element instanceof EventBObject) { + final EventBObject emfObject = (EventBObject) element; + final String packageNsURI = emfObject.eClass().getEPackage() + .getNsURI(); + + if (packageNsURI.equals(MachinePackage.eNS_URI)) { + return getImageForMachine(emfObject); + } else if (packageNsURI.equals(ContextPackage.eNS_URI)) { + return getImageForContext(emfObject); + } + } + + return super.getImage(element); + } + + private Image getImageForMachine(final EventBObject emfObject) { + if (machineImageSwitch == null) { + machineImageSwitch = new MachineImageSwitch(); + } + + return machineImageSwitch.doSwitch(emfObject); + } + + private Image getImageForContext(final EventBObject emfObject) { + if (contextImageSwitch == null) { + contextImageSwitch = new ContextImageSwitch(); + } + + return contextImageSwitch.doSwitch(emfObject); + } + + private StyledString getTextsForContext(final EventBObject emfObject) { + if (contextLabelSwitch == null) { + contextLabelSwitch = new ContextLabelSwitch(); + } + + return contextLabelSwitch.doSwitch(emfObject); + } + + private StyledString getTextsForMachine(final EventBObject emfObject) { + if (machineLabelSwitch == null) { + machineLabelSwitch = new MachineLabelSwitch(); + } + + return machineLabelSwitch.doSwitch(emfObject); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlinePage.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlinePage.java new file mode 100644 index 0000000000000000000000000000000000000000..21f350652f91b595e46996ecde4cae12aad8bf48 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/OutlinePage.java @@ -0,0 +1,36 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.views.contentoutline.ContentOutlinePage; +import org.eventb.texteditor.ui.editor.EventBTextEditor; + +public class OutlinePage extends ContentOutlinePage { + + private final EventBTextEditor editor; + + public OutlinePage(final EventBTextEditor editor) { + this.editor = editor; + } + + @Override + public void createControl(final Composite parent) { + super.createControl(parent); + + final TreeViewer viewer = getTreeViewer(); + viewer.setContentProvider(new ContentProvider()); + viewer.setLabelProvider(new DelegatingStyledCellLabelProvider( + new OutlineLabelProvider())); + viewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS); + + viewer.setInput(editor.getEditorInput()); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/RevealSelectionListener.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/RevealSelectionListener.java new file mode 100644 index 0000000000000000000000000000000000000000..e79f403aecd4b1fd5cedd20b0caee6bec30dcd72 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/outline/RevealSelectionListener.java @@ -0,0 +1,62 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.outline; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.ui.editors.text.TextEditor; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.ContextPackage; +import org.eventb.emf.core.machine.MachinePackage; +import org.eventb.texttools.model.texttools.TextRange; + +public class RevealSelectionListener implements ISelectionChangedListener { + private final INavigationProvider machineNavProvider = new MachineNavigationProvider(); + private final INavigationProvider contextNavProvider = new ContextNavigationProvider(); + private final TextEditor editor; + + public RevealSelectionListener(final TextEditor editor) { + this.editor = editor; + } + + public void selectionChanged(final SelectionChangedEvent event) { + final ISelection selection = event.getSelection(); + if (!(selection instanceof IStructuredSelection)) { + return; + } + + final IStructuredSelection structuredSelection = (IStructuredSelection) selection; + if (structuredSelection.size() != 1) { + return; + } + + final Object firstElement = structuredSelection.getFirstElement(); + if (!(firstElement instanceof EventBObject)) { + return; + } + + highlightElement((EventBObject) firstElement); + } + + private void highlightElement(final EventBObject element) { + TextRange range = null; + + final String packageNsURI = element.eClass().getEPackage().getNsURI(); + + if (packageNsURI.equals(MachinePackage.eNS_URI)) { + range = machineNavProvider.getHighlightRange(element); + } else if (packageNsURI.equals(ContextPackage.eNS_URI)) { + range = contextNavProvider.getHighlightRange(element); + } + + if (range != null) { + editor.selectAndReveal(range.getOffset(), range.getLength()); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/HighlightingPreferencePage.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/HighlightingPreferencePage.java new file mode 100644 index 0000000000000000000000000000000000000000..ffcc40a049d4b44afb7b6ba63dae89db8c741e66 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/HighlightingPreferencePage.java @@ -0,0 +1,186 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.preferences; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.preference.ColorSelector; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.eventb.texteditor.ui.TextDecoration; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; + +public class HighlightingPreferencePage extends PreferencePage implements + IWorkbenchPreferencePage { + + private final List<Field> fields = new ArrayList<Field>(); + + public HighlightingPreferencePage() { + super(); + setPreferenceStore(TextEditorPlugin.getPlugin().getPreferenceStore()); + setDescription("Please configure the syntax highlighting for the Event-B TextEditor. You can select the color and the style for each element type."); + } + + @Override + protected Control createContents(final Composite parent) { + final Composite colorComposite = new Composite(parent, SWT.NONE); + final GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginHeight = 0; + layout.marginWidth = 0; + colorComposite.setLayout(layout); + + for (final ESyntaxElement element : TextDecoration.ESyntaxElement + .values()) { + createField(element, colorComposite); + } + + loadPreferences(); + + colorComposite.layout(false); + return colorComposite; + } + + private void savePreferences() { + final IPreferenceStore store = getPreferenceStore(); + + for (final Field f : fields) { + final ESyntaxElement element = f.element; + + final int bold = f.boldButton.getSelection() ? SWT.BOLD : SWT.NONE; + final int italic = f.italicButton.getSelection() ? SWT.ITALIC + : SWT.NONE; + final int underline = f.underlineButton.getSelection() ? TextAttribute.UNDERLINE + : SWT.NONE; + final int style = bold | italic | underline; + + PreferenceConverter.setValue(store, element.getColorKey(), + f.colorSelector.getColorValue()); + store.setValue(element.getStyleKey(), style); + + element.resetToken(); + } + } + + private void loadPreferences() { + final IPreferenceStore store = getPreferenceStore(); + + for (final Field f : fields) { + final ESyntaxElement element = f.element; + final RGB color = PreferenceConverter.getColor(store, element + .getColorKey()); + final int style = store.getInt(element.getStyleKey()); + + setFieldValues(f, color, style); + } + } + + private void loadDefaultPreferences() { + final IPreferenceStore store = getPreferenceStore(); + + for (final Field f : fields) { + final ESyntaxElement element = f.element; + final RGB color = PreferenceConverter.getDefaultColor(store, + element.getColorKey()); + final int style = store.getDefaultInt(element.getStyleKey()); + + setFieldValues(f, color, style); + } + } + + private void setFieldValues(final Field f, final RGB color, final int style) { + f.colorSelector.setColorValue(color); + f.boldButton.setSelection((style & SWT.BOLD) == SWT.BOLD); + f.italicButton.setSelection((style & SWT.ITALIC) == SWT.ITALIC); + f.underlineButton + .setSelection((style & TextAttribute.UNDERLINE) == TextAttribute.UNDERLINE); + } + + private void createField(final ESyntaxElement element, + final Composite parent) { + final Composite field = new Composite(parent, SWT.NONE); + + final GridData gridData = new GridData(GridData.FILL_HORIZONTAL); + gridData.grabExcessHorizontalSpace = true; + + final GridLayout layout = new GridLayout(5, false); + // layout.marginHeight = 5; + layout.marginWidth = 5; + field.setLayout(layout); + field.setLayoutData(gridData); + + final Label label = new Label(field, SWT.NONE); + label.setText(element.getLabel()); + label.setLayoutData(gridData); + + final ColorSelector colorSelector = new ColorSelector(field); + + final Button boldButton = new Button(field, SWT.CHECK); + boldButton.setText("Bold"); + + final Button italicButton = new Button(field, SWT.CHECK); + italicButton.setText("Italic"); + + final Button underlineButton = new Button(field, SWT.CHECK); + underlineButton.setText("Underline"); + + fields.add(new Field(element, colorSelector, boldButton, italicButton, + underlineButton)); + } + + @Override + public boolean performOk() { + savePreferences(); + + // TODO find active editor and trigger reconcilePresentation on + // EventBPresentationReconciler + + return true; + } + + @Override + protected void performDefaults() { + loadDefaultPreferences(); + super.performDefaults(); + } + + public void init(final IWorkbench workbench) { + // TODO Auto-generated method stub + } + + private class Field { + protected final ESyntaxElement element; + protected final ColorSelector colorSelector; + protected final Button boldButton; + protected final Button italicButton; + protected final Button underlineButton; + + protected Field(final ESyntaxElement element, + final ColorSelector colorSelector, final Button boldButton, + final Button italicButton, final Button underlineButton) { + this.element = element; + this.colorSelector = colorSelector; + this.boldButton = boldButton; + this.italicButton = italicButton; + this.underlineButton = underlineButton; + } + } +} \ No newline at end of file diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/PreferenceInitializer.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/PreferenceInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..96efdb5ec94d17c9be922f23d72fc5ecc4ab3797 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/PreferenceInitializer.java @@ -0,0 +1,58 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.preferences; + +import static org.eclipse.swt.SWT.*; +import static org.eventb.texteditor.ui.TextDecoration.ESyntaxElement.*; + +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; + +/** + * Class used to initialize default preference values. + */ +public class PreferenceInitializer extends AbstractPreferenceInitializer { + + private IPreferenceStore store; + + /* + * (non-Javadoc) + * + * @seeorg.eclipse.core.runtime.preferences.AbstractPreferenceInitializer# + * initializeDefaultPreferences() + */ + @Override + public void initializeDefaultPreferences() { + store = TextEditorPlugin.getPlugin().getPreferenceStore(); + + setDefaultValues(Comment, COLOR_GRAY, ITALIC); + setDefaultValues(Label, COLOR_DARK_GRAY, NORMAL); + setDefaultValues(Keyword, COLOR_DARK_RED, BOLD); + setDefaultValues(MathKeyword, COLOR_DARK_RED, NORMAL); + setDefaultValues(GlobalVariable, COLOR_BLUE, NORMAL); + setDefaultValues(BoundedVariable, COLOR_DARK_GREEN, ITALIC); + setDefaultValues(Parameter, COLOR_BLUE, ITALIC); + setDefaultValues(Constant, COLOR_BLUE, BOLD); + setDefaultValues(Set, COLOR_DARK_GREEN, BOLD); + } + + private void setDefaultValues(final ESyntaxElement element, + final int color, final int style) { + PreferenceConverter.setDefault(store, element.getColorKey(), + getRGB(color)); + store.setDefault(element.getStyleKey(), style); + } + + private static RGB getRGB(final int colorCode) { + return Display.getDefault().getSystemColor(colorCode).getRGB(); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TemplatePrefPage.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TemplatePrefPage.java new file mode 100644 index 0000000000000000000000000000000000000000..af544dce3638128daa6d206b1e6be9577ae3cd88 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TemplatePrefPage.java @@ -0,0 +1,29 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.preferences; + +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.texteditor.templates.TemplatePreferencePage; +import org.eventb.texteditor.ui.TextEditorPlugin; + +public class TemplatePrefPage extends TemplatePreferencePage { + + @Override + public void init(final IWorkbench workbench) { + super.init(workbench); + + setTemplateStore(TextEditorPlugin.getPlugin().getTemplateStore()); + setContextTypeRegistry(TextEditorPlugin.getPlugin() + .getContextTypeRegistry()); + setPreferenceStore(TextEditorPlugin.getPlugin().getPreferenceStore()); + } + + @Override + protected boolean isShowFormatterSetting() { + return false; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TextEditorPreferencePage.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TextEditorPreferencePage.java new file mode 100644 index 0000000000000000000000000000000000000000..e6dbabb62f62127f7b498c4e78de6aaa0404813a --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/preferences/TextEditorPreferencePage.java @@ -0,0 +1,28 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.preferences; + +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +public class TextEditorPreferencePage extends FieldEditorPreferencePage + implements IWorkbenchPreferencePage { + public TextEditorPreferencePage() { + super(); + setDescription("This section provides preferences for the Event-B TextEditor."); + } + + @Override + protected void createFieldEditors() { + // nothing for now + } + + public void init(final IWorkbench workbench) { + // nothing for now + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/CommentTokenScanner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/CommentTokenScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..826338e7e0c2eef555b60df67f3ba78d528ea8a8 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/CommentTokenScanner.java @@ -0,0 +1,27 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import org.eclipse.jface.text.rules.EndOfLineRule; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.Token; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; + +public class CommentTokenScanner extends RuleBasedScanner { + + public CommentTokenScanner() { + super(); + + final IRule[] rules = new IRule[2]; + final Token token = ESyntaxElement.Comment.getToken(); + rules[0] = new EndOfLineRule("//", token); + rules[1] = new MultiLineRule("/*", "*/", token, (char) 0, true); + setRules(rules); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/EventBPresentationReconciler.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/EventBPresentationReconciler.java new file mode 100644 index 0000000000000000000000000000000000000000..862f0e95c4df3fa296d8fc50ea284cf0f0ad79c5 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/EventBPresentationReconciler.java @@ -0,0 +1,131 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.presentation.PresentationReconciler; +import org.eclipse.jface.text.rules.DefaultDamagerRepairer; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.build.dom.DomManager; +import org.eventb.texteditor.ui.build.dom.IComponentDom; +import org.eventb.texteditor.ui.build.dom.IDomChangeListener; +import org.eventb.texteditor.ui.reconciler.partitioning.PartitionScanner; + +/** + * This {@link PresentationReconciler} does basic syntax highlighting based on + * content types of partitions. It reset text to default style if default + * content type is found. Beside that it recognizes the following elements in + * EventB components: + * <ul> + * <li>Comment</li> + * <li>Label</li> + * <li>Structural keywords</li> + * <li>Formula keywords</li> + * </ul> + * + */ +public class EventBPresentationReconciler extends PresentationReconciler + implements IDomChangeListener { + private static final DomManager domManager = TextEditorPlugin + .getDomManager(); + private SemanticTokenScanner semanticScanner; + private ITextViewer viewer; + private IComponentDom dom = null; + private Resource resource; + + public EventBPresentationReconciler() { + super(); + init(); + } + + private void init() { + // init semantic token scanner + semanticScanner = new SemanticTokenScanner(); + + // default content type: semantic highlighing + DefaultDamagerRepairer damager = new DefaultDamagerRepairer( + semanticScanner); + setDamager(damager, IDocument.DEFAULT_CONTENT_TYPE); + setRepairer(damager, IDocument.DEFAULT_CONTENT_TYPE); + + // comments + damager = new DefaultDamagerRepairer(new CommentTokenScanner()); + setDamager(damager, PartitionScanner.CONTENT_TYPE_COMMENT); + setRepairer(damager, PartitionScanner.CONTENT_TYPE_COMMENT); + + // labels + damager = new DefaultDamagerRepairer(new LabelTokenScanner()); + setDamager(damager, PartitionScanner.CONTENT_TYPE_LABEL); + setRepairer(damager, PartitionScanner.CONTENT_TYPE_LABEL); + } + + public void reconcilePresentation() { + if (viewer == null && viewer.getTextWidget() == null + && viewer.getTextWidget().isDisposed()) { + return; + } + + viewer.getTextWidget().getDisplay().asyncExec(new Runnable() { + public void run() { + viewer.invalidateTextPresentation(); + } + }); + } + + @Override + public void install(final ITextViewer viewer) { + super.install(viewer); + this.viewer = viewer; + domManager.addDomChangeListener(this); + } + + @Override + public void uninstall() { + domManager.removeDomChangeListener(this); + super.uninstall(); + + setInputResource(null); + } + + public void setInputResource(final Resource input) { + resource = input; + + if (input != null) { + dom = domManager.getDom(input); + } else { + dom = null; + } + + semanticScanner.setInputResource(input); + } + + public void domChanged(final IComponentDom changedDom) { + checkInit(); + + // we are directly affected by the update + if (changedDom == dom) { + reconcilePresentation(); + } else if (dom != null) { + // any (transitive) relation to the changed dom? + final Set<IComponentDom> referencedDoms = dom + .getReferencedDoms(true); + if (referencedDoms.contains(changedDom)) { + reconcilePresentation(); + } + } + } + + private void checkInit() { + if (dom == null && resource != null) { + dom = domManager.getDom(resource); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierDetector.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..669807e224f1050b8fb9a0198015fda2020d4e3d --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierDetector.java @@ -0,0 +1,19 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import org.eclipse.jface.text.rules.IWordDetector; + +public class IdentifierDetector implements IWordDetector { + public boolean isWordStart(final char c) { + return Character.isJavaIdentifierStart(c); + } + + public boolean isWordPart(final char c) { + return Character.isJavaIdentifierPart(c) || c == '\''; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierRule.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierRule.java new file mode 100644 index 0000000000000000000000000000000000000000..4f3e3cfd0d07c62c7756c869481e352faac2fc81 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/IdentifierRule.java @@ -0,0 +1,143 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import org.eclipse.jface.text.rules.ICharacterScanner; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.IWordDetector; +import org.eclipse.jface.text.rules.Token; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; +import org.eventb.texteditor.ui.build.dom.IDom; + +public class IdentifierRule implements IRule { + + /** The word detector used by this rule. */ + private final IWordDetector fDetector = new IdentifierDetector(); + + /** + * The default token to be returned on success and if nothing else has been + * specified. + */ + private final IToken fDefaultToken = new Token(null); + + /** Buffer used for pattern detection. */ + private final StringBuffer fBuffer = new StringBuffer(); + + private int offset; + + private IDom dom; + + public IdentifierRule() { + } + + /* + * @see IRule#evaluate(ICharacterScanner) + */ + public IToken evaluate(final ICharacterScanner scanner) { + if (dom != null) { + // read next word + readIdentifier(scanner); + + if (fBuffer.length() > 0) { + final String buffer = fBuffer.toString(); + + // determine type for identifier + IDom.IdentifierType type = null; + final IDom scope = dom.getScopingDom(offset); + if (scope != null) { + type = scope.getIdentifierType(buffer); + } + + // resolve text attribute + final IToken token = getToken(type); + if (token != null) { + return token; + } + + if (fDefaultToken.isUndefined()) { + unreadBuffer(scanner); + } + + return fDefaultToken; + } + } + + return Token.UNDEFINED; + } + + private void readIdentifier(final ICharacterScanner scanner) { + fBuffer.setLength(0); + + if (newIdentifierBegins(scanner)) { + int c = scanner.read(); + + if (c != ICharacterScanner.EOF && fDetector.isWordStart((char) c)) { + do { + fBuffer.append((char) c); + c = scanner.read(); + } while (c != ICharacterScanner.EOF + && fDetector.isWordPart((char) c)); + scanner.unread(); + } else { + scanner.unread(); + } + } + } + + private boolean newIdentifierBegins(final ICharacterScanner scanner) { + + // read char before offset + scanner.unread(); + final int c = scanner.read(); + + if (fDetector.isWordStart((char) c) || fDetector.isWordPart((char) c)) { + return false; + } + + return true; + } + + private IToken getToken(final IDom.IdentifierType type) { + if (type != null) { + switch (type) { + case GlobalVariable: + return ESyntaxElement.GlobalVariable.getToken(); + case LocalVariable: + return ESyntaxElement.BoundedVariable.getToken(); + case Parameter: + return ESyntaxElement.Parameter.getToken(); + case Constant: + return ESyntaxElement.Constant.getToken(); + case Set: + return ESyntaxElement.Set.getToken(); + } + } + + return null; + } + + /** + * Returns the characters in the buffer to the scanner. + * + * @param scanner + * the scanner to be used + */ + protected void unreadBuffer(final ICharacterScanner scanner) { + for (int i = fBuffer.length() - 1; i >= 0; i--) { + scanner.unread(); + } + } + + public void setOffset(final int offset) { + this.offset = offset; + } + + public void setDom(final IDom dom) { + this.dom = dom; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/KeywordTokenScanner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/KeywordTokenScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..2d22b6e1ecb09628afda612f74586503a670453d --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/KeywordTokenScanner.java @@ -0,0 +1,32 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.Token; +import org.eventb.texteditor.ui.reconciler.partitioning.KeywordDetector; +import org.eventb.texteditor.ui.reconciler.partitioning.WordPredicateRule; + +public class KeywordTokenScanner extends RuleBasedScanner { + public KeywordTokenScanner(final String[] keywords, + final TextAttribute textAttribute) { + super(); + + final IToken token = new Token(textAttribute); + final WordPredicateRule keywordRule = new WordPredicateRule( + new KeywordDetector()); + + for (final String keyword : keywords) { + keywordRule.addWord(keyword, token); + } + + setRules(new IRule[] { keywordRule }); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/LabelTokenScanner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/LabelTokenScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..e9d4e50446d0de1a4e5a5636c3d23eef9c91f022 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/LabelTokenScanner.java @@ -0,0 +1,22 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.WordRule; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; +import org.eventb.texteditor.ui.reconciler.partitioning.LabelDetector; + +public class LabelTokenScanner extends RuleBasedScanner { + public LabelTokenScanner() { + super(); + + setRules(new IRule[] { new WordRule(new LabelDetector(), + ESyntaxElement.Label.getToken(), true) }); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/SemanticTokenScanner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/SemanticTokenScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..e8f742eb33bb1538f982a2c1518b98c2bd874bd1 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/SemanticTokenScanner.java @@ -0,0 +1,115 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.Token; +import org.eventb.texteditor.ui.TextEditorPlugin; +import org.eventb.texteditor.ui.TextDecoration.ESyntaxElement; +import org.eventb.texteditor.ui.build.dom.DomManager; +import org.eventb.texteditor.ui.build.dom.IComponentDom; +import org.eventb.texteditor.ui.reconciler.partitioning.KeywordDetector; +import org.eventb.texteditor.ui.reconciler.partitioning.WordPredicateRule; +import org.eventb.texttools.Constants; + +public class SemanticTokenScanner extends RuleBasedScanner { + private final DomManager domManager = TextEditorPlugin.getDomManager(); + private final IdentifierRule identifierRule; + private IComponentDom currentDom; + private Resource resource; + + public SemanticTokenScanner() { + super(); + setRules(getRules()); + identifierRule = new IdentifierRule(); + } + + private IRule[] getRules() { + final List<IRule> rules = new ArrayList<IRule>(); + + // rule for structural keywords + final WordPredicateRule structKWRule = new WordPredicateRule( + new KeywordDetector()); + for (final String keyword : Constants.structural_keywords) { + structKWRule.addWord(keyword, ESyntaxElement.Keyword.getToken()); + } + rules.add(structKWRule); + + // rule for formula keywords + final WordPredicateRule formulaKwRule = new WordPredicateRule( + new KeywordDetector()); + for (final String keyword : Constants.formula_keywords) { + formulaKwRule.addWord(keyword, ESyntaxElement.MathKeyword + .getToken()); + } + rules.add(formulaKwRule); + + return rules.toArray(new IRule[rules.size()]); + } + + @Override + public IToken nextToken() { + final int startOffset = fOffset; + + final IToken nextToken = super.nextToken(); + if (acceptToken(nextToken)) { + return nextToken; + } + + fTokenOffset = startOffset; + fOffset = startOffset; + fColumn = UNDEFINED; + + checkInit(); + identifierRule.setOffset(fOffset); + final IToken token = identifierRule.evaluate(this); + + if (!token.isUndefined()) { + return token; + } + + if (read() == EOF) { + return Token.EOF; + } + + return fDefaultReturnToken; + } + + private boolean acceptToken(final IToken nextToken) { + /* + * If the next token is part of an identifier too (e.g. a event name) + * don't accept this token. + */ + final int c = read(); + if (Character.isJavaIdentifierPart(c) || c == '\'') { + unread(); + return false; + } + + return nextToken == ESyntaxElement.Keyword.getToken() + || nextToken == ESyntaxElement.MathKeyword.getToken() + || nextToken.isEOF(); + } + + public void setInputResource(final Resource resource) { + this.resource = resource; + currentDom = null; + } + + private void checkInit() { + if (currentDom == null && resource != null) { + currentDom = domManager.getDom(resource); + identifierRule.setDom(currentDom); + } + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/KeywordDetector.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/KeywordDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..2e368897e0b18c5cd2aadf3f1c1bf7b69bd895f3 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/KeywordDetector.java @@ -0,0 +1,20 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler.partitioning; + +import org.eclipse.jface.text.rules.IWordDetector; + +public class KeywordDetector implements IWordDetector { + public boolean isWordStart(final char c) { + return Character.isLetter(c) || c == '\u2115' || c == '\u2119' + || c == '\u2124'; + } + + public boolean isWordPart(final char c) { + return Character.isLetterOrDigit(c) || c == '\u0031'; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/LabelDetector.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/LabelDetector.java new file mode 100644 index 0000000000000000000000000000000000000000..9b7936117a5201b00bb0a8909745bdfcdf553d06 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/LabelDetector.java @@ -0,0 +1,19 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler.partitioning; + +import org.eclipse.jface.text.rules.IWordDetector; + +public class LabelDetector implements IWordDetector { + public boolean isWordStart(final char c) { + return c == '@'; + } + + public boolean isWordPart(final char c) { + return !Character.isWhitespace(c); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/PartitionScanner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/PartitionScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..2da75a89256afd91427c2cf1e4acc7074cfe1c61 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/PartitionScanner.java @@ -0,0 +1,63 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler.partitioning; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.rules.EndOfLineRule; +import org.eclipse.jface.text.rules.IPredicateRule; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.RuleBasedPartitionScanner; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.rules.WordPatternRule; + +public class PartitionScanner extends RuleBasedPartitionScanner { + + private static final String PREFIX = "content_type_"; + public static final String CONTENT_TYPE_STRUCTURAL_KEYWORD = PREFIX + + "structural_keyword"; + public static final String CONTENT_TYPE_FORMULA_KEYWORD = PREFIX + + "formula_keyword"; + public static final String CONTENT_TYPE_COMMENT = PREFIX + "comment"; + public static final String CONTENT_TYPE_LABEL = PREFIX + "label"; + + public static final String[] CONTENT_TYPES = { + IDocument.DEFAULT_CONTENT_TYPE, CONTENT_TYPE_STRUCTURAL_KEYWORD, + CONTENT_TYPE_FORMULA_KEYWORD, CONTENT_TYPE_COMMENT, + CONTENT_TYPE_LABEL }; + + public static final Token TOKEN_STRUCTURAL_KEYWORD = new Token( + CONTENT_TYPE_STRUCTURAL_KEYWORD); + public static final Token TOKEN_FORMULA_KEYWORD = new Token( + CONTENT_TYPE_FORMULA_KEYWORD); + public static final Token TOKEN_COMMENT = new Token(CONTENT_TYPE_COMMENT); + public static final Token TOKEN_LABEL = new Token(CONTENT_TYPE_LABEL); + + public PartitionScanner() { + super(); + + final List<IPredicateRule> rules = getRules(); + final IPredicateRule[] result = new IPredicateRule[rules.size()]; + rules.toArray(result); + setPredicateRules(result); + } + + private List<IPredicateRule> getRules() { + final List<IPredicateRule> rules = new ArrayList<IPredicateRule>(); + // comment rules + rules.add(new EndOfLineRule("//", TOKEN_COMMENT)); + rules.add(new MultiLineRule("/*", "*/", TOKEN_COMMENT, (char) 0, true)); + + // label rule + rules.add(new WordPatternRule(new LabelDetector(), "@", null, + PartitionScanner.TOKEN_LABEL)); + + return rules; + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/Partitioner.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/Partitioner.java new file mode 100644 index 0000000000000000000000000000000000000000..020e1d819d438bcd4a4086e27f014e65bf2db14a --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/Partitioner.java @@ -0,0 +1,21 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler.partitioning; + +import org.eclipse.jface.text.rules.FastPartitioner; + +public class Partitioner extends FastPartitioner { + + public Partitioner(final PartitionScanner partitionScanner, + final String[] contentTypes) { + super(partitionScanner, contentTypes); + } + + public void clearCache() { + clearPositionCache(); + } +} diff --git a/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/WordPredicateRule.java b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/WordPredicateRule.java new file mode 100644 index 0000000000000000000000000000000000000000..296b1ee07aa16f54f7f5edb93b10c2691b2f8e70 --- /dev/null +++ b/org.eventb.texteditor.ui/src/org/eventb/texteditor/ui/reconciler/partitioning/WordPredicateRule.java @@ -0,0 +1,47 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texteditor.ui.reconciler.partitioning; + +import org.eclipse.jface.text.rules.ICharacterScanner; +import org.eclipse.jface.text.rules.IPredicateRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.IWordDetector; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.rules.WordRule; + +public class WordPredicateRule extends WordRule implements IPredicateRule { + + public WordPredicateRule(final IWordDetector _detector) { + super(_detector); + } + + public IToken getSuccessToken() { + return fDefaultToken; + } + + @Override + public IToken evaluate(final ICharacterScanner scanner) { + // read char before offset + scanner.unread(); + final int c = scanner.read(); + + if (fDetector.isWordStart((char) c) || fDetector.isWordPart((char) c)) { + return Token.UNDEFINED; + } + + return super.evaluate(scanner); + } + + public IToken evaluate(final ICharacterScanner _scanner, + final boolean _resume) { + return evaluate(_scanner); + } + + public void clearWords() { + fWords.clear(); + } +} diff --git a/org.eventb.texteditor.ui/templates.xml b/org.eventb.texteditor.ui/templates.xml new file mode 100644 index 0000000000000000000000000000000000000000..6e175646ffd8101fb3aba42c30cb9a93bb155270 --- /dev/null +++ b/org.eventb.texteditor.ui/templates.xml @@ -0,0 +1,424 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<templates> + +<!-- +<template + id="" + name="" + description="" + context="org.eventb.texteditor." + enabled="" + ></template> + +contexts: +org.eventb.texteditor.anywhere +org.eventb.texteditor.machine +org.eventb.texteditor.events +org.eventb.texteditor.context +--> + +<!-- + ######## MACHINE context ######## +--> +<template + id="machine" + name="machine" + description="Basic machine structure" + context="org.eventb.texteditor.machine" + enabled="true" + >machine ${Machine_Name}
variables ${var}
invariants @${inv1} ${var} : ${type}
events
	event INITIALISATION
	then
		@${act1} ${var} := ${value}
	end

	event ${Eventname}
	end

end</template> + +<!-- + ######## CONTEXT context ######## +--> +<template + id="context" + name="context" + description="Basic context structure" + context="org.eventb.texteditor.context" + enabled="true" + >context ${Context_Name}

end</template> + +<!-- + ######## EVENT context ######## +--> + +<template + id="event_minimal" + name="eventstructure (minimal)" + description="Basic event structure" + context="org.eventb.texteditor.events" + enabled="true" + >event ${Eventname}
		then @${act1} ${var}:=${param}
	end</template> + +<template + id="event_parameter" + name="eventstructure (with parameter)" + description="Event (with parameter) structure" + context="org.eventb.texteditor.events" + enabled="true" + >event ${Eventname}
		any ${param}
		where @${guard} ${param} : ${type}
		then @${act1} ${var}:=${param}
	end
</template> + +<template + id="event_full" + name="eventstructure (complete)" + description="Complete event structure" + context="org.eventb.texteditor.events" + enabled="true" + >event ${Eventname}
		refines ${RefinedEvent}
		any ${param}
		where @${guard} ${param} : ${type}
		with @${y} ${param} = ${y}
		then @${act1} ${var}:=${param}
	end</template> + +<template + id="where" + name="where" + description="Create a guards block" + context="org.eventb.texteditor.events" + enabled="true" + >where	
@${grd} ${predicate}	</template> + +<template + id="with" + name="with" + description="Create a witnesses block" + context="org.eventb.texteditor.events" + enabled="true" + >with	
@${variable} ${predicate}	</template> + +<template + id="then" + name="then" + description="Create a action block" + context="org.eventb.texteditor.events" + enabled="true" + >then	
@${act} ${assignment}	</template> + +<template + id="guard" + name="guard" + description="Create a new guard" + context="org.eventb.texteditor.events" + enabled="true" + >@${grd} ${predicate}	</template> + +<template + id="assignment_becomes_equal" + name="assignment_equal" + description="Create a new 'becomes equal' assignment" + context="org.eventb.texteditor.events" + enabled="true" + >@${act} ${var} := ${value}	</template> + +<template + id="assignment_becomes_such" + name="assignment_such" + description="Create a new 'becomes such that' assignment" + context="org.eventb.texteditor.events" + enabled="true" + >@${act} ${var} :| ${value}	</template> + +<template + id="assignment_becomes_member" + name="assignment_member" + description="Create a new 'becomes member of' assignment" + context="org.eventb.texteditor.events" + enabled="true" + >@${act} ${var} :: ${value}	</template> + + +<!-- + ######## ANYWHERE context ######## +--> + +<template + id="equivalence1" + name="equivalence" + description="Equivalence: ⇔ ${P}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⇔ ${P}</template> +<template + id="equivalence2" + name="equivalence" + description="Equivalence: ${P1} ⇔ ${P2}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${P1} ⇔ ${P2}</template> + +<template + id="implication1" + name="implication" + description="Implication: ⇒ ${P}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⇒ ${P}</template> +<template + id="implication2" + name="implication" + description="Implication: ${P1} ⇒ ${P2}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${P1} ⇒ ${P2}</template> + +<template + id="and1" + name="and_predicate" + description="Logical and: ∧ ${P}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∧ ${P}</template> +<template + id="and2" + name="and_predicate" + description="Logical and: ${P1} ∧ ${P2}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${P1} ∧ ${P2}</template> + +<template + id="or1" + name="or_predicate" + description="Logical or: ${P1} ∨ ${P2}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${P1} ∨ ${P2}</template> +<template + id="or2" + name="or_predicate" + description="Logical or: ∨ ${P}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∨ ${P}</template> + +<template + id="not" + name="not" + description="Not: ¬ ${P1}" + context="org.eventb.texteditor.anywhere" + enabled="true" + >¬ ${P1}</template> + +<template + id="true_predicate" + name="true_predicate" + description="Predicate true: ⊤" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⊤</template> + +<template + id="false_predicate" + name="false_predicate" + description="Predicate false: ⊥" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⊥</template> + +<template + id="forall1" + name="forall" + description="For all predicate" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∀ ${var} · ${var}</template> +<template + id="forall2" + name="forall" + description="For all predicate (with type)" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∀ ${var} · ${var} ∈ ${set}</template> +<template + id="forall3" + name="forall" + description="For all predicate (with type and implication)" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∀ ${var} · ${var} ∈ ${set} ⇒ ${P}</template> + +<template + id="exists1" + name="exists" + description="Exists predicate" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∃ ${var} · ${var}</template> +<template + id="exists2" + name="exists" + description="Exists predicate (with type)" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∃ ${var} · ${var} ∈ ${set}</template> +<template + id="exists3" + name="exists" + description="Exists predicate (with type and implication)" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∃ ${var} · ${var} ∈ ${set} ⇒ ${P}</template> + +<template + id="relation1" + name="relation" + description="Relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >∃ ${E}</template> +<template + id="relation2" + name="relation" + description="Relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ∃ ${E2}</template> + +<template + id="total_relation1" + name="totalRelation" + description="Total relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + > ${E}</template> +<template + id="total_relation2" + name="totalRelation" + description="Total relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1}  ${E2}</template> + +<template + id="surjective_relation1" + name="surjectiveRelation" + description="Total relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + > ${E}</template> +<template + id="surjective_relation2" + name="surjectiveRelation" + description="Surjective relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1}  ${E2}</template> + +<template + id="surjective_total_relation1" + name="surjectiveTotalRelation" + description="Surjective total relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + > ${E}</template> +<template + id="surjective_total_relation2" + name="surjectiveTotalRelation" + description="Surjective total relation expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1}  ${E2}</template> + +<template + id="partial_function1" + name="partialFunction" + description="Partial function expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⇸ ${E}</template> +<template + id="partial_function2" + name="partialFunction" + description="Partial function expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ⇸ ${E2}</template> + +<template + id="total_function1" + name="totalFunction" + description="Total function expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >→ ${E}</template> +<template + id="total_function2" + name="totalFunction" + description="Total function expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} → ${E2}</template> + +<template + id="partial_injection1" + name="partialInjection" + description="Partial injection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⤔ ${E}</template> +<template + id="partial_injection2" + name="partialInjection" + description="Partial injection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ⤔ ${E2}</template> + +<template + id="total_injection1" + name="totalInjection" + description="Total injection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >↣ ${E}</template> +<template + id="total_injection2" + name="totalInjection" + description="Total injection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ↣ ${E2}</template> + +<template + id="partial_surjection1" + name="partialSurjection" + description="Partial surjection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⤀ ${E}</template> +<template + id="partial_surjection2" + name="partialSurjection" + description="Partial surjection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ⤀ ${E2}</template> + +<template + id="total_surjection1" + name="totalSurjection" + description="Total surjection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >↠ ${E}</template> +<template + id="total_surjection2" + name="totalSurjection" + description="Total surjection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ↠ ${E2}</template> + +<template + id="bijection1" + name="bijection" + description="Bijection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >⤖ ${E}</template> +<template + id="bijection2" + name="bijection" + description="Bijection expression" + context="org.eventb.texteditor.anywhere" + enabled="true" + >${E1} ⤖ ${E2}</template> + +</templates> \ No newline at end of file diff --git a/org.eventb.texttools/.classpath b/org.eventb.texttools/.classpath new file mode 100644 index 0000000000000000000000000000000000000000..e89ef6cd4e764ef85f4209624426134214bd8cb0 --- /dev/null +++ b/org.eventb.texttools/.classpath @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <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.pde.core.requiredPlugins"/> + <classpathentry kind="src" path="src_generated"/> + <classpathentry exported="true" kind="lib" path="lib/aspectjrt.jar"/> + <classpathentry kind="lib" path="lib/EventBParser.jar" sourcepath="/de.be4.eventb.structparser"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eventb.texttools/.project b/org.eventb.texttools/.project new file mode 100644 index 0000000000000000000000000000000000000000..936ba24962a14bd8ff48500da35b74f50b4e1d99 --- /dev/null +++ b/org.eventb.texttools/.project @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eventb.texttools</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature> + </natures> +</projectDescription> diff --git a/org.eventb.texttools/.settings/org.eclipse.jdt.core.prefs b/org.eventb.texttools/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000000000000000000000000000000000..0178f55888c2ce9819b5d9864de0938aa301633d --- /dev/null +++ b/org.eventb.texttools/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Sun May 10 11:23:16 CEST 2009 +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.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/org.eventb.texttools/META-INF/MANIFEST.MF b/org.eventb.texttools/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..16453fd500efce05f63152cc768c25e26d70b854 --- /dev/null +++ b/org.eventb.texttools/META-INF/MANIFEST.MF @@ -0,0 +1,26 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Event-B EMF Texttools +Bundle-SymbolicName: org.eventb.texttools;singleton:=true +Bundle-Version: 1.1.2 +Bundle-Activator: org.eventb.texttools.TextToolsPlugin +Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,3.6.0)", + org.eclipse.jface.text;bundle-version="[3.5.0,3.6.0)", + org.eventb.emf.formulas;bundle-version="[1.0.3,2.0.0)", + org.eventb.emf.persistence;bundle-version="[1.1.2,2.0.0)", + org.eventb.core.ast;bundle-version="[1.2.0,1.3.0)", + org.rodinp.core;bundle-version="[1.2.0,1.3.0)", + org.eclipse.emf.compare;bundle-version="[1.0.0,2.0.0)", + org.eclipse.emf.compare.diff;bundle-version="[1.0.0,2.0.0)", + org.eclipse.emf.compare.match;bundle-version="[1.0.0,2.0.0)" +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Export-Package: org.eventb.texttools, + org.eventb.texttools.formulas, + org.eventb.texttools.merge, + org.eventb.texttools.model.texttools, + org.eventb.texttools.prettyprint +Bundle-ClassPath: lib/aspectjrt.jar, + lib/EventBParser.jar, + . +Bundle-Vendor: Heinrich-Heine University Dusseldorf diff --git a/org.eventb.texttools/build.properties b/org.eventb.texttools/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..2edb91ddd1a5e4288c5e0e3b412b6bfe9d3f7780 --- /dev/null +++ b/org.eventb.texttools/build.properties @@ -0,0 +1,10 @@ +source.. = src/,\ + src_generated/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.xml,\ + lib/EventBParser.jar,\ + lib/aspectjrt.jar +src.includes = lib/EventBParser.jar,\ + lib/aspectjrt.jar diff --git a/org.eventb.texttools/model/texttools.ecore b/org.eventb.texttools/model/texttools.ecore new file mode 100644 index 0000000000000000000000000000000000000000..4ab5f22fb76db89a06adb87d8a6aed061f5f91e6 --- /dev/null +++ b/org.eventb.texttools/model/texttools.ecore @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ecore:EPackage xmi:version="2.0" + xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="texttools" + nsURI="http://emf.eventb.org/models/core/texttools" nsPrefix="texttools"> + <eClassifiers xsi:type="ecore:EClass" name="TextRange"> + <eStructuralFeatures xsi:type="ecore:EAttribute" name="offset" lowerBound="1" + eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" transient="true" + defaultValueLiteral="0"/> + <eStructuralFeatures xsi:type="ecore:EAttribute" name="length" lowerBound="1" + eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt" transient="true" + defaultValueLiteral="0"/> + <eStructuralFeatures xsi:type="ecore:EAttribute" name="subTextRanges" lowerBound="1" + transient="true"> + <eGenericType eClassifier="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EMap"> + <eTypeArguments eClassifier="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/> + <eTypeArguments eClassifier="#//TextRange"/> + </eGenericType> + </eStructuralFeatures> + </eClassifiers> +</ecore:EPackage> diff --git a/org.eventb.texttools/model/texttools.genmodel b/org.eventb.texttools/model/texttools.genmodel new file mode 100644 index 0000000000000000000000000000000000000000..f050e8f674bed78bf1c7de8ab1ead8ccf56e4a63 --- /dev/null +++ b/org.eventb.texttools/model/texttools.genmodel @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<genmodel:GenModel xmi:version="2.0" + xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" + xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" modelDirectory="/org.eventb.texttools/src" + modelPluginID="org.eventb.texttools" modelName="Texttools" importerID="org.eclipse.emf.importer.ecore" + complianceLevel="5.0" copyrightFields="false"> + <foreignModel>texttools.ecore</foreignModel> + <genPackages prefix="Texttools" basePackage="org.eventb.texttools.model" disposableProviderFactory="true" + ecorePackage="texttools.ecore#/"> + <genClasses ecoreClass="texttools.ecore#//TextRange"> + <genFeatures createChild="false" ecoreFeature="ecore:EAttribute texttools.ecore#//TextRange/offset"/> + <genFeatures createChild="false" ecoreFeature="ecore:EAttribute texttools.ecore#//TextRange/length"/> + </genClasses> + </genPackages> +</genmodel:GenModel> diff --git a/org.eventb.texttools/plugin.properties b/org.eventb.texttools/plugin.properties new file mode 100644 index 0000000000000000000000000000000000000000..fafcf04c1cb1f28d64ab65d0225494e6e340ac6f --- /dev/null +++ b/org.eventb.texttools/plugin.properties @@ -0,0 +1,8 @@ + +# <copyright> +# </copyright> +# +# $Id$ + +pluginName = Event-B EMF Texttools +providerName = Heinrich-Heine University Dusseldorf diff --git a/org.eventb.texttools/plugin.xml b/org.eventb.texttools/plugin.xml new file mode 100644 index 0000000000000000000000000000000000000000..13948a526b0994f470a45b011f783b32239bbcf1 --- /dev/null +++ b/org.eventb.texttools/plugin.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.2"?> +<plugin> + <extension + point="org.rodinp.core.attributeTypes"> + <attributeType + id="text_representation" + kind="string" + name="Text Representation"> + </attributeType> + <attributeType + id="text_lastmodified" + kind="long" + name="Text LastModified"> + </attributeType> + </extension> + <extension + point="org.eclipse.emf.compare.match.engine"> + <matchengine + label="EventB Match Engine" + engineClass="org.eventb.texttools.merge.EventBMatchEngine" + fileExtension="bum,buc"> + </matchengine> + </extension> + <extension + point="org.eclipse.emf.compare.diff.mergerprovider"> + <mergerprovider + fileExtension="bum,buc" + mergerProviderClass="org.eventb.texttools.merge.MergerProvider" + priority="high"> + </mergerprovider> + </extension> +</plugin> diff --git a/org.eventb.texttools/src/org/eventb/texttools/Constants.java b/org.eventb.texttools/src/org/eventb/texttools/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..7b483b7e4a3d33c578dd4a5b2c802e96b1cd09e1 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/Constants.java @@ -0,0 +1,54 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +public interface Constants { + public static final String MACHINE = "machine"; + public static final String CONTEXT = "context"; + public static final String REFINES = "refines"; + public static final String SEES = "sees"; + public static final String EXTENDS = "extends"; + public static final String VARIABLES = "variables"; + public static final String INVARIANTS = "invariants"; + public static final String THEOREM = "theorem"; + public static final String VARIANT = "variant"; + public static final String EVENTS = "events"; + public static final String EVENT = "event"; + public static final String ORDINARY = "ordinary"; + public static final String ANTICIPATED = "anticipated"; + public static final String CONVERGENT = "convergent"; + public static final String ANY = "any"; + public static final String WHERE = "where"; + public static final String WHEN = "when"; + public static final String WITH = "with"; + public static final String THEN = "then"; + public static final String BEGIN = "begin"; + public static final String AXIOMS = "axioms"; + public static final String CONSTANTS = "constants"; + public static final String SETS = "sets"; + public static final String END = "end"; + + public static final String[] structural_keywords = { MACHINE, CONTEXT, + REFINES, SEES, EXTENDS, VARIABLES, INVARIANTS, THEOREM, VARIANT, + EVENTS, EVENT, ORDINARY, ANTICIPATED, CONVERGENT, ANY, WHERE, + WHEN, WITH, THEN, BEGIN, AXIOMS, CONSTANTS, SETS, END }; + + public static final String[] machine_keywords = { MACHINE, REFINES, SEES, + EXTENDS, VARIABLES, INVARIANTS, THEOREM, VARIANT, EVENTS, END }; + + public static final String[] event_keywords = { REFINES, EXTENDS, EVENT, + ORDINARY, ANTICIPATED, CONVERGENT, ANY, WHERE, WHEN, WITH, THEN, + BEGIN, END }; + + public static final String[] context_keywords = { CONTEXT, EXTENDS, AXIOMS, + CONSTANTS, SETS, END }; + + public static final String[] formula_keywords = { "BOOL", "FALSE", "TRUE", + "bool", "card", "dom", "finite", "id", "inter", "max", "min", + "mod", "pred", "prj1", "prj2", "ran", "succ", "union", "\u2115", + "\u2115\u0031", "\u2119", "\u2119\u0031", "\u2124" }; +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/ParseException.java b/org.eventb.texttools/src/org/eventb/texttools/ParseException.java new file mode 100644 index 0000000000000000000000000000000000000000..cd7973a63c12fd94114b454ea3d426965a3ed09f --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/ParseException.java @@ -0,0 +1,35 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +@SuppressWarnings("serial") +public class ParseException extends Exception { + + private final int line; + private final int position; + private final int tokenLength; + + public ParseException(final String message, final int line, + final int position, final int tokenLength) { + super(message); + this.line = line; + this.position = position; + this.tokenLength = tokenLength; + } + + public int getLine() { + return line; + } + + public int getPosition() { + return position; + } + + public int getTokenLength() { + return tokenLength; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/Parser.java b/org.eventb.texttools/src/org/eventb/texttools/Parser.java new file mode 100644 index 0000000000000000000000000000000000000000..0513888e2a0688d09cf5a2ee839d751522c9c5ca --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/Parser.java @@ -0,0 +1,111 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +import org.eclipse.jface.text.IDocument; +import org.eventb.emf.core.EventBObject; +import org.eventb.texttools.internal.parsing.TransformationVisitor; + +import de.be4.eventb.core.parser.BException; +import de.be4.eventb.core.parser.EventBLexerException; +import de.be4.eventb.core.parser.EventBParseException; +import de.be4.eventb.core.parser.EventBParser; +import de.be4.eventb.core.parser.node.Start; +import de.be4.eventb.core.parser.node.Token; +import de.be4.eventb.core.parser.parser.ParserException; +import de.hhu.stups.sablecc.patch.SourcePositions; +import de.hhu.stups.sablecc.patch.SourcecodeRange; + +public class Parser { + private final EventBParser parser = new EventBParser(); + private final TransformationVisitor transformer = new TransformationVisitor(); + + /** + * Parses the content of the given {@link IDocument}. + * + * @param <T> + * @param document + * @return + * @throws ParseException + * If the contents cannot be parsed + * @throws IllegalArgumentException + * If called with <code>null</code> as document parameter + */ + public <T extends EventBObject> T parse(final IDocument document) + throws ParseException { + if (document == null) { + throw new IllegalArgumentException( + "Parser may not be called without input document"); + } + + final String input = document.get(); + + try { + final Start rootNode = parser.parse(input, false); + return transformer.<T>transform(rootNode, document); + } catch (final BException e) { + final Exception cause = e.getCause(); + + if (cause instanceof ParserException) { + final ParserException ex = (ParserException) cause; + final Token token = ex.getToken(); + + throw new ParseException( + adjustMessage(ex.getLocalizedMessage()), token + .getLine() - 1, token.getPos() - 1, token + .getText().length()); + } + if (cause instanceof EventBLexerException) { + final EventBLexerException ex = (EventBLexerException) cause; + final String lastText = ex.getLastText(); + + throw new ParseException( + adjustMessage(ex.getLocalizedMessage()), ex + .getLastLine() - 1, ex.getLastPos() - 1, + lastText.length()); + } + + if (cause instanceof EventBParseException) { + final EventBParseException ex = (EventBParseException) cause; + final SourcecodeRange range = ex.getRange(); + final SourcePositions positions = parser.getSourcePositions(); + + if (range != null && positions != null) { + throw new ParseException(adjustMessage(ex + .getLocalizedMessage()), positions + .getBeginLine(range) - 1, positions + .getBeginColumn(range) - 1, positions + .getRangeString(range).length()); + } else { + final Token token = ex.getToken(); + if (token != null) { + throw new ParseException(adjustMessage(ex + .getLocalizedMessage()), token.getLine() - 1, + token.getPos() - 1, token.getText().length()); + } + } + } + + throw new ParseException(e.getLocalizedMessage(), 0, 0, 1); + } + } + + private String adjustMessage(final String localizedMessage) { + final StringBuilder result = new StringBuilder(localizedMessage); + + // remove position information if found + final int posEnd = result.indexOf("] "); + if (result.charAt(0) == '[' && posEnd > 0) { + result.delete(0, posEnd + 2); + } + + // make sure first character is upercase + result.setCharAt(0, Character.toUpperCase(result.charAt(0))); + + return result.toString().trim(); + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/PersistenceHelper.java b/org.eventb.texttools/src/org/eventb/texttools/PersistenceHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e1a16f87ee1196038dd4825cd3b111d19ae4b1b8 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/PersistenceHelper.java @@ -0,0 +1,270 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +import java.io.IOException; +import java.util.Collections; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +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.emf.common.util.EList; +import org.eclipse.emf.common.util.EMap; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eventb.emf.core.Attribute; +import org.eventb.emf.core.AttributeType; +import org.eventb.emf.core.CoreFactory; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.texttools.merge.ModelMerge; +import org.eventb.texttools.prettyprint.PrettyPrinter; + +public class PersistenceHelper { + + public static IResource getIResource(final Resource resource) { + final URI uri = resource.getURI(); + if (uri.isPlatformResource()) { + return ResourcesPlugin.getWorkspace().getRoot().findMember( + uri.toPlatformString(true)); + } + + return null; + } + + public static void saveText(final Resource resource, + final boolean overwrite, final IProgressMonitor monitor) + throws CoreException { + try { + resource.save(Collections.EMPTY_MAP); + + /* + * Try to set timestamp to the same as in the annotation. Setting on + * both Resource and IResource to be save. + */ + final long textTimestamp = getTextTimestamp(resource); + resource.setTimeStamp(textTimestamp); + getIResource(resource).setLocalTimeStamp(textTimestamp); + } catch (final IOException e) { + throw new CoreException(new Status(IStatus.ERROR, + TextToolsPlugin.PLUGIN_ID, "Saving to RodinDB failed", e)); + } + } + + public static void addTextAnnotation(final Resource resource, + final String textRepresentation, final long timeStamp) + throws CoreException { + final EventBNamedCommentedComponentElement component = getComponent(resource); + if (component != null) { + addTextAnnotation(component, textRepresentation, timeStamp); + } else { + throw new CoreException(new Status(IStatus.ERROR, + TextToolsPlugin.PLUGIN_ID, + "Resource has no EventBComponent")); + } + } + + public static void addTextAnnotation(final EventBElement element, + final String textRepresentation, final long timeStamp) { + final EMap<String, Attribute> attributes = element.getAttributes(); + + // update text representation + Attribute textAttribute = attributes + .get(TextToolsPlugin.TYPE_TEXTREPRESENTATION.getId()); + if (textAttribute == null) { + textAttribute = CoreFactory.eINSTANCE.createAttribute(); + textAttribute.setType(AttributeType.STRING); + attributes.put(TextToolsPlugin.TYPE_TEXTREPRESENTATION.getId(), + textAttribute); + } + textAttribute.setValue(textRepresentation); + + // update timestamp + Attribute timeAttribute = attributes + .get(TextToolsPlugin.TYPE_LASTMODIFIED.getId()); + if (timeAttribute == null) { + timeAttribute = CoreFactory.eINSTANCE.createAttribute(); + timeAttribute.setType(AttributeType.LONG); + attributes.put(TextToolsPlugin.TYPE_LASTMODIFIED.getId(), + timeAttribute); + } + timeAttribute.setValue(timeStamp); + } + + private static void mergeComponents(final EventBNamedCommentedComponentElement oldVersion, + final EventBNamedCommentedComponentElement newVersion, final IProgressMonitor monitor) { + try { + final ModelMerge merge = new ModelMerge(oldVersion, newVersion); + merge.applyChanges(monitor); + } catch (final InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public static void mergeRootElement(final Resource resource, + final EventBNamedCommentedComponentElement newVersion, final IProgressMonitor monitor) { + final EventBNamedCommentedComponentElement component = getComponent(resource); + if (component != null) { + mergeComponents(component, newVersion, monitor); + } else { + resource.getContents().add(newVersion); + } + } + + public static String loadText(final Resource resource, + final String linebreak) throws IOException { + // make sure resource is loaded + if (!resource.isLoaded()) { + resource.load(Collections.EMPTY_MAP); + } + + // check if we have the most recent text representation + if (isTextUptodate(resource)) { + // we should find a text representation in the EMF + final String text = getTextAnnotation(resource); + + if (text != null) { + return text; + } + } + + /* + * Reload resource to get latest changes. This is necessary if the model + * was already loaded and external changes have occured meanwhile. Then + * unloading+loading makes sure the EMF persistence loads those changes. + */ + resource.unload(); + resource.load(Collections.EMPTY_MAP); + + // pretty-print the machine/context + final EventBNamedCommentedComponentElement rootElement = getComponent(resource); + if (rootElement != null) { + return getPrettyPrint(rootElement, linebreak); + } else { + throw new IOException( + "Cannot find load Event-B component: No machine/context found"); + } + } + + public static String getTextAnnotation(final Resource resource) { + final EMap<String, Attribute> attributes = getAttributesMap(resource); + if (attributes != null) { + final Attribute attr = attributes + .get(TextToolsPlugin.TYPE_TEXTREPRESENTATION.getId()); + + if (attr != null) { + return (String) attr.getValue(); + } + } + + return null; + } + + public static EClass getComponentType(final Resource resource) { + final EventBNamedCommentedComponentElement component = getComponent(resource); + + if (component != null) { + return component.eClass(); + } + + return null; + } + + private static EventBNamedCommentedComponentElement getComponent(final Resource resource) { + final EList<EObject> contents = resource.getContents(); + if (contents.size() > 0 && contents.get(0) instanceof EventBNamedCommentedComponentElement) { + return (EventBNamedCommentedComponentElement) contents.get(0); + } + + return null; + } + + /** + * Extracts the timestamp of the latest saved text representation from the + * EMF and returns it. + * + * @param resource + * @return timestamp or <code>-1</code> if none is found + */ + private static long getTextTimestamp(final Resource resource) { + final EMap<String, Attribute> attributes = getAttributesMap(resource); + if (attributes != null) { + final Attribute attr = attributes + .get(TextToolsPlugin.TYPE_LASTMODIFIED.getId()); + return attr != null ? (Long) attr.getValue() : -1; + } + + return -1; + } + + private static EMap<String, Attribute> getAttributesMap( + final Resource resource) { + final EList<EObject> contents = resource.getContents(); + if (contents.size() > 0) { + final EObject object = contents.get(0); + if (object instanceof EventBNamedCommentedComponentElement) { + final EventBNamedCommentedComponentElement component = (EventBNamedCommentedComponentElement) object; + return component.getAttributes(); + } + } + + return null; + } + + /** + * Checks if the text representation saved in the EMF is up-to-date. The + * timestamps in the EMF and of the underlying file are compared for this + * decision. + * + * @param resource + * @return <code>true</code> if there was no external change and the text + * representation is still up-to-date + */ + private static boolean isTextUptodate(final Resource resource) { + final long textTimestamp = getTextTimestamp(resource); + + try { + final IResource file = getIResource(resource); + // refresh to get latest timestamp + file.refreshLocal(IResource.DEPTH_ONE, null); + final long resourceTimestamp = file.getLocalTimeStamp(); + + // easy case: text is newer than resource + if (textTimestamp >= resourceTimestamp) { + return true; + } + + final long diff = resourceTimestamp - textTimestamp; + + // tolerate 50ms offset (time to save file) + // TODO this is ugly!!! + if (diff < 50) { + return true; + } + } catch (final CoreException e) { + TextToolsPlugin.getDefault().getLog().log( + new Status(IStatus.ERROR, TextToolsPlugin.PLUGIN_ID, + "Error checking file timestamps", e)); + } + + return false; + } + + private static String getPrettyPrint(final EventBElement rootElement, + final String linebreak) { + final StringBuilder buffer = new StringBuilder(); + new PrettyPrinter(buffer, linebreak, null).prettyPrint(rootElement); + + return buffer.toString(); + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/ResourceManager.java b/org.eventb.texttools/src/org/eventb/texttools/ResourceManager.java new file mode 100644 index 0000000000000000000000000000000000000000..4c3736bfc990737f3e64d2b51bc5ce201bb92a4a --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/ResourceManager.java @@ -0,0 +1,55 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IProject; +import org.eclipse.emf.common.command.BasicCommandStack; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; +import org.eclipse.emf.edit.provider.ComposedAdapterFactory; +import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory; +import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory; +import org.eventb.emf.core.context.provider.ContextItemProviderAdapterFactory; +import org.eventb.emf.core.machine.provider.MachineItemProviderAdapterFactory; +import org.eventb.emf.core.provider.CoreItemProviderAdapterFactory; + +public class ResourceManager { + private final Map<IProject, AdapterFactoryEditingDomain> projectEditingDomains = new HashMap<IProject, AdapterFactoryEditingDomain>(); + + public AdapterFactoryEditingDomain getEditingDomain(final IProject project) { + if (!projectEditingDomains.containsKey(project)) { + final AdapterFactoryEditingDomain editingDomain = initializeEditingDomain(); + projectEditingDomains.put(project, editingDomain); + } + + return projectEditingDomains.get(project); + } + + private AdapterFactoryEditingDomain initializeEditingDomain() { + // Create an adapter factory that yields item providers. + final ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory( + ComposedAdapterFactory.Descriptor.Registry.INSTANCE); + + adapterFactory + .addAdapterFactory(new ResourceItemProviderAdapterFactory()); + adapterFactory.addAdapterFactory(new CoreItemProviderAdapterFactory()); + adapterFactory + .addAdapterFactory(new MachineItemProviderAdapterFactory()); + adapterFactory + .addAdapterFactory(new ContextItemProviderAdapterFactory()); + adapterFactory + .addAdapterFactory(new ReflectiveItemProviderAdapterFactory()); + + final BasicCommandStack commandStack = new BasicCommandStack(); + + return new AdapterFactoryEditingDomain(adapterFactory, commandStack, + new HashMap<Resource, Boolean>()); + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/TextPositionUtil.java b/org.eventb.texttools/src/org/eventb/texttools/TextPositionUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..31849504a9bbba36ded1960f3b99b1887539006c --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/TextPositionUtil.java @@ -0,0 +1,235 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eventb.emf.core.EventBObject; +import org.eventb.texttools.model.texttools.TextRange; +import org.eventb.texttools.model.texttools.TexttoolsFactory; + +public class TextPositionUtil { + /** + * Source key for the {@link EAnnotation} containing {@link TextRange}. + */ + public static final String ANNOTATION_TEXTRANGE = "http://emf.eventb.org/models/core/texttools/TextRange"; + + /** + * <p> + * Extracts a {@link TextRange} object from the annotations of the given + * {@link EModelElement}. The <code>contents</code> attribute of the + * annotation is search for an object of type {@link TextRange} and the + * first one found is returned. + * </p> + * <p> + * If no matching object can be found <code>null</code> is returned. + * </p> + * + * @param annotation + * @return + */ + public static TextRange getTextRange(final EModelElement element) { + if (element != null) { + final EAnnotation annotation = element + .getEAnnotation(ANNOTATION_TEXTRANGE); + + if (annotation != null) { + final EList<EObject> contents = annotation.getContents(); + + for (final EObject object : contents) { + if (object instanceof TextRange) { + return (TextRange) object; + } + } + } + } + + return null; + } + + public static void annotatePosition(final EModelElement element, + final TextRange range) { + if (range != null) { + final EAnnotation annotation = EcoreFactory.eINSTANCE + .createEAnnotation(); + annotation.setEModelElement(element); + annotation.setSource(TextPositionUtil.ANNOTATION_TEXTRANGE); + annotation.getContents().add(range); + + element.getEAnnotations().add(annotation); + } + } + + public static void annotatePosition(final EModelElement element, + final int startPos, final int length) { + final TextRange range = TexttoolsFactory.eINSTANCE.createTextRange(); + range.setOffset(startPos); + range.setLength(length); + + annotatePosition(element, range); + } + + /** + * <p> + * Gets the position annotation of the given {@link EModelElement} and if + * it's not <code>null</code> the given {@link TextRange} is attached to it + * under the given key. + * </p> + * <p> + * This can be used to store position information about subelements, i.e., + * strings. First annotate the EventB element normally. Then create a + * {@link TextRange} object for each string for which you want to store the + * positions. Then pass the parent EventB element, the new {@link TextRange} + * to this method and an appropriate {@link String} as key to this method. + * </p> + * + * @see + * @param emfElement + * Parent element which already has a position annotation to + * which the given annotation is to be attached. + * @param range + * The annotation which will be attached to the parent's position + * annotation. + * @param key + * A key to store the position under. It will be needed to + * retrieve the position later. So it's helpful to use the string + * itself. + */ + public static void addInternalPosition(final EModelElement emfElement, + final String key, final TextRange range) { + Assert.isNotNull(emfElement); + Assert.isNotNull(range); + Assert.isNotNull(key); + + final TextRange parentRange = getTextRange(emfElement); + + if (parentRange != null) { + getSubRangeMap(parentRange).put(key, range); + } + } + + /** + * Replace the {@link TextRange} that is associated with the given + * <code>oldKey</code>. Several cases are possible: + * <ul> + * <li><code>newKey == null</code>: The old {@link TextRange} is just + * removed.</li> + * <li><code>newKey == oldKey</code>: The {@link TextRange} is simply + * replaced.</li> + * <li><code>newKey != null && newKey != oldKey</code>: The + * {@link TextRange} under key <code>oldKey</code> is removed and the + * <code>newRange</code> is added under <code>newKey</code>.</li> + * </ul> + * + * @param emfElement + * @param oldKey + * @param newKey + * @param newRange + */ + public static void replaceInternalPosition(final EModelElement emfElement, + final String oldKey, final String newKey, final TextRange newRange) { + Assert.isNotNull(emfElement); + Assert.isNotNull(oldKey); + + final TextRange parentRange = getTextRange(emfElement); + + if (parentRange != null) { + final Map<String, TextRange> subRangeMap = getSubRangeMap(parentRange); + subRangeMap.remove(oldKey); + + if (newKey != null && newRange != null) { + subRangeMap.put(newKey, newRange); + } + } + } + + /** + * Extracts a {@link TextRange} for a substring from the given + * {@link EModelElement}'s annotations. The given key is used to find the + * correct {@link TextRange} for the substring. + * + * @see #addInternalPosition(EModelElement, String, TextRange) + * @param emfElement + * Parent element with a position annotation. + * @param key + * Key which has been used to store the position information. + * @return The relevant {@link TextRange} if available. Returns + * <code>null</code> if the {@link EModelElement} has no position + * annotation or if this annotation didn't contain matching key. + */ + public static TextRange getInternalPosition(final EModelElement emfElement, + final String key) { + Assert.isNotNull(emfElement); + Assert.isNotNull(key); + + final TextRange parentRange = getTextRange(emfElement); + + if (parentRange != null) { + return getSubRangeMap(parentRange).get(key); + } + + return null; + } + + private static Map<String, TextRange> getSubRangeMap( + final TextRange parentRange) { + Map<String, TextRange> result = parentRange.getSubTextRanges(); + + if (result == null) { + result = new HashMap<String, TextRange>(); + parentRange.setSubTextRanges(result); + } + + return result; + } + + /** + * Creates a new {@link TextRange} instance initialised with the value of + * the given {@link EventBObject}. So the returned object may be changed + * without changing the the position of the given emf object. + * + * @param element + * {@link EventBObject} with original position + * @return New {@link TextRange} instance or <code>null</code> if no + * original text position could be found. + */ + public static TextRange createTextRange(final EventBObject element) { + final TextRange origRange = getTextRange(element); + + if (origRange != null) { + final TextRange result = TexttoolsFactory.eINSTANCE + .createTextRange(); + result.setLength(origRange.getLength()); + result.setOffset(origRange.getOffset()); + return result; + } else { + return null; + } + } + + /** + * Pushes the offset back by the given length and corrects the total length + * of this {@link TextRange}. + * + * @param range + * @param length + */ + public static void correctStartOffset(final TextRange range, + final int length) { + if (range != null) { + range.setOffset(range.getOffset() + length + 1); + range.setLength(range.getLength() - length - 1); + } + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/TextToolsPlugin.java b/org.eventb.texttools/src/org/eventb/texttools/TextToolsPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..93e59682ad8d5882f2e8639c61dfcb4f28f1f018 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/TextToolsPlugin.java @@ -0,0 +1,75 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools; + +import org.eclipse.core.runtime.Plugin; +import org.osgi.framework.BundleContext; +import org.rodinp.core.IAttributeType; +import org.rodinp.core.RodinCore; + +/** + * The activator class controls the plug-in life cycle + */ +public class TextToolsPlugin extends Plugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.eventb.texttools"; + + public static final IAttributeType.String TYPE_TEXTREPRESENTATION = RodinCore + .getStringAttrType(PLUGIN_ID + ".text_representation"); + public static final IAttributeType.Long TYPE_LASTMODIFIED = RodinCore + .getLongAttrType(PLUGIN_ID + ".text_lastmodified"); + + // The shared instance + private static TextToolsPlugin plugin; + + private ResourceManager resourceManager; + + /** + * The constructor + */ + public TextToolsPlugin() { + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext) + */ + @Override + public void start(final BundleContext context) throws Exception { + super.start(context); + plugin = this; + resourceManager = new ResourceManager(); + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext) + */ + @Override + public void stop(final BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static TextToolsPlugin getDefault() { + return plugin; + } + + public ResourceManager getResourceManager() { + return resourceManager; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/ContextResolveSwitch.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/ContextResolveSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..7756c80bdaf2fe365e28611d92edbac57748654d --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/ContextResolveSwitch.java @@ -0,0 +1,50 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.ecore.EModelElement; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.util.ContextSwitch; + +public class ContextResolveSwitch extends ContextSwitch<Boolean> { + + private final List<FormulaParseException> exceptions = new ArrayList<FormulaParseException>(); + + @Override + public Boolean caseEModelElement(final EModelElement object) { + return true; + } + + @Override + public Boolean caseEventBObject(final EventBObject object) { + return true; + } + + @Override + public Boolean caseEventBNamedCommentedPredicateElement(final EventBNamedCommentedPredicateElement object) { + try { + FormulaResolver.resolve(object); + } catch (final FormulaParseException e) { + handleError(e); + } + + // no need to traverse any children + return false; + } + + private void handleError(final FormulaParseException e) { + exceptions.add(e); + } + + public List<FormulaParseException> getExceptions() { + return exceptions; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/ExtensionHelper.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/ExtensionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..0b53b41c4c09c8123972e4e24f36ea0208fa52d3 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/ExtensionHelper.java @@ -0,0 +1,39 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import org.eclipse.emf.common.util.EList; +import org.eventb.emf.core.AbstractExtension; +import org.eventb.emf.formulas.BFormula; + +public class ExtensionHelper { + + /** + * Searches for a {@link BFormula} extension in the list and returns the + * first one found as the expected type <code>T</code>. Misuse will cause a + * {@link ClassCastException} to be thrown. If no matching extension element + * is found <code>null</code> is returned. + * + * @param <T> + * @param extensions + * @return Formula casted to type <code>T</code> or <code>null</code> if no + * matching one is found. + * @throws ClassCastException + */ + @SuppressWarnings("unchecked") + public static <T extends BFormula> T getFormula( + final EList<AbstractExtension> extensions) + throws ClassCastException { + for (final AbstractExtension extension : extensions) { + if (extension instanceof BFormula) { + return (T) extension; + } + } + + return null; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaParseException.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaParseException.java new file mode 100644 index 0000000000000000000000000000000000000000..73ed827c270ce61c4613ea4604a036918d53e47e --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaParseException.java @@ -0,0 +1,80 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import java.util.List; + +import org.eventb.core.ast.ASTProblem; +import org.eventb.core.ast.SourceLocation; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBExpression; + +@SuppressWarnings("serial") +public class FormulaParseException extends Exception { + + private final EventBElement emfExpression; + + private final List<ASTProblem> astProblems; + + private final String formula; + + public FormulaParseException(final EventBElement emfExpr, + final String formula, final List<ASTProblem> problems) { + emfExpression = emfExpr; + this.formula = formula; + astProblems = problems; + } + + /** + * The {@link EventBExpression} which caused the problem. The Rodin formula + * which was parsed can be found in {@link EventBExpression#getExpression()} + * . + * + * @return + */ + public EventBElement getEmfObject() { + return emfExpression; + } + + /** + * The {@link ASTProblem}s that occured during parsing the formula (see + * {@link #getEmfObject()} -> {@link EventBExpression#getExpression()}. + * + * @return + */ + public List<ASTProblem> getAstProblems() { + return astProblems; + } + + public String getFormula() { + return formula; + } + + @Override + public String getLocalizedMessage() { + final StringBuilder buffer = new StringBuilder(); + buffer.append("Parse problems in formula '"); + buffer.append(getFormula()); + buffer.append("': ["); + + List<ASTProblem> problems = getAstProblems(); + for (int i = 0; i < problems.size(); i++) { + ASTProblem problem = problems.get(i); + + buffer.append(" ("); + SourceLocation location = problem.getSourceLocation(); + buffer.append(location.getStart() + "-" +location.getEnd()); + buffer.append(") "); + buffer.append(String + .format(problem.getMessage().toString(), problem.getArgs())); + } + + buffer.append(" ]"); + + return buffer.toString(); + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaResolver.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..1218583c337505da76778812be6da6c4335d69fc --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/FormulaResolver.java @@ -0,0 +1,136 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import java.util.List; + +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eventb.core.ast.ASTProblem; +import org.eventb.core.ast.FormulaFactory; +import org.eventb.core.ast.IParseResult; +import org.eventb.core.ast.LanguageVersion; +import org.eventb.emf.core.EventBCommentedExpressionElement; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.formulas.BFormula; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; + +public class FormulaResolver { + + private enum FormulaType { + Expression, Predicate, Assignment + }; + + private static FormulaFactory formulaFactory = FormulaFactory.getDefault(); + + public static List<FormulaParseException> resolveAllFormulas( + final EventBNamedCommentedComponentElement astRoot) { + // traverse tree using an iterator + final TreeIterator<EObject> iterator = EcoreUtil.getAllContents( + astRoot, false); + List<FormulaParseException> exceptions = null; + + if (astRoot instanceof Machine) { + final MachineResolveSwitch switcher = new MachineResolveSwitch(); + while (iterator.hasNext()) { + // visit node + final EObject next = iterator.next(); + final Boolean visitChildren = switcher.doSwitch(next); + + if (visitChildren != null ? !visitChildren : true) { + iterator.prune(); + } + } + exceptions = switcher.getExceptions(); + } else if (astRoot instanceof Context) { + final ContextResolveSwitch switcher = new ContextResolveSwitch(); + while (iterator.hasNext()) { + // visit node + final EObject next = iterator.next(); + final Boolean visitChildren = switcher.doSwitch(next); + + if (visitChildren != null ? !visitChildren : true) { + iterator.prune(); + } + } + exceptions = switcher.getExceptions(); + } + + return exceptions; + } + + public static void resolve(final EventBCommentedExpressionElement emfExpr) + throws FormulaParseException { + final String expression = emfExpr.getExpression(); + resolve(emfExpr, expression, formulaFactory.parseExpression(expression, + LanguageVersion.V2, emfExpr), FormulaType.Expression); + } + + public static void resolve(final EventBNamedCommentedPredicateElement emfPredicate) + throws FormulaParseException { + final String predicate = emfPredicate.getPredicate(); + resolve(emfPredicate, predicate, formulaFactory.parsePredicate( + predicate, LanguageVersion.V2, emfPredicate), + FormulaType.Predicate); + } + + public static void resolve(final Action emfAction) + throws FormulaParseException { + final String action = emfAction.getAction(); + resolve(emfAction, action, formulaFactory.parseAssignment(action, + LanguageVersion.V2, emfAction), FormulaType.Assignment); + } + + private static void resolve(final EventBElement emfExpr, + final String content, final IParseResult parseResult, + final FormulaType type) throws FormulaParseException { + // parsing comes first + final List<ASTProblem> problems = parseResult.getProblems(); + + // no need to continue if any problems occured + if (problems.size() > 0) { + throw new FormulaParseException(emfExpr, content, problems); + } + + final ResolveVisitor visitor = new ResolveVisitor(); + BFormula formula = null; + final int offset = getOffset(emfExpr, content); + + switch (type) { + case Expression: + formula = visitor + .convert(parseResult.getParsedExpression(), offset); + break; + case Predicate: + formula = visitor.convert(parseResult.getParsedPredicate(), offset); + break; + case Assignment: + formula = visitor + .convert(parseResult.getParsedAssignment(), offset); + break; + + default: + break; + } + + emfExpr.getExtensions().add(formula); + } + + private static int getOffset(final EventBElement emfObject, + final String content) { + final TextRange range = TextPositionUtil.getInternalPosition(emfObject, + content); + return range != null ? range.getOffset() : 0; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/MachineResolveSwitch.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/MachineResolveSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..ce4ff576bd9b7c0ff84a1259a418f22fff3d329b --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/MachineResolveSwitch.java @@ -0,0 +1,76 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.emf.ecore.EModelElement; +import org.eventb.emf.core.EventBCommentedExpressionElement; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.util.MachineSwitch; + +public class MachineResolveSwitch extends MachineSwitch<Boolean> { + + private final List<FormulaParseException> exceptions = new ArrayList<FormulaParseException>(); + + @Override + public Boolean caseEModelElement(final EModelElement object) { + return true; + } + + @Override + public Boolean caseEventBObject(final EventBObject object) { + return true; + } + + @Override + public Boolean caseEventBNamedCommentedPredicateElement(final EventBNamedCommentedPredicateElement object) { + try { + FormulaResolver.resolve(object); + } catch (final FormulaParseException e) { + handleError(e); + } + + // no need to traverse any children + return false; + } + + @Override + public Boolean caseEventBCommentedExpressionElement(final EventBCommentedExpressionElement object) { + try { + FormulaResolver.resolve(object); + } catch (final FormulaParseException e) { + handleError(e); + } + + // no need to traverse any children + return false; + } + + @Override + public Boolean caseAction(final Action object) { + try { + FormulaResolver.resolve(object); + } catch (final FormulaParseException e) { + handleError(e); + } + + // no need to traverse any children + return false; + } + + private void handleError(final FormulaParseException e) { + exceptions.add(e); + } + + public List<FormulaParseException> getExceptions() { + return exceptions; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/formulas/ResolveVisitor.java b/org.eventb.texttools/src/org/eventb/texttools/formulas/ResolveVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..9555e5006efd1316ebf0f3686296f42f5736d4d9 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/formulas/ResolveVisitor.java @@ -0,0 +1,726 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.formulas; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Stack; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eventb.core.ast.Assignment; +import org.eventb.core.ast.AssociativeExpression; +import org.eventb.core.ast.AssociativePredicate; +import org.eventb.core.ast.AtomicExpression; +import org.eventb.core.ast.BecomesEqualTo; +import org.eventb.core.ast.BecomesMemberOf; +import org.eventb.core.ast.BecomesSuchThat; +import org.eventb.core.ast.BinaryExpression; +import org.eventb.core.ast.BinaryPredicate; +import org.eventb.core.ast.BoolExpression; +import org.eventb.core.ast.BoundIdentDecl; +import org.eventb.core.ast.BoundIdentifier; +import org.eventb.core.ast.Expression; +import org.eventb.core.ast.Formula; +import org.eventb.core.ast.FreeIdentifier; +import org.eventb.core.ast.ISimpleVisitor; +import org.eventb.core.ast.IntegerLiteral; +import org.eventb.core.ast.LiteralPredicate; +import org.eventb.core.ast.MultiplePredicate; +import org.eventb.core.ast.Predicate; +import org.eventb.core.ast.QuantifiedExpression; +import org.eventb.core.ast.QuantifiedPredicate; +import org.eventb.core.ast.RelationalPredicate; +import org.eventb.core.ast.SetExtension; +import org.eventb.core.ast.SimplePredicate; +import org.eventb.core.ast.SourceLocation; +import org.eventb.core.ast.UnaryExpression; +import org.eventb.core.ast.UnaryPredicate; +import org.eventb.emf.formulas.BAssignmentResolved; +import org.eventb.emf.formulas.BExpressionResolved; +import org.eventb.emf.formulas.BFormula; +import org.eventb.emf.formulas.BPredicateResolved; +import org.eventb.emf.formulas.BecomesEqualToAssignment; +import org.eventb.emf.formulas.BecomesMemberOfAssignment; +import org.eventb.emf.formulas.BecomesSuchThatAssignment; +import org.eventb.emf.formulas.BinaryOperator; +import org.eventb.emf.formulas.BoundIdentifierExpression; +import org.eventb.emf.formulas.Constant; +import org.eventb.emf.formulas.ExistPredicate; +import org.eventb.emf.formulas.ForallPredicate; +import org.eventb.emf.formulas.FormulasFactory; +import org.eventb.emf.formulas.FormulasPackage; +import org.eventb.emf.formulas.IdentifierExpression; +import org.eventb.emf.formulas.IntegerLiteralExpression; +import org.eventb.emf.formulas.MultiOperand; +import org.eventb.emf.formulas.QuantifiedIntersectionExpression1; +import org.eventb.emf.formulas.QuantifiedIntersectionExpression2; +import org.eventb.emf.formulas.QuantifiedUnionExpression1; +import org.eventb.emf.formulas.QuantifiedUnionExpression2; +import org.eventb.emf.formulas.SetComprehensionExpression1; +import org.eventb.emf.formulas.SetComprehensionExpression2; +import org.eventb.emf.formulas.UnaryOperator; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.TextToolsPlugin; + +public class ResolveVisitor implements ISimpleVisitor { + private static final Map<Integer, EClass> idToEClass = new HashMap<Integer, EClass>(); + + @SuppressWarnings("unchecked") + private Map<BFormula, Formula> emfToRodinElements; + + private final Stack<BFormula> stack = new Stack<BFormula>(); + private final Stack<List<String>> boundIdDeclStack = new Stack<List<String>>(); + + private int textOffset; + + static { + // Constants: + // INTEGER, NATURAL, NATURAL1, BOOL, TRUE, BTRUE, FALSE, BFALSE, + // EMPTYSET + idToEClass.put(Formula.INTEGER, FormulasPackage.eINSTANCE.getINT()); + idToEClass.put(Formula.NATURAL, FormulasPackage.eINSTANCE.getNAT()); + idToEClass.put(Formula.NATURAL1, FormulasPackage.eINSTANCE.getNAT1()); + idToEClass.put(Formula.BOOL, FormulasPackage.eINSTANCE.getBOOL()); + idToEClass.put(Formula.TRUE, FormulasPackage.eINSTANCE.getTRUE()); + idToEClass.put(Formula.BTRUE, FormulasPackage.eINSTANCE.getTRUTH()); + idToEClass.put(Formula.FALSE, FormulasPackage.eINSTANCE.getFALSE()); + idToEClass.put(Formula.BFALSE, FormulasPackage.eINSTANCE.getFALSITY()); + idToEClass.put(Formula.EMPTYSET, FormulasPackage.eINSTANCE + .getEMPTYSET()); + idToEClass.put(Formula.KPRED, FormulasPackage.eINSTANCE + .getPredExpression()); + idToEClass.put(Formula.KSUCC, FormulasPackage.eINSTANCE + .getSuccExpression()); + + // Unary operators: + // KCARD, POW, POW1, KUNION, KINTER, KDOM, KRAN, KPRJ1, KPRJ2, KID, + // KMIN, KMAX, CONVERSE, UNMINUS + // NOT, KFINITE, KBOOL + idToEClass.put(Formula.KCARD, FormulasPackage.eINSTANCE + .getCardExpression()); + idToEClass.put(Formula.POW, FormulasPackage.eINSTANCE + .getPowExpression()); + idToEClass.put(Formula.POW1, FormulasPackage.eINSTANCE + .getPow1Expression()); + idToEClass.put(Formula.KUNION, FormulasPackage.eINSTANCE + .getKUnionExpression()); + idToEClass.put(Formula.KINTER, FormulasPackage.eINSTANCE + .getKIntersectionExpression()); + idToEClass.put(Formula.KDOM, FormulasPackage.eINSTANCE + .getDomainExpression()); + idToEClass.put(Formula.KRAN, FormulasPackage.eINSTANCE + .getRangeExpression()); + idToEClass.put(Formula.KPRJ1, FormulasPackage.eINSTANCE + .getPrj1Expression()); + idToEClass.put(Formula.KPRJ1_GEN, FormulasPackage.eINSTANCE + .getPrj1GenExpression()); + idToEClass.put(Formula.KPRJ2, FormulasPackage.eINSTANCE + .getPrj2Expression()); + idToEClass.put(Formula.KPRJ2_GEN, FormulasPackage.eINSTANCE + .getPrj2GenExpression()); + idToEClass.put(Formula.KID, FormulasPackage.eINSTANCE + .getIdentityExpression()); + idToEClass.put(Formula.KID_GEN, FormulasPackage.eINSTANCE + .getIdentityGenExpression()); + idToEClass.put(Formula.KMIN, FormulasPackage.eINSTANCE + .getMinExpression()); + idToEClass.put(Formula.KMAX, FormulasPackage.eINSTANCE + .getMaxExpression()); + idToEClass.put(Formula.CONVERSE, FormulasPackage.eINSTANCE + .getInverseExpression()); + idToEClass.put(Formula.UNMINUS, FormulasPackage.eINSTANCE + .getUnaryMinusExpression()); + idToEClass + .put(Formula.NOT, FormulasPackage.eINSTANCE.getNotPredicate()); + idToEClass.put(Formula.KFINITE, FormulasPackage.eINSTANCE + .getFinitePredicate()); + idToEClass.put(Formula.KBOOL, FormulasPackage.eINSTANCE + .getBoolExpression()); + + // Binary operators: EQUAL, NOTEQUAL, LT, LE, GT, GE, IN, NOTIN, SUBSET, + // NOTSUBSET, SUBSETEQ, NOTSUBSETEQ, MAPSTO, REL, TREL, SREL, STREL, + // PFUN, TFUN, PINJ, TINJ, PSUR, TSUR, TBIJ, SETMINUS, CPROD, DPROD, + // PPROD, DOMRES, DOMSUB, RANRES, RANSUB, UPTO, MINUS, DIV, MOD, EXPN, + // FUNIMAGE, RELIMAGE, LIMP, LEQV + idToEClass.put(Formula.EQUAL, FormulasPackage.eINSTANCE + .getEqualPredicate()); + idToEClass.put(Formula.NOTEQUAL, FormulasPackage.eINSTANCE + .getNotEqualPredicate()); + idToEClass + .put(Formula.LT, FormulasPackage.eINSTANCE.getLessPredicate()); + idToEClass.put(Formula.LE, FormulasPackage.eINSTANCE + .getLessEqualPredicate()); + idToEClass.put(Formula.GT, FormulasPackage.eINSTANCE + .getGreaterPredicate()); + idToEClass.put(Formula.GE, FormulasPackage.eINSTANCE + .getGreaterEqualPredicate()); + idToEClass.put(Formula.IN, FormulasPackage.eINSTANCE + .getBelongPredicate()); + idToEClass.put(Formula.NOTIN, FormulasPackage.eINSTANCE + .getNotBelongPredicate()); + idToEClass.put(Formula.SUBSET, FormulasPackage.eINSTANCE + .getSubsetStrictPredicate()); + idToEClass.put(Formula.NOTSUBSET, FormulasPackage.eINSTANCE + .getNotSubsetStrictPredicate()); + idToEClass.put(Formula.SUBSETEQ, FormulasPackage.eINSTANCE + .getSubsetPredicate()); + idToEClass.put(Formula.NOTSUBSETEQ, FormulasPackage.eINSTANCE + .getNotSubsetPredicate()); + idToEClass.put(Formula.MAPSTO, FormulasPackage.eINSTANCE + .getMapletExpression()); + idToEClass.put(Formula.REL, FormulasPackage.eINSTANCE + .getRelationExpression()); + idToEClass.put(Formula.TREL, FormulasPackage.eINSTANCE + .getTotalRelationExpression()); + idToEClass.put(Formula.SREL, FormulasPackage.eINSTANCE + .getSurjectiveRelationExpression()); + idToEClass.put(Formula.STREL, FormulasPackage.eINSTANCE + .getTotalSurjectiveRelationExpression()); + idToEClass.put(Formula.PFUN, FormulasPackage.eINSTANCE + .getPartialFunctionExpression()); + idToEClass.put(Formula.TFUN, FormulasPackage.eINSTANCE + .getTotalFunctionExpression()); + idToEClass.put(Formula.PINJ, FormulasPackage.eINSTANCE + .getPartialInjectionExpression()); + idToEClass.put(Formula.TINJ, FormulasPackage.eINSTANCE + .getTotalInjectionExpression()); + idToEClass.put(Formula.PSUR, FormulasPackage.eINSTANCE + .getPartialSurjectionExpression()); + idToEClass.put(Formula.TSUR, FormulasPackage.eINSTANCE + .getTotalSurjectionExpression()); + idToEClass.put(Formula.TBIJ, FormulasPackage.eINSTANCE + .getTotalBijectionExpression()); + idToEClass.put(Formula.SETMINUS, FormulasPackage.eINSTANCE + .getSetSubtractionExpression()); + idToEClass.put(Formula.CPROD, FormulasPackage.eINSTANCE + .getCartesianProductExpression()); + idToEClass.put(Formula.DPROD, FormulasPackage.eINSTANCE + .getDirectProductExpression()); + idToEClass.put(Formula.DOMRES, FormulasPackage.eINSTANCE + .getDomainRestrictionExpression()); + idToEClass.put(Formula.DOMSUB, FormulasPackage.eINSTANCE + .getDomainSubtractionExpression()); + idToEClass.put(Formula.RANRES, FormulasPackage.eINSTANCE + .getRangeRestrictionExpression()); + idToEClass.put(Formula.RANSUB, FormulasPackage.eINSTANCE + .getRangeSubtractionExpression()); + idToEClass.put(Formula.UPTO, FormulasPackage.eINSTANCE + .getUptoExpression()); + idToEClass.put(Formula.MINUS, FormulasPackage.eINSTANCE + .getSubtractExpression()); + idToEClass.put(Formula.DIV, FormulasPackage.eINSTANCE + .getDivisionExpression()); + idToEClass.put(Formula.MOD, FormulasPackage.eINSTANCE + .getModuloExpression()); + idToEClass.put(Formula.EXPN, FormulasPackage.eINSTANCE + .getExponentiationExpression()); + idToEClass.put(Formula.FUNIMAGE, FormulasPackage.eINSTANCE + .getFunctionExpression()); + idToEClass.put(Formula.RELIMAGE, FormulasPackage.eINSTANCE + .getImageExpression()); + idToEClass.put(Formula.LIMP, FormulasPackage.eINSTANCE + .getImplicationPredicate()); + idToEClass.put(Formula.LEQV, FormulasPackage.eINSTANCE + .getEquivalencePredicate()); + + // Multi operand predicates/expressions + idToEClass.put(Formula.BUNION, FormulasPackage.eINSTANCE + .getUnionExpression()); + idToEClass.put(Formula.BINTER, FormulasPackage.eINSTANCE + .getIntersectionExpression()); + idToEClass.put(Formula.BCOMP, FormulasPackage.eINSTANCE + .getBackwardCompositionExpression()); + idToEClass.put(Formula.FCOMP, FormulasPackage.eINSTANCE + .getForwardCompositionExpression()); + idToEClass.put(Formula.OVR, FormulasPackage.eINSTANCE + .getRelationalOverridingExpression()); + idToEClass.put(Formula.PLUS, FormulasPackage.eINSTANCE + .getAddExpression()); + idToEClass.put(Formula.MUL, FormulasPackage.eINSTANCE + .getMulExpression()); + idToEClass.put(Formula.LAND, FormulasPackage.eINSTANCE + .getAndPredicate()); + idToEClass.put(Formula.LOR, FormulasPackage.eINSTANCE.getOrPredicate()); + idToEClass.put(Formula.SETEXT, FormulasPackage.eINSTANCE + .getSetExpression()); + idToEClass.put(Formula.KPARTITION, FormulasPackage.eINSTANCE + .getPartitionPredicate()); + + // Quantifiers: + // FORALL, EXISTS + idToEClass.put(Formula.FORALL, FormulasPackage.eINSTANCE + .getForallPredicate()); + idToEClass.put(Formula.EXISTS, FormulasPackage.eINSTANCE + .getExistPredicate()); + // cannot decide QUNION and QINTER here + } + + public BExpressionResolved convert(final Expression rodinExpr, + final int offset) { + traverseFormula(rodinExpr, offset); + return (BExpressionResolved) stack.pop(); + } + + public BPredicateResolved convert(final Predicate predicate, + final int offset) { + traverseFormula(predicate, offset); + return (BPredicateResolved) stack.pop(); + } + + public BAssignmentResolved convert(final Assignment assignment, + final int offset) { + traverseFormula(assignment, offset); + return (BAssignmentResolved) stack.pop(); + } + + @SuppressWarnings("unchecked") + private void traverseFormula(final Formula rodinFormula, final int offset) { + emfToRodinElements = new HashMap<BFormula, Formula>(); + + textOffset = offset; + + rodinFormula.accept(this); + assert stack.size() == 1; + } + + /** + * Handles a newly created emf node, i.e., push to stack, store mapping to + * Rodin element + * + * @param node + * @param rodinFormula + */ + @SuppressWarnings("unchecked") + private void storeNode(final BFormula node, final Formula rodinFormula) { + annotatePosition(node, rodinFormula); + emfToRodinElements.put(node, rodinFormula); + stack.push(node); + } + + @SuppressWarnings("unchecked") + public Map<BFormula, Formula> getEmfToRodinMapping() { + return emfToRodinElements; + } + + public void visitAssociativeExpression( + final AssociativeExpression expression) { + handleMultiChildren(expression, expression.getChildren()); + } + + public void visitAssociativePredicate(final AssociativePredicate predicate) { + handleMultiChildren(predicate, predicate.getChildren()); + } + + public void visitAtomicExpression(final AtomicExpression expression) { + final EClass eClass = getMatchingEClass(expression); + final EObject eObject = FormulasFactory.eINSTANCE.create(eClass); + storeNode((BFormula) eObject, expression); + } + + public void visitBecomesEqualTo(final BecomesEqualTo assignment) { + final FreeIdentifier[] identifiers = assignment + .getAssignedIdentifiers(); + visitChildren(identifiers); + + final Expression[] expressions = assignment.getExpressions(); + visitChildren(expressions); + + final BecomesEqualToAssignment newNode = FormulasFactory.eINSTANCE + .createBecomesEqualToAssignment(); + + // attach expression children + final EList<BExpressionResolved> exprChildren = newNode + .getExpressions(); + for (int i = 0; i < expressions.length; i++) { + exprChildren.add(0, (BExpressionResolved) stack.pop()); + } + + // attach identifier children + final EList<IdentifierExpression> identChildren = newNode + .getIdentifiers(); + for (int i = 0; i < identifiers.length; i++) { + identChildren.add(0, (IdentifierExpression) stack.pop()); + } + + storeNode(newNode, assignment); + } + + public void visitBecomesMemberOf(final BecomesMemberOf assignment) { + final FreeIdentifier[] identifiers = assignment + .getAssignedIdentifiers(); + visitChildren(identifiers); + + final Expression expression = assignment.getSet(); + visitChildren(expression); + + final BecomesMemberOfAssignment newNode = FormulasFactory.eINSTANCE + .createBecomesMemberOfAssignment(); + + // attach expression child + newNode.setExpression((BExpressionResolved) stack.pop()); + + // attach identifier children + final EList<IdentifierExpression> identChildren = newNode + .getIdentifiers(); + for (int i = 0; i < identifiers.length; i++) { + identChildren.add(0, (IdentifierExpression) stack.pop()); + } + + storeNode(newNode, assignment); + } + + public void visitBecomesSuchThat(final BecomesSuchThat assignment) { + final FreeIdentifier[] identifiers = assignment + .getAssignedIdentifiers(); + visitChildren(identifiers); + + // add bound identifier declarations + final List<String> primedIdents = new LinkedList<String>(); + for (int i = 0; i < identifiers.length; i++) { + primedIdents.add(identifiers[i].getName() + "'"); + } + boundIdDeclStack.push(primedIdents); + + final Predicate predicate = assignment.getCondition(); + visitChildren(predicate); + + boundIdDeclStack.pop(); + + final BecomesSuchThatAssignment newNode = FormulasFactory.eINSTANCE + .createBecomesSuchThatAssignment(); + + // attach expression child + newNode.setPredicate((BPredicateResolved) stack.pop()); + + // attach identifier children + final EList<IdentifierExpression> identChildren = newNode + .getIdentifiers(); + for (int i = 0; i < identifiers.length; i++) { + identChildren.add(0, (IdentifierExpression) stack.pop()); + } + + storeNode(newNode, assignment); + } + + public void visitBinaryExpression(final BinaryExpression expression) { + handleTwoChildren(expression, expression.getLeft(), expression + .getRight()); + } + + public void visitBinaryPredicate(final BinaryPredicate predicate) { + handleTwoChildren(predicate, predicate.getLeft(), predicate.getRight()); + } + + public void visitBoolExpression(final BoolExpression expression) { + handleSingleChild(expression, expression.getPredicate()); + } + + public void visitBoundIdentDecl(final BoundIdentDecl boundIdentDecl) { + final BoundIdentifierExpression node = FormulasFactory.eINSTANCE + .createBoundIdentifierExpression(); + node.setName(boundIdentDecl.getName()); + storeNode(node, boundIdentDecl); + } + + public void visitBoundIdentifier(final BoundIdentifier identifierExpression) { + final BoundIdentifierExpression node = FormulasFactory.eINSTANCE + .createBoundIdentifierExpression(); + + final List<String> identDecls = boundIdDeclStack.peek(); + final int index = identifierExpression.getBoundIndex(); + final String name = identDecls.get(index); + node.setName(name); + + storeNode(node, identifierExpression); + } + + public void visitFreeIdentifier(final FreeIdentifier identifierExpression) { + final IdentifierExpression node = FormulasFactory.eINSTANCE + .createIdentifierExpression(); + node.setName(identifierExpression.getName()); + storeNode(node, identifierExpression); + } + + public void visitIntegerLiteral(final IntegerLiteral expression) { + final IntegerLiteralExpression node = FormulasFactory.eINSTANCE + .createIntegerLiteralExpression(); + node.setNumber(expression.getValue()); + + storeNode(node, expression); + } + + public void visitLiteralPredicate(final LiteralPredicate predicate) { + final Constant node = (Constant) FormulasFactory.eINSTANCE + .create(getMatchingEClass(predicate)); + storeNode(node, predicate); + } + + public void visitMultiplePredicate(final MultiplePredicate predicate) { + handleMultiChildren(predicate, predicate.getChildren()); + } + + public void visitQuantifiedExpression(final QuantifiedExpression expression) { + final BoundIdentDecl[] localIdentifiers = expression + .getBoundIdentDecls(); + boundIdDeclStack.push(convertIdentDecl(localIdentifiers)); + for (final BoundIdentDecl boundIdentDecl : localIdentifiers) { + boundIdentDecl.accept(this); + } + + expression.getPredicate().accept(this); + expression.getExpression().accept(this); + boundIdDeclStack.pop(); + + final boolean isFullType = localIdentifiers.length > 0; + BExpressionResolved node = null; + EList<IdentifierExpression> identifiers = null; + + switch (expression.getTag()) { + case Formula.QUNION: + if (isFullType) { + final QuantifiedUnionExpression1 newNode = FormulasFactory.eINSTANCE + .createQuantifiedUnionExpression1(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + identifiers = newNode.getIdentifiers(); + node = newNode; + } else { + final QuantifiedUnionExpression2 newNode = FormulasFactory.eINSTANCE + .createQuantifiedUnionExpression2(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + node = newNode; + } + break; + case Formula.QINTER: + if (isFullType) { + final QuantifiedIntersectionExpression1 newNode = FormulasFactory.eINSTANCE + .createQuantifiedIntersectionExpression1(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + identifiers = newNode.getIdentifiers(); + node = newNode; + } else { + final QuantifiedIntersectionExpression2 newNode = FormulasFactory.eINSTANCE + .createQuantifiedIntersectionExpression2(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + node = newNode; + } + break; + case Formula.CSET: + if (isFullType) { + final SetComprehensionExpression1 newNode = FormulasFactory.eINSTANCE + .createSetComprehensionExpression1(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + identifiers = newNode.getIdentifiers(); + node = newNode; + } else { + final SetComprehensionExpression2 newNode = FormulasFactory.eINSTANCE + .createSetComprehensionExpression2(); + newNode.setExpression((BExpressionResolved) stack.pop()); + newNode.setPredicate((BPredicateResolved) stack.pop()); + node = newNode; + } + break; + + default: + // TODO handle unexpected tags + break; + } + + // attach declared local variables (coming in reverse order from stack) + for (int i = 0; i < localIdentifiers.length; i++) { + identifiers.add(0, (IdentifierExpression) stack.pop()); + } + + storeNode(node, expression); + } + + public void visitQuantifiedPredicate(final QuantifiedPredicate predicate) { + // visit children + final BoundIdentDecl[] localIdentifiers = predicate + .getBoundIdentDecls(); + boundIdDeclStack.push(convertIdentDecl(localIdentifiers)); + for (final BoundIdentDecl boundIdentDecl : localIdentifiers) { + boundIdentDecl.accept(this); + } + + predicate.getPredicate().accept(this); + boundIdDeclStack.pop(); + + final BFormula node = (BFormula) FormulasFactory.eINSTANCE + .create(getMatchingEClass(predicate)); + EList<BoundIdentifierExpression> identifiers = null; + + switch (predicate.getTag()) { + // FORALL, EXISTS + case Formula.FORALL: + final ForallPredicate forallPred = (ForallPredicate) node; + identifiers = forallPred.getIdentifiers(); + forallPred.setPredicate((BPredicateResolved) stack.pop()); + break; + case Formula.EXISTS: + final ExistPredicate existPred = (ExistPredicate) node; + identifiers = existPred.getIdentifiers(); + existPred.setPredicate((BPredicateResolved) stack.pop()); + break; + + default: + // TODO handle unexpected tags + break; + } + + // attach declared local variables (coming in reverse order from stack) + for (int i = 0; i < localIdentifiers.length; i++) { + identifiers.add(0, (BoundIdentifierExpression) stack.pop()); + } + + storeNode(node, predicate); + } + + public void visitRelationalPredicate(final RelationalPredicate predicate) { + handleTwoChildren(predicate, predicate.getLeft(), predicate.getRight()); + } + + public void visitSetExtension(final SetExtension expression) { + handleMultiChildren(expression, expression.getMembers()); + } + + public void visitSimplePredicate(final SimplePredicate predicate) { + handleSingleChild(predicate, predicate.getExpression()); + } + + public void visitUnaryExpression(final UnaryExpression expression) { + handleSingleChild(expression, expression.getChild()); + } + + public void visitUnaryPredicate(final UnaryPredicate predicate) { + handleSingleChild(predicate, predicate.getChild()); + } + + @SuppressWarnings("unchecked") + private void handleSingleChild(final Formula rodinNode, final Formula child) { + // visit child + child.accept(this); + + // create node itself + final UnaryOperator node = (UnaryOperator) FormulasFactory.eINSTANCE + .create(getMatchingEClass(rodinNode)); + + // add the children nodes + node.setChild(stack.pop()); + + storeNode(node, rodinNode); + } + + @SuppressWarnings("unchecked") + private void handleTwoChildren(final Formula rodinNode, + final Formula leftChild, final Formula rightChild) { + // visit children + visitChildren(leftChild); + visitChildren(rightChild); + + // create node itself + final BinaryOperator node = (BinaryOperator) FormulasFactory.eINSTANCE + .create(getMatchingEClass(rodinNode)); + + // add the children nodes + node.setRight(stack.pop()); + node.setLeft(stack.pop()); + + storeNode(node, rodinNode); + } + + @SuppressWarnings("unchecked") + private void handleMultiChildren(final Formula rodinNode, + final Formula[] rodinChildren) { + visitChildren(rodinChildren); + + // create node itself + final MultiOperand node = (MultiOperand) FormulasFactory.eINSTANCE + .create(getMatchingEClass(rodinNode)); + + // add the children nodes + final EList<BFormula> children = node.getChildren(); + for (int i = 0; i < rodinChildren.length; i++) { + children.add(0, stack.pop()); + } + + storeNode(node, rodinNode); + } + + @SuppressWarnings("unchecked") + private void visitChildren(final Formula[] rodinChildren) { + for (final Formula child : rodinChildren) { + child.accept(this); + } + } + + @SuppressWarnings("unchecked") + private void visitChildren(final Formula child) { + child.accept(this); + } + + private List<String> convertIdentDecl(final BoundIdentDecl[] identDecl) { + LinkedList<String> result = null; + if (!boundIdDeclStack.isEmpty()) { + result = new LinkedList<String>(boundIdDeclStack.peek()); + } else { + result = new LinkedList<String>(); + } + + for (int i = 0; i < identDecl.length; i++) { + result.addFirst(identDecl[i].getName()); + } + + return result; + } + + @SuppressWarnings("unchecked") + private void annotatePosition(final BFormula node, + final Formula rodinFormula) { + + final SourceLocation location = rodinFormula.getSourceLocation(); + if (location != null) { + final int startPos = location.getStart(); + final int endPos = location.getEnd(); + + TextPositionUtil.annotatePosition(node, textOffset + startPos, + endPos - startPos + 1); + } + + /* + * FIXME find out why SourceLocation is missing sometimes + */ + } + + @SuppressWarnings("unchecked") + private EClass getMatchingEClass(final Formula formula) { + final EClass eClass = idToEClass.get(formula.getTag()); + + if (eClass == null) { + final String message = "Unknown Rodin formula type: [" + + formula.getTag() + "] " + formula.toString(); + + TextToolsPlugin.getDefault().getLog().log( + new Status(IStatus.ERROR, TextToolsPlugin.PLUGIN_ID, + message)); + + throw new UnsupportedOperationException(message); + } + + return eClass; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/internal/parsing/TransformationVisitor.java b/org.eventb.texttools/src/org/eventb/texttools/internal/parsing/TransformationVisitor.java new file mode 100644 index 0000000000000000000000000000000000000000..22f8bbbec9ee05a26c1fcfa8da5d3b4c9bc8cdfc --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/internal/parsing/TransformationVisitor.java @@ -0,0 +1,435 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.internal.parsing; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eventb.emf.core.EventBCommented; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.Axiom; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.ContextFactory; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Convergence; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Invariant; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.MachineFactory; +import org.eventb.emf.core.machine.Variant; +import org.eventb.texttools.TextPositionUtil; +import org.eventb.texttools.model.texttools.TextRange; +import org.eventb.texttools.model.texttools.TexttoolsFactory; + +import de.be4.eventb.core.parser.analysis.DepthFirstAdapter; +import de.be4.eventb.core.parser.node.AAction; +import de.be4.eventb.core.parser.node.AAnticipatedConvergence; +import de.be4.eventb.core.parser.node.AAxiom; +import de.be4.eventb.core.parser.node.ACarrierSet; +import de.be4.eventb.core.parser.node.AConstant; +import de.be4.eventb.core.parser.node.AContextParseUnit; +import de.be4.eventb.core.parser.node.AConvergentConvergence; +import de.be4.eventb.core.parser.node.ADerivedAxiom; +import de.be4.eventb.core.parser.node.ADerivedInvariant; +import de.be4.eventb.core.parser.node.AEvent; +import de.be4.eventb.core.parser.node.AExtendedEventRefinement; +import de.be4.eventb.core.parser.node.AGuard; +import de.be4.eventb.core.parser.node.AInvariant; +import de.be4.eventb.core.parser.node.AMachineParseUnit; +import de.be4.eventb.core.parser.node.AOrdinaryConvergence; +import de.be4.eventb.core.parser.node.AParameter; +import de.be4.eventb.core.parser.node.ARefinesEventRefinement; +import de.be4.eventb.core.parser.node.AVariable; +import de.be4.eventb.core.parser.node.AVariant; +import de.be4.eventb.core.parser.node.AWitness; +import de.be4.eventb.core.parser.node.Node; +import de.be4.eventb.core.parser.node.PEventRefinement; +import de.be4.eventb.core.parser.node.TComment; +import de.be4.eventb.core.parser.node.TFormula; +import de.be4.eventb.core.parser.node.TIdentifierLiteral; +import de.be4.eventb.core.parser.node.TLabel; +import de.be4.eventb.core.parser.node.Token; +import de.hhu.stups.sablecc.patch.IToken; +import de.hhu.stups.sablecc.patch.PositionedNode; +import de.hhu.stups.sablecc.patch.SourcePosition; + +public class TransformationVisitor extends DepthFirstAdapter { + + private IDocument document; + + private final Stack<EventBObject> stack = new Stack<EventBObject>(); + private final Stack<Convergence> convergenceStack = new Stack<Convergence>(); + + @SuppressWarnings("unchecked") + public <T extends EventBObject> T transform(final Node rootNode, + final IDocument document) { + this.document = document; + stack.clear(); + convergenceStack.clear(); + + rootNode.apply(this); + + this.document = null; + + assert stack.size() == 1; + return (T) stack.pop(); + } + + @Override + public void outAMachineParseUnit(final AMachineParseUnit node) { + final Machine newNode = MachineFactory.eINSTANCE.createMachine(); + TextPositionUtil.annotatePosition(newNode, createTextRange(node)); + + handleNamedAndCommented(newNode, node, node.getName(), node + .getComments(), false); + handleList(newNode, newNode.getRefinesNames(), node.getRefinesNames()); + handleList(newNode, newNode.getSeesNames(), node.getSeenNames()); + + /* + * Child elements have been visited before and are lying on the stack in + * reverse order. + */ + handleList(newNode.getEvents(), node.getEvents().size()); + + // variant + if (node.getVariant() != null) { + newNode.setVariant((Variant) stack.pop()); + } + + // TODO theorems + // final EList<Theorem> theorems = newNode.getTheorems(); + // handleList(theorems, node.getTheorems().size()); + + handleList(newNode.getInvariants(), node.getInvariants().size()); + handleList(newNode.getVariables(), node.getVariables().size()); + + stack.push(newNode); + } + + @Override + public void outAContextParseUnit(final AContextParseUnit node) { + final Context newNode = ContextFactory.eINSTANCE.createContext(); + TextPositionUtil.annotatePosition(newNode, createTextRange(node)); + + handleNamedAndCommented(newNode, node, node.getName(), node + .getComments(), false); + handleList(newNode, newNode.getExtendsNames(), node.getExtendsNames()); + + /* + * Child elements have been visited before and are lying on the stack in + * reverse order. + */ + handleList(newNode.getAxioms(), node.getAxioms().size()); + + // TODO theorems + // final EList<Theorem> theorems = newNode.getTheorems(); + // handleList(theorems, node.getTheorems().size()); + handleList(newNode.getConstants(), node.getConstants().size()); + handleList(newNode.getSets(), node.getSets().size()); + + stack.push(newNode); + + } + + @Override + public void outAEvent(final AEvent node) { + final Event newNode = MachineFactory.eINSTANCE.createEvent(); + TextPositionUtil.annotatePosition(newNode, createTextRange(node)); + + handleNamedAndCommented(newNode, node, node.getName(), node + .getComments(), false); + + if (node.getConvergence() != null) { + newNode.setConvergence(convergenceStack.pop()); + } + + // event refinement is a bit more complicated + final PEventRefinement refinement = node.getRefinement(); + if (refinement != null) { + if (refinement instanceof ARefinesEventRefinement) { + handleList(newNode, newNode.getRefinesNames(), + ((ARefinesEventRefinement) refinement).getNames()); + newNode.setExtended(false); + } else { + final AExtendedEventRefinement extended = (AExtendedEventRefinement) refinement; + final TIdentifierLiteral extendsToken = extended.getName(); + final String extendsName = extendsToken.getText(); + newNode.getRefinesNames().add(extendsName); + storePosition(extendsName, newNode, extendsToken); + newNode.setExtended(true); + } + } else { + newNode.setExtended(false); + } + + /* + * Child elements have been visited before and are lying on the stack in + * reverse order. + */ + handleList(newNode.getActions(), node.getActions().size()); + handleList(newNode.getWitnesses(), node.getWitnesses().size()); + handleList(newNode.getGuards(), node.getGuards().size()); + handleList(newNode.getParameters(), node.getParameters().size()); + + stack.push(newNode); + + } + + @Override + public void outAVariable(final AVariable node) { + handleNamedAndCommented(MachineFactory.eINSTANCE.createVariable(), + node, node.getName(), node.getComments(), true); + } + + @Override + public void outAInvariant(final AInvariant node) { + handleLabeledPredicate(MachineFactory.eINSTANCE.createInvariant(), + node, node.getPredicate(), node.getName(), node.getComments(), + true); + } + + @Override + public void outADerivedInvariant(final ADerivedInvariant node) { + final Invariant invariant = MachineFactory.eINSTANCE.createInvariant(); + invariant.setTheorem(true); + handleLabeledPredicate(invariant, node, node.getPredicate(), node + .getName(), node.getComments(), true); + } + + @Override + public void outAVariant(final AVariant node) { + final Variant newNode = MachineFactory.eINSTANCE.createVariant(); + TextPositionUtil.annotatePosition(newNode, createTextRange(node)); + + handleComment(newNode, node.getComments()); + + final TFormula exprToken = node.getExpression(); + final String exprString = exprToken.getText(); + newNode.setExpression(exprString); + storePosition(exprString, newNode, exprToken); + + stack.push(newNode); + } + + @Override + public void outAOrdinaryConvergence(final AOrdinaryConvergence node) { + convergenceStack.push(Convergence.ORDINARY); + } + + @Override + public void outAAnticipatedConvergence(final AAnticipatedConvergence node) { + convergenceStack.push(Convergence.ANTICIPATED); + } + + @Override + public void outAConvergentConvergence(final AConvergentConvergence node) { + convergenceStack.push(Convergence.CONVERGENT); + } + + @Override + public void outAAction(final AAction node) { + final Action newNode = MachineFactory.eINSTANCE.createAction(); + handleNamedAndCommented(newNode, node, node.getName(), node + .getComments(), true); + + final TFormula actionToken = node.getAction(); + final String actionString = actionToken.getText(); + newNode.setAction(actionString); + storePosition(actionString, newNode, actionToken); + } + + @Override + public void outAWitness(final AWitness node) { + handleLabeledPredicate(MachineFactory.eINSTANCE.createWitness(), node, + node.getPredicate(), node.getName(), node.getComments(), true); + } + + @Override + public void outAGuard(final AGuard node) { + handleLabeledPredicate(MachineFactory.eINSTANCE.createGuard(), node, + node.getPredicate(), node.getName(), node.getComments(), true); + } + + @Override + public void outAParameter(final AParameter node) { + handleNamedAndCommented(MachineFactory.eINSTANCE.createParameter(), + node, node.getName(), node.getComments(), true); + } + + @Override + public void outAAxiom(final AAxiom node) { + handleLabeledPredicate(ContextFactory.eINSTANCE.createAxiom(), node, + node.getPredicate(), node.getName(), node.getComments(), true); + } + + @Override + public void outADerivedAxiom(final ADerivedAxiom node) { + final Axiom axiom = ContextFactory.eINSTANCE.createAxiom(); + axiom.setTheorem(true); + handleLabeledPredicate(axiom, node, node.getPredicate(), + node.getName(), node.getComments(), true); + } + + @Override + public void outACarrierSet(final ACarrierSet node) { + handleNamedAndCommented(ContextFactory.eINSTANCE.createCarrierSet(), + node, node.getName(), node.getComments(), true); + } + + @Override + public void outAConstant(final AConstant node) { + handleNamedAndCommented(ContextFactory.eINSTANCE.createConstant(), + node, node.getName(), node.getComments(), true); + } + + private void handleNamedAndCommented(final EventBNamed newNode, + final PositionedNode node, final Token name, + final LinkedList<TComment> comments, final boolean store) { + if (store) { + /* + * Need to annotate position before continuing because substrings + * will need the annotation. + */ + TextPositionUtil.annotatePosition((EventBElement) newNode, + createTextRange(node)); + } + + handleComment((EventBCommented) newNode, comments); + handleName(newNode, name); + + if (store) { + stack.push((EventBElement) newNode); + } + } + + private void handleLabeledPredicate(final EventBNamedCommentedPredicateElement newNode, + final Node node, final TFormula predicate, final TLabel name, + final LinkedList<TComment> comments, final boolean store) { + handleNamedAndCommented(newNode, node, name, comments, store); + + final String predText = predicate.getText(); + newNode.setPredicate(predText); + storePosition(predText, newNode, predicate); + } + + @SuppressWarnings("unchecked") + private <T extends EventBObject> void handleList(final EList<T> targetList, + final int childrenNumber) { + for (int i = 0; i < childrenNumber; i++) { + final T childNode = (T) stack.pop(); + targetList.add(0, childNode); + } + } + + private <T> void handleList(final EventBObject emfParent, + final EList<String> targetList, + final List<TIdentifierLiteral> children) { + for (final TIdentifierLiteral token : children) { + final String text = token.getText(); + targetList.add(text); + storePosition(text, emfParent, token); + } + } + + private void handleName(final EventBNamed emfElement, final Token nameToken) { + if (nameToken != null) { + final String name = nameToken.getText(); + emfElement.setName(name); + storePosition(name, (EventBElement) emfElement, nameToken); + } + } + + private void handleComment(final EventBCommented emfElement, + final LinkedList<TComment> comments) { + if (comments != null && comments.size() > 0) { + final StringBuffer buffer = new StringBuffer(); + + final Iterator<TComment> iterator = comments.iterator(); + while (iterator.hasNext()) { + final TComment comment = iterator.next(); + buffer.append(comment.getText()); + + if (iterator.hasNext()) { + buffer.append('\n'); + } + } + + final String completeComment = buffer.toString(); + + emfElement.setComment(completeComment); + + final TComment firstToken = comments.get(0); + final TextRange range = createTextRange(calculateOffset(firstToken + .getLine(), firstToken.getPos()), buffer.length()); + storePosition(completeComment, (EventBElement) emfElement, range); + } + } + + private void storePosition(final String keyString, + final EModelElement emfParent, final TextRange range) { + if (keyString == null) { + return; + } + + if (range != null) { + TextPositionUtil.addInternalPosition(emfParent, keyString, range); + } + } + + private void storePosition(final String keyString, + final EModelElement emfParent, final IToken token) { + storePosition(keyString, emfParent, createTextRange(token)); + } + + private TextRange createTextRange(final IToken token) { + final int offset = calculateOffset(token.getLine(), token.getPos()); + final int length = token.getText().length(); + + return createTextRange(offset, length); + } + + private TextRange createTextRange(final PositionedNode node) { + final int offset = calculateOffset(node.getStartPos()); + final int length = calculateOffset(node.getEndPos()) - offset; + + return createTextRange(offset, length); + } + + private TextRange createTextRange(final int offset, final int length) { + final TextRange range = TexttoolsFactory.eINSTANCE.createTextRange(); + range.setOffset(offset); + range.setLength(length); + + return range; + } + + private int calculateOffset(final int line, final int pos) { + try { + return document.getLineOffset(line - 1) + pos - 1; + } catch (final BadLocationException e) { + // IGNORE and return fallback value + return 0; + } + } + + private int calculateOffset(final SourcePosition position) { + if (position != null) { + return calculateOffset(position.getLine(), position.getPos()); + } + + return 0; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/merge/EventBMatchEngine.java b/org.eventb.texttools/src/org/eventb/texttools/merge/EventBMatchEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..34b9e1186c56cb8aadd563abd10376c587058e2b --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/merge/EventBMatchEngine.java @@ -0,0 +1,172 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.merge; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.compare.FactoryException; +import org.eclipse.emf.compare.match.engine.IMatchEngine; +import org.eclipse.emf.compare.match.engine.GenericMatchEngine; +import org.eclipse.emf.compare.match.internal.statistic.NameSimilarity; +import org.eclipse.emf.ecore.EObject; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.EventBPredicate; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Variant; + +/** + * A {@link IMatchEngine} which matches EventB emf models. The special structure + * of those models is taken into consideration to produce more exact match + * models. + */ +public class EventBMatchEngine extends GenericMatchEngine { + public static final String OPTION_DONT_COMPARE_COMPONENTS = "eventb.dont.compare.components"; + + private static final Map<String, Object> defaultOptions = new HashMap<String, Object>(); + + static { + defaultOptions.put(OPTION_DONT_COMPARE_COMPONENTS, false); + } + + public EventBMatchEngine() { + // needed for Extension Point + } + + public EventBMatchEngine(final Map<String, Object> options) { + this.options.putAll(options); + } + + @Override + public boolean isSimilar(final EObject obj1, final EObject obj2) + throws FactoryException { + /* + * Test if we consider components as match by default + */ + final Boolean dontCompareComponents = getOption(OPTION_DONT_COMPARE_COMPONENTS); + if (dontCompareComponents && areSameComponentType(obj1, obj2)) { + return true; + } + + /* + * Only one variant may exist in a model, so two variants are a match + */ + if (areVariants(obj1, obj2)) { + return true; + } + + /* + * Our models are typed strictly, that means: different type => no match + */ + if (!areSameType(obj1, obj2)) { + return false; + } + + /* + * Improve matching for events with same name + */ + if (obj1 instanceof Event) { + final double similarity = nameSimilarity(obj1, obj2); + if (similarity == 1) { + return true; + } + } + + // otherwise use default comparison of GenericMatchEngine + return super.isSimilar(obj1, obj2); + } + + @Override + protected double nameSimilarity(final EObject obj1, final EObject obj2) { + if (!areSameType(obj1, obj2)) { + return 0d; + } + + return NameSimilarity + .nameSimilarityMetric(getName(obj1), getName(obj2)); + } + + @Override + protected double contentSimilarity(final EObject obj1, final EObject obj2) + throws FactoryException { + if (!areSameType(obj1, obj2)) { + return 0d; + } + + double result = super.contentSimilarity(obj1, obj2); + + if (obj1 instanceof EventBPredicate) { + // give predicate equality extra weight + result = (result + 2 * contentSimilarity((EventBPredicate) obj1, + (EventBPredicate) obj2)) / 3; + } + + return result; + } + + private double contentSimilarity(final EventBPredicate pred1, + final EventBPredicate pred2) { + return NameSimilarity.nameSimilarityMetric(pred1.getPredicate(), pred2 + .getPredicate()); + } + + @SuppressWarnings("unchecked") + @Override + protected <T> T getOption(final String key) throws ClassCastException { + if (options.containsKey(key)) { + return (T) options.get(key); + } else { + return (T) defaultOptions.get(key); + } + } + + private String getName(final EObject object) { + /* + * Make sure correct names for EventBNamed elements are used. + */ + if (object instanceof EventBNamed) { + return ((EventBNamed) object).getName(); + } + + // fallback to default + try { + return NameSimilarity.findName(object); + } catch (final FactoryException e) { + return null; + } + } + + private boolean areSameType(final EObject obj1, final EObject obj2) { + return obj1 != null && obj2 != null + && obj1.eClass().equals(obj2.eClass()); + } + + /** + * Test if both given {@link EObject}s are {@link EventBComponent}s and of + * same type of component, includes <code>null</code> check. + * + * @param obj1 + * @param obj2 + * @return + */ + private boolean areSameComponentType(final EObject obj1, final EObject obj2) { + return areSameType(obj1, obj2) && obj1 instanceof EventBNamedCommentedComponentElement; + } + + /** + * Test if both given {@link EObject}s are of type {@link Variant}, includes + * <code>null</code> check. + * + * @param obj1 + * @param obj2 + * @return + */ + private boolean areVariants(final EObject obj1, final EObject obj2) { + return areSameType(obj1, obj2) && obj2 instanceof Variant; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/merge/MergerProvider.java b/org.eventb.texttools/src/org/eventb/texttools/merge/MergerProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..cdf6e8eec2b230709a53adc1b9ffdb1321641ebc --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/merge/MergerProvider.java @@ -0,0 +1,35 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.merge; + +import java.util.Map; + +import org.eclipse.emf.compare.diff.merge.IMerger; +import org.eclipse.emf.compare.diff.merge.IMergerProvider; +import org.eclipse.emf.compare.diff.metamodel.DiffElement; +import org.eclipse.emf.compare.diff.metamodel.ReferenceChangeRightTarget; +import org.eclipse.emf.compare.util.EMFCompareMap; + +public class MergerProvider implements IMergerProvider { + private Map<Class<? extends DiffElement>, Class<? extends IMerger>> mergerTypes; + + public Map<Class<? extends DiffElement>, Class<? extends IMerger>> getMergers() { + if (mergerTypes == null) { + mergerTypes = new EMFCompareMap<Class<? extends DiffElement>, Class<? extends IMerger>>(); + + mergerTypes.put(ReferenceChangeRightTarget.class, + ReferenceChangeRightTargetMerger.class); + // mergerTypes.put(ReferenceChangeRightTarget.class, + // DefaultMerger.class); + + // TODO maybe we'll need to implement that one too + // mergerTypes.put(ReferenceChangeLeftTarget.class, + // ReferenceChangeLeftTargetMerger.class); + } + return mergerTypes; + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/merge/ModelMerge.java b/org.eventb.texttools/src/org/eventb/texttools/merge/ModelMerge.java new file mode 100644 index 0000000000000000000000000000000000000000..3ca13536b2651e43bcb75a9b38af6834dd7952b0 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/merge/ModelMerge.java @@ -0,0 +1,249 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.merge; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.emf.common.util.BasicEList; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.compare.diff.merge.service.MergeService; +import org.eclipse.emf.compare.diff.metamodel.DiffElement; +import org.eclipse.emf.compare.diff.metamodel.DiffModel; +import org.eclipse.emf.compare.diff.metamodel.ModelElementChangeLeftTarget; +import org.eclipse.emf.compare.diff.service.DiffService; +import org.eclipse.emf.compare.match.MatchOptions; +import org.eclipse.emf.compare.match.engine.IMatchEngine; +import org.eclipse.emf.compare.match.metamodel.MatchModel; +import org.eclipse.emf.compare.match.service.MatchService; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl; +import org.eventb.emf.core.EventBNamedCommentedComponentElement; +import org.eventb.emf.core.Extension; +import org.eventb.emf.formulas.BFormula; +import org.eventb.texttools.PersistenceHelper; +import org.eventb.texttools.TextPositionUtil; + +/** + * <p> + * This class helps merging an core model instance which has been created by the + * parser back into the original model instance which was created from the + * RodinDB. + * </p> + * <p> + * It takes the original version (old version or left version) and a new version + * (right version) and merges the right into the left. This means it takes all + * changes from the right and applies them to the left. During this process it + * preserves those elements that are not part of the core model and have been + * added as {@link Extension}s or {@link EAnnotation}s. + * </p> + */ +public class ModelMerge { + private final EventBNamedCommentedComponentElement oldVersion; + private final EventBNamedCommentedComponentElement newVersion; + private final IResource resource; + private final Map<String, Object> matchOptions; + + public ModelMerge(final EventBNamedCommentedComponentElement oldVersion, + final EventBNamedCommentedComponentElement newVersion) { + this.oldVersion = oldVersion; + this.newVersion = newVersion; + + resource = PersistenceHelper.getIResource(oldVersion.eResource()); + + /* + * Configure the matching process: We want to match elements in the + * model by their similarity, so we ignore any IDs. + */ + matchOptions = new HashMap<String, Object>(); + matchOptions.put(MatchOptions.OPTION_IGNORE_ID, true); + matchOptions.put(MatchOptions.OPTION_IGNORE_XMI_ID, true); + matchOptions + .put(EventBMatchEngine.OPTION_DONT_COMPARE_COMPONENTS, true); + } + + /** + * Compares the two model versions that have been given to the constructor + * and merges all changes in the new version into the old version of the + * model. For this process certain changes are ignored: + * <ul> + * <li>RodinInternalAnnotations</li> + * <li>{@link Extension}s which are not {@link BFormula}s (they are changed + * by the text tools parser)</li> + * </ul> + * + * @throws InterruptedException + */ + public void applyChanges(final IProgressMonitor monitor) + throws InterruptedException { + final SubMonitor subMonitor = SubMonitor.convert(monitor, + "Analyzing model changes", 4); + + final IMatchEngine matchEngine = MatchService + .getBestMatchEngine(resource.getFileExtension()); + + // Workaround to make sure the models have an associated resource + // See setResourceFile() for Bug info + + // First find the file extension + String path = oldVersion.eResource().getURI().path(); + String extension = path.substring(path.lastIndexOf('.')); + File tmpFileNew = null; + + if (newVersion.eResource() == null) { + tmpFileNew = setResourceFile(newVersion, extension); + } + + matchOptions.put(MatchOptions.OPTION_PROGRESS_MONITOR, subMonitor + .newChild(1)); + final MatchModel matchModel = matchEngine.contentMatch(oldVersion, + newVersion, matchOptions); + + final DiffModel diff = getDiffModel(matchModel, subMonitor.newChild(2)); + final EList<DiffElement> ownedElements = diff.getOwnedElements(); + + if (ownedElements.size() > 0) { + // MergeService + // .merge(new ArrayList<DiffElement>(ownedElements), false); + + MergeService.merge(ownedElements, false); + + matchOptions.put(MatchOptions.OPTION_PROGRESS_MONITOR, subMonitor + .newChild(1)); + subMonitor.worked(1); + } + + // cleanup tmp files + if (tmpFileNew != null) { + EcoreUtil.remove(newVersion); + tmpFileNew.delete(); + } + } + + /** + * This method exists to work around a bug in EMF Compare 1.0.1. The compare + * framework only works if a model has an associated resource. see + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=258703 + */ + private File setResourceFile(EventBNamedCommentedComponentElement element, + String extension) { + try { + File tmpFile = File.createTempFile("camille-", extension); + tmpFile.deleteOnExit(); + URI uri = URI.createFileURI(tmpFile.getAbsolutePath()); + Resource resource = new XMLResourceImpl(uri); + resource.getContents().add(element); + return tmpFile; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Do the diff for the {@link MatchModel} and removed ignored elements + * afterwards. + * + * @param matchModel + * @param monitor + * @return + */ + private DiffModel getDiffModel(final MatchModel matchModel, + final IProgressMonitor monitor) { + final SubMonitor subMonitor = SubMonitor.convert(monitor, 2); + final DiffModel diff = DiffService.doDiff(matchModel); + subMonitor.worked(1); + + final EList<DiffElement> ownedElements = diff.getOwnedElements(); + final EList<DiffElement> cleanedDiffs = cleanDiffElements(ownedElements); + subMonitor.worked(1); + + ownedElements.clear(); + ownedElements.addAll(cleanedDiffs); + + return diff; + } + + /** + * Recursively remove all ignored diff elements. + * + * @see #isIgnoredDiff(DiffElement) + * @param elements + * @return + */ + private EList<DiffElement> cleanDiffElements( + final EList<DiffElement> elements) { + final EList<DiffElement> result = new BasicEList<DiffElement>(); + + if (elements != null) { + for (final DiffElement diffElement : elements) { + if (!isIgnoredDiff(diffElement)) { + // add this diff element to result + result.add(diffElement); + + // continue clean recursively + final EList<DiffElement> subDiffElements = diffElement + .getSubDiffElements(); + final EList<DiffElement> subResult = cleanDiffElements(subDiffElements); + subDiffElements.clear(); + subDiffElements.addAll(subResult); + } + } + } + + return result; + } + + /** + * Returns whether the given {@link DiffElement} should be ignored when + * applying the diff. + * + * @param diffElement + * @return + */ + private boolean isIgnoredDiff(final DiffElement diffElement) { + if (diffElement instanceof ModelElementChangeLeftTarget) { + /* + * EMF elements of the original model (left) show up as + * RemoveModelElement diffs when they are missing in the new version + * (right). If we don't want to remove them for the merged version + * we just ignore the diff element. + */ + final ModelElementChangeLeftTarget removedDiff = (ModelElementChangeLeftTarget) diffElement; + final EObject leftElement = removedDiff.getLeftElement(); + + /* + * The original model may contain arbitrary annotations which we + * didn't create or handle. + */ + if (leftElement instanceof EAnnotation + && !TextPositionUtil.ANNOTATION_TEXTRANGE + .equals(((EAnnotation) leftElement).getSource())) { + return true; + } + + /* + * Ignore all Extensions but our own, i.e. BFormula + */ + if (leftElement instanceof Extension + && !(leftElement instanceof BFormula)) { + return true; + } + } + + return false; + } + +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/merge/ReferenceChangeRightTargetMerger.java b/org.eventb.texttools/src/org/eventb/texttools/merge/ReferenceChangeRightTargetMerger.java new file mode 100644 index 0000000000000000000000000000000000000000..0d2e93c1596a7e8a8f9da1999054af0427c08a8c --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/merge/ReferenceChangeRightTargetMerger.java @@ -0,0 +1,204 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.merge; + +import java.util.Iterator; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.compare.EMFComparePlugin; +import org.eclipse.emf.compare.FactoryException; +import org.eclipse.emf.compare.diff.merge.DefaultMerger; +import org.eclipse.emf.compare.diff.merge.service.MergeService; +import org.eclipse.emf.compare.diff.metamodel.DiffElement; +import org.eclipse.emf.compare.diff.metamodel.ReferenceChangeRightTarget; +import org.eclipse.emf.compare.util.EFactory; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eventb.emf.core.EventBElement; +import org.eventb.emf.core.EventBNamedCommentedElement; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Machine; + +public class ReferenceChangeRightTargetMerger extends DefaultMerger { + @Override + public void applyInOrigin() { + final ReferenceChangeRightTarget theDiff = (ReferenceChangeRightTarget) diff; + final EObject element = theDiff.getLeftElement(); + final EReference reference = theDiff.getReference(); + + final EObject leftTarget = theDiff.getLeftTarget(); + EObject rightTarget = theDiff.getRightTarget(); + + + if (leftTarget instanceof Machine || rightTarget instanceof Machine) { + // only case: refines attribute of a machine + copyMachineRef((Machine) element, (Machine) leftTarget, + (Machine) rightTarget); + } else if (leftTarget instanceof Context + || rightTarget instanceof Context) { + copyContextRef(element, (Context) leftTarget, (Context) rightTarget); + } else if (leftTarget instanceof Event || rightTarget instanceof Event) { + copyEventRef((Event) element, (Event) leftTarget, + (Event) rightTarget); + } else { + /* + * Default implementation copied from + * ReferenceChangeRightTargetMerger (in internal package of + * compare), leave as is... + */ + MergeService.getCopier(diff).copyReferenceValue(reference, element, + rightTarget, leftTarget); + + // We'll now look through this reference's eOpposite as they are + // already + // taken care of + final Iterator<EObject> siblings = getDiffModel().eAllContents(); + while (siblings.hasNext()) { + final DiffElement op = (DiffElement) siblings.next(); + if (op instanceof ReferenceChangeRightTarget) { + final ReferenceChangeRightTarget link = (ReferenceChangeRightTarget) op; + // If this is my eOpposite, delete it from the DiffModel + // (merged + // along with this one) + if (link.getReference().equals(reference.getEOpposite()) + && link.getLeftTarget().equals(element)) { + removeFromContainer(link); + } + } + } + } + + super.applyInOrigin(); + } + + private void printInfo(String label, EObject target, EObject parent) { + System.out.println("====== " + label + " ========"); + if (!(target instanceof EventBNamedCommentedElement)) { + System.out.println(" Target not analysed: " + target); + + } else { + EventBNamedCommentedElement eventTarget = (EventBNamedCommentedElement) target; + System.out.println(" Target: " + eventTarget.doGetName() + + " (proxy: " + eventTarget.eIsProxy() + ")"); + if (target instanceof Event) { + System.out.println(" Refines: " + + ((Event) target).getRefinesNames()); + } + System.out.println(" Resource: " + eventTarget.eResource()); + } + + if (!(parent instanceof EventBNamedCommentedElement)) { + System.out.println(" Element not analysed: " + parent); + + } else { + EventBNamedCommentedElement eventElement = (EventBNamedCommentedElement) parent; + System.out.println(" Element: " + eventElement.doGetName() + + " (proxy: " + eventElement.eIsProxy() + ")"); + if (parent instanceof Event) { + System.out.println(" Refines: " + + ((Event) parent).getRefinesNames()); + } + System.out.println(" Resource: " + eventElement.eResource()); + } + } + + private void copyMachineRef(final Machine leftParent, + final Machine leftTarget, final Machine rightTarget) { + if (leftTarget != null && leftTarget.eIsProxy()) { + // simply rename in proxy + leftTarget.setName(rightTarget.getName()); + } else { + replaceInList(leftParent.getRefines(), leftTarget, rightTarget); + } + } + + private void copyEventRef(final Event leftParent, final Event leftTarget, + final Event rightTarget) { + if (leftTarget != null && leftTarget.eIsProxy()) { + // simply rename in proxy + leftTarget.setName(rightTarget.getName()); + } else { + replaceInList(leftParent.getRefines(), leftTarget, rightTarget); + } + } + + private void copyContextRef(final EObject leftParent, + final Context leftTarget, final Context rightTarget) { + if (leftTarget != null && leftTarget.eIsProxy()) { + // simply rename proxy + leftTarget.setName(rightTarget.getName()); + } else { + if (leftParent instanceof Machine) { + // references is a sees in a machine + replaceInList(((Machine) leftParent).getSees(), leftTarget, + rightTarget); + } else if (leftParent instanceof Context) { + // references is a extends in context + replaceInList(((Context) leftParent).getExtends(), leftTarget, + rightTarget); + } + } + } + + static <T extends EventBElement> void replaceInList( + final EList<T> refinesList, final T leftTarget, final T rightTarget) { + EObject newTarget = EcoreUtil.copy(rightTarget); + if (leftTarget != null) { + // search old position and replace + for (int i = 0; i < refinesList.size(); i++) { + if (refinesList.get(i) == leftTarget) { + refinesList.set(i, (T) newTarget); + break; + } + } + } else { + // just add reference + // TODO Do we need to care about the position? + refinesList.add((T) newTarget); + } + } + + @Override + public void undoInTarget() { + final ReferenceChangeRightTarget theDiff = (ReferenceChangeRightTarget) diff; + final EObject element = theDiff.getRightElement(); + final EObject rightTarget = theDiff.getRightTarget(); + + /* + * Default implementation copied from ReferenceChangeRightTargetMerger + * (in internal package of compare), leave as is... + */ + try { + EFactory.eRemove(element, theDiff.getReference().getName(), + rightTarget); + } catch (final FactoryException e) { + EMFComparePlugin.log(e, true); + } + + // we should now have a look for AddReferencesLinks needing this object + final Iterator<EObject> siblings = getDiffModel().eAllContents(); + while (siblings.hasNext()) { + final DiffElement op = (DiffElement) siblings.next(); + if (op instanceof ReferenceChangeRightTarget) { + final ReferenceChangeRightTarget link = (ReferenceChangeRightTarget) op; + // now if I'm in the target References I should put my copy in + // the origin + if (link.getReference().equals( + theDiff.getReference().getEOpposite()) + && link.getRightTarget().equals(element)) { + removeFromContainer(link); + } + } + } + + super.undoInTarget(); + } + +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TextRange.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TextRange.java new file mode 100644 index 0000000000000000000000000000000000000000..ae33b9d3852254f43f93b831e87fa6e21cd7b655 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TextRange.java @@ -0,0 +1,118 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools; + +import java.util.Map; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; + +/** + * <!-- begin-user-doc --> + * A representation of the model object '<em><b>Text Range</b></em>'. + * <!-- end-user-doc --> + * + * <p> + * The following features are supported: + * <ul> + * <li>{@link org.eventb.texttools.model.texttools.TextRange#getOffset <em>Offset</em>}</li> + * <li>{@link org.eventb.texttools.model.texttools.TextRange#getLength <em>Length</em>}</li> + * <li>{@link org.eventb.texttools.model.texttools.TextRange#getSubTextRanges <em>Sub Text Ranges</em>}</li> + * </ul> + * </p> + * + * @see org.eventb.texttools.model.texttools.TexttoolsPackage#getTextRange() + * @model + * @generated + */ +public interface TextRange extends EObject { + /** + * Returns the value of the '<em><b>Offset</b></em>' attribute. + * The default value is <code>"0"</code>. + * <!-- begin-user-doc --> + * <p> + * If the meaning of the '<em>Offset</em>' attribute isn't clear, + * there really should be more of a description here... + * </p> + * <!-- end-user-doc --> + * @return the value of the '<em>Offset</em>' attribute. + * @see #setOffset(int) + * @see org.eventb.texttools.model.texttools.TexttoolsPackage#getTextRange_Offset() + * @model default="0" required="true" transient="true" + * @generated + */ + int getOffset(); + + /** + * Sets the value of the '{@link org.eventb.texttools.model.texttools.TextRange#getOffset <em>Offset</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @param value the new value of the '<em>Offset</em>' attribute. + * @see #getOffset() + * @generated + */ + void setOffset(int value); + + /** + * Returns the value of the '<em><b>Length</b></em>' attribute. + * The default value is <code>"0"</code>. + * <!-- begin-user-doc --> + * <p> + * If the meaning of the '<em>Length</em>' attribute isn't clear, + * there really should be more of a description here... + * </p> + * <!-- end-user-doc --> + * @return the value of the '<em>Length</em>' attribute. + * @see #setLength(int) + * @see org.eventb.texttools.model.texttools.TexttoolsPackage#getTextRange_Length() + * @model default="0" required="true" transient="true" + * @generated + */ + int getLength(); + + /** + * Sets the value of the '{@link org.eventb.texttools.model.texttools.TextRange#getLength <em>Length</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @param value the new value of the '<em>Length</em>' attribute. + * @see #getLength() + * @generated + */ + void setLength(int value); + + /** + * Returns the value of the '<em><b>Sub Text Ranges</b></em>' attribute. + * <!-- begin-user-doc --> + * <p> + * If the meaning of the '<em>Sub Text Ranges</em>' attribute isn't clear, + * there really should be more of a description here... + * </p> + * <!-- end-user-doc --> + * @return the value of the '<em>Sub Text Ranges</em>' attribute. + * @see #setSubTextRanges(Map) + * @see org.eventb.texttools.model.texttools.TexttoolsPackage#getTextRange_SubTextRanges() + * @model required="true" transient="true" + * @generated + */ + Map<String, TextRange> getSubTextRanges(); + + /** + * Sets the value of the '{@link org.eventb.texttools.model.texttools.TextRange#getSubTextRanges <em>Sub Text Ranges</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @param value the new value of the '<em>Sub Text Ranges</em>' attribute. + * @see #getSubTextRanges() + * @generated + */ + void setSubTextRanges(Map<String, TextRange> value); + +} // TextRange diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsFactory.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b2c2dd968cd45ac06a4212cb2b5d998fd98ff60b --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsFactory.java @@ -0,0 +1,52 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools; + +import org.eclipse.emf.ecore.EFactory; + +/** + * <!-- begin-user-doc --> + * The <b>Factory</b> for the model. + * It provides a create method for each non-abstract class of the model. + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.TexttoolsPackage + * @generated + */ +public interface TexttoolsFactory extends EFactory { + /** + * The singleton instance of the factory. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + TexttoolsFactory eINSTANCE = org.eventb.texttools.model.texttools.impl.TexttoolsFactoryImpl.init(); + + /** + * Returns a new object of class '<em>Text Range</em>'. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return a new object of class '<em>Text Range</em>'. + * @generated + */ + TextRange createTextRange(); + + /** + * Returns the package supported by this factory. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the package supported by this factory. + * @generated + */ + TexttoolsPackage getTexttoolsPackage(); + +} //TexttoolsFactory diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsPackage.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsPackage.java new file mode 100644 index 0000000000000000000000000000000000000000..d8d82d8769478b9e93f04f0920b32bc27bfc4ba3 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/TexttoolsPackage.java @@ -0,0 +1,216 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcorePackage; + +/** + * <!-- begin-user-doc --> + * The <b>Package</b> for the model. + * It contains accessors for the meta objects to represent + * <ul> + * <li>each class,</li> + * <li>each feature of each class,</li> + * <li>each enum,</li> + * <li>and each data type</li> + * </ul> + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.TexttoolsFactory + * @model kind="package" + * @generated + */ +public interface TexttoolsPackage extends EPackage { + /** + * The package name. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + String eNAME = "texttools"; + + /** + * The package namespace URI. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + String eNS_URI = "http://emf.eventb.org/models/core/texttools"; + + /** + * The package namespace name. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + String eNS_PREFIX = "texttools"; + + /** + * The singleton instance of the package. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + TexttoolsPackage eINSTANCE = org.eventb.texttools.model.texttools.impl.TexttoolsPackageImpl.init(); + + /** + * The meta object id for the '{@link org.eventb.texttools.model.texttools.impl.TextRangeImpl <em>Text Range</em>}' class. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.impl.TextRangeImpl + * @see org.eventb.texttools.model.texttools.impl.TexttoolsPackageImpl#getTextRange() + * @generated + */ + int TEXT_RANGE = 0; + + /** + * The feature id for the '<em><b>Offset</b></em>' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + * @ordered + */ + int TEXT_RANGE__OFFSET = 0; + + /** + * The feature id for the '<em><b>Length</b></em>' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + * @ordered + */ + int TEXT_RANGE__LENGTH = 1; + + /** + * The feature id for the '<em><b>Sub Text Ranges</b></em>' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + * @ordered + */ + int TEXT_RANGE__SUB_TEXT_RANGES = 2; + + /** + * The number of structural features of the '<em>Text Range</em>' class. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + * @ordered + */ + int TEXT_RANGE_FEATURE_COUNT = 3; + + + /** + * Returns the meta object for class '{@link org.eventb.texttools.model.texttools.TextRange <em>Text Range</em>}'. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the meta object for class '<em>Text Range</em>'. + * @see org.eventb.texttools.model.texttools.TextRange + * @generated + */ + EClass getTextRange(); + + /** + * Returns the meta object for the attribute '{@link org.eventb.texttools.model.texttools.TextRange#getOffset <em>Offset</em>}'. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the meta object for the attribute '<em>Offset</em>'. + * @see org.eventb.texttools.model.texttools.TextRange#getOffset() + * @see #getTextRange() + * @generated + */ + EAttribute getTextRange_Offset(); + + /** + * Returns the meta object for the attribute '{@link org.eventb.texttools.model.texttools.TextRange#getLength <em>Length</em>}'. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the meta object for the attribute '<em>Length</em>'. + * @see org.eventb.texttools.model.texttools.TextRange#getLength() + * @see #getTextRange() + * @generated + */ + EAttribute getTextRange_Length(); + + /** + * Returns the meta object for the attribute '{@link org.eventb.texttools.model.texttools.TextRange#getSubTextRanges <em>Sub Text Ranges</em>}'. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the meta object for the attribute '<em>Sub Text Ranges</em>'. + * @see org.eventb.texttools.model.texttools.TextRange#getSubTextRanges() + * @see #getTextRange() + * @generated + */ + EAttribute getTextRange_SubTextRanges(); + + /** + * Returns the factory that creates the instances of the model. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the factory that creates the instances of the model. + * @generated + */ + TexttoolsFactory getTexttoolsFactory(); + + /** + * <!-- begin-user-doc --> + * Defines literals for the meta objects that represent + * <ul> + * <li>each class,</li> + * <li>each feature of each class,</li> + * <li>each enum,</li> + * <li>and each data type</li> + * </ul> + * <!-- end-user-doc --> + * @generated + */ + interface Literals { + /** + * The meta object literal for the '{@link org.eventb.texttools.model.texttools.impl.TextRangeImpl <em>Text Range</em>}' class. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.impl.TextRangeImpl + * @see org.eventb.texttools.model.texttools.impl.TexttoolsPackageImpl#getTextRange() + * @generated + */ + EClass TEXT_RANGE = eINSTANCE.getTextRange(); + + /** + * The meta object literal for the '<em><b>Offset</b></em>' attribute feature. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + EAttribute TEXT_RANGE__OFFSET = eINSTANCE.getTextRange_Offset(); + + /** + * The meta object literal for the '<em><b>Length</b></em>' attribute feature. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + EAttribute TEXT_RANGE__LENGTH = eINSTANCE.getTextRange_Length(); + + /** + * The meta object literal for the '<em><b>Sub Text Ranges</b></em>' attribute feature. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + EAttribute TEXT_RANGE__SUB_TEXT_RANGES = eINSTANCE.getTextRange_SubTextRanges(); + + } + +} //TexttoolsPackage diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TextRangeImpl.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TextRangeImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a685618972c0273b35fae6e378eee753427e98c2 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TextRangeImpl.java @@ -0,0 +1,275 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools.impl; + +import java.util.Map; +import org.eclipse.emf.common.notify.Notification; + +import org.eclipse.emf.ecore.EClass; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.impl.EModelElementImpl; +import org.eclipse.emf.ecore.impl.ENotificationImpl; +import org.eclipse.emf.ecore.impl.EObjectImpl; + +import org.eventb.texttools.model.texttools.TextRange; +import org.eventb.texttools.model.texttools.TexttoolsPackage; + +/** + * <!-- begin-user-doc --> + * An implementation of the model object '<em><b>Text Range</b></em>'. + * <!-- end-user-doc --> + * <p> + * The following features are implemented: + * <ul> + * <li>{@link org.eventb.texttools.model.texttools.impl.TextRangeImpl#getOffset <em>Offset</em>}</li> + * <li>{@link org.eventb.texttools.model.texttools.impl.TextRangeImpl#getLength <em>Length</em>}</li> + * <li>{@link org.eventb.texttools.model.texttools.impl.TextRangeImpl#getSubTextRanges <em>Sub Text Ranges</em>}</li> + * </ul> + * </p> + * + * @generated + */ +public class TextRangeImpl extends EObjectImpl implements TextRange { + /** + * The default value of the '{@link #getOffset() <em>Offset</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #getOffset() + * @generated + * @ordered + */ + protected static final int OFFSET_EDEFAULT = 0; + + /** + * The cached value of the '{@link #getOffset() <em>Offset</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #getOffset() + * @generated + * @ordered + */ + protected int offset = OFFSET_EDEFAULT; + + /** + * The default value of the '{@link #getLength() <em>Length</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #getLength() + * @generated + * @ordered + */ + protected static final int LENGTH_EDEFAULT = 0; + + /** + * The cached value of the '{@link #getLength() <em>Length</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #getLength() + * @generated + * @ordered + */ + protected int length = LENGTH_EDEFAULT; + + /** + * The cached value of the '{@link #getSubTextRanges() <em>Sub Text Ranges</em>}' attribute. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #getSubTextRanges() + * @generated + * @ordered + */ + protected Map<String, TextRange> subTextRanges; + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + protected TextRangeImpl() { + super(); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + protected EClass eStaticClass() { + return TexttoolsPackage.Literals.TEXT_RANGE; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public int getOffset() { + return offset; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public void setOffset(int newOffset) { + int oldOffset = offset; + offset = newOffset; + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, TexttoolsPackage.TEXT_RANGE__OFFSET, oldOffset, offset)); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public int getLength() { + return length; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public void setLength(int newLength) { + int oldLength = length; + length = newLength; + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, TexttoolsPackage.TEXT_RANGE__LENGTH, oldLength, length)); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public Map<String, TextRange> getSubTextRanges() { + return subTextRanges; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public void setSubTextRanges(Map<String, TextRange> newSubTextRanges) { + Map<String, TextRange> oldSubTextRanges = subTextRanges; + subTextRanges = newSubTextRanges; + if (eNotificationRequired()) + eNotify(new ENotificationImpl(this, Notification.SET, TexttoolsPackage.TEXT_RANGE__SUB_TEXT_RANGES, oldSubTextRanges, subTextRanges)); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + public Object eGet(int featureID, boolean resolve, boolean coreType) { + switch (featureID) { + case TexttoolsPackage.TEXT_RANGE__OFFSET: + return new Integer(getOffset()); + case TexttoolsPackage.TEXT_RANGE__LENGTH: + return new Integer(getLength()); + case TexttoolsPackage.TEXT_RANGE__SUB_TEXT_RANGES: + return getSubTextRanges(); + } + return super.eGet(featureID, resolve, coreType); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @SuppressWarnings("unchecked") + @Override + public void eSet(int featureID, Object newValue) { + switch (featureID) { + case TexttoolsPackage.TEXT_RANGE__OFFSET: + setOffset(((Integer)newValue).intValue()); + return; + case TexttoolsPackage.TEXT_RANGE__LENGTH: + setLength(((Integer)newValue).intValue()); + return; + case TexttoolsPackage.TEXT_RANGE__SUB_TEXT_RANGES: + setSubTextRanges((Map<String, TextRange>)newValue); + return; + } + super.eSet(featureID, newValue); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + public void eUnset(int featureID) { + switch (featureID) { + case TexttoolsPackage.TEXT_RANGE__OFFSET: + setOffset(OFFSET_EDEFAULT); + return; + case TexttoolsPackage.TEXT_RANGE__LENGTH: + setLength(LENGTH_EDEFAULT); + return; + case TexttoolsPackage.TEXT_RANGE__SUB_TEXT_RANGES: + setSubTextRanges((Map<String, TextRange>)null); + return; + } + super.eUnset(featureID); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + public boolean eIsSet(int featureID) { + switch (featureID) { + case TexttoolsPackage.TEXT_RANGE__OFFSET: + return offset != OFFSET_EDEFAULT; + case TexttoolsPackage.TEXT_RANGE__LENGTH: + return length != LENGTH_EDEFAULT; + case TexttoolsPackage.TEXT_RANGE__SUB_TEXT_RANGES: + return subTextRanges != null; + } + return super.eIsSet(featureID); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + public String toString() { + if (eIsProxy()) return super.toString(); + + StringBuffer result = new StringBuffer(super.toString()); + result.append(" (offset: "); + result.append(offset); + result.append(", length: "); + result.append(length); + result.append(", subTextRanges: "); + result.append(subTextRanges); + result.append(')'); + return result.toString(); + } + +} //TextRangeImpl diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsFactoryImpl.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..aeabcaa60ed48925b0599fe2d1136de00628d3d8 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsFactoryImpl.java @@ -0,0 +1,105 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools.impl; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; + +import org.eclipse.emf.ecore.impl.EFactoryImpl; + +import org.eclipse.emf.ecore.plugin.EcorePlugin; + +import org.eventb.texttools.model.texttools.*; + +/** + * <!-- begin-user-doc --> + * An implementation of the model <b>Factory</b>. + * <!-- end-user-doc --> + * @generated + */ +public class TexttoolsFactoryImpl extends EFactoryImpl implements TexttoolsFactory { + /** + * Creates the default factory implementation. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public static TexttoolsFactory init() { + try { + TexttoolsFactory theTexttoolsFactory = (TexttoolsFactory)EPackage.Registry.INSTANCE.getEFactory("http://emf.eventb.org/models/core/texttools"); + if (theTexttoolsFactory != null) { + return theTexttoolsFactory; + } + } + catch (Exception exception) { + EcorePlugin.INSTANCE.log(exception); + } + return new TexttoolsFactoryImpl(); + } + + /** + * Creates an instance of the factory. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TexttoolsFactoryImpl() { + super(); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + @Override + public EObject create(EClass eClass) { + switch (eClass.getClassifierID()) { + case TexttoolsPackage.TEXT_RANGE: return createTextRange(); + default: + throw new IllegalArgumentException("The class '" + eClass.getName() + "' is not a valid classifier"); + } + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TextRange createTextRange() { + TextRangeImpl textRange = new TextRangeImpl(); + return textRange; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TexttoolsPackage getTexttoolsPackage() { + return (TexttoolsPackage)getEPackage(); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @deprecated + * @generated + */ + @Deprecated + public static TexttoolsPackage getPackage() { + return TexttoolsPackage.eINSTANCE; + } + +} //TexttoolsFactoryImpl diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsPackageImpl.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsPackageImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..46dee7819872c118d46a93f48caecb067523c855 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/impl/TexttoolsPackageImpl.java @@ -0,0 +1,222 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools.impl; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EGenericType; +import org.eclipse.emf.ecore.EPackage; + +import org.eclipse.emf.ecore.impl.EPackageImpl; + +import org.eventb.texttools.model.texttools.TextRange; +import org.eventb.texttools.model.texttools.TexttoolsFactory; +import org.eventb.texttools.model.texttools.TexttoolsPackage; + +/** + * <!-- begin-user-doc --> + * An implementation of the model <b>Package</b>. + * <!-- end-user-doc --> + * @generated + */ +public class TexttoolsPackageImpl extends EPackageImpl implements TexttoolsPackage { + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + private EClass textRangeEClass = null; + + /** + * Creates an instance of the model <b>Package</b>, registered with + * {@link org.eclipse.emf.ecore.EPackage.Registry EPackage.Registry} by the package + * package URI value. + * <p>Note: the correct way to create the package is via the static + * factory method {@link #init init()}, which also performs + * initialization of the package, or returns the registered package, + * if one already exists. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see org.eclipse.emf.ecore.EPackage.Registry + * @see org.eventb.texttools.model.texttools.TexttoolsPackage#eNS_URI + * @see #init() + * @generated + */ + private TexttoolsPackageImpl() { + super(eNS_URI, TexttoolsFactory.eINSTANCE); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + private static boolean isInited = false; + + /** + * Creates, registers, and initializes the <b>Package</b> for this + * model, and for any others upon which it depends. Simple + * dependencies are satisfied by calling this method on all + * dependent packages before doing anything else. This method drives + * initialization for interdependent packages directly, in parallel + * with this package, itself. + * <p>Of this package and its interdependencies, all packages which + * have not yet been registered by their URI values are first created + * and registered. The packages are then initialized in two steps: + * meta-model objects for all of the packages are created before any + * are initialized, since one package's meta-model objects may refer to + * those of another. + * <p>Invocation of this method will not affect any packages that have + * already been initialized. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @see #eNS_URI + * @see #createPackageContents() + * @see #initializePackageContents() + * @generated + */ + public static TexttoolsPackage init() { + if (isInited) return (TexttoolsPackage)EPackage.Registry.INSTANCE.getEPackage(TexttoolsPackage.eNS_URI); + + // Obtain or create and register package + TexttoolsPackageImpl theTexttoolsPackage = (TexttoolsPackageImpl)(EPackage.Registry.INSTANCE.getEPackage(eNS_URI) instanceof TexttoolsPackageImpl ? EPackage.Registry.INSTANCE.getEPackage(eNS_URI) : new TexttoolsPackageImpl()); + + isInited = true; + + // Create package meta-data objects + theTexttoolsPackage.createPackageContents(); + + // Initialize created meta-data + theTexttoolsPackage.initializePackageContents(); + + // Mark meta-data to indicate it can't be changed + theTexttoolsPackage.freeze(); + + return theTexttoolsPackage; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public EClass getTextRange() { + return textRangeEClass; + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public EAttribute getTextRange_Offset() { + return (EAttribute)textRangeEClass.getEStructuralFeatures().get(0); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public EAttribute getTextRange_Length() { + return (EAttribute)textRangeEClass.getEStructuralFeatures().get(1); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public EAttribute getTextRange_SubTextRanges() { + return (EAttribute)textRangeEClass.getEStructuralFeatures().get(2); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TexttoolsFactory getTexttoolsFactory() { + return (TexttoolsFactory)getEFactoryInstance(); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + private boolean isCreated = false; + + /** + * Creates the meta-model objects for the package. This method is + * guarded to have no affect on any invocation but its first. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public void createPackageContents() { + if (isCreated) return; + isCreated = true; + + // Create classes and their features + textRangeEClass = createEClass(TEXT_RANGE); + createEAttribute(textRangeEClass, TEXT_RANGE__OFFSET); + createEAttribute(textRangeEClass, TEXT_RANGE__LENGTH); + createEAttribute(textRangeEClass, TEXT_RANGE__SUB_TEXT_RANGES); + } + + /** + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + private boolean isInitialized = false; + + /** + * Complete the initialization of the package and its meta-model. This + * method is guarded to have no affect on any invocation but its first. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public void initializePackageContents() { + if (isInitialized) return; + isInitialized = true; + + // Initialize package + setName(eNAME); + setNsPrefix(eNS_PREFIX); + setNsURI(eNS_URI); + + // Create type parameters + + // Set bounds for type parameters + + // Add supertypes to classes + + // Initialize classes and features; add operations and parameters + initEClass(textRangeEClass, TextRange.class, "TextRange", !IS_ABSTRACT, !IS_INTERFACE, IS_GENERATED_INSTANCE_CLASS); + initEAttribute(getTextRange_Offset(), ecorePackage.getEInt(), "offset", "0", 1, 1, TextRange.class, IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + initEAttribute(getTextRange_Length(), ecorePackage.getEInt(), "length", "0", 1, 1, TextRange.class, IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + EGenericType g1 = createEGenericType(ecorePackage.getEMap()); + EGenericType g2 = createEGenericType(ecorePackage.getEString()); + g1.getETypeArguments().add(g2); + g2 = createEGenericType(this.getTextRange()); + g1.getETypeArguments().add(g2); + initEAttribute(getTextRange_SubTextRanges(), g1, "subTextRanges", null, 1, 1, TextRange.class, IS_TRANSIENT, !IS_VOLATILE, IS_CHANGEABLE, !IS_UNSETTABLE, !IS_ID, IS_UNIQUE, !IS_DERIVED, IS_ORDERED); + + // Create resource + createResource(eNS_URI); + } + +} //TexttoolsPackageImpl diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsAdapterFactory.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsAdapterFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2b26fca17cc34d6400bbcebcabb532aecc2729dd --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsAdapterFactory.java @@ -0,0 +1,131 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools.util; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.Notifier; + +import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl; + +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; + +import org.eventb.texttools.model.texttools.*; + +/** + * <!-- begin-user-doc --> + * The <b>Adapter Factory</b> for the model. + * It provides an adapter <code>createXXX</code> method for each class of the model. + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.TexttoolsPackage + * @generated + */ +public class TexttoolsAdapterFactory extends AdapterFactoryImpl { + /** + * The cached model package. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + protected static TexttoolsPackage modelPackage; + + /** + * Creates an instance of the adapter factory. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TexttoolsAdapterFactory() { + if (modelPackage == null) { + modelPackage = TexttoolsPackage.eINSTANCE; + } + } + + /** + * Returns whether this factory is applicable for the type of the object. + * <!-- begin-user-doc --> + * This implementation returns <code>true</code> if the object is either the model's package or is an instance object of the model. + * <!-- end-user-doc --> + * @return whether this factory is applicable for the type of the object. + * @generated + */ + @Override + public boolean isFactoryForType(Object object) { + if (object == modelPackage) { + return true; + } + if (object instanceof EObject) { + return ((EObject)object).eClass().getEPackage() == modelPackage; + } + return false; + } + + /** + * The switch that delegates to the <code>createXXX</code> methods. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + protected TexttoolsSwitch<Adapter> modelSwitch = + new TexttoolsSwitch<Adapter>() { + @Override + public Adapter caseTextRange(TextRange object) { + return createTextRangeAdapter(); + } + @Override + public Adapter defaultCase(EObject object) { + return createEObjectAdapter(); + } + }; + + /** + * Creates an adapter for the <code>target</code>. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @param target the object to adapt. + * @return the adapter for the <code>target</code>. + * @generated + */ + @Override + public Adapter createAdapter(Notifier target) { + return modelSwitch.doSwitch((EObject)target); + } + + + /** + * Creates a new adapter for an object of class '{@link org.eventb.texttools.model.texttools.TextRange <em>Text Range</em>}'. + * <!-- begin-user-doc --> + * This default implementation returns null so that we can easily ignore cases; + * it's useful to ignore a case when inheritance will catch all the cases anyway. + * <!-- end-user-doc --> + * @return the new adapter. + * @see org.eventb.texttools.model.texttools.TextRange + * @generated + */ + public Adapter createTextRangeAdapter() { + return null; + } + + /** + * Creates a new adapter for the default case. + * <!-- begin-user-doc --> + * This default implementation returns null. + * <!-- end-user-doc --> + * @return the new adapter. + * @generated + */ + public Adapter createEObjectAdapter() { + return null; + } + +} //TexttoolsAdapterFactory diff --git a/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsSwitch.java b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..f2acbaecf67acd02b32577282ad41db3cb732d85 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/model/texttools/util/TexttoolsSwitch.java @@ -0,0 +1,137 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +/** + * <copyright> + * </copyright> + * + * $Id$ + */ +package org.eventb.texttools.model.texttools.util; + +import java.util.List; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; + +import org.eventb.texttools.model.texttools.*; + +/** + * <!-- begin-user-doc --> + * The <b>Switch</b> for the model's inheritance hierarchy. + * It supports the call {@link #doSwitch(EObject) doSwitch(object)} + * to invoke the <code>caseXXX</code> method for each class of the model, + * starting with the actual class of the object + * and proceeding up the inheritance hierarchy + * until a non-null result is returned, + * which is the result of the switch. + * <!-- end-user-doc --> + * @see org.eventb.texttools.model.texttools.TexttoolsPackage + * @generated + */ +public class TexttoolsSwitch<T> { + /** + * The cached model package + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + protected static TexttoolsPackage modelPackage; + + /** + * Creates an instance of the switch. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @generated + */ + public TexttoolsSwitch() { + if (modelPackage == null) { + modelPackage = TexttoolsPackage.eINSTANCE; + } + } + + /** + * Calls <code>caseXXX</code> for each class of the model until one returns a non null result; it yields that result. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the first non-null result returned by a <code>caseXXX</code> call. + * @generated + */ + public T doSwitch(EObject theEObject) { + return doSwitch(theEObject.eClass(), theEObject); + } + + /** + * Calls <code>caseXXX</code> for each class of the model until one returns a non null result; it yields that result. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the first non-null result returned by a <code>caseXXX</code> call. + * @generated + */ + protected T doSwitch(EClass theEClass, EObject theEObject) { + if (theEClass.eContainer() == modelPackage) { + return doSwitch(theEClass.getClassifierID(), theEObject); + } + else { + List<EClass> eSuperTypes = theEClass.getESuperTypes(); + return + eSuperTypes.isEmpty() ? + defaultCase(theEObject) : + doSwitch(eSuperTypes.get(0), theEObject); + } + } + + /** + * Calls <code>caseXXX</code> for each class of the model until one returns a non null result; it yields that result. + * <!-- begin-user-doc --> + * <!-- end-user-doc --> + * @return the first non-null result returned by a <code>caseXXX</code> call. + * @generated + */ + protected T doSwitch(int classifierID, EObject theEObject) { + switch (classifierID) { + case TexttoolsPackage.TEXT_RANGE: { + TextRange textRange = (TextRange)theEObject; + T result = caseTextRange(textRange); + if (result == null) result = defaultCase(theEObject); + return result; + } + default: return defaultCase(theEObject); + } + } + + /** + * Returns the result of interpreting the object as an instance of '<em>Text Range</em>'. + * <!-- begin-user-doc --> + * This implementation returns null; + * returning a non-null result will terminate the switch. + * <!-- end-user-doc --> + * @param object the target of the switch. + * @return the result of interpreting the object as an instance of '<em>Text Range</em>'. + * @see #doSwitch(org.eclipse.emf.ecore.EObject) doSwitch(EObject) + * @generated + */ + public T caseTextRange(TextRange object) { + return null; + } + + /** + * Returns the result of interpreting the object as an instance of '<em>EObject</em>'. + * <!-- begin-user-doc --> + * This implementation returns null; + * returning a non-null result will terminate the switch, but this is the last case anyway. + * <!-- end-user-doc --> + * @param object the target of the switch. + * @return the result of interpreting the object as an instance of '<em>EObject</em>'. + * @see #doSwitch(org.eclipse.emf.ecore.EObject) + * @generated + */ + public T defaultCase(EObject object) { + return null; + } + +} //TexttoolsSwitch diff --git a/org.eventb.texttools/src/org/eventb/texttools/prettyprint/ContextPrintSwitch.java b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/ContextPrintSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..099449082ca670547991a08c33d434e9983e5515 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/ContextPrintSwitch.java @@ -0,0 +1,92 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.prettyprint; + +import org.eclipse.emf.common.util.EList; +import org.eventb.emf.core.context.Axiom; +import org.eventb.emf.core.context.Context; +import org.eventb.emf.core.context.util.ContextSwitch; +import org.eventb.texttools.Constants; + +/** + * {@link ContextSwitch} which supports {@link PrettyPrinter}s. This class is + * not intended to be used by clients other than the {@link PrettyPrinter}. + */ +public class ContextPrintSwitch extends ContextSwitch<Boolean> implements + PrettyPrintConstants, Constants { + + private final PrettyPrinter printer; + + protected ContextPrintSwitch(final PrettyPrinter prettyPrinter) { + printer = prettyPrinter; + } + + @Override + public Boolean caseContext(final Context object) { + /* + * Header for this context + */ + printer.appendWithSpace(CONTEXT); + printer.append(object.getName()); + printer.appendComment(object); + + final EList<String> extendsNames = object.getExtendsNames(); + if (extendsNames.size() > 0) { + printer.append(SPACE); + printer.appendWithSpace(EXTENDS); + printer.appendStringList(extendsNames); + } + + printer.appendLineBreak(); + + /* + * Now all context clauses + */ + final boolean newLine = (Boolean) printer.getPreference( + PROP_NEWLINE_BETWEEN_CLAUSES, true); + + // constants + printer.appendNameList(object.getConstants(), CONSTANTS, newLine); + + // sets + printer.appendNameList(object.getSets(), SETS, newLine); + + // axioms + printAxioms(object.getAxioms(), newLine); + + /* + * context footer + */ + printer.adjustIndent(); + printer.appendWithLineBreak(END); + + return true; + } + + @Override + public Boolean caseAxiom(final Axiom object) { + printer.appendLabeledPredicate(object, true); + return true; + } + + private void printAxioms(final EList<Axiom> axioms, final boolean newLine) { + if (axioms.size() > 0) { + if (newLine) { + printer.appendLineBreak(); + } + + printer.appendWithLineBreak(AXIOMS); + printer.increaseIndentLevel(); + + for (final Axiom axiom : axioms) { + doSwitch(axiom); + } + + printer.decreaseIndentLevel(); + } + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/prettyprint/MachinePrintSwitch.java b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/MachinePrintSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..99eb64f8e65bd2e6b18364e9b665f8788df1aa18 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/MachinePrintSwitch.java @@ -0,0 +1,286 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.prettyprint; + +import java.util.Iterator; + +import org.eclipse.emf.common.util.EList; +import org.eventb.emf.core.machine.Action; +import org.eventb.emf.core.machine.Convergence; +import org.eventb.emf.core.machine.Event; +import org.eventb.emf.core.machine.Guard; +import org.eventb.emf.core.machine.Invariant; +import org.eventb.emf.core.machine.Machine; +import org.eventb.emf.core.machine.Variant; +import org.eventb.emf.core.machine.Witness; +import org.eventb.emf.core.machine.util.MachineSwitch; +import org.eventb.texttools.Constants; + +/** + * {@link MachineSwitch} which supports {@link PrettyPrinter}s. This class is + * not intended to be used by clients other than the {@link PrettyPrinter}. + */ +public class MachinePrintSwitch extends MachineSwitch<Boolean> implements + PrettyPrintConstants, Constants { + + private final PrettyPrinter printer; + + protected MachinePrintSwitch(final PrettyPrinter prettyPrinter) { + printer = prettyPrinter; + } + + @Override + public Boolean caseMachine(final Machine object) { + /* + * Header for this machine + */ + printer.appendWithSpace(MACHINE); + printer.append(object.getName()); + printer.appendComment(object); + + final EList<String> refinesNames = object.getRefinesNames(); + if (refinesNames.size() > 0) { + printer.append(SPACE); + printer.appendWithSpace(REFINES); + printer.appendStringList(refinesNames); + } + + final EList<String> seesNames = object.getSeesNames(); + if (seesNames.size() > 0) { + printer.append(SPACE); + printer.appendWithSpace(SEES); + printer.appendStringList(seesNames); + } + + printer.appendLineBreak(); + + /* + * Now all machine clauses + */ + final boolean newLine = (Boolean) printer.getPreference( + PROP_NEWLINE_BETWEEN_CLAUSES, true); + + // variables + printer.appendNameList(object.getVariables(), VARIABLES, newLine); + + // invariants + printInvariants(object.getInvariants(), newLine); + + // variant + final Variant variant = object.getVariant(); + if (variant != null) { + if (newLine) { + printer.appendLineBreak(); + } + doSwitch(variant); + } + + // events + printEvents(object.getEvents(), newLine); + + /* + * machine footer + */ + printer.adjustIndent(); + printer.appendWithLineBreak(END); + + return true; + } + + @Override + public Boolean caseInvariant(final Invariant object) { + printer.appendLabeledPredicate(object, true); + return true; + } + + @Override + public Boolean caseGuard(final Guard object) { + printer.appendLabeledPredicate(object, false); + return true; + } + + @Override + public Boolean caseWitness(final Witness object) { + printer.appendLabeledPredicate(object, false); + return true; + } + + @Override + public Boolean caseAction(final Action object) { + printer.adjustIndent(); + printer.appendLabel(object); + printer.appendFormula(object.getAction(), printer.hasComment(object)); + printer.appendComment(object); + + return true; + } + + @Override + public Boolean caseVariant(final Variant object) { + printer.appendWithSpace(VARIANT); + printer.appendFormula(object.getExpression(), printer + .hasComment(object)); + printer.appendComment(object); + + return true; + } + + @Override + public Boolean caseEvent(final Event object) { + /* + * event header + */ + printer.adjustIndent(); + + // convergence + final Convergence convergence = object.getConvergence(); + if (Convergence.CONVERGENT.equals(convergence) + && (Boolean) printer.getPreference(PROP_PRINT_ORDINARY_KEYWORD, + false)) { + printer.appendWithSpace(ORDINARY); + } else if (Convergence.ANTICIPATED.equals(convergence)) { + printer.appendWithSpace(ANTICIPATED); + } else if (Convergence.CONVERGENT.equals(convergence)) { + printer.appendWithSpace(CONVERGENT); + } + + // 'event' + name + printer.appendWithSpace(EVENT); + printer.append(object.getName()); + printer.appendComment(object); + + // refines / extends + final EList<String> refinesNames = object.getRefinesNames(); + if (refinesNames.size() > 0) { + if (printer.hasComment(object)) { + printer.adjustIndent(); + } else { + printer.append(SPACE); + } + + if (object.isExtended()) { + printer.appendWithSpace(EXTENDS); + } else { + printer.appendWithSpace(REFINES); + } + + printer.appendStringList(refinesNames); + printer.appendLineBreak(); + } + + if (!printer.hasComment(object) && refinesNames.size() == 0) { + printer.appendLineBreak(); + } + + /* + * event body + */ + printer.increaseIndentLevel(); + + // parameters + printer.appendNameList(object.getParameters(), ANY, false); + + // guards + final EList<Guard> guards = object.getGuards(); + if (guards.size() > 0) { + printer.adjustIndent(); + printer.appendWithLineBreak(WHERE); + + printer.increaseIndentLevel(); + for (final Guard guard : guards) { + doSwitch(guard); + } + printer.decreaseIndentLevel(); + } + + // witnesses + final EList<Witness> witnesses = object.getWitnesses(); + if (witnesses.size() > 0) { + printer.adjustIndent(); + printer.appendWithLineBreak(WITH); + + printer.increaseIndentLevel(); + for (final Witness witness : witnesses) { + doSwitch(witness); + } + printer.decreaseIndentLevel(); + } + + // actions + final EList<Action> actions = object.getActions(); + if (actions.size() > 0) { + printer.adjustIndent(); + printer.appendWithLineBreak(THEN); + + printer.increaseIndentLevel(); + for (final Action action : actions) { + doSwitch(action); + } + printer.decreaseIndentLevel(); + } + + /* + * event footer + */ + printer.decreaseIndentLevel(); + printer.adjustIndent(); + printer.appendWithLineBreak(END); + + return true; + } + + private void printInvariants(final EList<Invariant> invariants, + final boolean newLine) { + if (invariants.size() > 0) { + if (newLine) { + printer.appendLineBreak(); + } + + printer.appendWithLineBreak(INVARIANTS); + printer.increaseIndentLevel(); + + for (final Invariant invariant : invariants) { + doSwitch(invariant); + } + + printer.decreaseIndentLevel(); + } + } + + private void printEvents(final EList<Event> events, final boolean newLine) { + if (events.size() > 0) { + if (newLine) { + printer.appendLineBreak(); + } + + printer.appendWithLineBreak(EVENTS); + + final Boolean indentEvents = (Boolean) printer.getPreference( + PROP_INDENT_EVENTS, true); + final Boolean linebreakBetweenEvents = (Boolean) printer + .getPreference(PROP_NEWLINE_BETWEEN_EVENTS, true); + + if (indentEvents) { + printer.increaseIndentLevel(); + } + + for (final Iterator<Event> iterator = events.iterator(); iterator + .hasNext();) { + final Event event = iterator.next(); + doSwitch(event); + + if (linebreakBetweenEvents && iterator.hasNext()) { + printer.appendLineBreak(); + } + } + + if (indentEvents) { + printer.decreaseIndentLevel(); + } + } + } +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrintConstants.java b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrintConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..75f7cb10d30bfafae0a2ef8d91b0381df08ac0d2 --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrintConstants.java @@ -0,0 +1,24 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.prettyprint; + +public interface PrettyPrintConstants { + public static final String PROP_INDENT_DEPTH = "indentation.depth"; + public static final String PROP_USE_TABS_FOR_INDENTATION = "use.tabs.for.indentation"; + public static final String PROP_TAB_WIDTH = "tab.width"; + public static final String PROP_INDENT_EVENTS = "indent.events"; + public static final String PROP_NEWLINE_BETWEEN_CLAUSES = "newline.between.clauses"; + public static final String PROP_NEWLINE_BETWEEN_EVENTS = "newline.between.events"; + public static final String PROP_PRINT_ORDINARY_KEYWORD = "print.ordinary.keyword"; + + public static final char SPACE = ' '; + public static final char TAB = '\t'; + public static final char AT = '@'; + public static final String COMMENT_SINGLELINE_BEGIN = "// "; + public static final String COMMENT_MULTILINE_BEGIN = "/* "; + public static final String COMMENT_MULTILINE_END = " */"; +} diff --git a/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrinter.java b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrinter.java new file mode 100644 index 0000000000000000000000000000000000000000..e656b8c4a05544b93ed7188dd408de34757024aa --- /dev/null +++ b/org.eventb.texttools/src/org/eventb/texttools/prettyprint/PrettyPrinter.java @@ -0,0 +1,360 @@ +/** + * (c) 2009 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, + * Heinrich Heine Universitaet Duesseldorf + * This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) + * */ + +package org.eventb.texttools.prettyprint; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.emf.common.util.EList; +import org.eventb.emf.core.EventBCommented; +import org.eventb.emf.core.EventBDerived; +import org.eventb.emf.core.EventBNamed; +import org.eventb.emf.core.EventBNamedCommentedPredicateElement; +import org.eventb.emf.core.EventBObject; +import org.eventb.emf.core.context.ContextPackage; +import org.eventb.emf.core.machine.MachinePackage; +import org.eventb.texttools.Constants; + +public class PrettyPrinter implements PrettyPrintConstants { + protected final String linebreak; + + private final Map<String, Object> preferences; + private final MachinePrintSwitch machineSwitch; + private final ContextPrintSwitch contextSwitch; + + private final StringBuilder buffer; + private int indentationLevel = 0; + + private int indentWidth; + private int tabWidth; + private Boolean useTabsForIndentation; + + /** + * Creates a new pretty printer with the given configuration. + * + * @param buffer + * {@link StringBuilder} to which this pretty printer append its + * results. + * @param linebreak + * The linebreak character to be used. + * @param preferences + * A {@link Map<String, Object>} of preferences which shall be + * used while prettyprinting. Possible keys are available as + * constants in the {@link PrettyPrintConstants}. + */ + public PrettyPrinter(final StringBuilder buffer, final String linebreak, + final Map<String, Object> preferences) { + this.buffer = buffer; + this.linebreak = linebreak; + + if (preferences != null) { + this.preferences = preferences; + } else { + this.preferences = new HashMap<String, Object>(); + } + + machineSwitch = new MachinePrintSwitch(this); + contextSwitch = new ContextPrintSwitch(this); + + initPreferences(); + } + + private void initPreferences() { + indentWidth = (Integer) getPreference(PROP_INDENT_DEPTH, 2); + useTabsForIndentation = (Boolean) getPreference( + PROP_USE_TABS_FOR_INDENTATION, false); + tabWidth = (Integer) getPreference(PROP_TAB_WIDTH, 4); + } + + /** + * Creates a pretty print for the given {@link EventBObject} and appends it + * to the {@link StringBuilder} which was handed to this + * {@link PrettyPrinter} instance via the constructor. + * + * @param object + */ + public void prettyPrint(final EventBObject emfObject) { + final String packageNsURI = emfObject.eClass().getEPackage().getNsURI(); + + if (packageNsURI.equals(MachinePackage.eNS_URI)) { + machineSwitch.doSwitch(emfObject); + } else if (packageNsURI.equals(ContextPackage.eNS_URI)) { + contextSwitch.doSwitch(emfObject); + } + } + + protected Object getPreference(final String key, final Object defaultValue) { + if (preferences.containsKey(key)) { + return preferences.get(key); + } else { + return defaultValue; + } + } + + protected void changeIndent(final int byValue) { + Assert.isTrue(indentationLevel + byValue >= 0); + indentationLevel += byValue; + } + + protected void increaseIndentLevel() { + changeIndent(indentWidth); + } + + protected void decreaseIndentLevel() { + changeIndent(-indentWidth); + } + + protected void append(final char c) { + buffer.append(c); + } + + protected void append(final String string) { + buffer.append(string); + } + + protected void appendWithSpace(final String string) { + buffer.append(string); + buffer.append(SPACE); + } + + protected void appendWithSpace(final char c) { + buffer.append(c); + buffer.append(SPACE); + } + + protected void appendWithLineBreak(final String string) { + buffer.append(string); + appendLineBreak(); + } + + protected void appendLineBreak() { + buffer.append(linebreak); + } + + /** + * Appends the comment of the given {@link EventBCommented} if it contains a + * comment. + * + * @param commentedElement + * @return <code>true</code> if it contained a comment and this comment + * consisted of multiple lines, <code>false</code> otherwise + */ + protected void appendComment(final EventBCommented commentedElement) { + final String comment = commentedElement.getComment(); + + if (!hasComment(commentedElement)) { + return; + } + + // insert blank if previous char is not a whitespace + if (buffer.length() > 0 + && !Character.isWhitespace(buffer.charAt(buffer.length() - 1))) { + append(SPACE); + } + + final StringTokenizer tokenizer = new StringTokenizer(comment, "\n\r"); + + if (tokenizer.countTokens() <= 1) { + append(COMMENT_SINGLELINE_BEGIN); + append(comment.trim()); + appendLineBreak(); + } + // multi line comment + else { + appendLineBreak(); + adjustIndent(); + append(COMMENT_MULTILINE_BEGIN); + + final int subIdentation = getCurrentIndentation() + - indentationLevel - 1; + + append(tokenizer.nextToken().trim()); + appendLineBreak(); + + changeIndent(subIdentation); + + while (tokenizer.hasMoreTokens()) { + adjustIndent(); + append(tokenizer.nextToken().trim()); + + if (tokenizer.hasMoreTokens()) { + appendLineBreak(); + } + } + + append(COMMENT_MULTILINE_END); + appendLineBreak(); + + changeIndent(-subIdentation); + } + } + + protected boolean hasComment(final EventBCommented commentedElement) { + final String comment = commentedElement.getComment(); + return comment != null && comment.length() > 0; + } + + protected void ensureNewLine() { + final int lastLinebreak = buffer.lastIndexOf(linebreak); + + /* + * Check if: 1) last line has characters and 2) these characters are not + * all whitespaces + */ + if (lastLinebreak < buffer.length() - 1 + && buffer.substring(lastLinebreak, buffer.length() - 1).trim() + .length() > 0) { + // begin new line + appendLineBreak(); + } + } + + protected void adjustIndent() { + int indentRemaining = indentationLevel - getCurrentIndentation() + 1; + + if (useTabsForIndentation) { + while (indentRemaining >= tabWidth) { + append(TAB); + indentRemaining -= tabWidth; + } + } + + // then use spaces for rest + while (indentRemaining > 0) { + append(SPACE); + indentRemaining--; + } + } + + protected void appendLabeledPredicate(final EventBNamedCommentedPredicateElement object, + final boolean derivedPossible) { + adjustIndent(); + + // deal with derived predicates (theorems) + if (object instanceof EventBDerived + && ((EventBDerived) object).isTheorem()) { + appendWithSpace(Constants.THEOREM); + } + + appendLabel(object); + appendFormula(object.getPredicate(), hasComment(object)); + appendComment(object); + } + + /** + * Returns the indentation level of the current position, i.e., how many + * characters the buffer contains behind the last line break. + * + * @return + */ + private int getCurrentIndentation() { + final int lastLinebreak = buffer.lastIndexOf(linebreak); + return buffer.length() - lastLinebreak; + } + + protected void appendFormula(final String formula, + final boolean followedByComment) { + final StringTokenizer tokenizer = new StringTokenizer(formula, "\n\r"); + + if (tokenizer.countTokens() <= 1) { + if (!followedByComment) { + appendWithLineBreak(formula.trim()); + } else { + append(formula.trim()); + } + } else { + final int subIndent = getCurrentIndentation() - indentationLevel + - 1; + + appendWithLineBreak(tokenizer.nextToken().trim()); + changeIndent(subIndent); + + while (tokenizer.hasMoreTokens()) { + adjustIndent(); + final String line = tokenizer.nextToken().trim(); + + if (tokenizer.hasMoreTokens() || !followedByComment) { + appendWithLineBreak(line); + } else { + append(line); + } + } + + changeIndent(-subIndent); + } + + } + + protected void appendLabel(final EventBNamed namedElement) { + final String name = namedElement.getName(); + append(AT); + appendWithSpace(name); + } + + protected void appendStringList(final List<String> strings) { + for (int i = 0; i < strings.size(); i++) { + appendWithSpace(strings.get(i)); + } + } + + protected void appendNameList(final EList<? extends EventBNamed> list, + final String label, final boolean newLineBefore) { + if (list.size() > 0) { + if (newLineBefore) { + appendLineBreak(); + } + + adjustIndent(); + appendWithSpace(label); + appendNamedElementList(list); + } + } + + protected void appendNamedElementList( + final EList<? extends EventBNamed> elements) { + final int subIndent = getCurrentIndentation() - indentationLevel - 1; + changeIndent(subIndent); + + boolean lastBeganNewLine = true; + + for (int i = 0; i < elements.size(); i++) { + final EventBNamed element = elements.get(i); + EventBCommented commented = null; + + if (element instanceof EventBCommented) { + commented = (EventBCommented) element; + commented = hasComment(commented) ? commented : null; + + if (commented != null && !lastBeganNewLine) { + appendLineBreak(); + } + } + + adjustIndent(); + append(element.getName()); + + // begin a new line if we had a comment for this element + if (commented != null) { + appendComment(commented); + lastBeganNewLine = true; + + if (i < elements.size() - 1) { + adjustIndent(); + } + } else { + append(SPACE); + lastBeganNewLine = false; + } + } + + appendLineBreak(); + changeIndent(-subIndent); + } +}