diff options
| author | aarne <aarne@cs.chalmers.se> | 2008-06-25 16:54:35 +0000 |
|---|---|---|
| committer | aarne <aarne@cs.chalmers.se> | 2008-06-25 16:54:35 +0000 |
| commit | e9e80fc389365e24d4300d7d5390c7d833a96c50 (patch) | |
| tree | f0b58473adaa670bd8fc52ada419d8cad470ee03 /src/JavaGUI2/de/uka | |
| parent | b96b36f43de3e2f8b58d5f539daa6f6d47f25870 (diff) | |
changed names of resource-1.3; added a note on homepage on release
Diffstat (limited to 'src/JavaGUI2/de/uka')
42 files changed, 10095 insertions, 0 deletions
diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AbstractProber.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AbstractProber.java new file mode 100644 index 000000000..0439ec6a4 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AbstractProber.java @@ -0,0 +1,182 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.io.IOException; +import java.util.logging.*; + +/** + * A class that offers a basic readGfEdit method with a lot of + * hot spots where subclasses can plug in + * @author daniels + * + */ +abstract class AbstractProber { + /** + * reference to the editor whose readRefinementMenu method is used + */ + protected final GfCapsule gfCapsule; + protected static Logger logger = Logger.getLogger(AbstractProber.class.getName()); + + /** + * A constructor which sets some fields + * @param gfCapsule The encapsulation of GF + */ + public AbstractProber(GfCapsule gfCapsule) { + this.gfCapsule = gfCapsule; + } + + /** + * reads the hmsg part + * @param readresult the first line + * @return the first line of the next XML child. + * if no hmsg is present @see readresult is returned. + */ + protected String readHmsg(String readresult) { + if (readresult.equals("<hmsg>")) { + gfCapsule.skipChild("<hmsg>"); + try { + String next = gfCapsule.fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("2 " + next); + } + return next; + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + return ""; + } + } else { + return readresult; + } + } + + /** + * reads the linearization subtree. + * The first line should be already read + * @param readresult the first line with the opening tag + */ + protected void readLinearizations(String readresult) { + gfCapsule.skipChild("<linearizations>"); + } + + /** + * Reads the tree child of the XML from beginning to end + */ + protected void readTree() { + gfCapsule.skipChild("<tree>"); + } + + /** + * Reads the message child of the XML from beginning to end + */ + protected void readMessage() { + gfCapsule.skipChild("<message>"); + } + + /** + * Reads the menu child of the XML from beginning to end + */ + protected void readMenu() { + gfCapsule.skipChild("<menu>"); + } + + /** + * reads the output from GF starting with >gfedit< + * and last reads >/gfedit<. + */ + protected void readGfedit() { + try { + String next = ""; + //read <gfedit> + String readresult = gfCapsule.fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("1 " + next); + } + //read either <hsmg> or <lineatization> + readresult = gfCapsule.fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("1 " + next); + } + + Hmsg hmsg = gfCapsule.readHmsg(readresult); + next = hmsg.lastline; + + //in case there comes sth. unexpected before <linearizations> + //usually the while body is never entered + // %%% + while ((next!=null)&&((next.length()==0)||(!next.trim().equals("<linearizations>")))) { + next = gfCapsule.fromProc.readLine(); + if (next!=null){ + if (logger.isLoggable(Level.FINER)) { + logger.finer("1 " + next); + } + } else { + System.exit(0); + } + } + readLinearizations(next); + readTree(); + readMessage(); + readMenu(); + + for (int i=0; i<3 && !next.equals(""); i++){ + next = gfCapsule.fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("1 " + next); + } + } + + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + } + + } + + /** + * send a command to GF + * @param text the command, exactly the string that is going to be sent + */ + protected void send(String text) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("## send: '" + text + "'"); + } + gfCapsule.realSend(text); + } + + /** + * Just reads the complete output of a GF run and ignores it. + */ + protected void readAndIgnore() { + try { + StringBuffer debugCollector = new StringBuffer(); + String readresult = gfCapsule.fromProc.readLine(); + debugCollector.append(readresult).append('\n'); + if (logger.isLoggable(Level.FINER)) logger.finer("14 "+readresult); + while (readresult.indexOf("</gfedit>") == -1) { + readresult = gfCapsule.fromProc.readLine(); + debugCollector.append(readresult).append('\n'); + if (logger.isLoggable(Level.FINER)) logger.finer("14 "+readresult); + } + //read trailing newline: + readresult = gfCapsule.fromProc.readLine(); + debugCollector.append(readresult).append('\n'); + if (logger.isLoggable(Level.FINER)) logger.finer("14 "+readresult); + + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + } + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AstNodeData.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AstNodeData.java new file mode 100644 index 000000000..9a4c48911 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/AstNodeData.java @@ -0,0 +1,105 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.*; + +/** + * @author hdaniels + * An object of this type knows how it self should be rendered, + * via Printname how its children should be rendered. + * This means the tooltip information it got from there. + * Knows nothing directly of the type of the node, which an object of this class + * represents. That's whats GfAstNode is for. + */ +abstract class AstNodeData { + protected static Logger logger = Logger.getLogger(DynamicTree2.class.getName()); + /** + * @return the printname associated with this object + */ + public abstract Printname getPrintname(); + + /** + * @return the parameter tooltip that this node has as a child + * of its parent (who gave it to it depending on its position) + */ + public abstract String getParamTooltip(); + + /** + * keeps track of the number of children of this node. + * It has to be increased whenever a new child of this node is + * added. + */ + public int childNum = 0; + /** + * The position String in the GF AST for this node + * in Haskell notation. + */ + public final String position; + /** + * the GF node connected to this NodeData, not the JTree node + */ + public final GfAstNode node; + + /** + * If a subtyping witness is missing, then this flag is false + */ + public boolean subtypingStatus = true; + + /** + * if this is the active, selected, focused node + */ + public final boolean selected; + + /** + * The constraint, that is valid on this node. + * If this node introduced a node itself and did not just inherit + * one, they are just concatenated. + * Until now, only the presence of a non-empty string here is used, + * so that is not important yet. + */ + public final String constraint; + + /** + * some nodes like coerce should, if possible, be covered from the + * users eyes. If this variable is greater than -1, the child + * with that number is shown instead of this node. + * This node will not appear in the tree. + */ + public int showInstead = -1; + + /** + * A simple setter constructor, that sets the fields of this class (except showInstead) + * @param node the GF node connected to this NodeData, not the JTree node + * @param pos The position String in the GF AST for this node + * in Haskell notation. + * @param selected if this is the active, selected, focused node + * @param constraint The GF constraint introduced in this node + */ + protected AstNodeData(GfAstNode node, String pos, boolean selected, String constraint) { + this.node = node; + this.position = pos; + this.selected = selected; + // I have no better idea, how to clutch them together, since + // I don't need the content of this field right now. + this.constraint = node.constraint + constraint; + } + + public String toString() { + return this.node.getLine(); + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ChainCommandTuple.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ChainCommandTuple.java new file mode 100644 index 000000000..c0f6f0c0d --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ChainCommandTuple.java @@ -0,0 +1,60 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application +package de.uka.ilkd.key.ocl.gf; + +/** + * @author hdaniels + * For chain commands, it is not just enough, to save the command sent to GF + * and the respective show text. + * Then it would be unclear, which fun should determine the used printname. + * If none is given, the last one of a chain command is taken. + * But if a solve for example is to follow, this does not work. + * Thus, this class has some other fields to define the appearance of a + * chain command. + */ +class ChainCommandTuple extends StringTuple { + /** + * the fun, that selects the printname + */ + final public String fun; + /** + * Here the subcat of fun can be overwritten. + * Is used for the attributes of self. + */ + final public String subcat; + /** + * normally, the ';;' are counted. But if we know, how many commands we + * chain to each other, we can skip that step and use undoSteps instead + */ + final public int undoSteps; + + /** + * A simple setter constructor + * @param command The command sent to GF + * @param showtext The text, that GF would display if no matching + * printname is found. + * @param fun The fun that selects the used printname + * @param subcat the subcategory for the refinement menu, overwrites + * the one defined in the printname + * @param undoSteps how many undos are needed to undo this command + */ + public ChainCommandTuple(String command, String showtext, String fun, String subcat, int undoSteps) { + super(command, showtext); + this.fun = fun; + this.subcat = subcat; + this.undoSteps = undoSteps; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ConstraintCallback.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ConstraintCallback.java new file mode 100644 index 000000000..3a9432960 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ConstraintCallback.java @@ -0,0 +1,64 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.*; + +/** + * @author daniels + * Offers the interface that GFEditor2 uses to send back the constraint after editing. + * Has no dependancies on KeY or TogetherCC. + */ +abstract class ConstraintCallback { + + /** + * Does the logging. What else should it do? + */ + protected static Logger logger = Logger.getLogger(ConstraintCallback.class.getName()); + + /** + * The path name of the directory where the grammars reside + */ + String grammarsDir; + /** + * sets the directory where the grammars reside + * @param grammarsDir + */ + void setGrammarsDir(final String grammarsDir) { + this.grammarsDir = grammarsDir; + } + + /** + * gets the directory where the grammars reside + */ + String getGrammarsDir() { + return this.grammarsDir; + } + + /** + * Sends the finished OCL constraint back to Together to save it + * as a JavaDoc comment. + * @param constraint The OCL constraint in question. + */ + abstract void sendConstraint(String constraint); + + /** + * Sends the unfinished OCL constraint back to Together to save it + * as a GF tree as a JavaDoc comment. + * @param abs The GF tree in question + */ + abstract void sendAbstract(String abs); +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Display.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Display.java new file mode 100644 index 000000000..9ca39fc49 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Display.java @@ -0,0 +1,249 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.awt.Font; +import java.awt.Rectangle; +import java.util.Vector; +import javax.swing.JEditorPane; + +/** + * @author daniels + * Takes care of collecting the linearized text and the length calculations + */ +class Display { + /** + * If the linearization should be displayed as pure text + */ + private boolean doText; + /** + * If the linearization should be displayed as HTML + */ + private boolean doHtml; + /** + * collects the linearization after each append. + * what's in here are Strings + */ + private Vector linStagesHtml = new Vector(); + /** + * collects the linearization after each append. + * what's in here are Strings + */ + private Vector linStagesText = new Vector(); + /** + * Is used to calculate the length of a HTML snipplet. + * This pane is not displayed in hope to avoid any actual renderings + * which would just slow the length calculation down. + * Perhaps that's an abuse ... + * And perhaps this pane is not needed + */ + private JEditorPane htmlLengthPane = new JEditorPane(); + + /** + * initializes this object, nothing special + * @param dt 1 if only text is to be shown, 2 for only HTML, 3 for both. + * Other values are forbidden. + */ + public Display(int dt) { + setDisplayType(dt); + this.htmlLengthPane.setContentType("text/html"); + this.htmlLengthPane.setEditable(false); + } + + /** + * (de-)activates display of text and HTML according to dt + * @param dt 1 if only text is to be shown, 2 for only HTML, 3 for both. + */ + protected void setDisplayType(int dt) { + switch (dt) { + case 1: + doText = true; + doHtml = false; + break; + case 2: + doText = false; + doHtml = true; + break; + case 3: + doText = true; + doHtml = true; + break; + default: + doText = true; + doHtml = true; + break; + } + } + /** + * Resets the stored text, but leaves the scroll markers untouched. + */ + public void resetLin() { + linStagesHtml.clear(); + linStagesText.clear(); + htmlLengthPane.setText(""); + } + + /** + * @param font The Font, that is to be used. If null, the default of JTextPane is taken. + * @return the collected HTML text, that has been added to this object. + * <html> tags are wrapped around the result, if not already there. + */ + protected String getHtml(Font font) { + if (!doHtml) { + return ""; + } + String result; + if (this.linStagesHtml.size() > 0) { + String fontface; + if (font != null) { + //fontface = " style=\"font-size:" + font.getSize()+ "pt\""; + fontface = " style=\"font: " + font.getSize()+ "pt " + font.getName() + "\""; + } else { + fontface = ""; + } + result ="<html><body" + fontface + ">" + this.linStagesHtml.get(this.linStagesHtml.size() - 1).toString() + "</body></html>"; + } else { + result = ""; + } + return result; + } + + /** + * @return The collected pure text, that has been added to this object. + */ + protected String getText() { + if (!doText) { + return ""; + } + String result; + if (this.linStagesText.size() > 0) { + result = this.linStagesText.lastElement().toString(); + } else { + result = ""; + } + return result; + } + + /** + * Appends the given text to the respective fields from + * where it can be displayed later + * @param text The pure text that is to be appended. + * @param html The HTML text that is to be appended. + * Most likely the same as text + */ + protected void addToStages(final String text, final String html) { + //add to HTML + if (doHtml) { + final String newStageHtml; + if (this.linStagesHtml.size() > 0) { + newStageHtml = this.linStagesHtml.get(this.linStagesHtml.size() - 1) + html; + } else { + newStageHtml = html; + } + this.linStagesHtml.add(newStageHtml); + } + + //add to Text + if (doText) { + final String newStageText; + if (this.linStagesText.size() > 0) { + newStageText = linStagesText.get(linStagesText.size() - 1) + text; + } else { + newStageText = text; + } + this.linStagesText.add(newStageText); + } + } + + /** + * Adds toAdd to both the pure text as the HTML fields, they are inherently the same, + * since they are mapped to the same position in the AST. + * On the way of adding, some length calculations are done, which are used to + * create an HtmlMarkedArea object, which is ready to be used in GFEditor2. + * @param toAdd The String that the to-be-produced MarkedArea should represent + * @param position The position String in Haskell notation + * @param language the language of the current linearization + * @return the HtmlMarkedArea object that represents the given information + * and knows about its beginning and end in the display areas. + */ + protected MarkedArea addAsMarked(String toAdd, LinPosition position, String language) { + /** the length of the displayed HTML before the current append */ + int oldLengthHtml = 0; + if (doHtml) { + if (this.linStagesHtml.size() > 0) { + // is still in there. Does not absolutely need to be + // cached in a global variable + oldLengthHtml = this.htmlLengthPane.getDocument().getLength(); + } else { + oldLengthHtml = 0; + } + } + /** the length of the text before the current append */ + int oldLengthText = 0; + if (doText) { + if (this.linStagesText.size() > 0) { + // is still in there. Does not absolutely need to be + // cached in a global variable + oldLengthText = this.linStagesText.lastElement().toString().length(); + } else { + oldLengthText = 0; + } + } + addToStages(toAdd, toAdd); + //calculate beginning and end + //for HTML + int newLengthHtml = 0; + if (doHtml) { + final String newStageHtml = this.linStagesHtml.lastElement().toString(); + final String newHtml = Printname.htmlPrepend(newStageHtml, ""); + //yeah, daniels admits, this IS expensive + this.htmlLengthPane.setText(newHtml); + newLengthHtml = htmlLengthPane.getDocument().getLength(); + if (newLengthHtml < oldLengthHtml) { + newLengthHtml = oldLengthHtml; + } + } + //for text + int newLengthText = 0; + if (doText) { + newLengthText = this.linStagesText.lastElement().toString().length(); + } + final MarkedArea hma = new MarkedArea(oldLengthText, newLengthText, position, toAdd, oldLengthHtml, newLengthHtml, language); + return hma; + } + /** + * To store the scroll state of the pure text linearization area + */ + Rectangle recText = new Rectangle(); + /** + * To store the scroll state of the HTML linearization area + */ + Rectangle recHtml = new Rectangle(); + /** + * To store the scroll state of the pure text linearization area + */ + int scrollText = 0; + /** + * To store the scroll state of the HTML linearization area + */ + int scrollHtml = 0; + + + + public String toString() { + return getText(); + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/DynamicTree2.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/DynamicTree2.java new file mode 100644 index 000000000..5c88955d3 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/DynamicTree2.java @@ -0,0 +1,366 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005
+//
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; either version 2 of the License, or
+//(at your option) any later version.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//GNU General Public License for more details.
+//
+//You can either finde the file LICENSE or LICENSE.TXT in the source
+//distribution or in the .jar file of this application
+
+package de.uka.ilkd.key.ocl.gf;
+/*
+ * This code is based on an example provided by Richard Stanford,
+ * a tutorial reader.
+ */
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.tree.*;
+import javax.swing.event.*;
+
+import java.util.logging.*;
+
+//import de.uka.ilkd.key.util.KeYResourceManager;
+
+import java.awt.event.*;
+
+/**
+ * A GUI class, does store the tree, but does not create it.
+ * The tree is created in GFEditor2.
+ * This class displays the tree and let the user interact with it via mouse clicks.
+ */
+public class DynamicTree2 extends JPanel implements KeyListener {
+ protected static Logger logger = Logger.getLogger(DynamicTree2.class.getName());
+
+ public DefaultMutableTreeNode rootNode;
+ private DefaultTreeModel treeModel;
+ public JTree tree;
+ private Toolkit toolkit = Toolkit.getDefaultToolkit();
+ private GFEditor2 gfeditor;
+ protected TreePath oldSelection = null;
+
+ /**
+ * Initializes the display state of the tree panel, sets up the
+ * event handlers.
+ * Does not initialize the tree.
+ * @param gfe The editor object this object belongs to.
+ */
+ public DynamicTree2(GFEditor2 gfe) {
+
+ this.gfeditor = gfe;
+ rootNode = new DefaultMutableTreeNode("Root Node");
+ treeModel = new DefaultTreeModel(rootNode);
+ treeModel.addTreeModelListener(new MyTreeModelListener());
+
+ tree = new JTree(treeModel);
+ tree.setRootVisible(false);
+ tree.setEditable(false);
+ tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
+ tree.addKeyListener(this);
+
+ //Add listener to components that can bring up popup menus.
+ MouseListener popupListener = new PopupListener();
+ tree.addMouseListener(popupListener);
+
+ tree.addTreeSelectionListener(new TreeSelectionListener() {
+ /**
+ * Moves to the position of the selected node in GF.
+ * the following is assumed:
+ * gfeditor.nodeTable contains the positions for all selectionPathes.
+ */
+ public void valueChanged(TreeSelectionEvent e) {
+ if ((tree.getSelectionPath() != null) && tree.getSelectionPath().equals(oldSelection)) {
+ //nothing to be done here, probably
+ //triggered by showTree
+ return;
+ }
+ if (tree.getSelectionRows() != null) {
+ if (tree.getSelectionPath() == null) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("null root path");
+ }
+ } else {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("selected path" + tree.getSelectionPath());
+ }
+ }
+ String pos = gfeditor.getNodePosition(tree.getSelectionPath());
+ if (pos == null || "".equals(pos)) {
+ //default to sth. sensible
+ pos = "[]";
+ }
+ gfeditor.send("[t] mp " + pos);
+ }
+ oldSelection = tree.getSelectionPath();
+ }
+ });
+
+ tree.setCellRenderer(new MyRenderer());
+ tree.setShowsRootHandles(true);
+ ToolTipManager.sharedInstance().registerComponent(tree);
+ ToolTipManager.sharedInstance().setDismissDelay(60000);
+ setPreferredSize(new Dimension(200, 100));
+ JScrollPane scrollPane = new JScrollPane(tree);
+ setLayout(new GridLayout(1,0));
+ add(scrollPane);
+ }
+
+ /**
+ * Remove all nodes in the tree and
+ * form a dummy tree in treePanel
+ */
+ protected void resetTree() {
+ ((DefaultTreeModel)(tree.getModel())).setRoot(new DefaultMutableTreeNode("Root"));
+ ((DefaultTreeModel)(tree.getModel())).reload();
+ }
+
+ /** Remove all nodes except the root node. */
+ public void clear() {
+ ((DefaultTreeModel)(tree.getModel())).setRoot(null);
+ oldSelection = null;
+ //((DefaultTreeModel)(tree.getModel())).reload();
+ }
+
+ /** Remove the currently selected node. */
+ public void removeCurrentNode() {
+ TreePath currentSelection = tree.getSelectionPath();
+ if (currentSelection != null) {
+ DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode)
+ (currentSelection.getLastPathComponent());
+ MutableTreeNode parent = (MutableTreeNode)(currentNode.getParent());
+ if (parent != null) {
+ treeModel.removeNodeFromParent(currentNode);
+ return;
+ }
+ }
+
+ // Either there was no selection, or the root was selected.
+ toolkit.beep();
+ }
+
+ /**
+ * Add child to the root node.
+ * It will come last in this node.
+ * @param child the payload of the new node
+ * @return the tree node having child as the node data
+ */
+ public DefaultMutableTreeNode addObject(Object child) {
+ DefaultMutableTreeNode parentNode = null;
+ TreePath parentPath = tree.getSelectionPath();
+
+ if (parentPath == null) {
+ parentNode = rootNode;
+ } else {
+ parentNode = (DefaultMutableTreeNode)
+ (parentPath.getLastPathComponent());
+ }
+
+ return addObject(parentNode, child, true);
+ }
+
+ /**
+ * Add a new node containing child to the node parent.
+ * It will come last in this node.
+ * This method gets actually called
+ * @param parent the parent node of the to be created node
+ * @param child the wannabe node data
+ * @return the tree node having child as the node data and parent as the parent
+ */
+ public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent,
+ Object child) {
+ return addObject(parent, child, false);
+ }
+
+ /**
+ * Add child to the currently selected node (parent?).
+ * It will come last in this node.
+ * @param parent the parent node of the to be created node
+ * @param child the wannabe node data
+ * @param shouldBeVisible true iff the viewport should show the
+ * new node afterwards
+ * @return the tree node having child as the node data and parent
+ * as the parent
+ */
+ public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("node added: '" + child + "', parent: '" + parent + "'");
+ }
+ DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
+
+ if (parent == null) {
+ parent = rootNode;
+ }
+
+ treeModel.insertNodeInto(childNode, parent,
+ parent.getChildCount());
+
+ // Make sure the user can see the lovely new node.
+ if (shouldBeVisible) {
+ tree.scrollPathToVisible(new TreePath(childNode.getPath()));
+ }
+ return childNode;
+ }
+
+ class MyTreeModelListener implements TreeModelListener {
+ public void treeNodesChanged(TreeModelEvent e) {
+ DefaultMutableTreeNode node;
+ node = (DefaultMutableTreeNode)
+ (e.getTreePath().getLastPathComponent());
+
+ /*
+ * If the event lists children, then the changed
+ * node is the child of the node we've already
+ * gotten. Otherwise, the changed node and the
+ * specified node are the same.
+ */
+ try {
+ int index = e.getChildIndices()[0];
+ node = (DefaultMutableTreeNode)(node.getChildAt(index));
+ } catch (NullPointerException exc) {
+ System.err.println(exc.getMessage());
+ exc.printStackTrace();
+ }
+
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("The user has finished editing the node.");
+ logger.finer("New value: " + node.getUserObject());
+ }
+ }
+ public void treeNodesInserted(TreeModelEvent e) {
+ //nothing to be done here
+ }
+ public void treeNodesRemoved(TreeModelEvent e) {
+ //nothing to be done here
+ }
+ public void treeStructureChanged(TreeModelEvent e) {
+ //nothing to be done here
+ }
+ }
+
+ /**
+ * This tree cell renderer got overwritten to make it possible to show
+ * tooltips according to the user object
+ */
+ private class MyRenderer extends DefaultTreeCellRenderer {
+ //int counter = 0;
+ //final ImageIcon iconFilled;
+ //final ImageIcon iconOpen;
+
+// public MyRenderer() {
+// final URL urlOpen = KeYResourceManager.getManager().getResourceFile(DynamicTree2.class, "metal_leaf_open.png");
+// final URL urlFilled = KeYResourceManager.getManager().getResourceFile(DynamicTree2.class, "metal_leaf_filled.png");
+// iconOpen = new ImageIcon(urlOpen);
+// iconFilled = new ImageIcon(urlFilled);
+// }
+
+ /**
+ * The heart of this class, sets display and tooltip text
+ * depending on the user data
+ */
+ public Component getTreeCellRendererComponent(
+ JTree tree,
+ Object value,
+ boolean sel,
+ boolean expanded,
+ boolean leaf,
+ int row,
+ boolean hasFocus) {
+
+ super.getTreeCellRendererComponent(
+ tree, value, sel,
+ expanded, leaf, row,
+ hasFocus);
+ if (value instanceof DefaultMutableTreeNode) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
+ if (node.getUserObject() instanceof AstNodeData) {
+ AstNodeData and = (AstNodeData)node.getUserObject();
+ String ptt = and.getParamTooltip();
+ if (!and.subtypingStatus ) {
+ this.setForeground(Color.RED);
+ ptt = Printname.htmlAppend(ptt, "<p>Subtyping proof is missing. <br>If no refinements are offered here, then there is a subtyping error.");
+ }
+ this.setToolTipText(ptt);
+ this.setText(and.toString());
+// if (and.isMeta()) {
+// this.setLeafIcon(this.iconOpen);
+// } else {
+// this.setLeafIcon(this.iconFilled);
+// }
+ } else {
+ this.setToolTipText(null);
+ this.setText(value.toString());
+ }
+ } else {
+ this.setToolTipText(null);
+ this.setText(value.toString());
+ }
+ return this;
+ }
+
+ /**
+ * Checks if the current node represents an open metavariable
+ * or question mark
+ * @param value The payload of the node
+ * @return true iff value begins with a '?'
+ */
+ protected boolean isMetavariable(Object value) {
+ try {
+ DefaultMutableTreeNode node =
+ (DefaultMutableTreeNode)value;
+ String nodeInfo =
+ (String)(node.getUserObject());
+ if (nodeInfo.indexOf("?") == 0) {
+ return true;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ return false;
+ }
+
+ }//class
+
+ class PopupListener extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ gfeditor.maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ //nothing to be done here
+ }
+ }
+
+ /**
+ * Handle the key pressed event.
+ */
+ public void keyPressed(KeyEvent e) {
+ int keyCode = e.getKeyCode();
+ switch (keyCode){
+ case KeyEvent.VK_SPACE : gfeditor.send("'"); break;
+ case KeyEvent.VK_DELETE : gfeditor.send("d"); break;
+ }
+ }
+ /**
+ * Handle the key typed event.
+ */
+ public void keyTyped(KeyEvent e) {
+ //nothing to be done here
+ }
+ /**
+ * Handle the key released event.
+ */
+ public void keyReleased(KeyEvent e) {
+ //nothing to be done here
+ }
+
+}
+
+
diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ExportFormatMenu.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ExportFormatMenu.java new file mode 100644 index 000000000..076a9778f --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ExportFormatMenu.java @@ -0,0 +1,67 @@ +// This file is part of KeY - Integrated Deductive Software Design +// Copyright (C) 2001-2005 Universitaet Karlsruhe, Germany +// Universitaet Koblenz-Landau, Germany +// Chalmers University of Technology, Sweden +// +// The KeY system is protected by the GNU General Public License. +// See LICENSE.TXT for details. +// + +package de.uka.ilkd.key.ocl.gf; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +/** Provide a choice of output formats: OCL or Natural Language. NL can be + * formatted using either HTML or LaTeX. + */ +public class ExportFormatMenu extends JPanel +{ + public static int OCL = 0, HTML=1, LATEX=2; + + private static String[] menuStrings = { "OCL", + "Natural Language/HTML (requires GF)", + "Natural Language/LaTeX (requires GF)" + }; + + private JComboBox formatMenu; + private int selection; + + private ActionListener al = new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox cb = (JComboBox) e.getSource(); + String s = (String) cb.getSelectedItem(); + if (s.equals("OCL")) { + selection = OCL; + } else if (s.equals("Natural Language/HTML (requires GF)")) { + selection = HTML; + } else if (s.equals("Natural Language/LaTeX (requires GF)")) { + selection = LATEX; + } else { // should never occur + selection = OCL; + }; + } + }; + + public ExportFormatMenu() + { + super(); + this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS)); + formatMenu = new JComboBox(menuStrings); + formatMenu.setSelectedIndex(0); + formatMenu.addActionListener(al); + this.add(Box.createVerticalGlue()); + JLabel text = new JLabel("Choose output format:"); + this.add(text); + text.setAlignmentX(Component.CENTER_ALIGNMENT); + this.add(formatMenu); + } + + + public int getSelection() + { + return selection; + } +} + diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFCommand.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFCommand.java new file mode 100644 index 000000000..6e420a62b --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFCommand.java @@ -0,0 +1,137 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; +/** + * @author daniels + * A class that represents a GF command together with its printname. + * It also gives easy access to all the abuses of the printname like + * the subcategory, the tooltip, it knows about wrapping and so on. + * + * The static stuff could produce problems if the editor is started + * several times without closing it first. It probably should be moved + * into a manager class. + * Or the instances that get generated during one run all share the same + * "pseudo-static" Hashtables. This is probably better. + * + */ +abstract class GFCommand implements Comparable{ + + /** + * the subcategory of this command + */ + public abstract String getSubcat(); + /** + * the type of the command, r,w,ch,d,ac,... + */ + protected String commandType; + /** + * the type of the command, r,w,ch,d,ac,... + */ + public String getCommandType(){ + return commandType; + } + /** + * for wrap, the number of the argument the current node should become + */ + protected int argument; + + /** + * the actual command that this object should represent + */ + protected String command; + /** + * the actual command that this object should represent + */ + public String getCommand() { + return command; + } + + /** + * the Printname corresponding to the GF fun of this command + */ + protected Printname printname; + /** + * the Printname corresponding to the GF fun of this command + */ + public Printname getPrintname(){ + return printname; + } + + /** + * the text that is to be displayed as the tooltip + */ + public abstract String getTooltipText(); + + /** + * the text that is to be displayed in the refinement lists + */ + public abstract String getDisplayText(); + + /** + * the name of the fun that is used in this command + */ + protected String funName; + + /** + * if this is the first occurence of the current subcat + */ + protected boolean newSubcat; + + /** + * if this is the first occurence of the current subcat + */ + public boolean isNewSubcat() { + return newSubcat; + } + + /** + * Compares two GFCommands. + * LinkCommands are the least. Then the InputCommand (more than one + * does not happen). If that does not decide, the display name as a String does. + * @param o the other command. + * @return see above. + */ + public int compareTo(Object o) { + if (this.equals(o)) { + return 0; + } + if (this instanceof LinkCommand && !(o instanceof LinkCommand)) { + return -1; + } + if (!(this instanceof LinkCommand) && (o instanceof LinkCommand)) { + return 1; + } + //LinkCommands are dealt with, so from now on, they don't occur + if (this instanceof InputCommand && !(o instanceof InputCommand)) { + return -1; + } + if (!(this instanceof InputCommand) && (o instanceof InputCommand)) { + return 1; + } + if (! (o instanceof GFCommand)) { + //This should never occur! + return -1; + } else { + GFCommand ocmd = (GFCommand)o; + return this.getDisplayText().compareTo(ocmd.getDisplayText()); + } + } + + public String toString() { + return getDisplayText() + " \n " + getTooltipText(); + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFEditor2.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFEditor2.java new file mode 100644 index 000000000..cdda74168 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GFEditor2.java @@ -0,0 +1,2978 @@ +//Copyright (c) Janna Khegai 2004, Kristofer Johanisson 2004,
+// Hans-Joachim Daniels 2005
+//
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as publisrhed by
+//the Free Software Foundation; either version 2 of the License, or
+//(at your option) any later version.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//GNU General Public License for more details.
+//
+//You can either finde the file LICENSE or LICENSE.TXT in the source
+//distribution or in the .jar file of this application
+
+package de.uka.ilkd.key.ocl.gf;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.tree.*;
+import java.io.*;
+import java.util.*;
+import java.net.URL;
+import javax.swing.text.html.HTMLDocument;
+import java.net.MalformedURLException;
+import java.util.logging.*;
+import jargs.gnu.CmdLineParser;
+
+public class GFEditor2 extends JFrame {
+ /**
+ * the main logger for this class
+ */
+ private static Logger logger = Logger.getLogger(GFEditor2.class.getName());
+ /**
+ * debug stuff for the tree
+ */
+ private static Logger treeLogger = Logger.getLogger(DynamicTree2.class.getName());
+ /**
+ * red mark-up && html debug messages
+ */
+ private static Logger redLogger = Logger.getLogger(GFEditor2.class.getName() + "_Red");
+ /**
+ * pop-up/mouse handling debug messages
+ */
+ private static Logger popUpLogger = Logger.getLogger(GFEditor2.class.getName() + "_PopUp");
+ /**
+ * linearization marking debug messages
+ */
+ private static Logger linMarkingLogger = Logger.getLogger(GFEditor2.class.getName() + "_LinMarking");
+ /**
+ * keyPressedEvents & Co.
+ */
+ private static Logger keyLogger = Logger.getLogger(GFEditor2.class.getName() + "_key");
+ /**
+ * everything that is sent to GF
+ */
+ private static Logger sendLogger = Logger.getLogger(GFEditor2.class.getName() + ".send");
+ /**
+ * the first part of the name of the GF grammar file
+ */
+ public final static String modelModulName = "FromUMLTypes";
+ /**
+ * Does the saving of constraints in Together.
+ * Or to be more precise, itself knows nothing about Together.
+ * Only its subclasses. That way it can be compiled without KeY.
+ */
+ final private ConstraintCallback callback;
+ /**
+ * if the OCL features should be switched on
+ */
+ final private boolean oclMode;
+
+ /**
+ * does all direct interaction with GF
+ * (except for the probers)
+ */
+ private GfCapsule gfCapsule = null;
+ /**
+ * current Font
+ */
+ private Font font;
+ /**
+ * contains the offered fonts by name
+ */
+ private JMenu fontMenu;
+ /**
+ * offers a list of font sizes
+ */
+ private JMenu sizeMenu;
+
+ /**
+ * what is written here is parsed and the result inserted instead of tbe selection.
+ * No idea how this element is displayed
+ */
+ private JTextField parseField = new JTextField("textField!");
+
+ /**
+ * The position of the focus, that is, the currently selected node in the AST
+ */
+ private LinPosition focusPosition ;
+ /**
+ * When a new category is chosen, it is set to true.
+ * In the reset or a completely new state it is falsed.
+ * The structure of the GF output is different then and this must be taken
+ * care of.
+ */
+ private boolean newObject = false;
+ /**
+ * if the user enters text for the alpha conversion, he perhaps wants to input the same text again.
+ * Therefore it is saved.
+ */
+ private String alphaInput = "";
+ /**
+ * if a user sends a custom command to GF, he might want to do this
+ * again with the same command.
+ * Therefore it is saved.
+ */
+ private String commandInput = "";
+
+ /**
+ * default status text, just status
+ */
+ private final static String status = "status";
+ /**
+ * the language the possible actions are displayed
+ */
+ private String selectedMenuLanguage = "Abstract";
+ /**
+ * write-only variable, stores the current import paths
+ * reset after each reset.
+ */
+ private String fileString = "";
+ /**
+ * The mapping between Java tree pathes and GF AST positions
+ * is stored here.
+ */
+ private Hashtable nodeTable = new Hashtable();
+ /**
+ * This is necessary to map clicks in the tree, where in the event handler
+ * only the selection path is availble, to AST positions which can be
+ * sent to GF.
+ * @param key The TreeSelectionPath, that identifies the wanted node
+ * @return The AST position string of the given TreePath in the table
+ * of stored nodes.
+ */
+ protected String getNodePosition(Object key) {
+ return nodeTable.get(key).toString();
+ }
+ /**
+ * this FileChooser gets enriched with the Term/Text option
+ */
+ private JFileChooser saveFc = new JFileChooser("./");
+ /** used for new Topic, Import and Browse (readDialog) */
+ private JFileChooser fc = new JFileChooser("./");
+ private final static String [] modifyMenu = {"Modify", "identity","transfer",
+ "compute", "paraphrase", "generate","typecheck", "solve", "context" };
+ private static final String [] newMenu = {"New"};
+
+ /**
+ * Linearizations' display area
+ */
+ private JTextArea linearizationArea = new JTextArea();
+ /**
+ * The abstract syntax tree representation of the current editing object
+ */
+ private DynamicTree2 tree = new DynamicTree2(this);
+
+ /**
+ * Current Topic
+ */
+ private JLabel grammar = new JLabel("No topic ");
+ /**
+ * Writing the current editing object to file in the term or text
+ * format
+ */
+ private JButton save = new JButton("Save");
+ /**
+ * Reading both a new environment and an editing object from file.
+ * Current editing will be discarded
+ */
+ private JButton open = new JButton("Open");
+ /**
+ * Reading a new environment from file. Current editing will be
+ * discarded.
+ */
+ private JButton newTopic;
+ /** Sending a command to GF */
+ private JButton gfCommand;
+
+ /** Moving the focus to the previous metavariable */
+ private JButton leftMeta = new JButton("?<");
+ /** Moving the focus to the previous term */
+ private JButton left = new JButton("<");
+ /** Moving the focus to the top term */
+ private JButton top = new JButton("Top");
+ /** Moving the focus to the next term */
+ private JButton right = new JButton(">");
+ /** Moving the focus to the next metavariable */
+ private JButton rightMeta = new JButton(">?");
+ private final static String actionOnSubtermString = "Select Action on Subterm";
+ private JLabel subtermNameLabel = new JLabel();
+ private JLabel subtermDescLabel = new JLabel();
+ /** Refining with term or linearization from typed string or file */
+ private JButton read = new JButton("Read");
+ /** Performing alpha-conversion of bound variables */
+ private JButton alpha;
+ /** Generating random refinement */
+ private JButton random;
+ /** Going back to the previous state */
+ private JButton undo;
+ /** The main panel on which the others are put */
+ private JPanel coverPanel = new JPanel();
+ /** the dialog to read in Strings or Terms */
+ private ReadDialog readDialog;
+
+ /** The list of available categories to start editing */
+ private JComboBox newCategoryMenu = new JComboBox(newMenu);
+ /** Choosing a linearization method */
+ private JComboBox modify = new JComboBox(modifyMenu);
+ /** the panel with the more general command buttons */
+ private JPanel downPanel = new JPanel();
+ /** the splitpane containing tree on the left and linearization area on the right*/
+ private JSplitPane treePanel;
+ /** the upper button bar for New, Save */
+ private JPanel upPanel = new JPanel();
+ /** the panel that contains the navigation buttons and some explanatory text */
+ private JPanel middlePanel = new JPanel();
+ /** the panel that contains only the navigation buttons */
+ private JPanel middlePanelUp = new JPanel();
+ /** the panel that vontains the the explanatory text for the refinement menu */
+ private JPanel middlePanelDown = new JPanel(new BorderLayout());
+ /** splits between tree and lin above and nav buttons and refinements below */
+ private JSplitPane centerPanel;
+ /** the window that contains the refinements when in split mode */
+ private JFrame gui2 = new JFrame();
+ /** the main window with tree, lin and buttons when in split mode */
+ private JPanel centerPanel2= new JPanel();
+ /** contains refinment list and navigation buttons */
+ private JPanel centerPanelDown = new JPanel();
+ /** only contains the linearization area */
+ private JScrollPane outputPanelText = new JScrollPane(this.linearizationArea);
+ /** HTML Linearizations' display area */
+ private JTextPane htmlLinPane = new JTextPane();
+ /** only contains the HTML linearization area */
+ private JScrollPane outputPanelHtml = new JScrollPane(this.htmlLinPane);
+ /** contains both pure text and HTML areas */
+ private JSplitPane linSplitPane;
+ /** contains the linSplitPane and the status field below it */
+ private JPanel outputPanelUp = new JPanel(new BorderLayout());
+ /** contains statusLabel */
+ private JPanel statusPanel = new JPanel();
+ /** The type the currently focused term has */
+ private JLabel statusLabel = new JLabel(status);
+ /** the main menu in the top */
+ private JMenuBar menuBar= new JMenuBar();
+ /** View settings */
+ private JMenu viewMenu= new JMenu("View");
+ /**
+ * stores a list of all languages + abstract to select the language,
+ * in which the selectMenu will be filled.
+ */
+ private JMenu mlMenu= new JMenu("language");
+ /** Choosing the refinement options' representation */
+ private JMenu modeMenu= new JMenu("Menus");
+ /** Language settings */
+ private JMenu langMenu= new JMenu("Languages");
+ /** Main operations */
+ private JMenu fileMenu= new JMenu("File");
+ /** stores whether the refinement list should be in 'long' format */
+ private JRadioButtonMenuItem rbMenuItemLong;
+ /** stores whether the refinement list should be in 'short' format */
+ private JRadioButtonMenuItem rbMenuItemShort;
+ /** stores whether the refinement list should be in 'untyped' format */
+ private JRadioButtonMenuItem rbMenuItemUnTyped;
+ /**
+ * linked to rbMenuItemUnTyped.
+ * Is true if type information should be appended in the refinement menu
+ */
+ private boolean typedMenuItems = false;
+ /** stores whether the AST is visible or not */
+ private JCheckBoxMenuItem treeCbMenuItem;
+ /** in the save dialog whether to save as a Term or as linearized Text */
+ private ButtonGroup saveTypeGroup = new ButtonGroup();
+ /** the entries of the filter menu */
+ private final static String [] filterMenuContents = {"identity",
+ "erase", "take100", "text", "code", "latexfile",
+ "structured", "unstructured" };
+ /** Choosing the linearization representation format */
+ private JMenu filterMenu = new JMenu("Filter");
+ /** for managing the filter menu entries*/
+ private ButtonGroup filterButtonGroup = new ButtonGroup();
+
+ /** Some usability things can be switched off here for testing */
+ private JMenu usabilityMenu= new JMenu("Usability");
+ /**
+ * stores whether self and result should only be made visible
+ * if applicable
+ */
+ private JCheckBoxMenuItem selfresultCbMenuItem;
+ /** to switch grouping of entries in the refinement menu on and off */
+ private JCheckBoxMenuItem subcatCbMenuItem;
+ /** to switch sorting of entries in the refinement menu on and off */
+ private JCheckBoxMenuItem sortCbMenuItem;
+ /** to switch autocoercing */
+ private JCheckBoxMenuItem coerceCbMenuItem;
+ /** to switch reducing the argument 3 refinement menu of coerce on or off */
+ private JCheckBoxMenuItem coerceReduceCbMenuItem;
+ /** to switch highlighting subtyping errors on or off */
+ private JCheckBoxMenuItem highlightSubtypingErrorsCbMenuItem;
+ /** to switch hiding coerce on or off */
+ private JCheckBoxMenuItem hideCoerceCbMenuItem;
+ /** to switch hiding coerce even if parts are unrefined on or off */
+ private JCheckBoxMenuItem hideCoerceAggressiveCbMenuItem;
+ /** to switch the attributes of self in the refinement menu on or off */
+ private JCheckBoxMenuItem easyAttributesCbMenuItem;
+
+ /**
+ * if true, self and result are only shown if applicable,
+ * tied to @see selfresultCbMenuItem
+ */
+ private boolean showSelfResult = true;
+ /**
+ * if true, refinements are grouped by subcat
+ * tied to @see subcatCbMenuItem.
+ */
+ private boolean groupSubcat = true;
+ /**
+ * @return Returns whether subcategories should be grouped or not
+ */
+ protected boolean isGroupSubcat() {
+ return groupSubcat;
+ }
+ /**
+ * if true, refinements are grouped by subcat.
+ * tied to @see subcatCbMenuItem.
+ */
+ private boolean sortRefinements = true;
+ /**
+ * @return Returns if the refinements should get sorted.
+ */
+ protected boolean isSortRefinements() {
+ return sortRefinements;
+ }
+ /**
+ * if true, then Instances will automatically get wrapped with a coerce
+ * if encountered as meta in the active node
+ */
+ private boolean autoCoerce = false;
+ /**
+ * If this is true, the refinementmenu for argument 3 of coerce
+ * will be populated only with suiting refinements.
+ */
+ private boolean coerceReduceRM = false;
+ /**
+ * If true, then the AST will be checked for missing subtyping witnesses
+ */
+ private boolean highlightSubtypingErrors = false;
+ /**
+ * if true, filled in coercions will be hidden from the user
+ */
+ private boolean hideCoerce = false;
+ /**
+ * if true, filled in coercions will be hidden from the user
+ * even if they lack filled in type arguments
+ */
+ private boolean hideCoerceAggressive = false;
+ /**
+ * offer the attributes of self directly in the refinement menu
+ */
+ private boolean easyAttributes = false;
+
+
+ /**
+ * handles all the Printname naming and so on.
+ */
+ private PrintnameManager printnameManager;
+ /**
+ * @return Returns the printnameManager.
+ */
+ protected PrintnameManager getPrintnameManager() {
+ return printnameManager;
+ }
+
+
+ /**
+ * stores the current type. Since the parsing often fails, this is
+ * most often null, except for Int and String, which can be parsed.
+ */
+ private GfAstNode currentNode = null;
+ /** stores the displayed parts of the linearization */
+ private Display display = new Display(3);
+
+ /** takes care of the menus that display the available languages */
+ private LangMenuModel langMenuModel = new LangMenuModel();
+
+ //Now the stuff for choosing the wanted output type (pure text or HTML)
+ /**
+ * 1 for text, 2 for HTML, 3 for both
+ */
+ private int displayType = 1;
+ /**
+ * rbText, rbHtml and rbTextHtml are grouped here.
+ */
+ private ButtonGroup bgDisplayType = new ButtonGroup();
+ /**
+ * The button that switches the linearization view to text only
+ */
+ private JRadioButtonMenuItem rbText = new JRadioButtonMenuItem(new AbstractAction("pure text") {
+ public void actionPerformed(ActionEvent ae) {
+ int oldDisplayType = displayType;
+ displayType = 1;
+ display.setDisplayType(displayType);
+ outputPanelUp.removeAll();
+ outputPanelUp.add(outputPanelText, BorderLayout.CENTER);
+ outputPanelUp.add(statusPanel, BorderLayout.SOUTH);
+ if (ae != null && oldDisplayType == 2) { //not manually called in the beginning and only HTML
+ formLin();
+ }
+ outputPanelUp.validate();
+ }
+ });
+ /**
+ * The button that switches the linearization view to HTML only
+ */
+ private JRadioButtonMenuItem rbHtml = new JRadioButtonMenuItem(new AbstractAction("HTML") {
+ public void actionPerformed(ActionEvent ae) {
+ int oldDisplayType = displayType;
+ displayType = 2;
+ display.setDisplayType(displayType);
+ outputPanelUp.removeAll();
+ outputPanelUp.add(outputPanelHtml, BorderLayout.CENTER);
+ outputPanelUp.add(statusPanel, BorderLayout.SOUTH);
+ if (ae != null && oldDisplayType == 1) { //not manually called in the beginning and only text
+ formLin();
+ }
+ outputPanelUp.validate();
+ }
+ });
+ /**
+ * The button that switches the linearization view to both text and
+ * HTML separated with a JSplitpane
+ */
+ private JRadioButtonMenuItem rbTextHtml = new JRadioButtonMenuItem(new AbstractAction("text and HTML") {
+ public void actionPerformed(ActionEvent ae) {
+ int oldDisplayType = displayType;
+ displayType = 3;
+ display.setDisplayType(displayType);
+ linSplitPane.setLeftComponent(outputPanelText);
+ linSplitPane.setRightComponent(outputPanelHtml);
+ outputPanelUp.removeAll();
+ outputPanelUp.add(linSplitPane, BorderLayout.CENTER);
+ outputPanelUp.add(statusPanel, BorderLayout.SOUTH);
+ if (ae != null && oldDisplayType != 3) { //not manually called in the beginning and not both (the latter should always be true)
+ formLin();
+ }
+ outputPanelUp.validate();
+ }
+ });
+
+ /**
+ * Since the user will be able to send chain commands to GF,
+ * the editor has to keep track of them, since GF does not undo
+ * all parts with one undo, instead 'u n' with n as the number of
+ * individual commands, has to be sent.
+ */
+ private final Stack undoStack = new Stack();
+
+ /**
+ * for starting a SubtypingProber run
+ */
+ private JButton checkSubtyping;
+
+ /**
+ * handles the commands and how they are presented to the user
+ */
+ private RefinementMenu refinementMenu;
+ /**
+ * handles parsing and preparing for display
+ * of the linearization XML from GF.
+ * Also takes care of the click-in functionality.
+ */
+ private Linearization linearization;
+
+ /**
+ * Initializes GF with the given command, sets up the GUI
+ * and reads the first GF output
+ * @param gfcmd The command with all parameters, including -java
+ * that is to be executed. Will set up the GF side of this session.
+ * @param isHtml true iff the editor should start in HTML mode.
+ * @param baseURL the URL that is the base for all relative links in HTML
+ * @param isOcl if the OCL special features should be available
+ */
+ public GFEditor2(String gfcmd, boolean isHtml, URL baseURL, boolean isOcl) {
+ this.callback = null;
+ this.oclMode = isOcl;
+ Image icon = null;
+ try {
+ final URL iconURL = ClassLoader.getSystemResource("gf-icon.gif");
+ icon = Toolkit.getDefaultToolkit().getImage(iconURL);
+ } catch (NullPointerException npe) {
+ logger.info("gf-icon.gif could not be found.\n" + npe.getLocalizedMessage());
+ }
+ initializeGUI(baseURL, isHtml, icon);
+ initializeGF(gfcmd, null);
+ }
+
+ /**
+ * a specialized constructor for OCL comstraints
+ * Starts with a new Constraint and an initial syntax tree
+ * @param gfcmd The command with all parameters, including -java
+ * that is to be executed. Will set up the GF side of this session.
+ * @param callback The class responsible for saving the OCL constraint
+ * as a JavaDoc comment
+ * @param initAbs the initial abstract syntax tree
+ * @param pm to monitor the loading progress. May be null
+ */
+ public GFEditor2(String gfcmd, ConstraintCallback callback, String initAbs, ProgressMonitor pm) {
+ this.oclMode = true;
+ this.callback = callback;
+
+ Utils.tickProgress(pm, 5220, "Loading grammars");
+ initializeGF(gfcmd, pm);
+ Utils.tickProgress(pm, 9350, "Initializing GUI");
+ initializeGUI(null, true, null);
+
+ // send correct term (syntax tree)
+ //The initial GF constraint has until now always been
+ //automatically solvable. So don't startle the user
+ //with painting everything red.
+ send(initAbs + " ;; c solve ", false, 2);
+ processGfedit();
+ Utils.tickProgress(pm, 9700, "Loading finished");
+ pm.close();
+ logger.finer("GFEditor2 constructor finished");
+ }
+
+ /**
+ * Starts GF and sets up the reading facilities.
+ * Shouldn't be called twice.
+ * @param gfcmd The command for GF to be executed.
+ * expects the -java parameters and all grammar modules
+ * to be specified. Simply executes this command without any
+ * modifications.
+ * @param pm to monitor the loading progress. May be null
+ */
+ private void initializeGF(String gfcmd, ProgressMonitor pm){
+ Utils.tickProgress(pm, 5250, "Starting GF");
+ logger.fine("Trying: "+gfcmd);
+ gfCapsule = new GfCapsule(gfcmd);
+ processInit(pm, true);
+ resetPrintnames(false);
+ }
+
+ /**
+ * (re-)initializes this.printnameManager and loads the printnames from
+ * GF.
+ * @param replayState If GF should be called to give the same state as before,
+ * but without the message. Is needed, when this function is started by the user.
+ * If sth. else is sent to GF automatically, this is not needed.
+ */
+ private void resetPrintnames(boolean replayState) {
+ this.printnameManager = new PrintnameManager();
+ PrintnameLoader pl = new PrintnameLoader(gfCapsule, this.printnameManager, this.typedMenuItems);
+ if (!selectedMenuLanguage.equals("Abstract")) {
+ String sendString = selectedMenuLanguage;
+ pl.readPrintnames(sendString);
+ //empty GF command, clears the message, so that the printnames
+ //are not printed again when for example a 'ml' command comes
+ //next
+ if (replayState) {
+ send("gf ", true, 0);
+ }
+ }
+ }
+ /**
+ * reliefs the constructor from setting up the GUI stuff
+ * @param baseURL the base URL for relative links in the HTML view
+ * @param showHtml if the linearization area for HTML should be active
+ * instead of the pure text version
+ * @param icon The icon in the title bar of the main window.
+ * For KeY-usage, no icon is given and the Swing default is chosen
+ * instead.
+ */
+ private void initializeGUI(URL baseURL, boolean showHtml, Image icon) {
+ refinementMenu = new RefinementMenu(this);
+ this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ this.addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ endProgram();
+ }
+ });
+ setIconImage(icon);
+ this.readDialog = new ReadDialog(this);
+
+ //Add listener to components that can bring up popup menus.
+ MouseListener popupListener2 = new PopupListener();
+ linearizationArea.addMouseListener(popupListener2);
+ htmlLinPane.addMouseListener(popupListener2);
+
+ //now for the menus
+
+ setJMenuBar(menuBar);
+ setTitle("GF Syntax Editor");
+ viewMenu.setToolTipText("View settings");
+ fileMenu.setToolTipText("Main operations");
+ langMenu.setToolTipText("Language settings");
+ usabilityMenu.setToolTipText("Usability settings");
+ menuBar.add(fileMenu);
+ menuBar.add(langMenu);
+ menuBar.add(viewMenu);
+ menuBar.add(modeMenu);
+ menuBar.add(usabilityMenu);
+ modeMenu.setToolTipText("Choosing the refinement options' representation");
+
+ /**
+ * listens to the showTree JCheckBoxMenuItem and
+ * switches displaying the AST on or off
+ */
+ final ActionListener showTreeListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (!((JCheckBoxMenuItem)e.getSource()).isSelected()){
+ if (logger.isLoggable(Level.FINER)) logger.finer("showTree was selected");
+ treeCbMenuItem.setSelected(false);
+ if (((JRadioButtonMenuItem)viewMenu.getItem(2)).isSelected()) {
+ centerPanel.remove(treePanel);
+ centerPanel.setLeftComponent(outputPanelUp);
+ }
+ else {
+ centerPanel2.remove(treePanel);
+ centerPanel2.add(outputPanelUp, BorderLayout.CENTER);
+ }
+ }
+ else {
+ if (logger.isLoggable(Level.FINER)) logger.finer("showTree was not selected");
+ treeCbMenuItem.setSelected(true);
+ if (((JRadioButtonMenuItem)viewMenu.getItem(2)).isSelected()) {
+ centerPanel.remove(outputPanelUp);
+ treePanel.setRightComponent(outputPanelUp);
+ centerPanel.setLeftComponent(treePanel);
+ }
+ else {
+ centerPanel2.remove(outputPanelUp);
+ treePanel.setRightComponent(outputPanelUp);
+ centerPanel2.add(treePanel, BorderLayout.CENTER);
+ }
+ }
+ pack();
+ repaint();
+ }
+
+ };
+
+ treeCbMenuItem = new JCheckBoxMenuItem("Tree");
+ treeCbMenuItem.setActionCommand("showTree");
+ treeCbMenuItem.addActionListener(showTreeListener);
+ treeCbMenuItem.setSelected(true);
+
+ viewMenu.add(treeCbMenuItem);
+ viewMenu.addSeparator();
+
+ final Action saveAction = new SaveAction();
+ final Action openAction = new OpenAction();
+ final Action newTopicAction = new NewTopicAction();
+ final Action resetAction = new ResetAction();
+ final Action quitAction = new QuitAction();
+ final Action undoAction = new UndoAction();
+ final Action randomAction = new RandomAction();
+ final Action alphaAction = new AlphaAction();
+ final Action gfCommandAction = new GfCommandAction();
+ final Action readAction = new ReadAction();
+ final Action splitAction = new SplitAction();
+ final Action combineAction = new CombineAction();
+
+ JMenuItem fileMenuItem = new JMenuItem(openAction);
+ fileMenu.add(fileMenuItem);
+ fileMenuItem = new JMenuItem(newTopicAction);
+ fileMenu.add(fileMenuItem);
+ fileMenuItem = new JMenuItem(resetAction);
+ fileMenu.add(fileMenuItem);
+ fileMenuItem = new JMenuItem(saveAction);
+ fileMenu.add(fileMenuItem);
+ fileMenu.addSeparator();
+ fileMenuItem = new JMenuItem(quitAction);
+ fileMenu.add(fileMenuItem);
+
+ JRadioButtonMenuItem rbMenuItem = new JRadioButtonMenuItem(combineAction);
+ rbMenuItem.setSelected(true);
+ /* rbMenuItem.setMnemonic(KeyEvent.VK_R);
+ rbMenuItem.setAccelerator(KeyStroke.getKeyStroke(
+ KeyEvent.VK_1, ActionEvent.ALT_MASK));
+ rbMenuItem.getAccessibleContext().setAccessibleDescription(
+ "This doesn't really do anything");
+ */
+ ButtonGroup menuGroup = new ButtonGroup();
+ menuGroup.add(rbMenuItem);
+ viewMenu.add(rbMenuItem);
+
+ rbMenuItem = new JRadioButtonMenuItem(splitAction);
+ menuGroup.add(rbMenuItem);
+ viewMenu.add(rbMenuItem);
+
+ //Font stuff
+ final int DEFAULT_FONT_SIZE = 14;
+ GraphicsEnvironment gEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ /** The list of font names our environment offers us */
+ String[] envfonts = gEnv.getAvailableFontFamilyNames();
+
+ /** the list of fonts the environment offers us */
+ Font[] fontObjs = new Font[envfonts.length];
+ for (int fi = 0; fi < envfonts.length; fi++) {
+ fontObjs[fi] = new Font(envfonts[fi], Font.PLAIN,
+ DEFAULT_FONT_SIZE);
+ }
+ font = new Font(null, Font.PLAIN, DEFAULT_FONT_SIZE);
+ //font menus
+ viewMenu.addSeparator();
+ fontMenu = new JMenu("Font");
+ fontMenu.setToolTipText("Change font");
+ sizeMenu = new JMenu("Font Size");
+ sizeMenu.setToolTipText("Change font size");
+ viewMenu.add(sizeMenu);
+ viewMenu.add(fontMenu);
+
+ {
+ JMenuItem fontItem;
+ ActionListener fontListener = new ActionListener(){
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ JMenuItem source = (JMenuItem)ae.getSource();
+ font = new Font(source.getText(), Font.PLAIN, font.getSize());
+ fontEveryWhere(font);
+ } catch (ClassCastException e) {
+ logger.warning("Font change started on strange object\n" + e.getLocalizedMessage());
+ }
+ }
+ };
+ for (int i = 0; i < envfonts.length; i++) {
+ fontItem = new JMenuItem(envfonts[i]);
+ fontItem.addActionListener(fontListener);
+ fontItem.setFont(new Font(envfonts[i], Font.PLAIN, font.getSize()));
+ fontMenu.add(fontItem);
+ }
+ }
+ {
+ JMenuItem sizeItem;
+ ActionListener sizeListener = new ActionListener(){
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ JMenuItem source = (JMenuItem)ae.getSource();
+ font = new Font(font.getFontName(), Font.PLAIN, Integer.parseInt(source.getText()));
+ fontEveryWhere(font);
+ } catch (ClassCastException e) {
+ logger.warning("Font change started on strange object\n" + e.getLocalizedMessage());
+ } catch (NumberFormatException e) {
+ logger.warning("strange size entry\n" + e.getLocalizedMessage());
+ }
+ }
+ };
+ /** The list of offered font sizes */
+ int[] sizes = {14,18,22,26,30};
+ for (int i = 0; i < sizes.length; i++) {
+ sizeItem = new JMenuItem("" + sizes[i]);
+ sizeItem.addActionListener(sizeListener);
+ sizeMenu.add(sizeItem);
+ }
+ }
+ //font stuff over
+
+ filterMenu.setToolTipText("Choosing the linearization representation format");
+ {
+ ActionListener filterActionListener = new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ JMenuItem jmi = (JMenuItem)ae.getSource();
+ final String sendString = "f " + jmi.getActionCommand();
+ send(sendString);
+ }
+ };
+ JRadioButtonMenuItem jrbmi;
+ for (int i = 0; i < filterMenuContents.length; i++) {
+ jrbmi = new JRadioButtonMenuItem(filterMenuContents[i]);
+ jrbmi.setActionCommand(filterMenuContents[i]);
+ jrbmi.addActionListener(filterActionListener);
+ filterButtonGroup.add(jrbmi);
+ filterMenu.add(jrbmi);
+ }
+ }
+ viewMenu.addSeparator();
+ viewMenu.add(filterMenu);
+
+ mlMenu.setToolTipText("the language of the entries in the refinement menu");
+ modeMenu.add(mlMenu);
+ /**
+ * switches GF to either display the refinement menu commands
+ * either in long or short format
+ */
+ final ActionListener longShortListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String action = e.getActionCommand();
+ if ((action.equals("long")) || (action.equals("short"))) {
+ send("ms " + action);
+ return;
+ } else {
+ logger.warning("RadioListener on wrong object: " + action + "should either be 'typed' or 'untyped'");
+ }
+ }
+ };
+
+ modeMenu.addSeparator();
+ menuGroup = new ButtonGroup();
+ rbMenuItemLong = new JRadioButtonMenuItem("long");
+ rbMenuItemLong.setToolTipText("long format in the refinement menu, e.g. 'refine' instead of 'r'");
+ rbMenuItemLong.setActionCommand("long");
+ rbMenuItemLong.addActionListener(longShortListener);
+ menuGroup.add(rbMenuItemLong);
+ modeMenu.add(rbMenuItemLong);
+ rbMenuItemShort = new JRadioButtonMenuItem("short");
+ rbMenuItemShort.setToolTipText("short format in the refinement menu, e.g. 'r' instead of 'refine'");
+ rbMenuItemShort.setActionCommand("short");
+ rbMenuItemShort.setSelected(true);
+ rbMenuItemShort.addActionListener(longShortListener);
+ menuGroup.add(rbMenuItemShort);
+ modeMenu.add(rbMenuItemShort);
+ modeMenu.addSeparator();
+
+ /**
+ * switches GF to either display the refinement menu with or
+ * without type annotation ala " : Type"
+ */
+ final ActionListener unTypedListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String action = e.getActionCommand();
+ if ((action.equals("typed")) || (action.equals("untyped"))) {
+ send("mt " + action);
+ if ((action.equals("typed"))) {
+ typedMenuItems = true;
+ } else {
+ typedMenuItems = false;
+ }
+ resetPrintnames(true);
+ return;
+ } else {
+ logger.warning("RadioListener on wrong object: " + action + "should either be 'typed' or 'untyped'");
+ }
+ }
+ };
+ menuGroup = new ButtonGroup();
+ rbMenuItem = new JRadioButtonMenuItem("typed");
+ rbMenuItem.setToolTipText("append the respective types to the entries of the refinement menu");
+ rbMenuItem.setActionCommand("typed");
+ rbMenuItem.addActionListener(unTypedListener);
+ rbMenuItem.setSelected(false);
+ menuGroup.add(rbMenuItem);
+ modeMenu.add(rbMenuItem);
+ rbMenuItemUnTyped = new JRadioButtonMenuItem("untyped");
+ rbMenuItemUnTyped.setToolTipText("omit the types of the entries of the refinement menu");
+ rbMenuItemUnTyped.setSelected(true);
+ rbMenuItemUnTyped.setActionCommand("untyped");
+ rbMenuItemUnTyped.addActionListener(unTypedListener);
+ menuGroup.add(rbMenuItemUnTyped);
+ modeMenu.add(rbMenuItemUnTyped);
+
+
+ //usability menu
+ subcatCbMenuItem = new JCheckBoxMenuItem("group possible refinements");
+ subcatCbMenuItem.setActionCommand("subcat");
+ subcatCbMenuItem.setToolTipText("group the entries of the refinement menus as defined in the printnames for the selected menu language");
+ subcatCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ groupSubcat = subcatCbMenuItem.isSelected();
+ send("gf");
+ }
+ });
+ subcatCbMenuItem.setSelected(groupSubcat);
+ usabilityMenu.add(subcatCbMenuItem);
+
+ sortCbMenuItem = new JCheckBoxMenuItem("sort refinements");
+ sortCbMenuItem.setActionCommand("sortRefinements");
+ sortCbMenuItem.setToolTipText("sort the entries of the refinement menu");
+ sortCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ sortRefinements = sortCbMenuItem.isSelected();
+ send("gf");
+ }
+ });
+ sortCbMenuItem.setSelected(sortRefinements);
+ usabilityMenu.add(sortCbMenuItem);
+
+ //OCL specific stuff
+
+ if (oclMode) {
+ usabilityMenu.addSeparator();
+ }
+ selfresultCbMenuItem = new JCheckBoxMenuItem("skip self&result if possible");
+ selfresultCbMenuItem.setToolTipText("do not display self and result in the refinement menu, if they don't fit");
+ selfresultCbMenuItem.setActionCommand("selfresult");
+ selfresultCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ showSelfResult = selfresultCbMenuItem.isSelected();
+ send("gf");
+ }
+ });
+ selfresultCbMenuItem.setSelected(showSelfResult);
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(selfresultCbMenuItem);
+ }
+
+ coerceReduceCbMenuItem = new JCheckBoxMenuItem("only suiting subtype instances for coerce");
+ coerceReduceCbMenuItem.setToolTipText("For coerce, where the target type is already known, show only the functions that return a subtype of this type.");
+ coerceReduceCbMenuItem.setActionCommand("coercereduce");
+ coerceReduceCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ coerceReduceRM = coerceReduceCbMenuItem.isSelected();
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(coerceReduceCbMenuItem);
+ coerceReduceRM = true;
+ }
+ coerceReduceCbMenuItem.setSelected(coerceReduceRM);
+
+ coerceCbMenuItem = new JCheckBoxMenuItem("coerce automatically");
+ coerceCbMenuItem.setToolTipText("Fill in coerce automatically where applicable");
+ coerceCbMenuItem.setActionCommand("autocoerce");
+ coerceCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ autoCoerce = coerceCbMenuItem.isSelected();
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(coerceCbMenuItem);
+ autoCoerce = true;
+ }
+ coerceCbMenuItem.setSelected(autoCoerce);
+
+ highlightSubtypingErrorsCbMenuItem = new JCheckBoxMenuItem("highlight suptyping errors");
+ highlightSubtypingErrorsCbMenuItem.setToolTipText("Mark nodes in situations, if where a non-existing subtyping is expected.");
+ highlightSubtypingErrorsCbMenuItem.setActionCommand("highlightsubtypingerrors");
+ highlightSubtypingErrorsCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ highlightSubtypingErrors = highlightSubtypingErrorsCbMenuItem.isSelected();
+ send("[t] gf");
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(highlightSubtypingErrorsCbMenuItem);
+ highlightSubtypingErrors = true;
+ }
+ highlightSubtypingErrorsCbMenuItem.setSelected(highlightSubtypingErrors);
+
+ hideCoerceCbMenuItem = new JCheckBoxMenuItem("hide coerce if completely refined");
+ hideCoerceCbMenuItem.setToolTipText("<html>Hide coerce functions when all arguments are filled in.<br>Note that, when a subtyping error is introduced, they will be shown.</html>");
+ hideCoerceCbMenuItem.setActionCommand("hideCoerce");
+ hideCoerceCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ hideCoerce = hideCoerceCbMenuItem.isSelected();
+ //hideCoerceAggressiveCbMenuItem can only be used,
+ //if hideCoerce is active. But its state should survive.
+ hideCoerceAggressiveCbMenuItem.setEnabled(hideCoerce);
+ if (hideCoerce) {
+ hideCoerceAggressive = hideCoerceAggressiveCbMenuItem.isSelected();
+ } else {
+ hideCoerceAggressive = false;
+ }
+ send("[t] gf ", true, 0);
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(hideCoerceCbMenuItem);
+ hideCoerce = true;
+ }
+ hideCoerceCbMenuItem.setSelected(hideCoerce);
+
+
+ hideCoerceAggressiveCbMenuItem = new JCheckBoxMenuItem("hide coerce always");
+ hideCoerceAggressiveCbMenuItem.setActionCommand("hideCoerceAggressive");
+ hideCoerceAggressiveCbMenuItem.setToolTipText("<html>Hide coerce functions even if the type arguments are incomplete.<br>Note that, when a subtyping error is introduced, they will be shown.</html>");
+ hideCoerceAggressiveCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ hideCoerceAggressive = hideCoerceAggressiveCbMenuItem.isSelected();
+ send("[t] gf ", true, 0);
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(hideCoerceAggressiveCbMenuItem);
+ hideCoerceAggressive = true;
+ }
+ hideCoerceAggressiveCbMenuItem.setSelected(hideCoerceAggressive);
+
+
+ easyAttributesCbMenuItem = new JCheckBoxMenuItem("directly offer attributes of 'self'");
+ easyAttributesCbMenuItem.setActionCommand("easyAttributes");
+ easyAttributesCbMenuItem.setToolTipText("list suiting attributes of self directly in the refinement menu");
+ easyAttributesCbMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ easyAttributes = easyAttributesCbMenuItem.isSelected();
+ send("[t] gf ", true, 0);
+ }
+ });
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ usabilityMenu.add(easyAttributesCbMenuItem);
+ easyAttributes = true;
+ }
+ easyAttributesCbMenuItem.setSelected(easyAttributes);
+
+ //now for the other elements
+
+ //HTML components
+ this.htmlLinPane.setContentType("text/html");
+ this.htmlLinPane.setEditable(false);
+ if (this.htmlLinPane.getStyledDocument() instanceof HTMLDocument) {
+ try {
+ URL base;
+ if (baseURL == null) {
+ base = (new File("./")).toURL();
+ } else {
+ base = baseURL;
+ }
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("base for HTML: " + base);
+ }
+ ((HTMLDocument)this.htmlLinPane.getDocument()).setBase(base);
+ } catch (MalformedURLException me) {
+ logger.severe(me.getLocalizedMessage());
+ }
+ } else {
+ logger.warning("No HTMLDocument: " + this.htmlLinPane.getDocument().getClass().getName());
+ }
+ this.htmlLinPane.addCaretListener(new CaretListener() {
+ /**
+ * One can either click on a leaf in the lin area, or select a larger subtree.
+ * The corresponding tree node is selected.
+ */
+ public void caretUpdate(CaretEvent e) {
+ int start = htmlLinPane.getSelectionStart();
+ int end = htmlLinPane.getSelectionEnd();
+ if (popUpLogger.isLoggable(Level.FINER)) {
+ popUpLogger.finer("CARET POSITION: " + htmlLinPane.getCaretPosition()
+ + "\n-> SELECTION START POSITION: "+start
+ + "\n-> SELECTION END POSITION: "+end);
+ }
+ if (linMarkingLogger.isLoggable(Level.FINER)) {
+ if (end > 0 && (end < htmlLinPane.getDocument().getLength())) {
+ try {
+ linMarkingLogger.finer("CHAR: " + htmlLinPane.getDocument().getText(end, 1));
+ } catch (BadLocationException ble) {
+ linMarkingLogger.warning(ble.getLocalizedMessage());
+ }
+ }
+ }
+ // not null selection:
+ if (start < htmlLinPane.getDocument().getLength()) {
+ String position = linearization.markedAreaForPosHtml(start, end);
+ if (position != null) {
+ send("[t] mp " + position);
+ }
+ }//not null selection
+ }
+
+ });
+ this.linSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+ this.outputPanelText, outputPanelHtml);
+
+ //cp = getContentPane();
+ JScrollPane cpPanelScroll = new JScrollPane(coverPanel);
+ this.getContentPane().add(cpPanelScroll);
+ coverPanel.setLayout(new BorderLayout());
+ linearizationArea.setToolTipText("Linearizations' display area");
+ linearizationArea.addCaretListener(new CaretListener() {
+ /**
+ * One can either click on a leaf in the lin area, or select a larger subtree.
+ * The corresponding tree node is selected.
+ */
+ public void caretUpdate(CaretEvent e) {
+ int start = linearizationArea.getSelectionStart();
+ int end = linearizationArea.getSelectionEnd();
+ if (popUpLogger.isLoggable(Level.FINER)) {
+ popUpLogger.finer("CARET POSITION: "+linearizationArea.getCaretPosition()
+ + "\n-> SELECTION START POSITION: "+start
+ + "\n-> SELECTION END POSITION: "+end);
+ }
+ final int displayedTextLength = linearizationArea.getText().length();
+ if (linMarkingLogger.isLoggable(Level.FINER)) {
+ if (end>0&&(end<displayedTextLength)) {
+ linMarkingLogger.finer("CHAR: "+linearizationArea.getText().charAt(end));
+ }
+ }
+ // not null selection:
+ if (start < displayedTextLength) { //TODO was -1 before, why?
+ String position = linearization.markedAreaForPosPureText(start, end);
+ if (position != null) {
+ send("[t] mp " + position);
+ }
+ }//not null selection
+ }
+
+ });
+ linearizationArea.setEditable(false);
+ linearizationArea.setLineWrap(true);
+ linearizationArea.setWrapStyleWord(true);
+
+ parseField.setFocusable(true);
+ parseField.addKeyListener(new KeyListener() {
+ /** Handle the key pressed event. */
+ public void keyPressed(KeyEvent e) {
+ int keyCode = e.getKeyCode();
+ if (keyLogger.isLoggable(Level.FINER)) {
+ keyLogger.finer("Key pressed: " + e.toString());
+ }
+
+ if (keyCode == KeyEvent.VK_ENTER) {
+ getLayeredPane().remove(parseField);
+ send("[t] p "+parseField.getText());
+ if (logger.isLoggable(Level.FINE)) logger.fine("sending parse string: "+parseField.getText());
+ repaint();
+ } else if (keyCode == KeyEvent.VK_ESCAPE) {
+ getLayeredPane().remove(parseField);
+ repaint();
+ }
+ }
+
+ /**
+ * Handle the key typed event.
+ * We are not really interested in typed characters, thus empty
+ */
+ public void keyTyped(KeyEvent e) {
+ //needed for KeyListener, but not used
+ }
+
+ /** Handle the key released event. */
+ public void keyReleased(KeyEvent e) {
+ //needed for KeyListener, but not used
+ }
+ });
+ parseField.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ //do nothing
+ }
+ public void focusLost(FocusEvent e) {
+ getLayeredPane().remove(parseField);
+ repaint();
+ }
+ });
+ // System.out.println(output.getFont().getFontName());
+
+
+ //Now for the command buttons in the lower part
+ gfCommand = new JButton(gfCommandAction);
+ read = new JButton(readAction);
+ modify.setToolTipText("Choosing a linearization method");
+ alpha = new JButton(alphaAction);
+ random = new JButton(randomAction);
+ undo = new JButton(undoAction);
+ checkSubtyping = new JButton(new SubtypeAction());
+ downPanel.add(gfCommand);
+ downPanel.add(read);
+ downPanel.add(modify);
+ downPanel.add(alpha);
+ downPanel.add(random);
+ downPanel.add(undo);
+ if (oclMode) {
+ // only visible, if we really do OCL constraints
+ downPanel.add(checkSubtyping);
+ }
+
+ //now for the navigation buttons
+ leftMeta.setToolTipText("Moving the focus to the previous metavariable");
+ rightMeta.setToolTipText("Moving the focus to the next metavariable");
+ left.setToolTipText("Moving the focus to the previous term");
+ right.setToolTipText("Moving the focus to the next term");
+ top.setToolTipText("Moving the focus to the top term");
+ middlePanelUp.add(leftMeta);
+ middlePanelUp.add(left);
+ middlePanelUp.add(top);
+ middlePanelUp.add(right);
+ middlePanelUp.add(rightMeta);
+ middlePanelDown.add(subtermNameLabel, BorderLayout.WEST);
+ middlePanelDown.add(subtermDescLabel, BorderLayout.CENTER);
+ middlePanel.setLayout(new BorderLayout());
+ middlePanel.add(middlePanelUp, BorderLayout.NORTH);
+ middlePanel.add(middlePanelDown, BorderLayout.CENTER);
+
+ //now for the upper button bar
+ newTopic = new JButton(newTopicAction);
+ newCategoryMenu.setToolTipText("The list of available categories to start editing");
+ open.setToolTipText("Reading both a new environment and an editing object from file. Current editing will be discarded");
+ save.setToolTipText("Writing the current editing object to file in the term or text format");
+ grammar.setToolTipText("Current Topic");
+ newTopic.setToolTipText("Reading a new environment from file. Current editing will be discarded.");
+ upPanel.add(grammar);
+ upPanel.add(newCategoryMenu);
+ upPanel.add(open);
+ upPanel.add(save);
+ upPanel.add(newTopic);
+
+ statusLabel.setToolTipText("The current focus type");
+
+ tree.setToolTipText("The abstract syntax tree representation of the current editing object");
+ tree.resetTree();
+
+ bgDisplayType.add(rbText);
+ bgDisplayType.add(rbHtml);
+ bgDisplayType.add(rbTextHtml);
+ if (showHtml) {
+ rbHtml.setSelected(true);
+ rbHtml.getAction().actionPerformed(null);
+ } else {
+ rbText.setSelected(true);
+ rbText.getAction().actionPerformed(null);
+ }
+
+ viewMenu.addSeparator();
+ viewMenu.add(rbText);
+ viewMenu.add(rbHtml);
+ viewMenu.add(rbTextHtml);
+ display = new Display(displayType);
+ linearization = new Linearization(display);
+
+ statusPanel.setLayout(new GridLayout(1,1));
+ statusPanel.add(statusLabel);
+ treePanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+ tree, outputPanelUp);
+ treePanel.setDividerSize(5);
+ treePanel.setDividerLocation(100);
+ centerPanel2.setLayout(new BorderLayout());
+ gui2.setSize(350,100);
+ gui2.setTitle("Select Action on Subterm");
+ gui2.setLocationRelativeTo(treePanel);
+ centerPanelDown.setLayout(new BorderLayout());
+ centerPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+ treePanel, centerPanelDown);
+ centerPanel.setDividerSize(5);
+ centerPanel.setDividerLocation(250);
+ centerPanel.addKeyListener(tree);
+ centerPanel.setOneTouchExpandable(true);
+
+
+
+ centerPanelDown.add(middlePanel, BorderLayout.NORTH);
+ centerPanelDown.add(refinementMenu.getRefinementListsContainer(), BorderLayout.CENTER);
+ coverPanel.add(centerPanel, BorderLayout.CENTER);
+ coverPanel.add(upPanel, BorderLayout.NORTH);
+ coverPanel.add(downPanel, BorderLayout.SOUTH);
+
+
+
+ newCategoryMenu.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ if (!newCategoryMenu.getSelectedItem().equals("New")) {
+ send("[nt] n " + newCategoryMenu.getSelectedItem());
+ newCategoryMenu.setSelectedIndex(0);
+ }
+ }
+
+ });
+ save.setAction(saveAction);
+ open.setAction(openAction);
+
+ newCategoryMenu.setFocusable(false);
+ save.setFocusable(false);
+ open.setFocusable(false);
+ newTopic.setFocusable(false);
+ gfCommand.setFocusable(false);
+
+ leftMeta.setFocusable(false);
+ left.setFocusable(false);
+
+ /** handles the clicking of the navigation buttons */
+ ActionListener naviActionListener = new ActionListener() {
+ /**
+ * convenience method instead of 5 single ones
+ */
+ public void actionPerformed(ActionEvent ae) {
+ Object obj = ae.getSource();
+ if ( obj == leftMeta ) {
+ send("[t] <<");
+ }
+ if ( obj == left ) {
+ send("[t] <");
+ }
+ if ( obj == top ) {
+ send("[t] '");
+ }
+ if ( obj == right ) {
+ send("[t] >");
+ }
+ if ( obj == rightMeta ) {
+ send("[t] >>");
+ }
+ }
+ };
+
+ top.addActionListener(naviActionListener);
+ right.addActionListener(naviActionListener);
+ rightMeta.addActionListener(naviActionListener);
+ leftMeta.addActionListener(naviActionListener);
+ left.addActionListener(naviActionListener);
+ modify.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ if (!modify.getSelectedItem().equals("Modify")) {
+ send("[t] c " + modify.getSelectedItem());
+ modify.setSelectedIndex(0);
+ }
+ }
+ });
+
+ top.setFocusable(false);
+ right.setFocusable(false);
+ rightMeta.setFocusable(false);
+ read.setFocusable(false);
+ modify.setFocusable(false);
+ alpha.setFocusable(false);
+ random.setFocusable(false);
+ undo.setFocusable(false);
+
+ linearizationArea.addKeyListener(tree);
+ this.setSize(800, 600);
+ outputPanelUp.setPreferredSize(new Dimension(400,230));
+ treePanel.setDividerLocation(0.3);
+ //nodeTable.put(new TreePath(tree.rootNode.getPath()), "");
+
+ JRadioButton termButton = new JRadioButton("Term");
+ termButton.setActionCommand("term");
+ termButton.setSelected(true);
+ JRadioButton linButton = new JRadioButton("Text");
+ linButton.setActionCommand("lin");
+ // Group the radio buttons.
+ saveTypeGroup.add(linButton);
+ saveTypeGroup.add(termButton);
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setPreferredSize(new Dimension(70, 70));
+ buttonPanel.add(new JLabel("Format:"));
+ buttonPanel.add(linButton);
+ buttonPanel.add(termButton);
+ saveFc.setAccessory(buttonPanel);
+
+ fontEveryWhere(font);
+ this.setVisible(true);
+
+ }
+
+ /**
+ * send a command to GF and reads the returned XML
+ * @param text the command, exacltly the string that is going to be sent
+ */
+ protected void send(String text){
+ send(text, true, 1);
+ }
+
+ /**
+ * send a command to GF (indirectly).
+ * @param text the command, exactly the string that is going to be sent
+ * @param andRead if true, the returned XML will be read an displayed accordingly
+ * @param undoSteps How many undo steps need to be done to undo this command.
+ * If undoSteps == 0, then nothing is done. If it is < 0, it gets
+ * subtracted from the last number on the undoStack. That way, both
+ * this command and the last one get undone together (since the undo
+ * value is actually increased).
+ */
+ protected void send(String text, boolean andRead, int undoSteps) {
+ if (sendLogger.isLoggable(Level.FINE)) {
+ sendLogger.fine("## send: '" + text + "', undo steps: " + undoSteps);
+ }
+
+ this.display.resetLin();
+ display(false, true, false);
+ linearization.reset();
+ if (undoSteps > 0) { //undo itself should not push sth. on the stack, only pop
+ undoStack.push(new Integer(undoSteps));
+ } else if (undoSteps < 0) {
+ final int oldUndo = ((Integer)undoStack.pop()).intValue();
+ final int newUndo = oldUndo - undoSteps;
+ if (sendLogger.isLoggable(Level.FINER)) {
+ sendLogger.finer("modified undoStack, top was " + oldUndo + ", but is now: " + newUndo);
+ }
+ undoStack.push(new Integer(newUndo));
+ }
+ gfCapsule.realSend(text);
+
+ if (andRead) {
+ processGfedit();
+ }
+ }
+
+
+
+ /**
+ * Asks the respective read methods to read the front matter of GF.
+ * That can be the greetings and loading messages.
+ * The latter are always read.
+ * When <gfinit> is read, the function returns.
+ * @param pm to monitor the loading progress. May be null
+ * @param greetingsToo if the greeting text from GF is expected
+ */
+ private void processInit(ProgressMonitor pm, boolean greetingsToo) {
+ String next = null;
+ if (greetingsToo) {
+ StringTuple greetings = gfCapsule.readGfGreetings();
+ next = greetings.first;
+ this.display.addToStages(greetings.second, greetings.second.replaceAll("\\n", "<br>"));
+ display(true, true, false);
+ }
+ Utils.tickProgress(pm, 5300, null);
+ StringTuple loading = gfCapsule.readGfLoading(next, pm);
+ next = loading.first;
+ this.display.addToStages(loading.second, Utils.replaceAll(loading.second, "\n", "<br>\n"));
+ display(true, true, false);
+
+ if (next.equals("<gfinit>")) {
+ processGfinit();
+ }
+ }
+
+
+ /**
+ * Takes care of reading the <gfinit> part
+ * Fills the new category menu.
+ */
+ private void processGfinit() {
+ NewCategoryMenuResult ncmr = gfCapsule.readGfinit();
+ if (ncmr != null) {
+ formNewMenu(ncmr);
+ }
+ }
+
+ /**
+ * Takes care of reading the output from GF starting with
+ * >gfedit< and last reads >/gfedit<.
+ * Feeds the editor with what was read.
+ * This makes this method nearly the central method of the editor.
+ */
+ private void processGfedit() {
+ final GfeditResult gfedit = gfCapsule.readGfedit(newObject);
+ formHmsg(gfedit.hmsg);
+ //now the form methods are called:
+ DefaultMutableTreeNode topNode = null;
+ TreeAnalysisResult tar = new TreeAnalysisResult(null, -1, false, true, false, false, null, null);
+ TreeAnalyser treeAnalyser = new TreeAnalyser(autoCoerce, coerceReduceRM, easyAttributes, hideCoerce, hideCoerceAggressive, highlightSubtypingErrors, showSelfResult);
+ if (gfedit.hmsg.treeChanged && newObject) {
+ topNode = formTree(gfedit.treeString);
+ tar = treeAnalyser.analyseTree(topNode);
+ focusPosition = tar.focusPosition;
+ currentNode = tar.currentNode;
+ }
+ //only sent sth. to GF directly, if we have sth. to send, and if it is not forbidden
+ if (tar.command == null || !gfedit.hmsg.recurse) {
+ //for normal grammars (not the OCL ones),
+ //the nextCommand feature is not used, thus
+ //only this branch is executed.
+
+ // nothing special is to be done here,
+ // the tree analysis has
+ // not told us to send sth. to GF,
+ // so display the rest and do most of the
+ // expensive stuff
+
+ if (topNode != null) { //the case of !treeChanged or !newObject
+ DefaultMutableTreeNode transformedTreeRoot = TreeAnalyser.transformTree(topNode);
+ showTree(tree, transformedTreeRoot);
+ }
+
+
+ if (gfedit.gfCommands != null) {
+ final Vector usedCommandVector = RefinementMenuTransformer.transformRefinementMenu(tar, gfedit.gfCommands, gfCapsule);
+ final boolean isAbstract = "Abstract".equals(selectedMenuLanguage);
+ refinementMenu.formRefinementMenu(usedCommandVector, gfedit.hmsg.appendix, currentNode, isAbstract, tar.easyAttributes && tar.reduceCoerce, focusPosition, gfCapsule);
+ }
+ if (newObject) {
+ //MUST come after readLin, but since formLin is called later on too,
+ //this cannot be enforced with a local this.linearization
+ String linString = gfedit.linearizations;
+ //is set only here, when it is fresh
+ linearization.setLinearization(linString);
+ formLin();
+ }
+ if (gfedit.message != null && gfedit.message.length()>1) {
+ logger.fine("message found: '" + gfedit.message + "'");
+ this.display.addToStages("\n-------------\n" + gfedit.message, "<br><hr>" + gfedit.message);
+ //in case no language is displayed
+ display(true, false, false);
+ }
+ } else {
+ // OK, sth. has to be sent to GF without displaying
+ // the linearization of this run
+ send(tar.command, true, - tar.undoSteps);
+ }
+ refinementMenu.requestFocus();
+ }
+
+ /**
+ * prints the available command line options
+ */
+ private static void printUsage() {
+ System.err.println("Usage: java -jar [-h/--html] [-b/--base baseURL] [-o/--ocl] [grammarfile(s)]");
+ System.err.println("where -h activates the HTML mode");
+ System.err.println("and -b sets the base location to which links in HTML are relative to. "
+ + "Default is the current directory.");
+ }
+
+ /**
+ * starts the editor
+ * @param args only the first parameter is used, it has to be a complete GF command,
+ * which is executed and thus should load the needed grammars
+ */
+ public static void main(String args[]) {
+ //command line parsing
+ CmdLineParser parser = new CmdLineParser();
+ CmdLineParser.Option optHtml = parser.addBooleanOption('h', "html");
+ CmdLineParser.Option optBase = parser.addStringOption('b', "base");
+ CmdLineParser.Option optOcl = parser.addBooleanOption('o', "ocl");
+ CmdLineParser.Option gfBin = parser.addStringOption('g', "gfbin");
+ // Parse the command line options.
+
+ try {
+ parser.parse(args);
+ }
+ catch (CmdLineParser.OptionException e) {
+ System.err.println(e.getMessage());
+ printUsage();
+ System.exit(2);
+ }
+ Boolean isHtml = (Boolean)parser.getOptionValue(optHtml, Boolean.FALSE);
+ String baseString = (String)parser.getOptionValue(optBase, null);
+ String gfBinString = (String)parser.getOptionValue(gfBin, null);
+ Boolean isOcl = (Boolean)parser.getOptionValue(optOcl, Boolean.FALSE);
+ String[] otherArgs = parser.getRemainingArgs();
+
+ URL myBaseURL;
+ if (baseString != null) {
+ try {
+ myBaseURL = new URL(baseString);
+ } catch (MalformedURLException me) {
+ logger.warning(me.getLocalizedMessage());
+ me.printStackTrace();
+ myBaseURL = null;
+ }
+ } else {
+ myBaseURL = null;
+ }
+
+// if (logger.isLoggable(Level.FINER)) {
+// logger.finer(isHtml + " : " + baseString + " : " + otherArgs);
+// }
+ //construct the call to GF
+ String gfCall = ((gfBinString != null && !gfBinString.equals(""))? gfBinString : "gf");
+ gfCall += " -java";
+ for (int i = 0; i < otherArgs.length; i++) {
+ gfCall = gfCall + " " + otherArgs[i];
+ }
+ Locale.setDefault(Locale.US);
+ logger.info("call to GF: " + gfCall);
+ GFEditor2 gui = new GFEditor2(gfCall, isHtml.booleanValue(), myBaseURL, isOcl.booleanValue());
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("main finished");
+ }
+ }
+
+ /**
+ * Calls the Java GF GUI to edit an OCL constraint. To be called by GFinterface
+ * @param gfCmd the command to start the GF, must include the -java and all modules
+ * @param callback the callback class that knows how to store the constraints
+ * @param initAbs the initial abstract syntax tree (not OCL)
+ * @param initDefault if initAbs is empty, then initDefault is used
+ * @param pm to monitor the loading progress. May be null
+ */
+ static void mainConstraint(String gfCmd, ConstraintCallback callback, String initAbs, String initDefault, ProgressMonitor pm) {
+ Locale.setDefault(Locale.US);
+ GFEditor2 gui;
+ if (initAbs.equals("")) {
+ gui = new GFEditor2(gfCmd, callback, "[ctn] g " + initDefault, pm);
+ } else {
+ gui = new GFEditor2(gfCmd, callback, "[ctn] g " + initAbs, pm);
+ }
+
+ }
+
+
+ /**
+ * we should not end the program, just close the GF editor
+ * possibly sending something back to KeY
+ */
+ private void endProgram(){
+ String saveQuestion;
+ if (this.callback == null) {
+ saveQuestion = "Save text before exiting?";
+ } else {
+ send("' ;; >>");
+ if (this.currentNode.isMeta()) {
+ saveQuestion = "Incomplete OCL found.\nThis can only be saved (and loaded again) in an internal representation.\nStill save before exiting?";
+ } else {
+ saveQuestion = "Save constraint before exiting?";
+ }
+ }
+ int returnStatus;
+ if (this.newObject) {
+ returnStatus = JOptionPane.showConfirmDialog(this, saveQuestion, "Save before quitting?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE );
+ } else {
+ returnStatus = JOptionPane.NO_OPTION;
+ }
+ if (returnStatus == JOptionPane.CANCEL_OPTION) {
+ return;
+ } else if (returnStatus == JOptionPane.NO_OPTION) {
+ shutDown();
+ return;
+ }
+ if (this.callback != null) {
+ try {
+ // quit should always work even if we cannot send something proper
+ // back to Together/KeY.
+ // Hence this try-catch
+ if (returnStatus == JOptionPane.YES_OPTION) {
+ //check, if there are open metavariables
+ //send("' ;; >>"); already done above
+ if (!this.currentNode.isMeta()) {
+ logger.info("No metavariables found, saving OCL");
+ //no open nodes, we can save OCL
+ String ocl = (String)linearization.getLinearizations().get(modelModulName + "OCL");
+ if (ocl == null) {
+ //OCL not present, so switch it on
+ langMenuModel.setActive(modelModulName + "OCL", true);
+ send("on " + modelModulName + "OCL");
+ ocl = (String)linearization.getLinearizations().get(modelModulName + "OCL");
+ }
+ ocl = Utils.compactSpaces(ocl.trim()).trim();
+
+ this.callback.sendConstraint(ocl);
+ } else {
+ logger.info("Metavariables found, saving AST");
+ //Abstract is always present
+ String abs = (String)linearization.getLinearizations().get("Abstract");
+ //then remove duplicate white space
+ abs = removeMetavariableNumbers(abs).replaceAll("\\s+", " ").trim();
+ this.callback.sendAbstract(abs);
+ }
+
+ }
+ } catch (Exception e) { // just print information about the exception
+ System.err.println("GFEditor2.endProgram() Caught an Exception.");
+ System.err.println("e.getLocalizedMessage(): " + e.getLocalizedMessage());
+ System.err.println("e.toString(): " + e);
+ System.err.println("e.printStackTrace():");
+ e.printStackTrace(System.err);
+ } finally {
+ if (this.callback != null) { // send linearization as a class invariant
+ Utils.cleanupFromUMLTypes(callback.getGrammarsDir());
+ }
+ shutDown();
+ }
+ } else if (returnStatus == JOptionPane.YES_OPTION) {
+ final Action saveAction = new SaveAction();
+ saveAction.actionPerformed(null);
+ shutDown();
+ }
+ }
+
+ /**
+ * In the GF AST, all metavariables have numbers behind them,
+ * like ?4. But GF cannot parse these, so the numbers have to be
+ * removed.
+ * Be aware, that this method also replaces ?n inside String literals!
+ * @param abs The GF AST
+ * @return abs, but without numbers behind the '?'
+ */
+ private static String removeMetavariableNumbers(String abs) {
+ return abs.replaceAll("\\?\\d+", "\\?");
+ }
+
+
+ /**
+ * Shuts down GF and terminates the edior
+ */
+ private void shutDown() {
+ try {
+ send("q", false, 1); // tell external GF process to quit
+ } finally {
+ removeAll();
+ dispose();
+ }
+ }
+
+ /**
+ * Performs some global settings like setting treeChanged and newObject,
+ * which can depend on the hmsg.
+ * Also the display gets cleared of wished so.
+ * @param hmsg The parsed hmsg.
+ */
+ private void formHmsg(Hmsg hmsg){
+ if (hmsg.clear) {
+ //clear output before linearization
+ this.display.resetLin();
+ display(true, false, false);
+ linearization.reset();
+ }
+ if (hmsg.newObject) {
+ this.newObject = true;
+ }
+ }
+
+ /**
+ * Fills the new category menu and sets the label 'grammar' to
+ * display the name of the abstract grammar.
+ * Fills langMenuModel and registers the presence of the
+ * loaded languages in linearization.linearizations.
+ */
+ private void formNewMenu (NewCategoryMenuResult nmr) {
+ //fill newCategoryMenu
+ for (int i = 0; i < nmr.menuContent.length; i++) {
+ newCategoryMenu.addItem(nmr.menuContent[i]);
+ }
+ //add the languages to the menu
+ for (int i = 0; i < nmr.languages.length; i++) {
+ final boolean active;
+ if (nmr.languages[i].equals("Abstract")) {
+ active = false;
+ } else {
+ active = true;
+ }
+ this.langMenuModel.add(nmr.languages[i], active);
+
+ //select FromUMLTypesOCL by default
+ if (nmr.languages[i].equals(modelModulName + "OCL")) {
+ this.selectedMenuLanguage = modelModulName + "OCL";
+ //TODO select OCL also in the menu
+ }
+ //'register' the presence of this language if possible
+ if (linearization != null) {
+ linearization.getLinearizations().put(nmr.languages[i], null);
+ }
+ }
+ //tell the user, which abstract grammar is used
+ //and save the import path
+ grammar.setText(nmr.grammarName);
+ for (int i = 0; i < nmr.paths.length; i++) {
+ fileString +="--" + nmr.paths[i] +"\n";
+ if (nmr.paths[i].lastIndexOf('.')!=nmr.paths[i].indexOf('.'))
+ grammar.setText(nmr.paths[i].substring(0,
+ nmr.paths[i].indexOf('.')).toUpperCase()+" ");
+ }
+
+ }
+
+
+
+
+ /**
+ * Parses the GF-output between <linearization> </linearization> tags
+ *
+ * Expects the linearization string to be given to this.linearization.
+ */
+ private void formLin(){
+ //reset previous output
+ this.display.resetLin();
+
+ linearization.parseLin(langMenuModel);
+ display(true, false, true);
+
+ //do highlighting
+ this.linearizationArea.getHighlighter().removeAllHighlights();
+ this.htmlLinPane.getHighlighter().removeAllHighlights();
+
+ Vector mahsVector = linearization.calculateHighlights(focusPosition);
+ for (Iterator it = mahsVector.iterator(); it.hasNext();) {
+ MarkedAreaHighlightingStatus mahs = (MarkedAreaHighlightingStatus)it.next();
+ //now highlight
+ if (mahs.focused && mahs.incorrect) {
+ highlight(mahs.ma, Color.ORANGE);
+ highlightHtml(mahs.ma, Color.ORANGE);
+ } else if (mahs.focused) {
+ highlight(mahs.ma, linearizationArea.getSelectionColor());
+ highlightHtml(mahs.ma, linearizationArea.getSelectionColor());
+ } else if (mahs.incorrect) {
+ highlight(mahs.ma, Color.RED);
+ highlightHtml(mahs.ma, Color.RED);
+ }
+ }
+ }
+
+
+
+
+
+ /**
+ * Small method that takes this.display and displays its content
+ * accordingly to what it is (pure text/HTML)
+ * @param doDisplay If the text should get displayed
+ * @param saveScroll if the old scroll state should be saved
+ * @param restoreScroll if the old scroll state should be restored
+ */
+ private void display(boolean doDisplay, boolean saveScroll, boolean restoreScroll) {
+ //Display the pure text
+ final String text = this.display.getText();
+ if (doDisplay) {
+ this.linearizationArea.setText(text);
+ }
+ if (restoreScroll) {
+ //this.outputPanelText.getVerticalScrollBar().setValue(this.display.scrollText);
+ this.linearizationArea.scrollRectToVisible(this.display.recText);
+ }
+ if (saveScroll) {
+ //this.display.scrollText = this.outputPanelText.getVerticalScrollBar().getValue();
+ this.display.recText = this.linearizationArea.getVisibleRect();
+ }
+
+ //Display the HTML
+ final String html = this.display.getHtml(this.font);
+ if (doDisplay) {
+ this.htmlLinPane.setText(html);
+ }
+ if (restoreScroll) {
+ //this.outputPanelHtml.getVerticalScrollBar().setValue(this.display.scrollHtml);
+ this.htmlLinPane.scrollRectToVisible(this.display.recHtml);
+ }
+ if (saveScroll) {
+ //this.display.scrollHtml = this.outputPanelHtml.getVerticalScrollBar().getValue();
+ this.display.recHtml = this.htmlLinPane.getVisibleRect();
+ }
+ }
+
+ /**
+ * Highlights the given MarkedArea in htmlLinPane
+ * @param ma the MarkedArea
+ * @param color the color the highlight should get
+ */
+ private void highlightHtml(final MarkedArea ma, Color color) {
+ try {
+ int begin = ma.htmlBegin;
+ int end = ma.htmlEnd;
+ //When creating the HtmlMarkedArea, we don't know, if
+ //it is going to be the last or not.
+ if (end > this.htmlLinPane.getDocument().getLength()) {
+ end = this.htmlLinPane.getDocument().getLength();
+ }
+ this.htmlLinPane.getHighlighter().addHighlight(begin, end, new DefaultHighlighter.DefaultHighlightPainter(color));
+ if (redLogger.isLoggable(Level.FINER)) {
+ redLogger.finer("HTML HIGHLIGHT: " + this.htmlLinPane.getDocument().getText(begin, end - begin) + "; Color:" + color);
+ }
+ } catch (BadLocationException e) {
+ redLogger.warning("HTML highlighting problem!\n" + e.getLocalizedMessage() + " : " + e.offsetRequested() + "\nHtmlMarkedArea: " + ma + "\nhtmlLinPane length: " + this.htmlLinPane.getDocument().getLength());
+ }
+ }
+
+ /**
+ * Highlights the given MarkedArea in linearizationArea
+ * @param ma the MarkedArea
+ * @param color the color the highlight should get
+ */
+ private void highlight(final MarkedArea ma, Color color) {
+ try {
+ int begin = ma.begin;
+ int end = ma.end ;
+ //When creating the MarkedArea, we don't know, if
+ //it is going to be the last or not.
+ if (end > this.linearizationArea.getText().length()) {
+ end = this.linearizationArea.getText().length() + 1;
+ }
+ this.linearizationArea.getHighlighter().addHighlight(begin, end, new DefaultHighlighter.DefaultHighlightPainter(color));
+ if (redLogger.isLoggable(Level.FINER)) {
+ redLogger.finer("HIGHLIGHT: " + this.linearizationArea.getText(begin, end - begin) + "; Color:" + color);
+ }
+ } catch (BadLocationException e) {
+ redLogger.warning("highlighting problem!\n" + e.getLocalizedMessage() + " : " + e.offsetRequested() + "\nMarkedArea: " + ma + "\nlinearizationArea length: " + this.linearizationArea.getText().length());
+ }
+ }
+
+
+ /**
+ * Sets the font on all the GUI-elements to font.
+ * @param newFont the font everything should have afterwards
+ */
+ private void fontEveryWhere(Font newFont) {
+ linearizationArea.setFont(newFont);
+ htmlLinPane.setFont(newFont);
+ parseField.setFont(newFont);
+ tree.tree.setFont(newFont);
+ refinementMenu.setFont(newFont);
+ save.setFont(newFont);
+ grammar.setFont(newFont);
+ open.setFont(newFont);
+ newTopic.setFont(newFont);
+ gfCommand.setFont(newFont);
+ leftMeta.setFont(newFont);
+ left.setFont(newFont);
+ top.setFont(newFont);
+ right.setFont(newFont);
+ rightMeta.setFont(newFont);
+ subtermDescLabel.setFont(newFont);
+ subtermNameLabel.setFont(newFont);
+ read.setFont(newFont);
+ alpha.setFont(newFont);
+ random.setFont(newFont);
+ undo.setFont(newFont);
+ checkSubtyping.setFont(newFont);
+ filterMenu.setFont(newFont);
+ setSubmenuFont(filterMenu, newFont, false);
+ modify.setFont(newFont);
+ statusLabel.setFont(newFont);
+ menuBar.setFont(newFont);
+ newCategoryMenu.setFont(newFont);
+ readDialog.setFont(newFont);
+ mlMenu.setFont(newFont);
+ setSubmenuFont(mlMenu, newFont, false);
+ modeMenu.setFont(newFont);
+ setSubmenuFont(modeMenu, newFont, false);
+ langMenu.setFont(newFont);
+ setSubmenuFont(langMenu, newFont, false);
+ fileMenu.setFont(newFont);
+ setSubmenuFont(fileMenu, newFont, false);
+ usabilityMenu.setFont(newFont);
+ setSubmenuFont(usabilityMenu, newFont, false);
+ viewMenu.setFont(newFont);
+ setSubmenuFont(viewMenu, newFont, false);
+ setSubmenuFont(sizeMenu, newFont, false);
+ setSubmenuFont(fontMenu, newFont, true);
+ //update also the HTML with the new size
+ display(true, false, true);
+ }
+
+ /**
+ * Set the font in the submenus of menu.
+ * Recursion depth is 1, so subsubmenus don't get fontified.
+ * @param subMenu The menu whose submenus should get fontified
+ * @param font the chosen font
+ * @param onlySize If only the font size or the whole font should
+ * be changed
+ */
+ private void setSubmenuFont(JMenu subMenu, Font font, boolean onlySize) {
+ for (int i = 0; i<subMenu.getItemCount(); i++)
+ {
+ JMenuItem item = subMenu.getItem(i);
+ if (item != null) {
+ //due to a bug in the jvm (already reported) deactivated
+ if (false && onlySize) {
+ Font newFont = new Font(item.getFont().getName(), Font.PLAIN, font.getSize());
+ item.setFont(newFont);
+ } else {
+ item.setFont(font);
+ }
+ //String name = item.getClass().getName();
+ //if (logger.isLoggable(Level.FINER)) logger.finer(name);
+ }
+ }
+ }
+
+ /**
+ * Writes the given String to the given Filename
+ * @param str the text to be written
+ * @param fileName the name of the file that is to be filled
+ */
+ static void writeOutput(String str, String fileName) {
+
+ try {
+ FileOutputStream fos = new FileOutputStream(fileName);
+ Writer out = new OutputStreamWriter(fos, "UTF8");
+ out.write(str);
+ out.close();
+ } catch (IOException e) {
+ JOptionPane.showMessageDialog(null,
+ "Document is empty!","Error", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+ /**
+ * Parses the GF-output between <tree> </tree> tags
+ * and build the corresponding tree.
+ *
+ * parses the already read XML for the tree and stores the tree nodes
+ * in nodeTable with their numbers as keys
+ *
+ * Also does some tree analyzing, if other actions have to be taken.
+ *
+ * @param treeString the string representation for the XML tree
+ * @return null, if no commands have to be executed afterwards.
+ * If the result is non-null, then result.s should be sent to GF
+ * afterwards, and no other form-method on this read-run is to be executed.
+ * result.i is the amount of undo steps that this command needs.
+ */
+ private DefaultMutableTreeNode formTree(String treeString) {
+ if (treeLogger.isLoggable(Level.FINER)) {
+ treeLogger.finer("treeString: "+ treeString);
+ }
+
+ /**
+ * stores the nodes and the indention of their children.
+ * When all children of a node are read,
+ * the next brethren / uncle node 'registers' with the same
+ * indention depth to show that the next children are his.
+ */
+ Hashtable parentNodes = new Hashtable();
+ String s = treeString;
+ /** consecutive node numbering */
+ int index = 0;
+ /** the node that gets created from the current line */
+ DefaultMutableTreeNode newChildNode=null;
+ /** is a star somewhere in treestring? 1 if so, 0 otherwise */
+ int star = 0;
+ if (s.indexOf('*')!=-1) {
+ star = 1;
+ }
+ DefaultMutableTreeNode topNode = null;
+ while (s.length()>0) {
+ /**
+ * every two ' ' indicate one tree depth level
+ * shift first gets assigned the indention depth in
+ * characters, later the tree depth
+ */
+ int shift = 0;
+ boolean selected = false;
+ while ((s.length()>0) && ((s.charAt(0)=='*')||(s.charAt(0)==' '))){
+ if (s.charAt(0) == '*') {
+ selected = true;
+ }
+ s = s.substring(1);
+ shift++;
+ }
+ if (s.length()>0) {
+ /** to save the top node*/
+ boolean isTop = false;
+ int j = s.indexOf("\n");
+ //is sth like "andS : Sent ", i.e. "fun : type " before trimming
+ String gfline = s.substring(0, j).trim();
+ GfAstNode node = new GfAstNode(gfline);
+ // use indentation to calculate the parent
+ index++;
+ s = s.substring(j+1);
+ shift = (shift - star)/2;
+
+ /**
+ * we know the parent, so we can ask it for the param information
+ * for the next child (the parent knows how many it has already)
+ * and save it in an AstNodeData
+ */
+ DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNodes.get(new Integer(shift));
+
+ /** compute the now child's position */
+ String newPos;
+ if ((parent != null) && (parent.getUserObject() instanceof AstNodeData) && parent.getUserObject() != null) {
+ AstNodeData pand = (AstNodeData)parent.getUserObject();
+ newPos = LinPosition.calculateChildPosition(pand.position, pand.childNum++);
+ } else {
+ //only the case for the root node
+ newPos = "[]";
+ isTop = true;
+ }
+
+ //default case, if we can get more information, this is overwritten
+ AstNodeData and;
+ Printname childPrintname = null;
+ if (!node.isMeta()) {
+ childPrintname = this.printnameManager.getPrintname(node.getFun());
+ }
+ Printname parentPrintname = null;
+ AstNodeData parentAnd = null;
+ String parentConstraint = "";
+ if (parent != null) {
+ parentAnd = (AstNodeData)parent.getUserObject();
+ if (parentAnd != null) {
+ parentConstraint = parentAnd.constraint;
+ }
+ }
+ if (childPrintname != null) {
+ //we know this one
+ and = new RefinedAstNodeData(childPrintname, node, newPos, selected, parentConstraint);
+ } else if (parent != null && node.isMeta()) {
+ //new child without refinement
+ if (parentAnd != null) {
+ parentPrintname = parentAnd.getPrintname();
+ }
+ if (parentPrintname != null) {
+ int paramPosition = parent.getChildCount();
+ String paramName = parentPrintname.getParamName(paramPosition);
+ if (paramName == null) {
+ paramName = node.getFun();
+ }
+ //if tooltip turns out to be null that's OK
+ String paramTooltip = parentPrintname.htmlifySingleParam(paramPosition);
+// if (logger.isLoggable(Level.FINER)) {
+// logger.finer("new node-parsing: '" + name + "', fun: '" + fun + "', type: '" + paramType + "'");
+// }
+ and = new UnrefinedAstNodeData(paramTooltip, node, newPos, selected, parentConstraint);
+
+ } else {
+ and = new RefinedAstNodeData(null, node, newPos, selected, parentConstraint);
+ }
+ } else {
+ //something unparsable, bad luck
+ //or refined and not described
+ and = new RefinedAstNodeData(null, node, newPos, selected, parentConstraint);
+ }
+
+ //add to the parent node
+ newChildNode = new DefaultMutableTreeNode(and);
+ if ((parent != null) && (newChildNode != null)) {
+ parent.add(newChildNode);
+ }
+ parentNodes.put(new Integer(shift+1), newChildNode);
+ if (isTop) {
+ topNode = newChildNode;
+ }
+ }
+ }
+ //to be deferred to later step in readGfEdit
+ return topNode;
+ }
+
+ /**
+ * Shows the tree, scrolls to the selected node and updates the
+ * mapping table between displayed node paths and AST positions.
+ * @param myTreePanel the panel of GFEditor2
+ * @param topNode The root node of the tree, that has the other nodes
+ * already as its children
+ */
+ private void showTree(DynamicTree2 myTreePanel, DefaultMutableTreeNode topNode) {
+ myTreePanel.clear();
+ nodeTable.clear();
+ //the rootNode is not shown, therefore, a dummy node plays this role
+ final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
+ rootNode.add(topNode);
+ ((DefaultTreeModel)(myTreePanel.tree.getModel())).setRoot(rootNode);
+ /**
+ * the path in the JTree (not in GF repesentation!) to the
+ * current new node.
+ */
+ TreePath path=null;
+ TreePath selectionPath = null;
+ // now fill nodeTable
+ for (Enumeration e = rootNode.breadthFirstEnumeration() ; e.hasMoreElements() ;) {
+ DefaultMutableTreeNode currNode = (DefaultMutableTreeNode)e.nextElement();
+ AstNodeData and = (AstNodeData)currNode.getUserObject();
+
+ path = new TreePath(currNode.getPath());
+ if (and == null) {
+ //only the case for the root node
+ nodeTable.put(path, "[]");
+ } else {
+ nodeTable.put(path, and.position);
+ if (and.selected) {
+ selectionPath = path;
+ if (treeLogger.isLoggable(Level.FINE)) {
+ treeLogger.fine("new selectionPath: " + selectionPath);
+ }
+
+ DefaultMutableTreeNode parent = null;
+ if (currNode.getParent() instanceof DefaultMutableTreeNode) {
+ parent = (DefaultMutableTreeNode)currNode.getParent();
+ }
+ Printname parentPrintname = null;
+ //display the current refinement description
+ if ((parent != null)
+ && (parent.getUserObject() != null)
+ && (parent.getUserObject() instanceof AstNodeData)
+ ) {
+ AstNodeData parentAnd = (AstNodeData)parent.getUserObject();
+ parentPrintname = parentAnd.getPrintname();
+ }
+ // set the description of the current parameter to a more
+ // prominent place
+ String paramName = null;
+ int paramPosition = -1;
+ if (parentPrintname != null) {
+ paramPosition = parent.getIndex(currNode);
+ paramName = parentPrintname.getParamName(paramPosition);
+ }
+ if (paramName == null) {
+ subtermNameLabel.setText(actionOnSubtermString);
+ subtermDescLabel.setText("");
+ } else {
+ subtermNameLabel.setText("<html><b>" + paramName + ": </b></html>");
+ String paramDesc = parentPrintname.getParamDescription(paramPosition);
+ if (paramDesc == null) {
+ subtermDescLabel.setText("");
+ } else {
+ subtermDescLabel.setText("<html>" + paramDesc + "</html>");
+ }
+ }
+ statusLabel.setText(and.node.getType());
+ }
+ }
+ }
+ //also set the old selectionPath since we know that we do know,
+ //that the selectionChanged event is bogus.
+ myTreePanel.oldSelection = selectionPath;
+ myTreePanel.tree.setSelectionPath(selectionPath);
+ myTreePanel.tree.scrollPathToVisible(selectionPath);
+ //show the selected as the 'selected' one in the JTree
+ myTreePanel.tree.makeVisible(selectionPath);
+ gui2.toFront();
+ }
+
+
+ /**
+ * Removes anything but the "new" from the new category menu
+ */
+ private void resetNewCategoryMenu() {
+ //remove everything except "New"
+ while (1< newCategoryMenu.getItemCount())
+ newCategoryMenu.removeItemAt(1);
+ }
+
+
+ /**
+ * Pops up a window for input of the wanted data and asks ic
+ * afterwards, if the data has the right format.
+ * Then gives that to GF.
+ * TODO Is called from RefinementMenu, but uses display. Where to put?
+ * @param ic the InputCommand that specifies the wanted format/type
+ */
+ protected void executeInputCommand(InputCommand ic) {
+ String s = (String)JOptionPane.showInputDialog(
+ this,
+ ic.getTitleText(),
+ ic.getTitleText(),
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ null,
+ "");
+ StringBuffer reason = new StringBuffer();
+ Object value = ic.validate(s, reason);
+ if (value != null) {
+ send("[t] g "+value);
+ if (logger.isLoggable(Level.FINER)) {
+ logger.finer("sending string " + value);
+ }
+ } else {
+ this.display.addToStages("\n" + reason.toString(), "<p>" + reason.toString() + "</p>");
+ display(true, false, false);
+ }
+ }
+
+
+
+ /**
+ * Handles the showing of the popup menu and the parse field
+ * @param e the MouseEvent, that caused the call of this function
+ */
+ protected void maybeShowPopup(MouseEvent e) {
+ //int i=outputVector.size()-1;
+ // right click:
+ if (e.isPopupTrigger()) {
+ if (popUpLogger.isLoggable(Level.FINER)) {
+ popUpLogger.finer("changing pop-up menu2!");
+ }
+ JPopupMenu popup2 = refinementMenu.producePopup();
+ popup2.show(e.getComponent(), e.getX(), e.getY());
+ }
+ // middle click
+ if (e.getButton() == MouseEvent.BUTTON2) {
+ // selection Exists:
+ if (popUpLogger.isLoggable(Level.FINER)) {
+ popUpLogger.finer(e.getX() + " " + e.getY());
+ }
+ String selectedText;
+
+ if (currentNode.isMeta()) {
+ // we do not want the ?3 to be in parseField, that disturbs
+ selectedText = "";
+ } else {
+ final String language;
+ //put together the currently focused text
+ if (e.getComponent() instanceof JTextComponent) {
+ JTextComponent jtc = (JTextComponent)e.getComponent();
+ int pos = jtc.viewToModel(e.getPoint());
+ final boolean htmlClicked = (jtc instanceof JTextPane);
+ language = linearization.getLanguageForPos(pos, htmlClicked);
+ } else {
+ language = "Abstract";
+ }
+ selectedText = linearization.getSelectedLinearization(language, focusPosition);
+
+ }
+ //compute the size of parseField
+ if (selectedText.length()<5)
+// if (treeCbMenuItem.isSelected())
+// parseField.setBounds(e.getX()+(int)Math.round(tree.getBounds().getWidth()), e.getY()+80, 400, 40);
+// else
+ parseField.setBounds(e.getX(), e.getY()+80, 400, 40);
+ else
+// if (treeCbMenuItem.isSelected())
+// parseField.setBounds(e.getX()+(int)Math.round(tree.getBounds().getWidth()), e.getY()+80, selectedText.length()*20, 40);
+// else
+ parseField.setBounds(e.getX(), e.getY()+80, selectedText.length()*20, 40);
+ getLayeredPane().add(parseField, new Integer(1), 0);
+ parseField.setText(selectedText);
+ parseField.requestFocusInWindow();
+ }
+ }
+
+ /**
+ * Adds toHmsg to the [hmsg] part of command, if that is present.
+ * If not, prepends toHmsg in square brackets to command
+ * @param command The command for GF
+ * @param toHmsg the text, that should occur inside [] before the command
+ * @return the updated command (s.a.)
+ */
+ private static String addToHmsg(String command, String toHmsg) {
+ command = command.trim();
+ if (command.startsWith("[")) {
+ command = "[" + toHmsg + command.substring(1);
+ } else {
+ command = "[" + toHmsg + "] " + command;
+ }
+ return command;
+ }
+
+ /**
+ * pop-up menu (adapted from DynamicTree2):
+ * @author janna
+ */
+ class PopupListener extends MouseAdapter {
+ public void mousePressed(MouseEvent e) {
+ // int selStart = tree.getRowForLocation(e.getX(), e.getY());
+ // output.setSelectionRow(selStart);
+ if (popUpLogger.isLoggable(Level.FINER)) {
+ popUpLogger.finer("mouse pressed2: "+linearizationArea.getSelectionStart()+" "+linearizationArea.getSelectionEnd());
+ }
+ maybeShowPopup(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ //nothing to be done here
+ }
+ }
+
+ /**
+ * Encapsulates the opening of terms or linearizations to a file.
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class OpenAction extends AbstractAction {
+ public OpenAction() {
+ super("Open Text", null);
+ putValue(SHORT_DESCRIPTION, "Opens abstract syntax trees or linearizations for the current grammar");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (saveFc.getChoosableFileFilters().length<2)
+ saveFc.addChoosableFileFilter(new GrammarFilter());
+ int returnVal = saveFc.showOpenDialog(GFEditor2.this);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ resetNewCategoryMenu();
+ langMenuModel.resetLanguages();
+
+ File file = saveFc.getSelectedFile();
+ // opening the file for editing :
+ if (logger.isLoggable(Level.FINER)) logger.finer("opening: "+ file.getPath().replace('\\', File.separatorChar));
+ if (saveTypeGroup.getSelection().getActionCommand().equals("term")) {
+ if (logger.isLoggable(Level.FINER)) logger.finer(" opening as a term ");
+ send("[nt] open "+ file.getPath().replace('\\', File.separatorChar));
+ }
+ else {
+ if (logger.isLoggable(Level.FINER)) logger.finer(" opening as a linearization ");
+ send("[nt] openstring "+ file.getPath().replace('\\', File.separatorChar));
+ }
+
+ fileString ="";
+ grammar.setText("No Topic ");
+ }
+ }
+ }
+
+ /**
+ * Encapsulates the saving of terms or linearizations to a file.
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class SaveAction extends AbstractAction {
+ public SaveAction() {
+ super("Save As", null);
+ putValue(SHORT_DESCRIPTION, "Saves either the current linearizations or the AST");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (saveFc.getChoosableFileFilters().length<2)
+ saveFc.addChoosableFileFilter(new GrammarFilter());
+ int returnVal = saveFc.showSaveDialog(GFEditor2.this);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ File file = saveFc.getSelectedFile();
+ if (logger.isLoggable(Level.FINER)) logger.finer("saving as " + file);
+ final String abstractLin = linearization.getLinearizations().get("Abstract").toString();
+
+ if (saveTypeGroup.getSelection().getActionCommand().equals("term")) {
+ // saving as a term
+ writeOutput(removeMetavariableNumbers(abstractLin), file.getPath());
+ } else {
+ // saving as a linearization:
+ /** collects the show linearizations */
+ StringBuffer text = new StringBuffer();
+ /** if sth. at all is shown already*/
+ boolean sthAtAll = false;
+ for (Iterator it = linearization.getLinearizations().keySet().iterator(); it.hasNext();) {
+ Object key = it.next();
+ if (!key.equals("Abstract")) {
+ if (sthAtAll) {
+ text.append("\n\n");
+ }
+ text.append(linearization.getLinearizations().get(key));
+ sthAtAll = true;
+ }
+ }
+ if (sthAtAll) {
+ writeOutput(text.toString(), file.getPath());
+ if (logger.isLoggable(Level.FINER)) logger.finer(file + " saved.");
+ } else {
+ if (logger.isLoggable(Level.FINER)) logger.warning("no concrete language shown, saving abstract");
+ writeOutput(removeMetavariableNumbers(abstractLin), file.getPath());
+ if (logger.isLoggable(Level.FINER)) logger.finer(file + " saved.");
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Encapsulates adding new languages for the current abstract grammar.
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class ImportAction extends AbstractAction {
+ public ImportAction() {
+ super("Add", null);
+ putValue(SHORT_DESCRIPTION, "add another concrete language for the current abstract grammar");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ //add another language (Add...)
+ if (fc.getChoosableFileFilters().length<2)
+ fc.addChoosableFileFilter(new GrammarFilter());
+ int returnVal = fc.showOpenDialog(GFEditor2.this);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ File file = fc.getSelectedFile();
+
+ resetNewCategoryMenu();
+ langMenuModel.resetLanguages();
+ // importing a new language :
+ if (logger.isLoggable(Level.FINER)) logger.finer("importing: "+ file.getPath().replace('\\','/'));
+ fileString ="";
+ //TODO does that load paths in UNIX-notation under windows?
+ send("i "+ file.getPath().replace('\\',File.separatorChar), false, 1);
+ processGfinit();
+ processGfedit();
+ }
+ }
+
+ }
+
+ /**
+ * Encapsulates starting over with a new grammar.
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class NewTopicAction extends AbstractAction {
+ public NewTopicAction() {
+ super("New Grammar", null);
+ putValue(SHORT_DESCRIPTION, "dismiss current editing and load a new grammar");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_N));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (fc.getChoosableFileFilters().length<2)
+ fc.addChoosableFileFilter(new GrammarFilter());
+ int returnVal = fc.showOpenDialog(GFEditor2.this);
+ if (returnVal == JFileChooser.APPROVE_OPTION) {
+ int n = JOptionPane.showConfirmDialog(GFEditor2.this,
+ "This will dismiss the previous editing. Would you like to continue?",
+ "Starting a new topic", JOptionPane.YES_NO_OPTION);
+ if (n == JOptionPane.YES_OPTION){
+ File file = fc.getSelectedFile();
+ // importing a new grammar :
+ newObject = false;
+ statusLabel.setText(status);
+ subtermDescLabel.setText("");
+ subtermNameLabel.setText("");
+ refinementMenu.reset();
+ tree.resetTree();
+ resetNewCategoryMenu();
+ langMenuModel.resetLanguages();
+ selectedMenuLanguage = "Abstract";
+ rbMenuItemShort.setSelected(true);
+ rbMenuItemUnTyped.setSelected(true);
+ typedMenuItems = false;
+
+ fileString="";
+ grammar.setText("No Topic ");
+ display.resetLin();
+ display(true, true, false);
+ undoStack.clear();
+ send(" e "+ file.getPath().replace('\\',File.separatorChar), false, 1);
+ processInit(null, false);
+ processGfedit();
+ resetPrintnames(true);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Encapsulates starting over without loading new grammars
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class ResetAction extends AbstractAction {
+ public ResetAction() {
+ super("Reset", null);
+ putValue(SHORT_DESCRIPTION, "discard everything including the loaded grammars");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_R));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ newObject = false;
+ statusLabel.setText(status);
+ subtermDescLabel.setText("");
+ subtermNameLabel.setText("");
+ refinementMenu.reset();
+ tree.resetTree();
+ langMenuModel.resetLanguages();
+ resetNewCategoryMenu();
+ selectedMenuLanguage = "Abstract";
+
+ rbMenuItemShort.setSelected(true);
+ rbMenuItemUnTyped.setSelected(true);
+ typedMenuItems = false;
+
+ fileString="";
+ grammar.setText("No Topic ");
+ undoStack.clear();
+ send("e", false, 1);
+ processGfinit();
+ }
+
+ }
+
+ /**
+ * Encapsulates exiting the program
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class QuitAction extends AbstractAction {
+ public QuitAction() {
+ super("Quit", null);
+ putValue(SHORT_DESCRIPTION, "exit the editor");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_Q));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ endProgram();
+ }
+
+ }
+
+ /**
+ * Encapsulates the random command for GF
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class RandomAction extends AbstractAction {
+ public RandomAction() {
+ super("Random", null);
+ putValue(SHORT_DESCRIPTION, "build a random AST from the current cursor position");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_M));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ send("[t] a");
+ }
+
+ }
+
+ /**
+ * Encapsulates the undo command for GF
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class UndoAction extends AbstractAction {
+ public UndoAction() {
+ super("Undo", null);
+ putValue(SHORT_DESCRIPTION, "undo the last command");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_U));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ int undoSteps = 1;
+ if (!undoStack.empty()) {
+ undoSteps = ((Integer)undoStack.pop()).intValue();
+ }
+ send("[t] u " + undoSteps, true, 0);
+ }
+ }
+
+ /**
+ * Encapsulates alpha command for GF
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class AlphaAction extends AbstractAction {
+ public AlphaAction() {
+ super("Alpha", null);
+ putValue(SHORT_DESCRIPTION, "Performing alpha-conversion, rename bound variables");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String s = JOptionPane.showInputDialog("Type string:", alphaInput);
+ if (s!=null) {
+ alphaInput = s;
+ send("[t] x "+s);
+ }
+ }
+
+ }
+
+ /**
+ * Encapsulates the input dialog and sending of arbitrary commands to GF
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class GfCommandAction extends AbstractAction {
+ public GfCommandAction() {
+ super("GF command", null);
+ putValue(SHORT_DESCRIPTION, "send a command to GF");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_G));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_G, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String s = JOptionPane.showInputDialog("Command:", commandInput);
+ if (s!=null) {
+ commandInput = s;
+ s = addToHmsg(s, "t");
+ if (logger.isLoggable(Level.FINER)) logger.finer("sending: "+ s);
+ send(s);
+ }
+ }
+ }
+
+ /**
+ * Encapsulates the showing of the read dialog
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class ReadAction extends AbstractAction {
+ public ReadAction() {
+ super("Read", null);
+ putValue(SHORT_DESCRIPTION, "Refining with term or linearization from typed string or file");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_E));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ readDialog.show();
+ }
+
+ }
+
+ /**
+ * Encapsulates the splitting of the main window
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class SplitAction extends AbstractAction {
+ public SplitAction() {
+ super("Split Windows", null);
+ putValue(SHORT_DESCRIPTION, "Splits the refinement menu into its own window");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_L));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_L, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ coverPanel.remove(centerPanel);
+ centerPanel2.add(middlePanelUp, BorderLayout.SOUTH);
+ if (((JCheckBoxMenuItem)viewMenu.getItem(0)).isSelected()) {
+ centerPanel2.add(treePanel, BorderLayout.CENTER);
+ }
+ else {
+ centerPanel2.add(outputPanelUp, BorderLayout.CENTER);
+ }
+ coverPanel.add(centerPanel2, BorderLayout.CENTER);
+ gui2.getContentPane().add(refinementMenu.getRefinementListsContainer());
+ gui2.setVisible(true);
+ pack();
+ repaint();
+ }
+
+ }
+
+ /**
+ * Encapsulates the combining of the main window
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class CombineAction extends AbstractAction {
+ public CombineAction() {
+ super("One Window", null);
+ putValue(SHORT_DESCRIPTION, "Refinement menu and linearization areas in one window");
+ putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_W));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ coverPanel.remove(centerPanel2);
+ middlePanel.add(middlePanelUp, BorderLayout.NORTH);
+ if (((JCheckBoxMenuItem)viewMenu.getItem(0)).isSelected()) { gui2.setVisible(false);
+ centerPanel.setLeftComponent(treePanel);
+ }
+ else {
+ centerPanel.setLeftComponent(outputPanelUp);
+ gui2.setVisible(false);
+ }
+ coverPanel.add(centerPanel, BorderLayout.CENTER);
+ centerPanelDown.add(refinementMenu.getRefinementListsContainer(), BorderLayout.CENTER);
+ //centerPanelDown.add(refinementMenu.refinementSubcatPanel, BorderLayout.EAST);
+ pack();
+ repaint();
+ }
+
+ }
+
+ /**
+ * Starts a run on the AST to hunt down open subtyping witnesses
+ * Is not local in initializeGUI because jswat cannot have active breakpoints in such a class, whyever.
+ * @author daniels
+ */
+ class SubtypeAction extends AbstractAction {
+ public SubtypeAction() {
+ super("Close Subtypes", null);
+ putValue(SHORT_DESCRIPTION, "try to automatically refine Subtype relations");
+ //putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_U));
+ putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_T, ActionEvent.CTRL_MASK));
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String resetCommand;
+ int usteps ;
+ if (focusPosition != null) {
+ //go back to where we come from
+ resetCommand = "[t] mp " + focusPosition.position;
+ usteps = 1;
+ } else {
+ resetCommand = "[t] gf";
+ usteps = 0;
+ }
+ SubtypingProber sp = new SubtypingProber(gfCapsule);
+ int undos = sp.checkSubtyping();
+ send(resetCommand , true, undos + usteps);
+ }
+ }
+
+
+
+ /**
+ * Takes care, which classes are present and which states they have.
+ * @author daniels
+ */
+ class LangMenuModel implements LanguageManager{
+ Logger menuLogger = Logger.getLogger("de.uka.ilkd.key.ocl.gf.GFEditor2.MenuModel");
+ /**
+ * Just a mutable tuple of language name and whether this language
+ * is displayed or not.
+ */
+ class LangActiveTuple {
+ String lang;
+ boolean active;
+ public LangActiveTuple(String lang, boolean active) {
+ this.lang = lang;
+ this.active = active;
+ }
+ public String toString() {
+ return lang + " : " + active;
+ }
+ }
+
+ private Vector languages = new Vector();
+ /** the group containing RadioButtons for the language the menus
+ * should have
+ */
+ private ButtonGroup languageGroup = new ButtonGroup();
+
+ void updateMenus() {
+ for (Iterator it = this.languages.iterator(); it.hasNext(); ) {
+ LangActiveTuple lat = (LangActiveTuple)it.next();
+ boolean alreadyPresent = false;
+ // language already in the list of available languages?
+ for (int i=0; i<langMenu.getItemCount()-2;i++)
+ if ((langMenu.getItem(i) != null) && langMenu.getItem(i).getText().equals(lat.lang)) {
+ alreadyPresent = true;
+ break;
+ }
+ if (!alreadyPresent) {
+ //add item to the language list:
+ JCheckBoxMenuItem cbMenuItem = new JCheckBoxMenuItem(lat.lang);
+ if (menuLogger.isLoggable(Level.FINER)) menuLogger.finer("menu item: " + lat.lang);
+ cbMenuItem.setSelected(lat.active);
+ cbMenuItem.setActionCommand("lang");
+ cbMenuItem.addActionListener(this.langDisplayListener);
+ langMenu.insert(cbMenuItem, langMenu.getItemCount()-2);
+
+ JRadioButtonMenuItem rbMenuItem = new JRadioButtonMenuItem(lat.lang);
+ rbMenuItem.setActionCommand(lat.lang);
+ rbMenuItem.addActionListener(this.menuLanguageListener);
+ languageGroup.add(rbMenuItem);
+ if (lat.lang.equals(selectedMenuLanguage)) {
+ if (menuLogger.isLoggable(Level.FINER)) {
+ menuLogger.finer("Selecting " + selectedMenuLanguage);
+ }
+ rbMenuItem.setSelected(true);
+ }
+ mlMenu.add(rbMenuItem);
+
+ }
+ }
+ //stolen from fontEverywhere
+ setSubmenuFont(langMenu, font, false);
+ setSubmenuFont(mlMenu, font, false);
+ }
+
+ /**
+ * Sets language myLang to myActive.
+ * Does nothing, if myLang is not already there.
+ * @param myLang The name of the language
+ * @param myActive whether the language is displayed or not
+ */
+ void setActive(String myLang, boolean myActive) {
+ boolean alreadyThere = false;
+ for (Iterator it = this.languages.iterator(); it.hasNext(); ) {
+ LangActiveTuple current = (LangActiveTuple)it.next();
+ if (current.lang.equals(myLang)) {
+ current.active = myActive;
+ alreadyThere = true;
+ }
+ }
+ if (!alreadyThere) {
+ menuLogger.warning(myLang + " not yet known");
+ }
+ }
+
+ /**
+ * Checks if myLang is already present, and if not,
+ * adds it. In that case, myActive is ignored.
+ * @param myLang The name of the language
+ * @param myActive whether the language is displayed or not
+ */
+ public void add(String myLang, boolean myActive) {
+ boolean alreadyThere = false;
+ for (Iterator it = this.languages.iterator(); it.hasNext(); ) {
+ LangActiveTuple current = (LangActiveTuple)it.next();
+ if (current.lang.equals(myLang)) {
+ alreadyThere = true;
+ }
+ }
+ if (!alreadyThere) {
+ if (menuLogger.isLoggable(Level.FINER)) {
+ menuLogger.finer(myLang + " added");
+ }
+ LangActiveTuple lat = new LangActiveTuple(myLang, myActive);
+ this.languages.add(lat);
+ }
+ updateMenus();
+ }
+
+ /**
+ * @param myLang The language in question
+ * @return true iff the language is present and set to active,
+ * false otherwise.
+ */
+ public boolean isLangActive(String myLang) {
+ for (Iterator it = this.languages.iterator(); it.hasNext(); ) {
+ LangActiveTuple current = (LangActiveTuple)it.next();
+ if (current.lang.equals(myLang)) {
+ return current.active;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * initializes a virgin languages menu
+ */
+ public LangMenuModel() {
+ resetLanguages();
+ }
+
+
+ /**
+ * Resets the Languages menu so that it only contains a seperator and the Add button.
+ * Resets the shown menu languages.
+ * Resets the CheckBoxes that display the available languages.
+ */
+ void resetLanguages() {
+ langMenu.removeAll();
+ langMenu.addSeparator();
+ JMenuItem fileMenuItem = new JMenuItem(new ImportAction());
+ langMenu.add(fileMenuItem);
+
+ mlMenu.removeAll();
+ this.languageGroup = new ButtonGroup();
+ this.languages = new Vector();
+ updateMenus();
+ }
+
+
+ /**
+ * Listens to the language menu RadioButtons and sends the
+ * menu language changing commands suiting to the respective
+ * button to GF.
+ * Operates on selectedMenuLanguage from GFEditor2.
+ */
+ private ActionListener menuLanguageListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ final String action = e.getActionCommand();
+ // must be a menu language
+ selectedMenuLanguage = action;
+ final String sendLang;
+ if (action.equals("Abstract")) {
+ sendLang = "Abs";
+ } else {
+ sendLang = action;
+ }
+ send("ml " + sendLang);
+ resetPrintnames(true);
+
+ return;
+ }
+ };
+
+ /**
+ * listens to the CheckBoxes in the Language menu and switches the
+ * correspondend languages on or off when the user clicks on them
+ */
+ private ActionListener langDisplayListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (newObject) {
+ //clear display of text and HTML
+ display.resetLin();
+ display(true, false, true);
+ formLin();
+ }
+ final String lang = ((JCheckBoxMenuItem)e.getSource()).getText();
+ if (((JCheckBoxMenuItem)e.getSource()).isSelected()){
+ if (menuLogger.isLoggable(Level.FINER)) {
+ menuLogger.finer("turning on language '" + lang + "'");
+ }
+ setActive(lang, true);
+ send("on " + lang);
+ }
+ else{
+ if (menuLogger.isLoggable(Level.FINER)) {
+ menuLogger.finer("turning off language '" + lang + "'");
+ }
+ setActive(lang, false);
+ send("off " + lang);
+ }
+ return;
+ }
+ };
+ }
+}
diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfAstNode.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfAstNode.java new file mode 100644 index 000000000..8912d0778 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfAstNode.java @@ -0,0 +1,121 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * @author daniels + * This Class represents a parsed node in the GF AST. + * It knows about types, bound variables, funs. + * But nothing about printnames. That's what AstNodeData is for. + */ +class GfAstNode { + /** + * contains the types of the bound variables in the order of their occurence + */ + protected final String[] boundTypes; + /** + * contains the names of the bound variables in the order of their occurence + */ + protected final String[] boundNames; + /** + * The type of this AST node + */ + private final String type; + /** + * @return The type of this AST node + */ + protected String getType() { + return type; + } + /** + * the fun represented in this AST node + */ + private final String fun; + + /** + * @return the fun represented in this AST node. + * Can be a metavariable like "?1" + */ + protected String getFun() { + return fun; + } + + /** + * @return true iff the node is a metavariable, i.e. open and not + * yet refined. + */ + protected boolean isMeta() { + return fun.startsWith("?"); + } + /** + * the line that was used to build this node + */ + private final String line; + /** + * @return the line that was used to build this node + */ + protected String getLine() { + return line; + } + + /** + * The constraint attached to this node + */ + public final String constraint; + + /** + * feed this constructor the line that appears in the GF AST and + * it will get chopped at the right points. + * @param line The line from GF without the * in the beginning. + */ + protected GfAstNode(final String line) { + this.line = line.trim(); + final int index = this.line.lastIndexOf(" : "); + String typeString = this.line.substring(index + 3); + final int constraintIndex = typeString.indexOf(" {"); + if (constraintIndex > -1) { + this.constraint = typeString.substring(constraintIndex + 1).trim(); + this.type = typeString.substring(0, constraintIndex).trim(); + } else { + this.constraint = ""; + this.type = typeString; + } + String myFun = this.line.substring(0, index); + if (myFun.startsWith("\\(")) { + final int end = myFun.lastIndexOf(") -> "); + String boundPart = myFun.substring(2, end); + String[] bounds = boundPart.split("\\)\\s*\\,\\s*\\("); + this.boundNames = new String[bounds.length]; + this.boundTypes = new String[bounds.length]; + for (int i = 0; i < bounds.length;i++) { + //System.out.print("+" + bounds[i] + "-"); + int colon = bounds[i].indexOf(" : "); + this.boundNames[i] = bounds[i].substring(0, colon); + this.boundTypes[i] = bounds[i].substring(colon + 3); + //System.out.println(boundNames[i] + " ;; " + boundTypes[i]); + } + myFun = myFun.substring(end + 5); + } else { + this.boundNames = new String[0]; + this.boundTypes = new String[0]; + } + this.fun = myFun; + } + + public String toString() { + return this.line; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfCapsule.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfCapsule.java new file mode 100644 index 000000000..c1d012a02 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfCapsule.java @@ -0,0 +1,621 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.ProgressMonitor; + +class GfCapsule { + /** + * XML parsing debug messages + */ + private static Logger xmlLogger = Logger.getLogger(GfCapsule.class.getName() + ".xml"); + /** + * generic logging of this class + */ + private static Logger logger = Logger.getLogger(GfCapsule.class.getName()); + /** + * The output from GF is in here. + * Only the read methods, initializeGF and the prober objects access this. + */ + BufferedReader fromProc; + /** + * Used to leave messages for GF here. + * But <b>only</b> in send and special probers that clean up with undo + * after them (or don't change the state like PrintnameLoader). + */ + BufferedWriter toProc; + + /** + * Starts GF with the given command gfcmd in another process. + * Sets up the reader and writer to that process. + * Does in it self not read anything from GF. + * @param gfcmd The complete command to start GF, including 'gf' itself. + */ + public GfCapsule(String gfcmd){ + try { + Process extProc = Runtime.getRuntime().exec(gfcmd); + InputStreamReader isr = new InputStreamReader( + extProc.getInputStream(),"UTF8"); + this.fromProc = new BufferedReader (isr); + String defaultEncoding = isr.getEncoding(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("encoding "+defaultEncoding); + } + this.toProc = new BufferedWriter(new OutputStreamWriter(extProc.getOutputStream(),"UTF8")); + } catch (IOException e) { + JOptionPane.showMessageDialog(new JFrame(), "Could not start " + gfcmd+ + "\nCheck your $PATH", "Error", + JOptionPane.ERROR_MESSAGE); + throw new RuntimeException("Could not start " + gfcmd+ + "\nCheck your $PATH"); + } + } + + + /** + * Does the actual writing of command to the GF process via STDIN + * @param command exactly the string that is going to be sent + */ + protected void realSend(String command) { + try { + toProc.write(command, 0, command.length()); + toProc.newLine(); + toProc.flush(); + } catch (IOException e) { + System.err.println("Could not write to external process " + e); + } + + } + + /** + * reads the part between >gfinit< and >/gfinit< + * @return the data for the new category menu + */ + protected NewCategoryMenuResult readGfinit() { + try { + //read <hmsg> or <newcat> or <topic> (in case of no grammar loaded) + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("12 "+readresult); + //when old grammars are loaded, the first line looks like + //"reading grammar of old format letter.Abs.gfreading old file letter.Abs.gf<gfinit>" + if (readresult.indexOf("<gfinit>") > -1) { + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("12 "+readresult); + } + //no command appendix expected or applicable here, so appendix is discarded + Hmsg hmsg = readHmsg(readresult); + String next = hmsg.lastline; + //no hmsg supported here. Wouldn't be applicable. + //the reading above is to silently ignore it intead of failing. + //formHmsg(hmsg); + + if ((next!=null) && ((next.indexOf("newcat") > -1) + || (next.indexOf("topic") > -1))) { + NewCategoryMenuResult ncmr = readNewMenu(); + return ncmr; + } + + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + } + return null; + } + + /** + * reads the greeting text from GF + * @return S tuple with first = the last read GF line, + * which should be the first loading line + * and second = The greetings string + */ + protected StringTuple readGfGreetings() { + try { + String readresult = ""; + StringBuffer outputStringBuffer = new StringBuffer(); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("1 "+readresult); + while ((readresult.indexOf("gf")==-1) && (readresult.trim().indexOf("<") < 0)){ + outputStringBuffer.append(readresult).append("\n"); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("1 "+readresult); + } + return new StringTuple(readresult, outputStringBuffer.toString()); + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + return new StringTuple("", e.getLocalizedMessage()); + } + + } + + /** + * reads the loading and compiling messages from GF + * @param readresult the first loading line + * @param pm to monitor the loading progress. May be null + * @return A tuple with first = the first line from >gfinit< or >gfedit< + * and second = the loading message as pure text + */ + protected StringTuple readGfLoading(String readresult, ProgressMonitor pm) { + try { + // in case nothing has been loaded first, the that has to be done now + if (readresult == null) { + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("1 " + readresult); + } + StringBuffer textPure = new StringBuffer(); + int progress = 5300; + while (!(readresult.indexOf("<gfinit>") > -1 || (readresult.indexOf("<gfmenu>") > -1))){ + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("1 "+readresult); + textPure.append(readresult).append("\n"); + progress += 12; + Utils.tickProgress(pm, progress, null); + } + //when old grammars are loaded, the first line looks like + //"reading grammar of old format letter.Abs.gfreading old file letter.Abs.gf<gfinit>" + //without newlines + final int beginInit = readresult.indexOf("<gfinit>"); + if (beginInit > 0) { + textPure.append(readresult.substring(0, beginInit)).append("\n"); + //that is the expected result + readresult = "<gfinit>"; + } + return new StringTuple(readresult, textPure.toString()); + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + return new StringTuple("", e.getLocalizedMessage()); + } + + } + + + /** + * Reads the <gfedit> part from GF's XML output. + * The different subtags are put into the result + * @param newObj If a new object in the editor has been started. + * If the to-be-read hmsg contains the newObject flag, + * that overwrites this parameter + * @return the read tags, partially halfy parsed, partially raw. + * The way the different form methods expect it. + */ + protected GfeditResult readGfedit(boolean newObj) { + try { + String next = ""; + //read <gfedit> + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("11 "+readresult); + //read either <hsmg> or <lineatization> + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("11 "+readresult); + + //hmsg stuff + final Hmsg hmsg = readHmsg(readresult); + next = hmsg.lastline; + + //reading <linearizations> + //seems to be the only line read here + //this is here to give as some sort of catch clause. + while ((next!=null)&&((next.length()==0)||(next.indexOf("<linearizations>")==-1))) { + next = fromProc.readLine(); + if (next!=null){ + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("10 "+next); + } else { + System.exit(0); + } + } + readresult = next; + String lin = readLin(); + final String treeString = readTree(); + final String message = readMessage(); + //read the menu stuff + Vector gfCommandVector; + if (newObj || hmsg.newObject) { + gfCommandVector = readRefinementMenu(); + } else { + while(readresult.indexOf("</menu")==-1) { + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("12 " + readresult); + } + gfCommandVector = null; + } + // "" should occur quite fast, but it has not already been read, + // since the last read line is "</menu>" + for (int i = 0; i < 3 && !readresult.equals(""); i++){ + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("11 " + readresult); + } + //all well, return the read stuff + return new GfeditResult(gfCommandVector, hmsg, lin, message, treeString); + + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + } + //nothing well, return bogus stuff + return new GfeditResult(new Vector(), new Hmsg("", "", false, false, false, false, true), "", "", ""); + + } + + /** + * reads the linearizations in all language. + * seems to expect the first line of the XML structure + * (< lin) already to be read + * Accumulates the GF-output between <linearization> </linearization> tags + */ + protected String readLin(){ + StringBuffer lins = new StringBuffer(); + try { + //read <linearizations> + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 " + readresult); + lins.append(readresult).append('\n'); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + while ((readresult != null) && (readresult.indexOf("/linearization") == -1)){ + lins.append(readresult).append('\n'); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + } + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + } + return lins.toString(); + } + + /** + * reads in the tree and calls formTree without start end end tag of tree + * expects the first starting XML tag tree to be already read + * @return the read tags for the tree or null if a read error occurs + */ + protected String readTree(){ + String treeString = ""; + try { + //read <tree> + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + while (readresult.indexOf("/tree") == -1){ + treeString += readresult + "\n"; + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + } + return treeString; + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + return null; + } + } + + /** + * Parses the GF-output between <message> </message> tags + * and returns it. + * @return The read message. + */ + protected String readMessage(){ + String s =""; + try { + // read <message> + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("6 " + readresult); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 " + readresult); + while (readresult.indexOf("/message") == -1){ + s += readresult + "\n"; + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 " + readresult); + } + return s; + } catch(IOException e){ + System.err.println(e.getLocalizedMessage()); + e.printStackTrace(); + return e.getLocalizedMessage(); + } + } + + /** + * reads the cat entries and puts them into result.menuContent, + * after that reads + * the names of the languages and puts them into the result.languages + * The loaded grammar files are put into result.paths, + * a guessed grammar name into result.grammarName + * Parses the GF-output between <gfinit> tags + */ + protected NewCategoryMenuResult readNewMenu () { + //here the read stuff is sorted into + String grammarName = ""; + final Vector languages = new Vector(); + final Vector menuContent = new Vector(); + final Vector paths = new Vector(); + + boolean more = true; + try { + //read first cat + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) { + xmlLogger.finer("2 " + readresult); + } + if (readresult.indexOf("(none)") > -1) { + //no topics present + more = false; + } + + while (more){ + //adds new cat s to the menu + if (readresult.indexOf("topic") == -1) { + final String toAdd = readresult.substring(6); + menuContent.add(toAdd); + } else { + more = false; + } + //read </newcat + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("2 " + readresult); + //read <newcat (normally) + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("3 " + readresult); + if (readresult.indexOf("topic") != -1) { + //no more categories + more = false; + } + //read next cat / topic + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("4 " + readresult); + } + //set topic + grammarName = readresult.substring(4) + " "; + //read </topic> + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("2 " + readresult); + //read <language> + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("3 " + readresult); + //read actual language + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("4 " + readresult); + + //read the languages and select the last non-abstract + more = true; + while (more){ + if ((readresult.indexOf("/gfinit") == -1) + && (readresult.indexOf("lin") == -1)) { + //form lang and Menu menu: + final String langName = readresult.substring(4); + languages.add(langName); + } else { + more = false; + } + // read </language> + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("2 " + readresult); + // read <language> or </gfinit...> + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("3 " + readresult); + if ((readresult.indexOf("/gfinit") != -1) + || (readresult.indexOf("lin") != -1)) { + more = false; + } + // registering the file name: + if (readresult.indexOf("language") != -1) { + String path = readresult.substring(readresult.indexOf('=') + 1, + readresult.indexOf('>')); + path = path.substring(path.lastIndexOf(File.separatorChar) + 1); + if (xmlLogger.isLoggable(Level.FINE)) xmlLogger.fine("language: " + path); + paths.add(path); + } + // in case of finished, read the final "" after </gfinit>, + // otherwise the name of the next language + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("4 " + readresult); + } + } catch(IOException e){ + xmlLogger.warning(e.getMessage()); + } + String[] menuContentArray = Utils.vector2Array(menuContent); + String[] languagesArray = Utils.vector2Array(languages); + String[] pathsArray = Utils.vector2Array(paths); + NewCategoryMenuResult result = new NewCategoryMenuResult(grammarName, menuContentArray, languagesArray, pathsArray); + return result; + } + + /** + * Reads the hmsg part of the XML that is put out from GF. + * Everything in [] given in front of a GF command will be rewritten here. + * This method does nothing when no hmsg part is present. + * + * If a '$' appears in this string, everything that comes after it + * will be in result.second. + * ;; and [] don't work in the [] for the hmsg, + * therfore the following replacements are done: + * %% for ;; + * ( for [ + * ) for ] + * + * If one of the characters c,t,n comes before, the following is done: + * c The output will be cleared before the linearization (TODO: done anyway?) + * t The treeChanged flag will be set to true + * n The newObject flag will be set to true + * p No other probing run should be done (avoid cycles) + * r To prevent the execution of automatically triggered commands to prevent recursion + * + * @param prevreadresult The last line read from GF + * @return first: the last line this method has read; + * second: the string after $, null if that is not present + */ + protected Hmsg readHmsg(String prevreadresult){ + if ((prevreadresult!=null)&&(prevreadresult.indexOf("<hmsg>") > -1)) { + StringBuffer s =new StringBuffer(""); + String commandAppendix = null; + try { + boolean onceAgain = true; + boolean recurse = true; + boolean newObj = false; + boolean treeCh = false; + boolean clear = false; + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 "+readresult); + while (readresult.indexOf("/hmsg")==-1){ + s.append(readresult).append('\n'); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 "+readresult); + } + int commandAppendixStart = s.indexOf("$"); + if (commandAppendixStart > -1 && commandAppendixStart < s.length() - 1) { //present, but not the last character + commandAppendix = s.substring(commandAppendixStart + 1, s.indexOf("\n")); //two \n trail the hmsg + //;; and [] don't work in the [] for the hmsg + commandAppendix = Utils.replaceAll(commandAppendix, "%%", ";;"); + commandAppendix = Utils.replaceAll(commandAppendix, "(", "["); + commandAppendix = Utils.replaceAll(commandAppendix, ")", "]"); + } else { + commandAppendixStart = s.length(); + } + if (s.indexOf("c") > -1 && s.indexOf("c") < commandAppendixStart) { + //clear output before linearization + clear = true; + } + if (s.indexOf("t") > -1 && s.indexOf("t") < commandAppendixStart) { + //tree has changed + treeCh = true; + } + if (s.indexOf("p") > -1 && s.indexOf("p") < commandAppendixStart) { + //we must not probe again + onceAgain = false; + } + if (s.indexOf("r") > -1 && s.indexOf("r") < commandAppendixStart) { + //we must not probe again + recurse = false; + } + + if (s.indexOf("n") > -1 && s.indexOf("n") < commandAppendixStart) { + //a new object has been created + newObj = true; + } + if (logger.isLoggable(Level.FINE)) { + if (commandAppendix != null) { + logger.fine("command appendix read: '" + commandAppendix + "'"); + } + } + return new Hmsg(readresult, commandAppendix, onceAgain, recurse, newObj, treeCh, clear); + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + return new Hmsg("", null, false, true, false, true, false); + } + } else { + return new Hmsg(prevreadresult, null, true, true, false, true, false); + } + } + + /** + * Parses the GF-output between <menu> and </menu> tags + * and puts a StringTuple for each show/send pair into the + * return vector. + * @return A Vector of StringTuple as described above + */ + protected Vector readRefinementMenu (){ + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("list model changing! "); + String s =""; + Vector printnameVector = new Vector(); + Vector commandVector = new Vector(); + Vector gfCommandVector = new Vector(); + try { + //read <menu> + String readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("7 " + readresult); + //read item + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + while (readresult.indexOf("/menu")==-1){ + //read show + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + while (readresult.indexOf("/show") == -1){ + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("9 " + readresult); + if (readresult.indexOf("/show") == -1) { + if (readresult.length()>8) + s += readresult.trim(); + else + s += readresult; + } + } + // if (s.charAt(0)!='d') + // listModel.addElement("Refine " + s); + // else + String showText = s; + printnameVector.addElement(s); + s = ""; + //read /show + //read send + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + String myCommand = readresult; + commandVector.add(readresult); + //read /send (discarded) + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + + // read /item + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + readresult = fromProc.readLine(); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("8 " + readresult); + + StringTuple st = new StringTuple(myCommand.trim(), showText); + gfCommandVector.addElement(st); + } + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + } + return gfCommandVector; + } + /** + * Reads the output from GF until the ending tag corresponding to the + * given opening tag is read. + * @param opening tag in the format of >gfinit< + */ + protected void skipChild(String opening) { + String closing = (new StringBuffer(opening)).insert(1, '/').toString(); + try { + String nextRead = fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("3 " + nextRead); + } + while (!nextRead.trim().equals(closing)) { + nextRead = fromProc.readLine(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("3 " + nextRead); + } + } + } catch (IOException e) { + System.err.println("Could not read from external process:\n" + e); + } + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfeditResult.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfeditResult.java new file mode 100644 index 000000000..ccc75ff26 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GfeditResult.java @@ -0,0 +1,61 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.Vector; +/** + * Encapsulates the <gfedit> XML tree from GF. + * @author hdaniels + */ +class GfeditResult { + /** + * The fully parsed <hmsg> subtree + */ + final Hmsg hmsg; + /** + * A Vector of StringTuple where first is the command for GF + * and second is the show text + */ + final Vector gfCommands; + /** + * The tree from GF isn't XML anyway, so here it is in all its raw glory + */ + final String treeString; + /** + * if GF had something extra to tell, it can be found here + */ + final String message; + /** + * The XML for the linearizations in all languages + */ + final String linearizations; + /** + * A simple setter constructor + * @param gfCommands A Vector of StringTuple where first is the command for GF + * and second is the show text + * @param hmsg The fully parsed <hmsg> subtree + * @param linearizations The XML for the linearizations in all languages + * @param message the GF message + * @param treeString The tree from GF + */ + public GfeditResult(Vector gfCommands, Hmsg hmsg, String linearizations, String message, String treeString) { + this.gfCommands = gfCommands; + this.hmsg = hmsg; + this.linearizations = linearizations; + this.message = message; + this.treeString = treeString; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GrammarFilter.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GrammarFilter.java new file mode 100644 index 000000000..e8bd59c66 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/GrammarFilter.java @@ -0,0 +1,46 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.io.File; +import javax.swing.filechooser.*; + +public class GrammarFilter extends FileFilter { + + // Accept all directories and all gf, gfcm files. + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } + + String extension = Utils.getExtension(f); + if (extension != null) { + if (extension.equals(Utils.gf) || + extension.equals(Utils.gfcm)) { + return true; + } else { + return false; + } + } + + return false; + } + + // The description of this filter + public String getDescription() { + return "Just Grammars (*.gf, *.gfcm)"; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Hmsg.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Hmsg.java new file mode 100644 index 000000000..0a640f787 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Hmsg.java @@ -0,0 +1,77 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * The parsed format of the hmsg, that GF sents, if a command in java mode + * was prefixed with [something]. + * And that something gets parsed and stored in this representation. + * @author daniels + */ +class Hmsg { + /** + * The last read line + */ + String lastline = ""; + /** + * the String that should be appended to all commands of the + * next refinement menu + */ + String appendix = null; + /** + * If the editor shall probe once again for missing subtyping witnesses. + * Unused. + */ + boolean onceAgain = false; + /** + * If false, no commands are executed automatically + * in the next GF reading run + */ + boolean recurse = false; + /** + * if the newObject flag should be set + */ + boolean newObject = false; + /** + * if the command changed the tree, so that it has to be rebuilt + */ + boolean treeChanged = false; + /** + * if the display should be cleared + */ + boolean clear = false; + /** + * A simple setter constructor + * @param lastRead The last read line + * @param appendix the String that should be appended to all commands of the + * next refinement menu + * @param onceAgain + * @param recurse If false, no commands are executed automatically + * in the next GF reading run + * @param newObject if the newObject flag should be set + * @param treeChanged if the command changed the tree, so that it has to be rebuilt + * @param clear if the display should get cleared + */ + public Hmsg(String lastRead, String appendix, boolean onceAgain, boolean recurse, boolean newObject, boolean treeChanged, boolean clear) { + this.lastline = lastRead; + this.appendix = appendix; + this.onceAgain = onceAgain; + this.recurse = recurse; + this.newObject = newObject; + this.treeChanged = treeChanged; + this.clear = clear; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/InputCommand.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/InputCommand.java new file mode 100644 index 000000000..d047b943b --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/InputCommand.java @@ -0,0 +1,141 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; +import java.util.HashSet; + +/** + * @author daniels + * + * This class represents a fake command, i.e. nothing is send to GF here. + * Instead this class acts more like a placeholder for the input dialog. + * This dialog is handled in GFEditor2 when a InputCommand is executed. + * Reason: No GUI stuff in the command. + */ +class InputCommand extends GFCommand { + public static InputCommand intInputCommand = new InputCommand("read in Integer", + "opens a dialog window in which an Integer can be entered", + int.class, + "Please enter an Integer"); + public static InputCommand stringInputCommand = new InputCommand("read in String", + "opens a dialog window in which a String can be entered", + String.class, + "Please enter a String"); + + protected InputCommand(final String description, final String ttt, Class type, final String title) { + this.type = type; + this.tooltipText = ttt; + this.displayText = description; + this.titleText = title; + this.command = type.getName(); + } + + protected Class type; + + /** + * the text that is to be displayed as the title in the input window + */ + protected final String titleText; + /** + * the text that is to be displayed as the title in the input window + */ + public String getTitleText() { + return titleText; + } + + /** + * stores the entered values, so they can be offered to the user + * the next time, in case, he wants them again. + */ + protected final HashSet enteredValues = new HashSet(); + + /** + * the text that is to be displayed as the tooltip + */ + protected final String tooltipText; + /** + * the text that is to be displayed as the tooltip + */ + public String getTooltipText() { + return tooltipText; + } + + /** + * the text that is to be displayed in the refinement lists + */ + protected final String displayText; + /** + * the text that is to be displayed in the refinement lists + */ + public String getDisplayText() { + return displayText; + } + /** + * the subcategory of this command + */ + public String getSubcat() { + return null; + } + + /** + * Checks if the given String can be converted into + * the Type of this InputCommand (int or String). + * If that is possible, the converted object is saved + * in enteredValues for later redisplay for the user. + * @param o The String the user has typed + * @param reason If the entered String is not parseable as the expected + * type, an error message is appended to this StringBuffer, so better + * give an empty one. + * @return an Object whose toString() should send the right + * thing to GF. + * Maybe null, if this "conversion" failed. + */ + protected Object validate(String o, StringBuffer reason) { + Object result = null; + if (type == int.class) { + int i; + try { + i = Integer.parseInt(o); + result = new Integer(i); + } catch (NumberFormatException e) { + reason.append("Input format error: '" + o + "' is no Integer"); + } + } else if (type == String.class) { + if (o != null) { + result = "\"" + o.toString() + "\""; + } + } + if (result != null) { + this.enteredValues.add(result); + } + return result; + } + + /** + * selects the suiting InputCommand for the given full name of a type + * @param typeName at the moment, either int.class.getName() or String.class.getName() + * @return intInputCommand for int, stringInputCommand for String or null otherwise + */ + protected static InputCommand forTypeName(String typeName) { + InputCommand ic = null; + if (typeName.equals(int.class.getName())) { + ic = InputCommand.intInputCommand; + } else if (typeName.equals(String.class.getName())) { + ic = InputCommand.stringInputCommand; + } + return ic; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LanguageManager.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LanguageManager.java new file mode 100644 index 000000000..39e3e6fb1 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LanguageManager.java @@ -0,0 +1,39 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * Sadly, this class is a hack. + * It serves as the pointer type to an inner class of GFEditor2. + * Two of its methods are needed outside after refactoring. + * @author daniels + * + */ +interface LanguageManager { + /** + * @param myLang The language in question + * @return true iff the language is present and set to active, + * false otherwise. + */ + public boolean isLangActive(String myLang); + /** + * Checks if myLang is already present, and if not, + * adds it. In that case, myActive is ignored. + * @param myLang The name of the language + * @param myActive whether the language is displayed or not + */ + public void add(String myLang, boolean myActive); +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinPosition.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinPosition.java new file mode 100644 index 000000000..cf2963210 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinPosition.java @@ -0,0 +1,157 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005
+//
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; either version 2 of the License, or
+//(at your option) any later version.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//GNU General Public License for more details.
+//
+//You can either finde the file LICENSE or LICENSE.TXT in the source
+//distribution or in the .jar file of this application
+
+package de.uka.ilkd.key.ocl.gf;
+
+/**
+ * represents a position in the AST in Haskell notation together
+ * with a flag that indicates whether at least one constraint does not hold or
+ * if all hold (correct/incorrect).
+ * Class is immutable.
+ */
+class LinPosition {
+ /**
+ * a position in the AST in Haskell notation
+ */
+ final public String position;
+
+ /**
+ * true means green, false means red (janna guesses)
+ */
+ final public boolean correctPosition;
+
+ /**
+ * creates a LinPosition
+ * @param p the position in the AST in Haskell notation like [0,1,2]
+ * @param cor false iff there are violated constraints
+ */
+ LinPosition(String p, boolean cor) {
+ position = p;
+ correctPosition = cor;
+ }
+
+ /**
+ * Creates a position string in Haskell notation for the argument
+ * number nr of this node.
+ * @param nr The number of the wanted argument
+ * @return the position string for the nrth child
+ */
+ public String childPosition(int nr) {
+ return calculateChildPosition(this.position, nr);
+ }
+
+ /**
+ * Creates a position string in Haskell notation for the argument
+ * number nr for the position pos
+ * @param pos The position of the to be parent
+ * @param nr The number of the wanted argument
+ * @return the position string for the nrth child of pos
+ */
+ protected static String calculateChildPosition(String pos, int nr) {
+ if ("[]".equals(pos)) {
+ return "[" + nr + "]";
+ } else {
+ return pos.trim().substring(0, pos.length() - 1) + "," + nr + "]";
+ }
+ }
+
+ /**
+ * Creates a position string in Haskell notation for the argument
+ * number nr for the position pos' parent, i.e. brethren number nr.
+ * Example: calculateBrethrenPosition("[0,0,1]", 3).equals("[0,0,3]")
+ * @param pos The position of a brethren of the wanted
+ * @param nr The number of the wanted brethren
+ * @return the position string for the nrth brother of pos
+ */
+ protected static String calculateBrethrenPosition(String pos, int nr) {
+ if ("[]".equals(pos)) {
+ return "[]"; //no brethren possible here
+ } else if (pos.lastIndexOf(',') == -1) {
+ return "[" + nr + "]"; //one below top
+ } else {
+ final String newPos = pos.substring(0, pos.lastIndexOf(',') + 1) + nr + "]";
+ return newPos;
+ }
+
+ }
+
+ /**
+ * compares two position strings and returns true, if superPosition is
+ * a prefix of subPosition, that is, if subPosition is in a subtree of
+ * superPosition
+ * @param superPosition the position String in Haskell notation
+ * ([0,1,0,4]) of the to-be super-branch of subPosition
+ * @param subPosition the position String in Haskell notation
+ * ([0,1,0,4]) of the to-be (grand-)child-branch of superPosition
+ * @return true iff superPosition denotes an ancestor of subPosition
+ */
+ public static boolean isSubtreePosition(final LinPosition superPosition, final LinPosition subPosition) {
+ if (superPosition == null || subPosition == null) {
+ return false;
+ }
+ String superPos = superPosition.position;
+ String subPos = subPosition.position;
+ if (superPos.length() < 2 || subPos.length() < 2 ) {
+ return false;
+ }
+ superPos = superPos.substring(1, superPos.length() - 1);
+ subPos = subPos.substring(1, subPos.length() - 1);
+ boolean result = subPos.startsWith(superPos);
+ return result;
+ }
+
+ /**
+ * Returns the biggest position of first and second.
+ * Each word in the linearization area has the corresponding
+ * position in the tree. The position-notion is taken from
+ * GF-Haskell, where empty position ("[]")
+ * represents tree-root, "[0]" represents first child of the root,
+ * "[0,0]" represents the first grandchild of the root etc.
+ * So comparePositions("[0]","[0,0]")="[0]"
+ */
+ public static String maxPosition(String first, String second) {
+ String common ="[]";
+ int i = 1;
+ while ((i<Math.min(first.length()-1,second.length()-1))&&(first.substring(0,i+1).equals(second.substring(0,i+1)))) {
+ common=first.substring(0,i+1);
+ i+=2;
+ }
+ if (common.charAt(common.length()-1)==']') {
+ return common;
+ } else {
+ return common+"]";
+ }
+ }
+
+ /**
+ * @return The Haskell position string for the parent of this position.
+ * If self is already the top node, [] is returned.
+ */
+ public String parentPosition() {
+ if (this.position.equals("[]")) {
+ return this.position;
+ } else if (this.position.lastIndexOf(',') == -1) {
+ return "[]"; //one below top
+ } else {
+ final String newPos = this.position.substring(0, this.position.lastIndexOf(',')) + "]";
+ return newPos;
+ }
+ }
+
+ public String toString() {
+ return position + (correctPosition ? " correct" : " incorrect");
+ }
+}
+
diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Linearization.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Linearization.java new file mode 100644 index 000000000..5ff78202b --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Linearization.java @@ -0,0 +1,760 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Encapsulates everything that has to do with the linearization. + * It is parsed here, and also the indices for click-in for pure text and HTML + * are managed here. They get calculated in GfCapsule. + * The result can be directly displayed, and this class has methods to translate + * the indices back to the respective tree positions. + * @author daniels + */ +class Linearization { + /** + * linearization marking debug messages + */ + protected static Logger logger = Logger.getLogger(Linearization.class.getName()); + + /** + * contains all the linearization pieces as HtmlMarkedArea + * Needed to know to which node in the AST a word in the linHtmlPane + * area belongs. + */ + private Vector htmlOutputVector = new Vector(); + /** + * the GF-output between <linearization> </linearization> tags is stored here. + * Must be saved in case the displayed languages are changed. + * Only written in readLin + */ + private String linearization = ""; + /** + * stack for storing the current position: + * When displaying, we start with the root of the AST. + * Whenever we start to display a node, it is pushed, and when it is completely displayed, we pop it. + * Only LinPositions are stored in here + * local in formLin? + * */ + private Vector currentPosition = new Vector(); + + /** + * Must be the same Display as GFEditor2 uses + */ + private Display display; + /** + * to collect the linearization strings + */ + private HashMap linearizations = new HashMap(); + + + + /** + * Initializes this object and binds it to the given Display + * @param display The display, that the editor uses + */ + public Linearization(Display display) { + this.display = display; + } + + /** + * @return Returns the linearizations. + */ + HashMap getLinearizations() { + return linearizations; + } + + /** + * @param linearization The linearization to set. + */ + void setLinearization(String linearization) { + this.linearization = linearization; + } + + /** + * resets the output mechanism. + */ + void reset() { + htmlOutputVector = new Vector(); + } + + /** + * Returns the widest position (see comments to comparePositions) + * covered in the string from begin to end in the + * linearization area. + * @param begin The index in htmlOutputVector of the first MarkedArea, that is possibly the max + * @param end The index in htmlOutputVector of the last MarkedArea, that is possibly the max + * @return the position in GF Haskell notation (hdaniels guesses) + */ + private String findMax(int begin, int end) { + String max = (((MarkedArea)this.htmlOutputVector.elementAt(begin)).position).position; + for (int i = begin+1; i <= end; i++) + max = LinPosition.maxPosition(max,(((MarkedArea)this.htmlOutputVector.elementAt(i)).position).position); + return max; + } + + /** + * Appends the string restString to display. + * It parses the subtree tags and registers them. + * The focus tag is expected to be replaced by subtree. + * @param restString string to append, with tags in it. + * @param clickable if true, the text is appended and the subtree tags are + * parsed. If false, the text is appended, but the subtree tags are ignored. + * @param doDisplay true iff the output is to be displayed. + * Implies, if false, that clickable is treated as false. + * @param language the current linearization language + */ + private String appendMarked(String restString, final boolean clickable, boolean doDisplay, String language) { + String appendedPureText = ""; + if (restString.length()>0) { + /** + * the length of what is already displayed of the linearization. + * Alternatively: What has been processed in restString since + * subtreeBegin + */ + int currentLength = 0; + /** position of <subtree */ + int subtreeBegin; + /** position of </subtree */ + int subtreeEnd; + + if (clickable && doDisplay) { + subtreeBegin = Utils.indexOfNotEscaped(restString, "<subtree"); + subtreeEnd = Utils.indexOfNotEscaped(restString, "</subtree"); + // cutting subtree-tags: + while ((subtreeEnd>-1)||(subtreeBegin>-1)) { + /** + * length of the portion that is to be displayed + * in the current run of appendMarked. + * For HTML this would have to be calculated + * in another way. + */ + final int newLength; + + if ((subtreeEnd==-1)||((subtreeBegin<subtreeEnd)&&(subtreeBegin>-1))) { + final int subtreeTagEnd = Utils.indexOfNotEscaped(restString, ">",subtreeBegin); + final int nextOpeningTagBegin = Utils.indexOfNotEscaped(restString, "<", subtreeTagEnd); + + //getting position: + final int posStringBegin = Utils.indexOfNotEscaped(restString, "[",subtreeBegin); + final int posStringEnd = Utils.indexOfNotEscaped(restString, "]",subtreeBegin); + final LinPosition position = new LinPosition(restString.substring(posStringBegin,posStringEnd+1), + restString.substring(subtreeBegin,subtreeTagEnd).indexOf("incorrect")==-1); + + // is something before the tag? + // is the case in the first run + if (subtreeBegin-currentLength>1) { + if (logger.isLoggable(Level.FINER)) { + logger.finer("SOMETHING BEFORE THE TAG"); + } + if (this.currentPosition.size()>0) + newLength = register(currentLength, subtreeBegin, (LinPosition)this.currentPosition.elementAt(this.currentPosition.size()-1), restString, language); + else + newLength = register(currentLength, subtreeBegin, new LinPosition("[]", + restString.substring(subtreeBegin,subtreeTagEnd).indexOf("incorrect")==-1), restString, language); + } else { // nothing before the tag: + //the case in the beginning + if (logger.isLoggable(Level.FINER)) { + logger.finer("NOTHING BEFORE THE TAG"); + } + if (nextOpeningTagBegin>0) { + newLength = register(subtreeTagEnd+2, nextOpeningTagBegin, position, restString, language); + } else { + newLength = register(subtreeTagEnd+2, restString.length(), position, restString, language); + } + restString = removeSubTreeTag(restString,subtreeBegin, subtreeTagEnd+1); + } + currentLength += newLength ; + } else { + // something before the </subtree> tag: + if (subtreeEnd-currentLength>1) { + if (logger.isLoggable(Level.FINER)) { + logger.finer("SOMETHING BEFORE THE </subtree> TAG"); + } + if (this.currentPosition.size()>0) + newLength = register(currentLength, subtreeEnd, (LinPosition)this.currentPosition.elementAt(this.currentPosition.size()-1), restString, language); + else + newLength = register(currentLength, subtreeEnd, new LinPosition("[]", + restString.substring(subtreeBegin,subtreeEnd).indexOf("incorrect")==-1), restString, language); + currentLength += newLength ; + } + // nothing before the tag: + else + // punctuation after the </subtree> tag: + if (restString.substring(subtreeEnd+10,subtreeEnd+11).trim().length()>0) + { + if (logger.isLoggable(Level.FINER)) { + logger.finer("PUNCTUATION AFTER THE </subtree> TAG" + + "/n" + "STRING: " + restString); + } + //cutting the tag first!: + if (subtreeEnd>0) { + restString = removeSubTreeTag(restString,subtreeEnd-1, subtreeEnd+9); + } else { + restString = removeSubTreeTag(restString,subtreeEnd, subtreeEnd+9); + } + if (logger.isLoggable(Level.FINER)) { + logger.finer("STRING after cutting the </subtree> tag: "+restString); + } + // cutting the space in the last registered component: + if (this.htmlOutputVector.size()>0) { + ((MarkedArea)this.htmlOutputVector.elementAt(this.htmlOutputVector.size()-1)).end -=1; + if (currentLength>0) { + currentLength -=1; + } + } + if (logger.isLoggable(Level.FINER)) { + logger.finer("currentLength: " + currentLength); + } + // register the punctuation: + if (this.currentPosition.size()>0) { + newLength = register(currentLength, currentLength+2, (LinPosition)this.currentPosition.elementAt(this.currentPosition.size()-1), restString, language); + } else { + newLength = register(currentLength, currentLength+2, new LinPosition("[]", + true), restString, language); + } + currentLength += newLength ; + } else { + // just cutting the </subtree> tag: + restString = removeSubTreeTag(restString,subtreeEnd, subtreeEnd+10); + } + } + subtreeEnd = Utils.indexOfNotEscaped(restString, "</subtree"); + subtreeBegin = Utils.indexOfNotEscaped(restString, "<subtree"); + // if (debug2) + // System.out.println("/subtree index: "+l2 + "<subtree"+l); + if (logger.isLoggable(Level.FINER)) { + logger.finer("<-POSITION: "+subtreeBegin+" CURRLENGTH: "+currentLength + + "\n STRING: "+restString.substring(currentLength)); + } + } //while + } else { //no focus, no selection enabled (why ever) + //that means, that all subtree tags are removed here. + if (logger.isLoggable(Level.FINER)) { + logger.finer("NO SELECTION IN THE TEXT TO BE APPENDED!"); + } + //cutting tags from previous focuses if any: + int r = Utils.indexOfNotEscaped(restString, "</subtree>"); + while (r>-1) { + // check if punktualtion marks like . ! ? are at the end of a sentence: + if (restString.charAt(r+10)==' ') + restString = restString.substring(0,r)+restString.substring(r+11); + else + restString = restString.substring(0,r)+restString.substring(r+10); + r = Utils.indexOfNotEscaped(restString, "</subtree>"); + } + r = Utils.indexOfNotEscaped(restString, "<subtree"); + while (r>-1) { + int t = Utils.indexOfNotEscaped(restString, ">", r); + if (t<restString.length()-2) + restString = restString.substring(0,r)+restString.substring(t+2); + else + restString = restString.substring(0,r); + r = Utils.indexOfNotEscaped(restString, "<subtree"); + } + } + // appending: + restString = unescapeTextFromGF(restString); + if (logger.isLoggable(Level.FINER)) { + logger.finer(restString); + } + appendedPureText = restString.replaceAll("&-","\n "); + //display the text if not already done in case of clickable + if (!clickable && doDisplay) { + // the text has only been pruned from markup, but still needs + // to be displayed + this.display.addToStages(appendedPureText, appendedPureText); + } + } // else: nothing to append + return appendedPureText; + } + + /** + * Replaces a number of escaped characters by an unescaped version + * of the same length + * @param string The String with '\' as the escape character + * @return the same String, but with escaped characters removed + * + */ + static String unescapeTextFromGF(String string) { + final String more = "\\"+">"; + final String less = "\\"+"<"; + //%% by daniels, linearization output will be changed drastically + //(or probably will), so for now some hacks for -> and >= + string = Utils.replaceAll(string, "-" + more, "-> "); + string = Utils.replaceAll(string, "-" + more,"-> "); + string = Utils.replaceAll(string, more," >"); + string = Utils.replaceAll(string, less," <"); + //an escaped \ becomes a single \ + string = Utils.replaceAll(string, "\\\\"," \\"); + return string; + } + + + + /** + * The substring from start to end in workingString, together with + * position is saved as a MarkedArea in this.htmlOutputVector. + * The information from where to where the to be created MarkedArea + * extends, is calculated in this method. + * @param start The position of the first character in workingString + * of the part, that is to be registered. + * @param end The position of the last character in workingString + * of the part, that is to be registered. + * @param position the position in the tree that corresponds to + * the to be registered text + * @param workingString the String from which the displayed + * characters are taken from + * @param language the current linearization language + * @return newLength, the difference between end and start + */ + private int register(int start, int end, LinPosition position, String workingString, String language) { + /** + * the length of the piece of text that is to be appended now + */ + final int newLength = end-start; + // the tag has some words to register: + if (newLength>0) { + final String stringToAppend = workingString.substring(start,end); + //if (stringToAppend.trim().length()>0) { + + //get oldLength and add the new text + String toAdd = unescapeTextFromGF(stringToAppend); + final MarkedArea hma = this.display.addAsMarked(toAdd, position, language); + this.htmlOutputVector.add(hma); + if (logger.isLoggable(Level.FINER)) { + logger.finer("HTML added : " + hma); + } + } //some words to register + return newLength; + } + + /** + * removing subtree-tag in the interval start-end + * and updating the coordinates after that + * basically part of appendMarked + * No subtree is removed, just the tag. + * @param s The String in which the subtree tag should be removed + * @param start position in restString + * @param end position in restString + * @return the String without the subtree-tags in the given interval + */ + private String removeSubTreeTag (final String s, final int start, final int end) { + String restString = s; + if (logger.isLoggable(Level.FINER)) { + logger.finer("removing: "+ start +" to "+ end); + } + int difference =end-start+1; + int positionStart, positionEnd; + if (difference>20) { + positionStart = Utils.indexOfNotEscaped(restString, "[", start); + positionEnd = Utils.indexOfNotEscaped(restString, "]", start); + + currentPosition.addElement(new LinPosition( + restString.substring(positionStart, positionEnd+1), + restString.substring(start,end).indexOf("incorrect")==-1)); + } else if (currentPosition.size()>0) { + currentPosition.removeElementAt(currentPosition.size()-1); + } + if (start>0) { + restString = restString.substring(0,start)+restString.substring(end+1); + } else{ + restString = restString.substring(end+1); + } + return restString; + } + + /** + * Goes through the list of MarkedAreas and creates MarkedAreaHighlightingStatus + * objects for them, which contain fields for incorrect constraints + * and if they belong to the selected subtree. + * @param focusPosition The AST position of the selected node + * @return a Vector of MarkedAreaHighlightingStatus + */ + Vector calculateHighlights(LinPosition focusPosition) { + Vector result = new Vector(); + final HashSet incorrectMA = new HashSet(); + for (int i = 0; i<htmlOutputVector.size(); i++) { + final MarkedArea ma = (MarkedArea)this.htmlOutputVector.elementAt(i); + //check, if and how ma should be highlighted + boolean incorrect = false; + boolean focused = false; + if (logger.isLoggable(Level.FINER)) { + logger.finer("Highlighting: " + ma); + } + if (!ma.position.correctPosition) { + incorrectMA.add(ma); + incorrect = true; + } else { + //This could be quadratic, but normally on very + //few nodes constraints are introduced, so + //incorrectMA should not contain many elements. + MarkedArea incMA; + for (Iterator it = incorrectMA.iterator(); !incorrect && it.hasNext();) { + incMA = (MarkedArea)it.next(); + if (LinPosition.isSubtreePosition(incMA.position, ma.position)) { + incorrect = true; + } + } + } + if (LinPosition.isSubtreePosition(focusPosition, ma.position)) { + focused = true; + } + MarkedAreaHighlightingStatus mahs = new MarkedAreaHighlightingStatus(focused, incorrect, ma); + result.add(mahs); + } + return result; + } + + /** + * Parses the linearization XML and calls outputAppend + * @param langMan The LangMenuModel, but that is an inner class and only + * the methods in the Interface LanguageManager are used here. + */ + void parseLin(LanguageManager langMan) { + linearizations.clear(); + boolean firstLin=true; + //read first line like ' <lin lang=Abstract>' + String readResult = linearization.substring(0,linearization.indexOf('\n')); + //the rest of the linearizations + String lin = linearization.substring(linearization.indexOf('\n')+1); + //extract the language from readResult + int ind = Utils.indexOfNotEscaped(readResult, "="); + int ind2 = Utils.indexOfNotEscaped(readResult, ">"); + /** The language of the linearization */ + String language = readResult.substring(ind+1,ind2); + //the first direct linearization + readResult = lin.substring(0,lin.indexOf("</lin>")); + //the rest + lin = lin.substring(lin.indexOf("</lin>")); + while (readResult.length()>1) { + langMan.add(language,true); + // selected? + boolean visible = langMan.isLangActive(language); + if (visible && !firstLin) { + // appending sth. linearizationArea + this.display.addToStages("\n************\n", "<br><hr><br>"); + } + if (logger.isLoggable(Level.FINER)) { + logger.finer("linearization for the language: "+readResult); + } + + // change the focus tag into a subtree tag. + // focus handling now happens in GFEditor2::formTree + String readLin = readResult; + readLin = Utils.replaceNotEscaped(readLin, "<focus", "<subtree"); + readLin = Utils.replaceNotEscaped(readLin, "</focus", "</subtree"); + + final boolean isAbstract = "Abstract".equals(language); + // now do appending and registering + String linResult = appendMarked(readLin + '\n', !isAbstract, visible, language); + + if (visible) { + firstLin = false; + } + linearizations.put(language, linResult); + // read </lin> + lin = lin.substring(lin.indexOf('\n')+1); + // read lin or 'end' + if (lin.length()<1) { + break; + } + + readResult = lin.substring(0,lin.indexOf('\n')); + lin = lin.substring(lin.indexOf('\n')+1); + if (readResult.indexOf("<lin ")!=-1){ + //extract the language from readResult + ind = readResult.indexOf('='); + ind2 = readResult.indexOf('>'); + language = readResult.substring(ind+1,ind2); + readResult = lin.substring(0,lin.indexOf("</lin>")); + lin = lin.substring(lin.indexOf("</lin>")); + } + } + } + + /** + * + * @param language The concrete language of choice + * @return The linearization of the subtree starting with the currently + * selected node in the given language. + */ + String getSelectedLinearization(final String language, final LinPosition focusPosition) { + StringBuffer sel = new StringBuffer(); + for (int i = 0; i<htmlOutputVector.size(); i++) { + final MarkedArea ma = (MarkedArea)htmlOutputVector.elementAt(i); + if (language.equals(ma.language) && LinPosition.isSubtreePosition(focusPosition, ma.position)) { + sel.append(ma.words); + } + } + return sel.toString(); + } + + /** + * Takes the index of a caret position in the linearization area + * and returns the language of the clicked linearization. + * GF lists the different concrete languages one after the other, + * and this method looks at the linearization snipplets to get + * the language. + * If somehow no language can be found out, 'Abstract' is returned + * @param pos The index of the caret position + * @param htmlClicked If the HTML JTextPane has been clicked, + * false for the JTextArea + * @return the name of the concrete grammar (language) or Abstract + * (see above). + */ + String getLanguageForPos(int pos, final boolean htmlClicked) { + final String language; + MarkedArea ma = null; + if (htmlClicked) { + //HTML + for (int i = 0; i < htmlOutputVector.size(); i++) { + if ((pos >= ((MarkedArea)htmlOutputVector.get(i)).htmlBegin) && (pos <= ((MarkedArea)htmlOutputVector.get(i)).htmlEnd)) { + ma = (MarkedArea)htmlOutputVector.get(i); + break; + } + } + } else { + //assumably pure text + for (int i = 0; i < htmlOutputVector.size(); i++) { + if ((pos >= ((MarkedArea)htmlOutputVector.get(i)).begin) && (pos <= ((MarkedArea)htmlOutputVector.get(i)).end)) { + ma = (MarkedArea)htmlOutputVector.get(i); + break; + } + } + + } + if (ma != null && ma.language != null) { + language = ma.language; + } else { + language = "Abstract"; + } + return language; + } + + /** + * The user has either just clicked in the linearization area, + * which means start == end, or he has selected a text, so that + * start < end. + * This method figures out the smallest subtree whose linearization + * completely encompasses the area from start to end. + * This method is for the HTML linearization area. + * @param start The index of the caret position at the begin of the selection + * @param end The index of the caret position at the end of the selection + * @return The 'root' of the subtree described above + */ + String markedAreaForPosHtml(int start, int end) { + if (htmlOutputVector.isEmpty()) { + return null; + } + String position = null; //the result + String jPosition ="", iPosition=""; + MarkedArea jElement = null; + MarkedArea iElement = null; + int j = 0; + int i = htmlOutputVector.size()-1; + + if (logger.isLoggable(Level.FINER)) + for (int k=0; k < htmlOutputVector.size(); k++) { + logger.finer("element: "+k+" begin "+((MarkedArea)htmlOutputVector.elementAt(k)).htmlBegin+" " + + "\n-> end: "+((MarkedArea)htmlOutputVector.elementAt(k)).htmlEnd+" " + + "\n-> position: "+(((MarkedArea)htmlOutputVector.elementAt(k)).position).position+" " + + "\n-> words: "+((MarkedArea)htmlOutputVector.elementAt(k)).words); + } + // localizing end: + while ((j < htmlOutputVector.size()) && (((MarkedArea)htmlOutputVector.elementAt(j)).htmlEnd < end)) { + j++; + } + // localising start: + while ((i >= 0) && (((MarkedArea)htmlOutputVector.elementAt(i)).htmlBegin > start)) { + i--; + } + if (logger.isLoggable(Level.FINER)) { + logger.finer("i: "+i+" j: "+j); + } + if ((j < htmlOutputVector.size())) { + jElement = (MarkedArea)htmlOutputVector.elementAt(j); + jPosition = jElement.position.position; + // less & before: + if (i == -1) { // less: + if (end>=jElement.htmlBegin) { + iElement = (MarkedArea)htmlOutputVector.elementAt(0); + iPosition = iElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("Less: "+jPosition+" and "+iPosition); + } + position = findMax(0,j); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + } else { // before: + if (logger.isLoggable(Level.FINER)) { + logger.finer("BEFORE vector of size: "+htmlOutputVector.size()); + } + } + } else { // just: + iElement = (MarkedArea)htmlOutputVector.elementAt(i); + iPosition = iElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTED TEXT Just: "+iPosition +" and "+jPosition+"\n"); + } + position = findMax(i,j); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + } + } else if (i>=0) { // more && after: + iElement = (MarkedArea)htmlOutputVector.elementAt(i); + iPosition = iElement.position.position; + // more + if (start<=iElement.htmlEnd) { + jElement = (MarkedArea)htmlOutputVector.elementAt(htmlOutputVector.size()-1); + jPosition = jElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("MORE: "+iPosition+ " and "+jPosition); + } + position = findMax(i,htmlOutputVector.size()-1); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + // after: + } else if (logger.isLoggable(Level.FINER)) { + logger.finer("AFTER vector of size: "+htmlOutputVector.size()); + } + } else { // bigger: + iElement = (MarkedArea)htmlOutputVector.elementAt(0); + iPosition = iElement.position.position; + jElement = (MarkedArea)htmlOutputVector.elementAt(htmlOutputVector.size()-1); + jPosition = jElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("BIGGER: "+iPosition +" and "+jPosition+"\n" + + "\n-> SELECTEDTEXT: []\n"); + } + position = "[]"; + } + return position; + } + + /** + * The user has either just clicked in the linearization area, + * which means start == end, or he has selected a text, so that + * start < end. + * This method figures out the smallest subtree whose linearization + * completely encompasses the area from start to end. + * This method is for the pure text linearization area. + * @param start The index of the caret position at the begin of the selection + * @param end The index of the caret position at the end of the selection + * @return The 'root' of the subtree described above + */ + String markedAreaForPosPureText(int start, int end) { + if (htmlOutputVector.isEmpty()) { + return null; + } + //the result + String position = null; + //variables for confining the searched MarkedArea from + //start and end (somehow ...) + int j = 0; + int i = htmlOutputVector.size() - 1; + String jPosition ="", iPosition=""; + MarkedArea jElement = null; + MarkedArea iElement = null; + + if (logger.isLoggable(Level.FINER)) + for (int k = 0; k < htmlOutputVector.size(); k++) { + logger.finer("element: " + k + " begin " + ((MarkedArea)htmlOutputVector.elementAt(k)).begin + " " + + "\n-> end: " + ((MarkedArea)htmlOutputVector.elementAt(k)).end+" " + + "\n-> position: " + (((MarkedArea)htmlOutputVector.elementAt(k)).position).position+" " + + "\n-> words: " + ((MarkedArea)htmlOutputVector.elementAt(k)).words); + } + // localizing end: + while ((j < htmlOutputVector.size()) && (((MarkedArea)htmlOutputVector.elementAt(j)).end < end)) { + j++; + } + // localising start: + while ((i >= 0) && (((MarkedArea)htmlOutputVector.elementAt(i)).begin > start)) + i--; + if (logger.isLoggable(Level.FINER)) { + logger.finer("i: " + i + " j: " + j); + } + if ((j < htmlOutputVector.size())) { + jElement = (MarkedArea)htmlOutputVector.elementAt(j); + jPosition = jElement.position.position; + // less & before: + if (i==-1) { // less: + if (end>=jElement.begin) { + iElement = (MarkedArea)htmlOutputVector.elementAt(0); + iPosition = iElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("Less: "+jPosition+" and "+iPosition); + } + position = findMax(0,j); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + } else if (logger.isLoggable(Level.FINER)) { // before: + logger.finer("BEFORE vector of size: " + htmlOutputVector.size()); + } + } else { // just: + iElement = (MarkedArea)htmlOutputVector.elementAt(i); + iPosition = iElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTED TEXT Just: "+iPosition +" and "+jPosition+"\n"); + } + position = findMax(i,j); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + } + } else if (i>=0) { // more && after: + iElement = (MarkedArea)htmlOutputVector.elementAt(i); + iPosition = iElement.position.position; + // more + if (start<=iElement.end) { + jElement = (MarkedArea)htmlOutputVector.elementAt(htmlOutputVector.size() - 1); + jPosition = jElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("MORE: "+iPosition+ " and "+jPosition); + } + position = findMax(i, htmlOutputVector.size()-1); + if (logger.isLoggable(Level.FINER)) { + logger.finer("SELECTEDTEXT: "+position+"\n"); + } + } else if (logger.isLoggable(Level.FINER)) { // after: + logger.finer("AFTER vector of size: " + htmlOutputVector.size()); + } + } else { + // bigger: + iElement = (MarkedArea)htmlOutputVector.elementAt(0); + iPosition = iElement.position.position; + jElement = (MarkedArea)htmlOutputVector.elementAt(htmlOutputVector.size()-1); + jPosition = jElement.position.position; + if (logger.isLoggable(Level.FINER)) { + logger.finer("BIGGER: "+iPosition +" and "+jPosition+"\n" + + "\n-> SELECTEDTEXT: []\n"); + } + position = "[]"; + } + return position; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinkCommand.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinkCommand.java new file mode 100644 index 000000000..fb12c79b7 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/LinkCommand.java @@ -0,0 +1,85 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * @author daniels + * This class represents a link to a subcategory submenu. + * When it is encountered as the executed command, the corresponding + * menu gets opened. + */ +public class LinkCommand extends GFCommand { + + /** + * Since LinkCommand is not a real command, that is sent to GF, + * most fields are given dummy values here. + * The subcat is assigned its full display name and tooltip + * @param subcat The subcategory of the menu behind this command + * @param manager The PrintnameManager, that can map subcat to its + * full name + */ + public LinkCommand(final String subcat, final PrintnameManager manager) { + this.command = subcat; + this.newSubcat = false; + this.commandType = Printname.SUBCAT; + this.argument = -1; + this.funName = null; + this.printname = null; + + String dtext; + String ttext; + String fullname = manager.getFullname(subcat); + if (fullname == null) { + dtext = getSubcat(); + ttext = "open submenu " + getSubcat(); + } else { + ttext = Printname.htmlPrepend(Printname.extractTooltipText(fullname), "<i>open submenu</i> <br> "); + dtext = Printname.extractDisplayText(fullname); + } + this.tooltipText = ttext; + this.displayText = dtext; + + } + + /** + * the text that is to be displayed as the tooltip + */ + protected final String tooltipText; + /** + * the text that is to be displayed as the tooltip + */ + public String getTooltipText() { + return tooltipText; + } + + /** + * the text that is to be displayed in the refinement lists + */ + protected final String displayText; + /** + * the text that is to be displayed in the refinement lists + */ + public String getDisplayText() { + return displayText; + } + /** + * the subcategory of this command + */ + public String getSubcat() { + return this.command; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedArea.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedArea.java new file mode 100644 index 000000000..0f4422978 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedArea.java @@ -0,0 +1,84 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005
+//
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; either version 2 of the License, or
+//(at your option) any later version.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//GNU General Public License for more details.
+//
+//You can either finde the file LICENSE or LICENSE.TXT in the source
+//distribution or in the .jar file of this application
+
+package de.uka.ilkd.key.ocl.gf;
+
+/**
+ * Stores quasi a piece of the linearization area, that has a word, a beginning
+ * and an end in the linearization area and a position in the AST. It is used
+ * for clicking in the text
+ *
+ * @author janna, daniels
+ */
+class MarkedArea {
+ /**
+ * The starting position of the stored words
+ */
+ final public int begin;
+ /**
+ * The ending position of the stored words.
+ * Not final because of some punctuation issue daniels
+ * does not understand
+ */
+ public int end;
+ /**
+ * The position in the AST
+ */
+ final public LinPosition position;
+ /**
+ * The actual text of this area
+ */
+ final public String words;
+ /**
+ * the concrete grammar (or better, its linearization)
+ * this MarkedArea belongs to
+ */
+ final public String language;
+
+ /**
+ * the start index in the HTML area
+ */
+ final public int htmlBegin;
+ /**
+ * the end index in the HTML area
+ */
+ final public int htmlEnd;
+
+ /**
+ * A stand-alone constuctor which takes all values as arguments
+ * @param begin The starting position of the stored words
+ * @param end The ending position of the stored words
+ * @param position The position in the AST
+ * @param words The actual text of this area
+ * @param htmlBegin the start index in the HTML area
+ * @param htmlEnd the end index in the HTML area
+ * @param language the language of the current linearization
+ */
+ public MarkedArea(int begin, int end, LinPosition position, String words, int htmlBegin, int htmlEnd, String language) {
+ this.begin = begin;
+ this.end = end;
+ this.position = position;
+ this.words = words;
+ this.language = language;
+ this.htmlBegin = htmlBegin;
+ this.htmlEnd = htmlEnd;
+ }
+
+
+ public String toString() {
+ return begin + " - " + end + " : " + position + " = '" + words + "' ; HTML: " + htmlBegin + " - " + htmlEnd;
+ }
+}
+
diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedAreaHighlightingStatus.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedAreaHighlightingStatus.java new file mode 100644 index 000000000..f2c712a75 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/MarkedAreaHighlightingStatus.java @@ -0,0 +1,48 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * Stores a MarkedArea together with some status fields, which tell + * how it should get highlighted. + * No direct highlighting stuff in here, that's done in GFEditor2 + * @author daniels + */ +class MarkedAreaHighlightingStatus { + /** + * The MarkedArea, which contains the highlighting information + */ + final MarkedArea ma; + /** + * whether this MarkedArea is a subnode of the currently focused node + */ + final boolean focused; + /** + * whether this MarkedArea has (inherited) a GF constraint + */ + final boolean incorrect; + /** + * Initializes this immutable record class + * @param focused whether this MarkedArea is a subnode of the currently focused node + * @param incorrect whether this MarkedArea has (inherited) a GF constraint + * @param ma The MarkedArea, which contains the highlighting information + */ + public MarkedAreaHighlightingStatus(boolean focused, boolean incorrect, MarkedArea ma) { + this.focused = focused; + this.incorrect = incorrect; + this.ma = ma; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NewCategoryMenuResult.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NewCategoryMenuResult.java new file mode 100644 index 000000000..44880a00a --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NewCategoryMenuResult.java @@ -0,0 +1,57 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * GF sends the new menu as XML. + * After this has been parsed by GfCapsule, it is sent in this representation + * to GFEditor2. + * @author daniels + * + */ +class NewCategoryMenuResult { + /** + * The actual entries of the newMenu + */ + final String[] menuContent; + /** + * The languages, that GF sent + */ + final String[] languages; + /** + * the constituents of the import path? + */ + final String[] paths; + /** + * the name of the abstract grammar, also called topic + */ + final String grammarName; + + /** + * Just sets the attributes of this class + * @param grammarName the name of the abstract grammar, also called topic + * @param menuContent The actual entries of the newMenu + * @param languages The languages, that GF sent + * @param paths the constituents of the import path? + */ + public NewCategoryMenuResult(String grammarName, String[] menuContent, String[] languages, String paths[]) { + this.grammarName = grammarName; + this.menuContent = menuContent; + this.languages = languages; + this.paths = paths; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NoLineBreakFormatter.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NoLineBreakFormatter.java new file mode 100644 index 000000000..f241acb69 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/NoLineBreakFormatter.java @@ -0,0 +1,23 @@ +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * @author daniels + * A simple Formatter class, that does not introduce linebreaks, so that + * continous lines can be read under each other. + */ +public class NoLineBreakFormatter extends Formatter { + + /** + * @see java.util.logging.Formatter#format(java.util.logging.LogRecord) + */ + public String format(LogRecord record) { + final String shortLoggerName = record.getLoggerName().substring(record.getLoggerName().lastIndexOf('.') + 1); + return record.getLevel() + " : " + + shortLoggerName + " " + + record.getSourceMethodName() + " -:- " + + record.getMessage() + "\n"; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Printname.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Printname.java new file mode 100644 index 000000000..68feb6dd9 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Printname.java @@ -0,0 +1,569 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.Hashtable; +import java.util.Vector; +import java.util.logging.*; + +/** + * @author daniels + * + * A Printname allows easy access for all the information that is crammed + * into a printname in the GF grammars. + * This information consists of (in this order!) + * The tooltip text which is started with \\$ + * The subcategory which is started with \\% + * The longer explanation for the subcategory which directly follows the + * subcategory and is put into parantheses + * The parameter descriptions, which start with \\#name and is followed + * by their actual description. + * HTML can be used inside the descriptions and the tooltip text + */ +class Printname { + private static Logger subcatLogger = Logger.getLogger(Printname.class.getName()); + + /** + * delete is always the same and only consists of one letter, therefore static. + */ + public static final Printname delete = new Printname("d", "delete current sub-tree", false); + /** + * The ac command i always the same, therefore static + */ + public static final Printname addclip = new Printname("ac", "add to clipboard\\$<html>adds the current subtree to the clipboard.<br>It is offered in the refinement menu if the expected type fits to the one of the current sub-tree.</html>", false); + + /** + * @param arg The number of the argument, + * that will take the place of the selected fun + * @return a Printname for the 'ph arg' command + */ + public static Printname peelHead(int arg) { + final String cmd = "ph " + arg; + final String show = "peel head " + arg + "\\$removes this fun and moves its " + (arg + 1) + ". argument at its place instead"; + return new Printname(cmd, show, true); + } + + /** + * the type of the fun behind that printname (if applicable) + */ + protected final String type; + + /** + * If the command type will already + * be present in the display name and does not need to be added. + */ + protected final boolean funPresent; + /** + * The character that is the borderline between the text that + * is to be displayed in the JList and the ToolTip text + */ + public final static String TT_START = "\\$"; + /** + * the string that is followed by the sub-category shorthand + * in the refinement menu + */ + public final static String SUBCAT = "\\%"; + /** + * The string that is followed by a new parameter to the GF function + */ + public final static String PARAM = "\\#"; + /** + * If that follows "\#" in the parameter descriptions, then do an + * auto-coerce when this param is meta and selected + */ + public final static String AUTO_COERCE = "!"; + + /** + * the name of the fun that is used in this command + */ + protected final String fun; + + /** + * the printname of this function + */ + protected final String printname; + + /** + * to cache the printname, once it is constructed + */ + protected String displayedPrintname = null; + /** + * the name of the module the fun belongs to + * null means that the function is saved without module information, + * "" means that a GF command is represented + */ + protected final String module; + /** + * the name of the module the fun belongs to + * null means that the function is saved without module information, + * "" means that a GF command is represented + */ + public String getModule() { + return module; + } + + + /** the qualified function name, not needed yet */ + /* + public String getFunQualified() { + if (module != null && !module.equals("")) { + return module + "." + fun; + } else { + return fun; + } + } + */ + + /** + * the subcategory of this command + */ + protected final String subcat; + /** + * the subcategory of this command + */ + public String getSubcat() { + return subcat; + } + + /** + * The hashmap for the names of the sub categories, + * with the shortname starting with '%' as the key. + * It is important that all Printnames of one session share the same + * instance of Hashtable here. + * This field is not static because there can be several instances of + * the editor that shouldn't interfere. + */ + protected final Hashtable subcatNameHashtable; + + /** + * contains the names of the paramters of this function (String). + * Parallel with paramTexts + */ + protected final Vector paramNames = new Vector(); + + /** + * fetches the name of the nth parameter + * @param n the number of the wanted paramter + * @return the corresponding name, null if not found + */ + public String getParamName(int n) { + String name = null; + try { + name = (String)this.paramNames.get(n); + } catch (ArrayIndexOutOfBoundsException e) { + subcatLogger.fine(e.getLocalizedMessage()); + } + return name; + } + /** + * contains the descriptions of the paramters of this function (String). + * Parallel with paramNames + */ + protected final Vector paramTexts = new Vector(); + + /** + * tells, whether the nth parameter should be auto-coerced + * @param n the number of the parameter in question + * @return whether the nth parameter should be auto-coerced + */ + public boolean getParamAutoCoerce(int n) { + boolean result = false; + try { + result = ((Boolean)this.paramAutoCoerce.get(n)).booleanValue(); + } catch (ArrayIndexOutOfBoundsException e) { + subcatLogger.fine(e.getLocalizedMessage()); + } + return result; + } + + /** + * stores for the parameters whether they should be auto-coerced or not. + * parallel with paramNames + */ + protected final Vector paramAutoCoerce = new Vector(); + + /** + * Creates a Printname for a normal GF function + * @param myFun the function name + * @param myPrintname the printname given for this function + * @param myFullnames the Hashtable for the full names for the category + * names for the shortnames like \\%PREDEF + * @param type The type of this fun. + * If null, it won't be displayed in the refinement menu. + */ + public Printname(String myFun, String myPrintname, Hashtable myFullnames, String type) { + myFun = myFun.trim(); + myPrintname = myPrintname.trim(); + this.printname = myPrintname; + this.subcatNameHashtable = myFullnames; + this.type = type; + if (myFullnames == null) { + //if the menu language is abstract, no fullnames are loaded + //and the fun will be in the used showname + this.funPresent = true; + } else { + this.funPresent = false; + } + + //parse the fun name + { + int index = myFun.indexOf('.'); + if (index > -1) { + //a valid fun name must not be empty + this.fun = myFun.substring(index + 1); + this.module = myFun.substring(0, index); + } else { + this.fun = myFun; + this.module = null; + } + } + + //parse the parameters and cut that part + { + int index = Utils.indexOfNotEscaped(myPrintname, PARAM); + if (index > -1) { + String paramPart = myPrintname.substring(index); + String splitString; + //split takes a regexp as an argument. So we have to escape the '\' again. + if (PARAM.startsWith("\\")) { + splitString = "\\" + PARAM; + } else { + splitString = PARAM; + } + String[] params = paramPart.split(splitString); + //don't use the first split part, since it's empty + for (int i = 1; i < params.length; i++) { + String current = params[i]; + boolean autocoerce = false; + if (AUTO_COERCE.equals(current.substring(0,1))) { + autocoerce = true; + //cut the ! + current = current.substring(1); + } + int nameEnd = current.indexOf(' '); + int nameEnd2 = Utils.indexOfNotEscaped(current, PARAM); + if (nameEnd == -1) { + nameEnd = current.length(); + } + String name = current.substring(0, nameEnd); + String description; + if (nameEnd < current.length() - 1) { + description = current.substring(nameEnd + 1).trim(); + } else { + description = ""; + } + this.paramNames.addElement(name); + this.paramTexts.addElement(description); + this.paramAutoCoerce.addElement(new Boolean(autocoerce)); + } + myPrintname = myPrintname.substring(0, index); + } + } + + + //extract the subcategory part and cut that part + { + int index = Utils.indexOfNotEscaped(myPrintname, SUBCAT); + if (index > -1) { + String subcatPart = myPrintname.substring(index); + myPrintname = myPrintname.substring(0, index); + int indFull = subcatPart.indexOf('{'); + if (indFull > -1) { + int indFullEnd = subcatPart.indexOf('}', indFull + 1); + if (indFullEnd == -1) { + indFullEnd = subcatPart.length(); + } + String fullName = subcatPart.substring(indFull + 1, indFullEnd); + this.subcat = subcatPart.substring(0, indFull).trim(); + this.subcatNameHashtable.put(this.subcat, fullName); + if (subcatLogger.isLoggable(Level.FINER)) { + subcatLogger.finer("new fullname '" + fullName + "' for category (shortname) '" + this.subcat + "'"); + } + } else { + subcat = subcatPart.trim(); + } + + } else { + this.subcat = null; + } + } + } + + /** + * a constructor for GF command that don't represent functions, + * like d, ph, ac + * @param command the GF command + * @param explanation an explanatory text what this command does + * @param funPresent If explanation already contains the fun. + * If true, the fun won't be printed in the refinement menu. + */ + protected Printname(String command, String explanation, boolean funPresent) { + this.fun = command; + this.subcatNameHashtable = null; + this.subcat = null; + this.module = ""; + this.printname = explanation; + this.type = null; + this.funPresent = funPresent; + } + + /** + * Special constructor for bound variables. + * These printnames don't get saved since they don't always exist and + * also consist of quite few information. + * @param bound The name of the bound variable + */ + public Printname(String bound) { + this.fun = bound; + this.subcatNameHashtable = null; + this.subcat = null; + this.module = null; + this.printname = bound; + this.type = null; + this.funPresent = false; + } + + /** + * the text that is to be displayed in the refinement lists + */ + public String getDisplayText() { + String result; + result = extractDisplayText(this.printname); + return result; + } + + /** + * the text that is to be displayed as the tooltip. + * Will always be enclosed in <html> </html> tags. + */ + public String getTooltipText() { + if (this.displayedPrintname != null) { + return this.displayedPrintname; + } else { + String result; + result = extractTooltipText(this.printname); + if (this.paramNames.size() > 0) { + String params = htmlifyParams(); + //will result in <html> wrapping + result = htmlAppend(result, params); + } else { + //wrap in <html> by force + result = htmlAppend(result, ""); + } + this.displayedPrintname = result; + return result; + } + } + + /** + * extracts the part of the body of the printname that is the tooltip + * @param myPrintname the body of the printname + * @return the tooltip + */ + public static String extractTooltipText(String myPrintname) { + //if the description part of the fun has no \\$ to denote a tooltip, + //but the subcat description has one, than we must take extra + //caution + final int indTT = Utils.indexOfNotEscaped(myPrintname, TT_START); + final int indSC = Utils.indexOfNotEscaped(myPrintname, SUBCAT); + int ind; + if ((indSC > -1) && (indSC < indTT)) { + ind = -1; + } else { + ind = indTT; + } + String result; + if (ind > -1) { + result = myPrintname.substring(ind + TT_START.length()); + } else { + result = myPrintname; + } + ind = Utils.indexOfNotEscaped(result, SUBCAT); + if (ind > -1) { + result = result.substring(0, ind); + } + ind = Utils.indexOfNotEscaped(result, PARAM); + if (ind > -1) { + result = result.substring(0, ind); + } + return result; + } + + /** + * extracts the part of the body of the printname that is the + * text entry for the JList + * @param myPrintname the body of the printname + * @return the one-line description of this Printname's fun + */ + public static String extractDisplayText(String myPrintname) { + String result; + int ind = Utils.indexOfNotEscaped(myPrintname, TT_START); + if (ind > -1) { + result = myPrintname.substring(0, ind); + } else { + result = myPrintname; + } + ind = Utils.indexOfNotEscaped(result, SUBCAT); + if (ind > -1) { + result = result.substring(0, ind); + } + ind = Utils.indexOfNotEscaped(result, PARAM); + if (ind > -1) { + result = result.substring(0, ind); + } + + return result; + } + + /** + * Appends the given string insertion to original and + * returns the result. If original is already HTML, the appended + * text will get right before the </html> tag. + * If original is no HTML, it will be enclosed in <html> + * @param original The String that is to come before insertion + * @param insertion the String to be appended + * @return the aforementioned result. + */ + public static String htmlAppend(String original, String insertion) { + StringBuffer result; + if (original != null) { + result = new StringBuffer(original); + } else { + result = new StringBuffer(); + } + int htmlindex = result.indexOf("</html>"); + + if (htmlindex > -1) { + result.insert(htmlindex, insertion); + } else { + result.insert(0,"<html>").append(insertion).append("</html>"); + } + return result.toString(); + + } + + /** + * Prepends the given string insertion to original and + * returns the result. If original is already HTML, the appended + * text will get right after the <html> tag. + * If original is no HTML, it will be enclosed in <html> + * @param original The String that is to come after insertion + * @param insertion the String to be appended + * @return the aforementioned result. + */ + public static String htmlPrepend(String original, String insertion) { + StringBuffer result = new StringBuffer(original); + int htmlindex = result.indexOf("<html>"); + + if (htmlindex > -1) { + result.insert(htmlindex, insertion); + } else { + result.insert(0,insertion).insert(0,"<html>").append("</html>"); + } + return result.toString(); + + } + + /** + * wraps a single parameter with explanatory text + * into <dt> and <dd> tags + * @param which the number of the parameter + * @return the resulting String, "" if the wanted parameter + * is not stored (illegal index) + */ + protected String htmlifyParam(int which) { + try { + String result = "<dt>" + this.paramNames.get(which) + "</dt>" + + "<dd>" + this.paramTexts.get(which) + "</dd>"; + return result; + } catch (ArrayIndexOutOfBoundsException e) { + subcatLogger.fine(e.getLocalizedMessage()); + return ""; + } + + } + + /** + * wraps a single parameter together with its explanatory text into + * a HTML definition list (<dl> tags). + * Also the result is wrapped in <html> tags. + * @param which the number of the parameter + * @return the resulting definition list, null if the param is not found. + */ + public String htmlifySingleParam(int which) { + String text = htmlifyParam(which); + if (text.equals("")) { + return null; + } + String result = "<html><dl>" + text + "</dl></html>"; + return result; + } + /** + * looks up the description for parameter number 'which' and returns it. + * Returns null, if no parameter description is present. + * @param which The number of the parameter + * @return s.a. + */ + public String getParamDescription(int which) { + return (String)paramTexts.get(which); + } + + /** + * wraps all parameters together with their explanatory text into + * a HTML definition list (<dl> tags). + * No <html> tags are wrapped around here, that is sth. the caller + * has to do! + * @return the resulting definition list, "" if which is larger than + * the amount of stored params + */ + public String htmlifyParams() { + if (this.paramNames.size() == 0) { + return ""; + } + StringBuffer result = new StringBuffer("<h4>Parameters:</h4><dl>"); + for (int i = 0; i < this.paramNames.size(); i++) { + result.append(htmlifyParam(i)); + } + result.append("</dl>"); + return result.toString(); + } + + /** + * a testing method that is not called from KeY. + * Probably things like this should be automated via JUnit ... + * @param args not used + */ + public static void main(String[] args) { + String SandS = "boolean 'and' for sentences$true iff both of the two given sentences is equivalent to true%BOOL#alpha the first of the two and-conjoined sentences#beta the second of the and-conjoined sentences"; + String FandS = "andS"; + Hashtable ht = new Hashtable(); + Printname pn = new Printname(FandS, SandS, ht, null); + System.out.println(pn); + for (int i = 0; i < pn.paramNames.size(); i++) { + System.out.println(pn.htmlifySingleParam(i)); + } + System.out.println(pn.getTooltipText()); + SandS = "boolean 'and' for sentences$true iff both of the two given sentences is equivalent to true%BOOL"; + FandS = "andS"; + pn = new Printname(FandS, SandS, ht, null); + System.out.println("*" + pn.getTooltipText()); + } + + public String toString() { + return getDisplayText() + " \n " + getTooltipText() + " (" + this.paramNames.size() + ")"; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameLoader.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameLoader.java new file mode 100644 index 000000000..2cf5e9daa --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameLoader.java @@ -0,0 +1,112 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.logging.*; +/** + * @author daniels + * asks GF to print all available printnames, parses that list and generates + * the suiting Printname objects. + */ +public class PrintnameLoader extends AbstractProber { + private final static Logger nogger = Logger.getLogger(Printname.class.getName()); + /** + * The PrintnameManager on which the read Printnames + * will be registered with their fun name. + */ + private final PrintnameManager printnameManager; + /** + * Here, the funs with their types get stored + */ + private final Hashtable funTypes = new Hashtable(); + /** + * if the Printnames should have their type appended to their display names + */ + private final boolean loadTypes; + /** + * an initializing constructor, does nothing except setting fields + * @param gfCapsule the read/write encapsulation of GF + * @param pm The PrintnameManager on which the read Printnames + * will be registered with their fun name. + * @param withTypes true iff the Printnames should have their type + * appended to their display names + */ + public PrintnameLoader(GfCapsule gfCapsule, PrintnameManager pm, boolean withTypes) { + super(gfCapsule); + this.printnameManager = pm; + this.loadTypes = withTypes; + } + + /** + * Reads the tree child of the XML from beginning to end. + * Sets autocompleted to false, if the focus position is open. + */ + protected void readMessage() { + try { + String result = gfCapsule.fromProc.readLine(); + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("1 " + result); + } + //first read line is <message>, but this one gets filtered out in the next line + while (result.indexOf("/message")==-1){ + result = result.trim(); + if (result.startsWith("printname fun ")) { + //unescape backslashes. Probably there are more + result = Linearization.unescapeTextFromGF(result); + this.printnameManager.addNewPrintnameLine(result, this.funTypes); + } + + result = gfCapsule.fromProc.readLine(); + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("1 " + result); + } + } + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("finished loading printnames"); + } + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + } + + } + + /** + * asks GF to print a list of all available printnames and + * calls the registered PrintnameManager to register those. + * @param lang The module for which the grammars should be printed. + * If lang is "" or null, the last read grammar module is used. + */ + protected void readPrintnames(String lang) { + //before, we want the types to be read. + if (this.loadTypes) { + TypesLoader tl = new TypesLoader(gfCapsule, this.funTypes); + tl.readTypes(); + } + //prints the printnames of the last loaded grammar, + String sendString = "gf pg -printer=printnames"; + if (lang != null && !("".equals(lang))) { + sendString = sendString + " -lang=" + lang; + } + nogger.fine("collecting printnames :" + sendString); + send(sendString); + readGfedit(); + } + + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameManager.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameManager.java new file mode 100644 index 000000000..685dcf000 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/PrintnameManager.java @@ -0,0 +1,174 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; +import java.util.Hashtable; +import java.util.logging.*; + +/** + * @author daniels + * + * An object of this class manages a bunch of printlines which is comprised of + * storage and retrieval. Also giving the subcategory shortnames their long + * counterpart is done here. + */ +class PrintnameManager { + /** + * This constructor is a bit of a hack. + * It puts the \%SELF subcat into this.printnames. + * This subcat does not appear in the grammars and thus is + * introduced here. If it is defined there although, this + * definition is used. So it does not hurt. + */ + public PrintnameManager() { + this.subcatNames.put(SELF_SUBCAT, "properties of self\\$shortcuts to the properties of self, that have a fitting type"); + } + + /** + * The name of the subcat, that is used for the easy property access + * of self. + */ + static final String SELF_SUBCAT = "\\%SELF"; + + private static Logger logger = Logger.getLogger(Printname.class.getName()); + + protected final static String frontMatter = "printname fun "; + + /** + * The hashmap for the names of the sub categories, + * with the shortname starting with '%' as the key. + * It is important that all Printnames of one session share the same + * instance of Hashtable here. + * This field is not static because there can be several instances of + * the editor that shouldn't interfere. + */ + protected final Hashtable subcatNames = new Hashtable(); + + /** + * contains all the Printnames with the fun names as keys + */ + protected final Hashtable printnames = new Hashtable(); + + /** + * processes a line from the "gf pg -printer=printnames" command + * @param line the read line from GF + * Should look like + * printname fun neq = "<>," ++ ("parametrized" ++ ("disequality$to" ++ ("compare" ++ ("two" ++ ("instances" ++ ("on" ++ ("a" ++ ("specific" ++ "type%COMP")))))))) + * and needs to get like + * printname fun neq = "<>, parametrized disequality$to compare two instances on a specific type%COMP" + * @param funTypes contains funs, mapped to their types + */ + public void addNewPrintnameLine(String line, Hashtable funTypes) { + line = removePluses(line); + + //remove "printname fun " (the frontMatter) + final int index = line.indexOf(frontMatter); + line = line.substring(index + frontMatter.length()).trim(); + + //extract fun name + final int endFun = line.indexOf(' '); + final String fun = line.substring(0, endFun); + final String type = (String)funTypes.get(fun); + //extract printname + String printname = line.substring(line.indexOf('"') + 1, line.lastIndexOf('"')); + + addNewPrintname(fun, printname, type); + } + + /** + * The printname printer of pg spits out no String, but a wrapping of + * small Strings conjoined with ++ which includes lots of parantheses. + * @param line The GF line from pg -printer=printnames + * @return a String representing the actual printname without the clutter + */ + protected static String removePluses(String line) { + line = line.replaceAll("\"\\)*\\s*\\+\\+\\s*\\(*\""," "); + int index = line.lastIndexOf('"'); + line = line.substring(0, index + 1); + return line; + } + + + /** + * Constructs the actual printname and puts it into printnames + * @param myFun the GF abstract fun name + * @param myPrintname the printname given by GF + */ + protected void addNewPrintname(String myFun, String myPrintname, String type) { + if (logger.isLoggable(Level.FINER)) { + logger.finer("addNewPrintname, myFun = '" + myFun + "' , myPrintname = '" + myPrintname + "'"); + } + Printname printname = new Printname(myFun, myPrintname, this.subcatNames, type); + if (logger.isLoggable(Level.FINER)) { + logger.finer("printname = '" + printname + "'"); + } + this.printnames.put(myFun, printname); + } + + /** + * looks for the Printname corresponding to the given fun. + * If the fun is qualified with a module and no Printname + * is found with module, another lookup without the module part + * is made. + * @param myFun the GF abstract fun name (with or without qualification) + * @return the corresponding Printname iff that one exists, null otherwise + */ + public Printname getPrintname(String myFun) { + Printname result = null; + if (this.printnames.containsKey(myFun)) { + result = (Printname)this.printnames.get(myFun); + } else { + int index = myFun.indexOf('.'); + if (index > -1) { + //a valid fun name must not be empty + String fun = myFun.substring(index + 1); + if (printnames.containsKey(fun)) { + result = (Printname)this.printnames.get(fun); + } + } + } + if (result == null) { + //?n indicates that myFun is a metavariable of GF, + // which does not occur in the refinement menu. + // if that is not wanted, don't call this method! + if (!myFun.startsWith("?")) { + logger.fine("no printname for '" + myFun + "', pretend that it is a bound variable"); + return new Printname(myFun); + } + } + return result; + } + + /** + * looks up the full name for the subcategory name shortname. + * This is the %SOMETHING from the printname. + * @param shortname The subcat name which should get expanded + * @return the corresponding full name, maybe null! + */ + public String getFullname(String shortname) { + String result = (String)this.subcatNames.get(shortname); + return result; + } + + public static void main(String[] args) { + String a = "printname fun stringLiteral = \"arbitrary\" ++ (\"String$click\" ++ (\"read\" ++ (\"and\" ++ (\"enter\" ++ (\"the\" ++ (\"String\" ++ (\"in\" ++ (\"the\" ++ (\"dialog\" ++ (\"TODO%STRING(String\" ++ \"operations)\"))))))))))"; + System.out.println(a); + System.out.println(removePluses(a)); + a = "printname fun count = \"count\" ++ (\"the\" ++ (\"occurances\" ++ (\"of\" ++ (\"an\" ++ (\"object\" ++ (\"in\" ++ (\"the\" ++ \"collection.\")))))))++ (\"%COLL\" ++ (\"#collElemType\" ++ (\"The\" ++ (\"official\" ++ (\"element\" ++ (\"type\" ++ (\"of\" ++ (\"<i>coll</i>.<br>That\" ++ (\"is,\" ++ (\"the\" ++ (\"parameter\" ++ (\"type\" ++ (\"of\" ++ \"<i>coll</i>\")))))))))))++ (\"#set\" ++ (\"The\" ++ (\"Set\" ++ (\"in\" ++ (\"which\" ++ (\"occurances\" ++ (\"of\" ++ (\"<i>elem</i>\" ++ (\"are\" ++ (\"to\" ++ (\"be\" ++ \"counted.\")))))))))) ++ (\"#elem\" ++ (\"The\" ++ (\"instance\" ++ (\"of\" ++ (\"which\" ++ (\"the\" ++ (\"occurances\" ++ (\"in\" ++ (\"<i>coll</i>\" ++ (\"are\" ++ \"counted.\")))))))))))))"; + System.out.println(a); + System.out.println(removePluses(a)); + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ReadDialog.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ReadDialog.java new file mode 100644 index 000000000..3a0ea34f4 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ReadDialog.java @@ -0,0 +1,200 @@ +//Copyright (c) Janna Khegai 2004, Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import java.util.logging.*; + +/** + * Takes care of reading in Strings that are to be parsed and terms. + * @author daniels + * + */ +class ReadDialog implements ActionListener{ + /** XML parsing debug messages */ + private static Logger xmlLogger = Logger.getLogger(GFEditor2.class.getName() + "_XML"); + /** The window to which this class belongs */ + private final GFEditor2 owner; + /** is the main thing of this class */ + private final JDialog readDialog; + /** main area of the Read dialog (content pane)*/ + private final JPanel inputPanel = new JPanel(); + /** OK, Cancel, Browse in the Read dialog */ + private final JPanel inputPanel2 = new JPanel(); + /** in the Read dialog the OK button */ + private final JButton ok = new JButton("OK"); + /** in the Read dialog the Cancel button */ + private final JButton cancel = new JButton("Cancel"); + /** in the Read dialog the Browse button */ + private final JButton browse = new JButton("Browse..."); + /** groups inputField and inputLabel */ + private final JPanel inputPanel3 = new JPanel(); + /** for 'Read' to get the input */ + private final JTextField inputField = new JTextField(); + /** "Read: " */ + private final JLabel inputLabel = new JLabel("Read: "); + /** the radio group in the Read dialog to select Term or String */ + private final ButtonGroup readGroup = new ButtonGroup(); + /** to select to input a Term in the Read dialog */ + private final JRadioButton termReadButton = new JRadioButton("Term"); + /** to select to input a String in the Read dialog */ + private final JRadioButton stringReadButton = new JRadioButton("String"); + /** used for new Topic, Import and Browse (readDialog) */ + private final JFileChooser fc = new JFileChooser("./"); + /** + * if a user sends a custom command to GF, he might want to do this + * again with the same command. + * Therefore it is saved. + */ + private String parseInput = ""; + /** + * if the user enters a term, he perhaps wants to input the same text again. + * Therefore it is saved. + */ + private String termInput = ""; + + /** + * creates a modal dialog + * @param owner The parent for which this dialog shall be modal. + */ + protected ReadDialog(GFEditor2 owner) { + this.owner = owner; + readDialog= new JDialog(owner, "Input", true); + readDialog.setLocationRelativeTo(owner); + readDialog.getContentPane().add(inputPanel); + readDialog.setSize(480,135); + + termReadButton.setActionCommand("term"); + stringReadButton.setSelected(true); + stringReadButton.setActionCommand("lin"); + // Group the radio buttons. + readGroup.add(stringReadButton); + readGroup.add(termReadButton); + JPanel readButtonPanel = new JPanel(); + readButtonPanel.setLayout(new GridLayout(3,1)); + readButtonPanel.setPreferredSize(new Dimension(70, 70)); + readButtonPanel.add(new JLabel("Format:")); + readButtonPanel.add(stringReadButton); + readButtonPanel.add(termReadButton); + inputPanel.setLayout(new BorderLayout(10,10)); + inputPanel3.setLayout(new GridLayout(2,1,5,5)); + inputPanel3.add(inputLabel); + inputPanel3.add(inputField); + ok.addActionListener(this); + browse.addActionListener(this); + cancel.addActionListener(this); + inputField.setPreferredSize(new Dimension(300,23)); + inputPanel.add(inputPanel3, BorderLayout.CENTER); + inputPanel.add(new JLabel(" "), BorderLayout.WEST); + inputPanel.add(readButtonPanel, BorderLayout.EAST); + inputPanel.add(inputPanel2, BorderLayout.SOUTH); + inputPanel2.add(ok); + inputPanel2.add(cancel); + inputPanel2.add(browse); + } + + /** + * Shows this modal dialog. + * The previous input text will be there again. + * + */ + protected void show() { + if (stringReadButton.isSelected()) { + inputField.setText(this.parseInput); + } else { + inputField.setText(this.termInput); + } + this.readDialog.setVisible(true); + } + + /** + * Sets the font of all GUI elements to font + * @param font + */ + protected void setFont(Font font) { + ok.setFont(font); + cancel.setFont(font); + inputLabel.setFont(font); + browse.setFont(font); + termReadButton.setFont(font); + stringReadButton.setFont(font); + } + + /** + * the ActionListener method that does the user interaction + */ + public void actionPerformed(ActionEvent ae) { + Object obj = ae.getSource(); + + if ( obj == cancel ) { + readDialog.setVisible(false); + } + + if ( obj == browse ) { + if (fc.getChoosableFileFilters().length<2) + fc.addChoosableFileFilter(new GrammarFilter()); + int returnVal = fc.showOpenDialog(owner); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File file = fc.getSelectedFile(); + inputField.setText(file.getPath().replace('\\','/')); + } + } + + if ( obj == ok ) { + if (termReadButton.isSelected()) { + termInput = inputField.getText(); + if (termInput.indexOf(File.separatorChar)==-1){ + owner.send("[t] g "+termInput); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("sending term string"); + } else { + owner.send("[t] tfile "+termInput); + if (xmlLogger.isLoggable(Level.FINER)) { + xmlLogger.finer("sending file term: "+termInput); + } + } + } else { //String selected + parseInput = inputField.getText(); + if (parseInput.indexOf(File.separatorChar)==-1){ + owner.send("[t] p "+parseInput); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("sending parse string: "+parseInput); + } + else { + owner.send("[t] pfile "+parseInput); + if (xmlLogger.isLoggable(Level.FINER)) xmlLogger.finer("sending file parse string: "+parseInput); + } + } + readDialog.setVisible(false); + } + + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RealCommand.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RealCommand.java new file mode 100644 index 000000000..8d9b4f3f8 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RealCommand.java @@ -0,0 +1,255 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.HashSet; +import java.util.HashMap; +import java.util.logging.*; + +/** + * @author daniels + * This class represents a command, that is sent to GF. + * TODO Refactor the chain command stuff out of this class and make it a subclass + */ +class RealCommand extends GFCommand { + + /** + * maps shorthands to fullnames + */ + private final static HashMap fullnames = new HashMap(); + + private final static Logger logger = Logger.getLogger(Printname.class.getName()); + + /** + * The number of undo steps that is needed to undo this fun call + */ + public final int undoSteps; + + /** + * The text that GF sent to describe the command + */ + protected final String showText; + + protected final String subcat; + + /** + * Creates a Command that stands for a GF command, no link command + * sets all the attributes of this semi-immutable class. + * @param myCommand the actual GF command + * @param processedSubcats + * @param manager maps funs to previously read Printnames. + * Thus needs to be the same object. + * @param myShowText The text GF prints in the show part of the XML + * which should be the command followed by the printname + * @param mlAbstract is true, iff the menu language is set to Abstract + * Then no preloaded printnames are used. + * @param toAppend will be appended to the command, that is sent to GF. + * Normally, toAppend will be the empty String "". + * But it can be a chain command's second part. + * It will not be shown to the user. + */ + public RealCommand(final String myCommand, final HashSet processedSubcats, final PrintnameManager manager, final String myShowText, final boolean mlAbstract, final String toAppend) { + this(myCommand, processedSubcats, manager, myShowText, mlAbstract, toAppend, 1, null, null); + } + + /** + * Creates a Command that stands for a GF command, no link command + * sets all the attributes of this semi-immutable class. + * @param myCommand the actual GF command + * @param processedSubcats + * @param manager maps funs to previously read Printnames. + * Thus needs to be the same object. + * @param myShowText The text GF prints in the show part of the XML + * which should be the command followed by the printname + * @param mlAbstract is true, iff the menu language is set to Abstract + * Then no preloaded printnames are used. + * @param toAppend will be appended to the command, that is sent to GF. + * Normally, toAppend will be the empty String "". + * But it can be a chain command's second part. + * It will not be shown to the user. + * @param undoSteps The number of undo steps that is needed to undo this fun call + * @param printnameFun If the fun, that selects the printname, should not be read from + * myCommand. For single commands, this is the only fun. For chain command, the last is + * taken. With this parameter, this behaviour can be overwritten + * @param subcat Normally, every fun has its own Printname, which has a fixed + * category. Sometimes, for the properies of self for example, + * this should be overwritten. If null, the subcat from the printname is used. + */ + public RealCommand(final String myCommand, final HashSet processedSubcats, final PrintnameManager manager, final String myShowText, final boolean mlAbstract, String toAppend, int undoSteps, String printnameFun, String subcat) { + if (fullnames.isEmpty()) { + fullnames.put("w", "wrap"); + fullnames.put("r", "refine"); + fullnames.put("ch", "change head"); + fullnames.put("rc", "refine from history:"); + fullnames.put("ph", "peel head"); + } + if (logger.isLoggable(Level.FINEST)) { + logger.finest("new RealCommand: " + myCommand); + } + //if we have a ChainCommand, but undoSteps is just 1, count the undoSteps. + if ((undoSteps == 1) && (myCommand.indexOf(";;") > -1)) { + int occ = Utils.countOccurances(Utils.removeQuotations(myCommand), ";;") + 1; + this.undoSteps = occ; + } else { + this.undoSteps = undoSteps; + } + this.command = myCommand.trim(); + this.showText = myShowText; + this.subcat = subcat; + + //handle chain commands. + //Only the last command counts for the printname selection + final String lastCommand; + if (this.undoSteps > 1) { + //TODO: sth. like refine " f ;;d" ;; mp [2] will break here. + final int chainIndex = this.command.lastIndexOf(";;"); + lastCommand = this.command.substring(chainIndex + 2).trim(); + } else { + lastCommand = this.command; + } + + //extract command type + int ind = lastCommand.indexOf(' '); + if (ind > -1) { + this.commandType = lastCommand.substring(0, ind); + } else { + this.commandType = lastCommand; + } + + //extract the argument position for wrapping commands and cut that part + if (this.commandType.equals("w") || this.commandType.equals("ph")) { + int beforeNumber = lastCommand.lastIndexOf(' '); + int protoarg; + try { + String argumentAsString = lastCommand.substring(beforeNumber + 1); + protoarg = Integer.parseInt(argumentAsString); + } catch (Exception e) { + protoarg = -1; + } + this.argument = protoarg; + } else { + this.argument = -1; + } + + //extract the fun of the GF command + if (this.commandType.equals("w")) { + int beforePos = lastCommand.indexOf(' '); + int afterPos = lastCommand.lastIndexOf(' '); + if (beforePos > -1 && afterPos > beforePos) { + this.funName = lastCommand.substring(beforePos + 1, afterPos); + } else { + this.funName = null; + } + } else { + int beforePos = lastCommand.indexOf(' '); + if (beforePos > -1) { + this.funName = lastCommand.substring(beforePos + 1); + } else { + this.funName = null; + } + } + + //get corresponding Printname + if (this.commandType.equals("d")) { + this.printname = Printname.delete; + } else if (this.commandType.equals("ac")) { + this.printname = Printname.addclip; + } else if (this.commandType.equals("rc")) { + String subtree = this.showText.substring(3); + this.printname = new Printname(this.getCommand(), subtree + "\\$paste the previously copied subtree here<br>" + subtree, false); + } else if (this.commandType.equals("ph")) { + this.printname = Printname.peelHead(this.argument); + } else if (mlAbstract) { + //create a new Printname + this.printname = new Printname(funName, myShowText, null, null); + } else { //standard case + if (printnameFun == null) { + this.printname = manager.getPrintname(funName); + } else { + //overwrite mode. Until now, only for properties of self. + this.printname = manager.getPrintname(printnameFun); + } + } + + if (this.getSubcat() != null) { + if (processedSubcats.contains(this.getSubcat())) { + newSubcat = false; + } else { + newSubcat = true; + processedSubcats.add(this.getSubcat()); + } + } else { + newSubcat = false; + } + + //now append toAppend before it is too late. + //Only now, since it must not interfere with the things above. + if (toAppend != null) { + this.command += toAppend; + } + } + + /** + * the text that is to be displayed in the refinement lists + */ + public String getDisplayText() { + String result = ""; + if (this.printname.funPresent) { + result = this.printname.getDisplayText(); + } else { + if (fullnames.containsKey(this.commandType)) { + result = fullnames.get(this.commandType) + " '"; + } + result = result + this.printname.getDisplayText(); + if (fullnames.containsKey(this.commandType)) { + result = result + "'"; + } + } + if (this.commandType.equals("w")) { + String insertion = " as argument " + (this.argument + 1); + result = result + insertion; + } + if (this.printname.type != null) { + result = result + " : " + this.printname.type; + } + return result; + } + + /** + * the text that is to be displayed as the tooltip + */ + public String getTooltipText() { + String result; + result = this.printname.getTooltipText(); + if (this.commandType.equals("w")) { + String insertion = "<br>The selected sub-tree will be the " + (this.argument + 1) + ". argument of this refinement."; + result = Printname.htmlAppend(result, insertion); + } + return result; + } + + /** + * returns the subcat of this command + */ + public String getSubcat() { + if (this.subcat == null) { + return this.printname.getSubcat(); + } else { + //special case, only for properties of self so far + return this.subcat; + } + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinedAstNodeData.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinedAstNodeData.java new file mode 100644 index 000000000..bfd526593 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinedAstNodeData.java @@ -0,0 +1,68 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; +import java.util.logging.*; +/** + * @author daniels + * An object of this class represents a line in the GF abstract syntax tree + * in the graphical form. Well, not really, but how this line appears there + * and what its tooltip is is stored here. + * RefinedAstNodeData has its tooltip from the function it represents, not + * from its parent node. + */ +class RefinedAstNodeData extends AstNodeData { + + protected final Printname printname; + + /** + * all we have to know about an already refined node is its Printname + * and the GF line representing it + * @param pname the suiting Printname, may be null if the line could + * not be parsed + * @param node the GfAstNode for the current line + * @param pos The position in the GF AST of this node in Haskell notation + * @param selected if this is the selected node in the GF AST + * @param constraint A constraint from a parent node, that also + * applies for this node. + */ + public RefinedAstNodeData(Printname pname, GfAstNode node, String pos, boolean selected, String constraint) { + super(node, pos, selected, constraint); + this.printname = pname; + if (logger.isLoggable(Level.FINEST)) { + logger.finest(this.toString() + " - " + position); + } + } + + /** + * @return the printname associated with this object + */ + public Printname getPrintname() { + return this.printname; + } + + /** + * @return displays the tooltip of the registered Printname, + * which may be null + */ + public String getParamTooltip() { + if (getPrintname() != null) { + return getPrintname().getTooltipText(); + } else { + return null; + } + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenu.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenu.java new file mode 100644 index 000000000..46e4a2443 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenu.java @@ -0,0 +1,518 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this applicationpackage de.uka.ilkd.key.ocl.gf; + +package de.uka.ilkd.key.ocl.gf; + +import java.awt.Color; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.Collections; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.ListSelectionModel; + +/** + * Takes care of managing the commands, that GF sent, + * including subcategories and their menus. + * Manages the graphical lists. To display them, they are reachable + * via getRefinementListsContainer(). + * @author hdaniels + */ +class RefinementMenu { + /** + * logs things like selections and key events + */ + private static Logger logger = Logger.getLogger(RefinementMenu.class.getName()); + + /** + * the editor of which this menu is part of + */ + final private GFEditor2 editor; + /** + * the content of the refinementMenu + */ + public DefaultListModel listModel= new DefaultListModel(); + /** + * The list of current refinement options + */ + private JList refinementList = new JList(this.listModel); + /** + * to store the Vectors which contain the display names for the + * ListModel for refinementSubcatList for the different + * subcategory menus. + * The key is the shortname String, the value the Vector with the + * display Strings + */ + private Hashtable subcatListModelHashtable = new Hashtable(); + /** + * this ListModel gets refilled every time a %WHATEVER command, + * which stands for a shortname for a subcategory of commands + * in the ListModel of refinementList, is selected there + */ + private DefaultListModel refinementSubcatListModel = new DefaultListModel(); + /** + * The list of current refinement options in the subcategory menu + */ + private JList refinementSubcatList = new JList(this.refinementSubcatListModel); + /** + * the scrollpane containing the refinement subcategory + */ + private JScrollPane refinementSubcatPanel = new JScrollPane(this.refinementSubcatList); + /** + * store what the shorthand name for the current subcat is + */ + private String whichSubcat; + /** + * stores the two refinement JLists + */ + private JSplitPane refinementListsContainer; + /** + * the scrollpane containing the refinements + */ + private JScrollPane refinementPanel = new JScrollPane(this.refinementList); + /** + * here the GFCommand objects are stored + */ + private Vector gfcommands = new Vector(); + /** + * The cached popup menu containing the same stuff as the refinement list + */ + public JPopupMenu popup2 = new JPopupMenu(); + + /** + * Creates the panels for the refinement (subcat) menu + * @param editor the editor, that the refinement menu is part of + */ + protected RefinementMenu(GFEditor2 editor) { + this.editor = editor; + refinementListsContainer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,refinementPanel, refinementSubcatPanel); + refinementList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + final MouseListener mlRefinementList = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + refinementList.setSelectionBackground(refinementSubcatList.getSelectionBackground()); + boolean doubleClick = (e.getClickCount() == 2); + listAction(refinementList, refinementList.locationToIndex(e.getPoint()), doubleClick); + } + }; + refinementList.addMouseListener(mlRefinementList); + refinementList.addKeyListener(new KeyListener() { + /** Handle the key pressed event for the refinement list. */ + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("Key pressed: " + e.toString()); + } + + int index = refinementList.getSelectedIndex(); + if (index == -1) { + //nothing selected, so nothing to be seen here, please move along + } else if (keyCode == KeyEvent.VK_ENTER) { + listAction(refinementList, refinementList.getSelectedIndex(), true); + } else if (keyCode == KeyEvent.VK_DOWN && index < listModel.getSize() - 1) { + listAction(refinementList, index + 1, false); + } else if (keyCode == KeyEvent.VK_UP && index > 0) { + listAction(refinementList, index - 1, false); + } else if (keyCode == KeyEvent.VK_RIGHT) { + if (refinementSubcatList.getModel().getSize() > 0) { + refinementSubcatList.requestFocusInWindow(); + refinementSubcatList.setSelectedIndex(0); + refinementList.setSelectionBackground(Color.GRAY); + } + } + } + + /** + * Handle the key typed event. + * We are not really interested in typed characters, thus empty + */ + public void keyTyped(KeyEvent e) { + //needed for KeyListener, but not used + } + + /** Handle the key released event. */ + public void keyReleased(KeyEvent e) { + //needed for KeyListener, but not used + } + }); + + refinementSubcatList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + final MouseListener mlRefinementSubcatList = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + boolean doubleClick = (e.getClickCount() == 2); + listAction(refinementSubcatList, refinementSubcatList.locationToIndex(e.getPoint()), doubleClick); + refinementList.setSelectionBackground(Color.GRAY); + } + }; + refinementSubcatList.addMouseListener(mlRefinementSubcatList); + refinementSubcatList.addKeyListener(new KeyListener() { + /** Handle the key pressed event. */ + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + if (logger.isLoggable(Level.FINER)) { + logger.finer("Key pressed: " + e.toString()); + } + if (keyCode == KeyEvent.VK_ENTER) { + listAction(refinementSubcatList, refinementSubcatList.getSelectedIndex(), true); + } else if (keyCode == KeyEvent.VK_LEFT) { + refinementList.requestFocusInWindow(); + refinementSubcatList.clearSelection(); + refinementList.setSelectionBackground(refinementSubcatList.getSelectionBackground()); + } + } + + /** + * Handle the key typed event. + * We are not really interested in typed characters, thus empty + */ + public void keyTyped(KeyEvent e) { + //needed for KeyListener, but not used + } + + /** Handle the key released event. */ + public void keyReleased(KeyEvent e) { + //needed for KeyListener, but not used + } + }); + refinementList.setToolTipText("The list of current refinement options"); + refinementList.setCellRenderer(new ToolTipCellRenderer()); + refinementSubcatList.setToolTipText("The list of current refinement options"); + refinementSubcatList.setCellRenderer(new ToolTipCellRenderer()); + + } + + /** + * @return Returns the refinementListsContainer, + * which will contain both JLists. + */ + protected JSplitPane getRefinementListsContainer() { + return refinementListsContainer; + } + + /** + * handling the event of choosing the action at index from the list. + * That is either giving commands to GF or displaying the subcat menus + * @param list The list that generated this action + * @param index the index of the selected element in list + * @param doubleClick true iff a command should be sent to GF, + * false if only a new subcat menu should be opened. + */ + private void listAction(JList list, int index, boolean doubleClick) { + if (index == -1) { + if (logger.isLoggable(Level.FINER)) logger.finer("no selection"); + } else { + Object o; + if (list == refinementList) { + o = listModel.elementAt(index); + } else { + if (whichSubcat == null) { + //this is probably the case when no fitting properties of self + //are available and only a string is displayed in the submenu. + //clicking that string should do exactly nothing. + return; + } + Vector cmdvector = (Vector)this.subcatListModelHashtable.get(this.whichSubcat); + o = (cmdvector.get(index)); + } + GFCommand command = null; + if (o instanceof GFCommand) { + command = (GFCommand)o; + } else { + return; + } + if (command instanceof SelfPropertiesCommand) { + SelfPropertiesCommand spc = (SelfPropertiesCommand)command; + Vector selfs = spc.produceSubmenu(); + if (selfs.size() == 0) { + listModel.remove(index); + refinementSubcatListModel.clear(); + refinementSubcatListModel.addElement("No properties fit here"); + return; + } else { + this.subcatListModelHashtable.put(command.getSubcat(), selfs); + listModel.remove(index); + LinkCommand newLink = new LinkCommand(PrintnameManager.SELF_SUBCAT, editor.getPrintnameManager()); + listModel.add(index, newLink); + command = newLink; + } + } + if (command instanceof LinkCommand) { //includes SelfPropertiesCommand, which is intended + this.whichSubcat = command.getSubcat(); + refinementSubcatListModel.clear(); + Vector currentCommands = (Vector)this.subcatListModelHashtable.get(this.whichSubcat); + for (Iterator it = currentCommands.iterator(); it.hasNext();) { + this.refinementSubcatListModel.addElement(it.next()); + } + } else if (doubleClick && command instanceof InputCommand) { + InputCommand ic = (InputCommand)command; + editor.executeInputCommand(ic); + + } else if (doubleClick){ + refinementSubcatListModel.clear(); + if (command instanceof RealCommand) { + editor.send("[t] " + command.getCommand(), true, ((RealCommand)command).undoSteps); + } else { + //that shouldn't be the case ... + editor.send("[t] " + command.getCommand()); + } + } else if (list == refinementList){ + refinementSubcatListModel.clear(); + } + } + } + /** + * Produces the popup menu that represents the current refinements. + * An alternative to the refinement list. + * @return s.a. + */ + protected JPopupMenu producePopup() { + if (popup2.getComponentCount() > 0) { + return popup2; + } + for (int i = 0; i < this.listModel.size(); i++) { + GFCommand gfcmd = (GFCommand)this.listModel.get(i); + if (gfcmd instanceof LinkCommand) { + LinkCommand lc = (LinkCommand)gfcmd; + Vector subcatMenu = (Vector)this.subcatListModelHashtable.get(lc.getSubcat()); + JMenu tempMenu = new JMenu(lc.getDisplayText()); + tempMenu.setToolTipText(lc.getTooltipText()); + tempMenu.setFont(popup2.getFont()); + JMenuItem tempMenuItem; + for (Iterator it = subcatMenu.iterator(); it.hasNext();) { + GFCommand subgfcmd = (GFCommand)it.next(); + tempMenuItem = menuForCommand(subgfcmd); + if (tempMenuItem != null) { + tempMenu.add(tempMenuItem); + } + } + popup2.add(tempMenu); + } else { + JMenuItem tempMenu = menuForCommand(gfcmd); + if (tempMenu != null) { + popup2.add(tempMenu); + } + } + } + return popup2; + } + + /** + * takes a GFCommand and "transforms" it in a JMenuItem. + * These JMenuItems have their own listeners that take care of + * doing what is right ... + * @param gfcmd a RealCommand or an InputCommand + * (LinkCommand is ignored and produces null as the result) + * @return either the correspondend JMenuItem or null. + */ + private JMenuItem menuForCommand(GFCommand gfcmd) { + JMenuItem tempMenu = null; + if (gfcmd instanceof RealCommand){ + tempMenu = new JMenuItem(gfcmd.getDisplayText()); + tempMenu.setFont(popup2.getFont()); + tempMenu.setActionCommand(gfcmd.getCommand()); + tempMenu.setToolTipText(gfcmd.getTooltipText()); + tempMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + JMenuItem mi = (JMenuItem)ae.getSource(); + refinementSubcatListModel.clear(); + String command = "[t] " + mi.getActionCommand(); + editor.send(command); + } + }); + } else if (gfcmd instanceof InputCommand) { + tempMenu = new JMenuItem(gfcmd.getDisplayText()); + tempMenu.setFont(popup2.getFont()); + tempMenu.setActionCommand(gfcmd.getCommand()); + tempMenu.setToolTipText(gfcmd.getTooltipText()); + tempMenu.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent ae) { + JMenuItem mi = (JMenuItem)ae.getSource(); + String command = mi.getActionCommand(); + InputCommand ic = InputCommand.forTypeName(command); + if (ic != null) { + editor.executeInputCommand(ic); + } + } + }); + + } + return tempMenu; + } + /** + * Takes the StringTuples in gfCommandVector, creates the RealCommand + * objects for them. + * Goes through this list and groups the RealCommands + * according to their subcategory tag (which starts with %) + * If there is a "(" afterwards, everything until the before last + * character in the printname will be used as the display name + * for this subcategory. If this displayname is defined a second time, + * it will get overwritten. + * Sorting is also done here. + * Adding additional special commands like InputCommand happens here too. + * @param gfCommandVector contains all RealCommands, that are available + * at the moment + * @param toAppend will be appended to every command, that is sent to GF. + * Normally, toAppend will be the empty String "". + * But it can be a chain command's second part. + * @param isAbstract If the selected menu language is abstract or not + * @param easyAttributes if true, attributes of self will be added. + * @param focusPosition The current position of the focus in the AST. + * Needed for easy access to properties of self. + * @param gfCapsule The read/write encapsulation of the GF process. + * Needed for easy access to properties of self. + */ + protected void formRefinementMenu(final Vector gfCommandVector, final String toAppend, GfAstNode currentNode, final boolean isAbstract, boolean easyAttributes, LinPosition focusPosition, GfCapsule gfCapsule) { + this.listModel.clear(); + this.refinementSubcatListModel.clear(); + this.gfcommands.clear(); + this.subcatListModelHashtable.clear(); + this.whichSubcat = null; + this.popup2.removeAll(); + Vector prelListModel = new Vector(); + /** to keep track of subcats and their names */ + HashSet processedSubcats = new HashSet(); + //at the moment, we don't know yet, which subcats are + //nearly empty + for (Iterator it = gfCommandVector.iterator(); it.hasNext();) { + final StringTuple st = (StringTuple)it.next(); + GFCommand gfcommand; + if (st instanceof ChainCommandTuple) { + ChainCommandTuple cct = (ChainCommandTuple)st; + gfcommand = new RealCommand(st.first, processedSubcats, editor.getPrintnameManager(), st.second, isAbstract, toAppend, cct.undoSteps, cct.fun, cct.subcat); + } else { + gfcommand = new RealCommand(st.first, processedSubcats, editor.getPrintnameManager(), st.second, isAbstract, toAppend); + } + if ((!editor.isGroupSubcat()) || (gfcommand.getSubcat() == null)) { + prelListModel.addElement(gfcommand); + } else { + //put stuff in the correct Vector for the refinementSubcatListModel + Vector lm; + if (subcatListModelHashtable.containsKey(gfcommand.getSubcat())) { + lm = (Vector)this.subcatListModelHashtable.get(gfcommand.getSubcat()); + } else { + lm = new Vector(); + this.subcatListModelHashtable.put(gfcommand.getSubcat(), lm); + } + lm.addElement(gfcommand); + if (gfcommand.isNewSubcat()) { + GFCommand linkCmd = new LinkCommand(gfcommand.getSubcat(), editor.getPrintnameManager()); + prelListModel.addElement(linkCmd); + } + } + } + + //so we remove empty subcats now and replace them by their RealCommand + for (int i = 0; i < prelListModel.size(); i++) { + if (prelListModel.get(i) instanceof LinkCommand) { + LinkCommand lc = (LinkCommand) prelListModel.get(i); + Vector subcatMenu = (Vector)this.subcatListModelHashtable.get(lc.getSubcat()); + if (subcatMenu.size() == 1) { + RealCommand rc = (RealCommand)subcatMenu.get(0); + prelListModel.set(i, rc); + } + } + } + + + // Some types invite special treatment, like Int and String + // which can be read from the user. + if (currentNode.isMeta()) { + InputCommand usedInputCommand = null; + if (currentNode.getType().equals("Int")) { + usedInputCommand = InputCommand.intInputCommand; + prelListModel.addElement(usedInputCommand); + } if (currentNode.getType().equals("String")) { + usedInputCommand = InputCommand.stringInputCommand; + prelListModel.addElement(usedInputCommand); + } + if (usedInputCommand != null) { + for (Iterator it = usedInputCommand.enteredValues.iterator(); it.hasNext();) { + Object o = it.next(); + //for GF it seems to make no difference, + //if we use 'g' or 'r' as the command to send + //Int and String. 'r' is already supported + //by RealCommand, so I chose that. + RealCommand rc = new RealCommand("r " + o, processedSubcats, editor.getPrintnameManager(), "r " + o, isAbstract, toAppend); + prelListModel.addElement(rc); + } + } + } + + //add the special entry for the properties of self + if (easyAttributes) { + final SelfPropertiesCommand spc = new SelfPropertiesCommand(editor.getPrintnameManager(), gfCapsule, focusPosition, isAbstract, toAppend, processedSubcats); + prelListModel.add(spc); + } + + //now sort the preliminary listmodels + if (editor.isSortRefinements()) { + Collections.sort(prelListModel); + for (Iterator it = subcatListModelHashtable.values().iterator(); it.hasNext();) { + Vector slm = (Vector)it.next(); + Collections.sort(slm); + } + } + //now fill this.listModel + for (Iterator it = prelListModel.iterator(); it.hasNext();) { + Object next = it.next(); + this.listModel.addElement(next); + } + //select the first command in the refinement menu, if available + if (this.listModel.size() > 0) { + this.refinementList.setSelectedIndex(0); + } else { + this.refinementList.setSelectedIndex(-1); + } + this.refinementList.setSelectionBackground(refinementSubcatList.getSelectionBackground()); + } + + /** + * Requests the focus for the refinement list + */ + protected void requestFocus() { + refinementList.requestFocusInWindow(); + } + + /** + * clears the list model + */ + protected void reset() { + listModel.clear(); + } + + /** + * Applies newFont to the visible elements + * @param newFont The new font, what else? + */ + protected void setFont(Font newFont) { + refinementList.setFont(newFont); + refinementSubcatList.setFont(newFont); + popup2.setFont(newFont); + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuCollector.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuCollector.java new file mode 100644 index 000000000..4d7df9dac --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuCollector.java @@ -0,0 +1,51 @@ +package de.uka.ilkd.key.ocl.gf; + +import java.util.Vector; + +/** + * Asks GF the Vector of RefinementMenu entries. + * + * This class can be reused. + * @author daniels + */ +class RefinementMenuCollector extends AbstractProber { + /** + * here the result of this run is saved + */ + Vector refinementMenuContent = null; + /** + * Standard fill-in-the-parameters constructor + * @param gfCapsule The reader/writer to GF + */ + public RefinementMenuCollector(GfCapsule gfCapsule) { + super(gfCapsule); + } + + /** + * Asks GF (the same GF as the one editor has) to execute a command + * and returns the read refinement menu that is offered then. + * Uses the readRefinementMenu method from GFEditor2 which does not + * change any global variable besides GF itself. So that is safe. + * + * Note: This method does not do undo automatically, since it is + * intended to run several times in a row, so the u should be part of + * next command. + * @param command The command that is sent to GF. Should contain a mp + * to make sure that the command at the right position in the AST + * is read + * @return a Vector of StringTuple like readRefinementMenu does it. + */ + public Vector readRefinementMenu(String command) { + send(command); + readGfedit(); + return this.refinementMenuContent; + } + + /** + * parses the refinement menu part and stores it in this.refinementMenuContent + */ + protected void readMenu() { + this.refinementMenuContent = gfCapsule.readRefinementMenu(); + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuTransformer.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuTransformer.java new file mode 100644 index 000000000..ba1263db8 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/RefinementMenuTransformer.java @@ -0,0 +1,223 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is completely static and cannot be instantiated. + * @see #transformRefinementMenu(de.uka.ilkd.key.ocl.gf.TreeAnalysisResult, java.util.Vector, de.uka.ilkd.key.ocl.gf.GfCapsule) + * @author hdaniels + */ +class RefinementMenuTransformer { + /** + * if things are added to or removed from the refinement menu + */ + protected static Logger logger = Logger.getLogger(RefinementMenuTransformer.class.getName()); + + private RefinementMenuTransformer() { + //A private constructor enforces the noninstantiability + //of "RefinementMenuTransformer". + //(See item 3 of "Effective Java".) + } + + /** + * Depending on tar, the refinement menu given in raw form in oldMenu + * is transformed. + * That includes: + * - adding properties of self + * - producing a reduced version for subtyping below a coerce + * where only Instances of subtypes are listed + * - probes, if self and result are really applicable + * - changes the delete command, when an unrefined Instance + * argument of coerce is clicked on, to first delete the + * whole coerce to avoid sticking with wrong type arguments. + * @param tar TreeAnalyser has decided what to do here. That is followed. + * @param oldMenu The original content of the refinement menu. + * Is a Vector of StringTuple + * @param gfCapsule The encapsulation of GF regarding read/write access + * @return The refinement menu in its new form + */ + protected static Vector transformRefinementMenu(TreeAnalysisResult tar, Vector oldMenu, GfCapsule gfCapsule) { + //now do fill (or partially empty) the offered commands list + final Vector usedCommandVector; + if (tar.reduceCoerce) { + //is only true if switched on globally. + //And if conditions are right. + usedCommandVector = produceReducedCoerceRefinementMenu(tar.focusPosition.position, gfCapsule); + } else { + usedCommandVector = oldMenu; + } + if (tar.deleteAlsoAbove) { + String newPos = tar.focusPosition.parentPosition(); + StringTuple newDelete = new StringTuple("mp " + newPos + " ;; d", "delete current subtree\\$also delete the encompassing coercion "); + exchangeCommand(usedCommandVector, "d", newDelete); + } + if (tar.probeSelfResult) { + probeCompletability(usedCommandVector, tar.focusPosition, gfCapsule); + } + if (tar.easyAttributes && !tar.reduceCoerce) { + addSelfProperties(usedCommandVector, tar.focusPosition, gfCapsule); + } + return usedCommandVector; + } + + /** + * Looks at the subtyping witness of the same coerce as currentPos + * and collects the possible refinements for all offered subtypes. + * It assumes that argument 0 of coerce is automatically filled in. + * + * This method is surely <b>slow</b> since a lot of calls to GF is made + * here. + * @param currentPos musst point to a child of a coerce. + * @param gfCapsule The encapsulation of GF regarding read/write access + * @return a Vector of StringTuple as readRefinementMenu does. + * This Vector can be fed into formRefinementMenu. + */ + private static Vector produceReducedCoerceRefinementMenu(String currentPos, GfCapsule gfCapsule) { + final HashSet commands = new HashSet(); + RefinementMenuCollector rmc = new RefinementMenuCollector(gfCapsule); + //move to the subtype witness argument + final String collectSubtypesCommand = "mp " + LinPosition.calculateBrethrenPosition(currentPos, 2); + Vector possibleSubtypes = rmc.readRefinementMenu(collectSubtypesCommand); + String undoString = ""; + final String undoTemplate = "u 2 ;; "; + for (Iterator it = possibleSubtypes.iterator(); it.hasNext(); ) { + StringTuple nextCommand = (StringTuple)it.next(); +// if (!nextCommand.first.trim().startsWith("r")) { +// //no ac, d, rc or whatever wanted here. Only refine. +// continue; +// } + final String collectRefinementsCommand = undoString + nextCommand.first + " ;; mp " + currentPos; + undoString = undoTemplate; //for all following runs we want an undo before it + Vector nextRefinements = rmc.readRefinementMenu(collectRefinementsCommand); + commands.addAll(nextRefinements); + } + final String cleanupCommand = "u 3"; //undo the last command and also the first mp + rmc.readRefinementMenu(cleanupCommand); //no harm done here, collector won't get reused + Vector result = new Vector(commands); + return result; + } + + /** + * checks if result and self make sense in the current context. + * if not, they are removed from oldMenu + * @param oldMenu A Vector of StringTuple that represents the + * commands for the refinement menu + * @param focusPos The current position in the AST + * @param gfCapsule The encapsulation of GF regarding read/write access + */ + private static void probeCompletability(Vector oldMenu, LinPosition focusPos, GfCapsule gfCapsule) { + /** + * self and result both take two arguments. + * The first is the type, which is fixed + * if the second argument is refineable. + * Important is the second. + * This only is refineable for the real type of self/result + */ + if (focusPos == null) { + //sadly, we can't do much + return; + } + final String childPos = focusPos.childPosition(1); + final SelfResultProber cp = new SelfResultProber(gfCapsule); + for (int i = 0; i < oldMenu.size(); i++) { + String cmd = ((StringTuple)oldMenu.elementAt(i)).first; + if ((cmd != null) && ((cmd.indexOf("r core.self") > -1) || (cmd.indexOf("r core.result") > -1))) { + //the first mp is necessary for the second of self/result. + //without, GF will jump to a stupid position + String newCommand = "mp " + focusPos.position + " ;; " + cmd + " ;; mp " + childPos; + if (!cp.isAutoCompletable(newCommand, 3)) { + oldMenu.remove(i); + i -=1; + } + } + } + } + + /** + * Probes for the properties of self, that could be filled in at + * the current focus position. + * If it finds any, these are added to oldMenu + * This method will add all offered commands to the refinement menu, + * not only for suiting subtypes due to speed reasons. + * @param oldMenu A Vector of StringTuple. The menu with the commands + * and show texts as given by GF. Gets modified. + * @param focusPos The position of the GF focus in the AST + * @param gfCapsule The encapsulation of GF regarding read/write access + */ + private static void addSelfProperties(Vector oldMenu, LinPosition focusPos, GfCapsule gfCapsule) { + //solve in between to avoid some typing errors by closing some type arguments + final String probeCommand = "r core.implPropCall ;; mp " + focusPos.childPosition(2) + " ;; r core.self ;; solve ;; mp " + focusPos.childPosition(3); + final String deleteAppendix = " ;; d"; + final RefinementMenuCollector rmc = new RefinementMenuCollector(gfCapsule); + Vector futureRefinements = rmc.readRefinementMenu(probeCommand + deleteAppendix); + final int undos = 5; + final boolean singleRefinement; + if (futureRefinements.size() == 1) { + singleRefinement = true; + } else { + singleRefinement = false; + } + final String cleanupCommand = "u " + undos; + rmc.readRefinementMenu(cleanupCommand); //no harm done here + for (Iterator it = futureRefinements.iterator(); it.hasNext();) { + StringTuple st = (StringTuple)it.next(); + if (st.first.startsWith("r")) { //is a refinement, no ac or d + String newCommand; + //add the command that came before + final int cmdUndos; + if (singleRefinement) { + //that is an exceptional case, but might happen. + //Here we don't have to refine the final property + //at all, since GF does that automatically + newCommand = probeCommand + " ;; c solve"; + cmdUndos = 5; + } else { + //here the 'd' is not needed, since we know, + //that nothing is refined automatically + newCommand = probeCommand + " ;; " + st.first + " ;; c solve"; + cmdUndos = 6; + } + // now extract the fun of the property + String fun = st.first.substring(1).trim(); + ChainCommandTuple cct = new ChainCommandTuple(newCommand, st.second, fun, PrintnameManager.SELF_SUBCAT, cmdUndos); + if (logger.isLoggable(Level.FINER)) { + logger.finer("added " + cct); + } + oldMenu.add(cct); + } + } + } + + /** + * Goes through oldMenu and if it finds a command in there, where + * first.equals(oldCommand), this command is replaced by newCommand. + * oldMenu's content thus gets modified. + * @param oldMenu a Vector of StringTuple + * @param oldCommand a GF command string (what is sent, not the show text) + * @param newCommand a StringTuple representing what could be a pait from GF + */ + private static void exchangeCommand(Vector oldMenu, String oldCommand, StringTuple newCommand) { + for (int i = 0; i < oldMenu.size(); i++) { + StringTuple next = (StringTuple)oldMenu.get(i); + if (next.first.equals(oldCommand)) { + oldMenu.remove(i); + oldMenu.insertElementAt(newCommand, i); + } + } + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfPropertiesCommand.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfPropertiesCommand.java new file mode 100644 index 000000000..60ee86c64 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfPropertiesCommand.java @@ -0,0 +1,175 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this applicationpackage de.uka.ilkd.key.ocl.gf; + +package de.uka.ilkd.key.ocl.gf; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is an unclean hack. + * The whole refinement menu architecture expected, that everything is probed, + * when the refinement menu is getting created. + * But for getting only subtype correct properties of self needs a number of + * calls to GF, which could be deferred to not make things slower than they + * already are. + * This deferred probing is done in this class. + * @author daniels + * + */ +class SelfPropertiesCommand extends LinkCommand { + private final static Logger logger = Logger.getLogger(SelfPropertiesCommand.class.getName()); + private final GfCapsule gfCapsule; + private final LinPosition focusPos; + private final String toAppend; + private final boolean isAbstract; + private final HashSet processedSubcats; + private final PrintnameManager printnameManager; + + /** + * A simple setter constructor, no calculation done here. + * @param manager The printname manager, that knows, how the properties + * of self should be listed in the refinement menu + * @param gfCapsule The reader/writer abstraction from GF + * @param focusPos The position of the GF focus + * @param isAbstract if Abstract is the current menu language + * @param toAppend If something should be appended to the command + * @param processedSubcats Here, the subcat for self is put into + */ + public SelfPropertiesCommand(final PrintnameManager manager, GfCapsule gfCapsule, LinPosition focusPos, boolean isAbstract, String toAppend, HashSet processedSubcats) { + super(PrintnameManager.SELF_SUBCAT, manager); + this.gfCapsule = gfCapsule; + this.printnameManager = manager; + this.focusPos = focusPos; + this.processedSubcats = processedSubcats; + this.toAppend = toAppend; + this.isAbstract = isAbstract; + } + + /** + * @return a Vector of RealCommand containing the suitable properties + * of self at the current focus position. + * Subtyping is taken into account, so only properties with a subtype + * of the supertype of the coerce above (at other places this method + * is not applicable) show up in this menu. + * The method used is similiar to the one for Instances below a coerce. + */ + Vector produceSubmenu() { + logger.fine("SelfPropertiesCommand asked to produce a menu"); + //HashSet to prevent duplicates + final HashSet commands = new HashSet(); + RefinementMenuCollector rmc = new RefinementMenuCollector(gfCapsule); + //move to the subtype witness argument + final String collectSubtypesCommand = "mp " + LinPosition.calculateBrethrenPosition(focusPos.position, 2); + final Vector possibleSubtypes = rmc.readRefinementMenu(collectSubtypesCommand); + String undoString = ""; + int undos = 0; + //for the case, that there is only one possible refinement at all + //which gets automatically filled in + final StringBuffer singleReplacement = new StringBuffer(); + //loop through the offered Subtype refinements + for (Iterator it = possibleSubtypes.iterator(); it.hasNext(); ) { + StringTuple nextCommand = (StringTuple)it.next(); + if (!nextCommand.first.trim().startsWith("r")) { + //no ac, d, rc or whatever wanted here. Only refine. + continue; + } + final String commandPrefix = undoString + nextCommand.first + " ;; mp " + focusPos.position + " ;; "; + logger.finer("commandPrefix: " + commandPrefix); + Vector futureRefinements = new Vector(); + undos = addSelfProperties(futureRefinements, commandPrefix, singleReplacement); + undos += 2; // to undo commandPrefix + undoString = "u " + undos + " ;; "; //for all following runs we want an undo before it +// Vector nextRefinements = rmc.readRefinementMenu(collectRefinementsCommand); + commands.addAll(futureRefinements); + } + final String cleanupCommand = "u " + (undos + 1); //undo the last command and also the first mp + rmc.readRefinementMenu(cleanupCommand); //no harm done here, collector won't get reused + Vector result = new Vector(); + for (Iterator it = commands.iterator(); it.hasNext();) { + StringTuple st = (StringTuple)it.next(); + if ((commands.size() == 1) && (st instanceof ChainCommandTuple)) { + //the case when only one property is available at all. + //Then this will automatically be selected + //To compensate for that, singleRefinement is used. + //This will be just one refinement, otherwise, we + //wouldn't be in this branch. + //This refinement does not contain the actual r + //command and therefore needs one undo step less + ChainCommandTuple cct = (ChainCommandTuple)st; + st = new ChainCommandTuple(singleReplacement.toString(), cct.second, cct.fun, cct.subcat, cct.undoSteps - 1); + } + GFCommand gfcommand; + if (st instanceof ChainCommandTuple) { + ChainCommandTuple cct = (ChainCommandTuple)st; + gfcommand = new RealCommand(st.first, processedSubcats, printnameManager, st.second, isAbstract, toAppend, cct.undoSteps, cct.fun, cct.subcat); + } else { + gfcommand = new RealCommand(st.first, processedSubcats, printnameManager, st.second, isAbstract, toAppend); + } + result.add(gfcommand); + } + Collections.sort(result); + return result; + } + + /** + * Probes for the properties of self, that could be filled in at + * the current focus position. + * If it finds any, these are added to result. + * @param result The Vector, that will get filled with the collected + * chain commands + * @param commandPrefix The prefix, that is to be prepended to the + * probing command. Used for refining with a Subtype witness and a + * mp to the Instance position, where this method expects to start. + * @param singleReplacement This is a hack for cases, when GF refines + * an refinement automatically. If that happens only for one subtype, + * then GF would fill that in automatically even when the supertype is + * open. Therefore, it must be omitted in the actual command. + * But this situation can only be checked after all subtypes have been + * probed. + * @return the number of undo steps needed to undo the probing command + * (without prefix, that is handled by the caller) + */ + private int addSelfProperties(final Vector result, final String commandPrefix, final StringBuffer singleReplacement) { + //solve in between to avoid some typing errors by closing some type arguments + final String probeCommand = "r core.implPropCall ;; mp " + focusPos.childPosition(2) + " ;; r core.self ;; solve ;; mp " + focusPos.childPosition(3); + final String deleteAppendix = " ;; d"; + final RefinementMenuCollector rmc = new RefinementMenuCollector(gfCapsule); + final String actualProbeCommand = commandPrefix + probeCommand + deleteAppendix; + logger.finer("&&& actual probe command:: " + actualProbeCommand); + Vector futureRefinements = rmc.readRefinementMenu(actualProbeCommand); + final int undos = 5; + for (Iterator it = futureRefinements.iterator(); it.hasNext();) { + StringTuple st = (StringTuple)it.next(); + if (st.first.startsWith("r")) { //is a refinement, no ac or d + String newCommand; + //add the command that came before + final int cmdUndos; + if (futureRefinements.size() == 1) { + //the case, when only one property is defined in the grammar: + singleReplacement.append(probeCommand + " ;; c solve"); + } + newCommand = probeCommand + " ;; " + st.first + " ;; c solve"; + cmdUndos = 6; + // now extract the fun of the property + String fun = st.first.substring(1).trim(); + ChainCommandTuple cct = new ChainCommandTuple(newCommand, st.second, fun, PrintnameManager.SELF_SUBCAT, cmdUndos); + if (logger.isLoggable(Level.FINE)) { + logger.finer("added " + cct); + } + result.add(cct); + } + } + return undos; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfResultProber.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfResultProber.java new file mode 100644 index 000000000..664a9d918 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SelfResultProber.java @@ -0,0 +1,84 @@ +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.*; + +/** + * asks GF if the given commands leads to a situation, where + * something could be filled in automatically. + * This class is meant for self and result. + * @author daniels + */ +class SelfResultProber extends AbstractProber { + /** + * This field is true in the beginning of each run, and + * set to false, if the focus position when checking is found + * to be open. + */ + protected boolean autocompleted = true; + + protected static Logger nogger = Logger.getLogger(SelfResultProber.class.getName()); + /** + * A constructor which sets some fields + * @param gfCapsule The encapsulation of the running GF process + */ + public SelfResultProber(GfCapsule gfCapsule) { + super(gfCapsule); + } + + /** + * asks GF if the given commands leads to a situation, where + * something could be filled in automatically. + * This function is meant for self and result. + * IMPORTANT: Must be called <b>after</b> </gfedit> + * when no other method reads sth. from GF. + * It uses the same GF as everything else, since it tests if + * sth. is possible there. + * @param gfCommand the command to be tested. + * One has to chain a mp command to make GF go to the right place afterwards + * @param chainCount The number of chained commands in gfCommand. + * So many undos are done to clean up afterwards. + * @return true iff sth. could be filled in automatically + */ + public boolean isAutoCompletable(String gfCommand, int chainCount) { + this.autocompleted = true; + send(gfCommand); + readGfedit(); + final boolean result = this.autocompleted; + this.autocompleted = true; + //clean up and undo + send("u " + chainCount); + readAndIgnore(); + if (nogger.isLoggable(Level.FINE)) { + nogger.fine(result + " is the result for: '" + gfCommand +"'"); + } + return result; + } + + /** + * Reads the tree child of the XML from beginning to end. + * Sets autocompleted to false, if the focus position is open. + */ + protected void readTree() { + String treeString = gfCapsule.readTree(); + String[] treeArray = treeString.split("\\n"); + for (int i = 0; i < treeArray.length; i++) { + String result = treeArray[i].trim(); + if (result.startsWith("*")) { + result = result.substring(1).trim(); + if (result.startsWith("?")) { + //the normal case, focus position open + this.autocompleted = false; + } else if ((i >= 6) //that we could be at the instance argument at all + && (treeArray[i - 6].indexOf("coerce") > -1) //we are below a coerce + && (treeArray[i - 3].trim().startsWith("?")) //the Subtype argument is not filled in + // The super argument cannot be OclAnyC or even unrefined, because then this + // method wouldn't have been called. Thus, the Subtype argument is unique. + ){ + //we are below a coerce, but self would have a non-suiting subtype + this.autocompleted = false; + } + } + + } + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/StringTuple.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/StringTuple.java new file mode 100644 index 000000000..67f4326e1 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/StringTuple.java @@ -0,0 +1,54 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +/** + * Small tuple class for two Strings. + * The main use is grouping command and showname for GF commands before + * they are processed. + * This class is mutable. + * Equality is bound to the first argument. + * @author daniels + */ +class StringTuple { + String first; + String second; + + /** + * Just sets both values. + * @param f Well, the first String + * @param s Well, the second String + * (if it is used at all) + */ + public StringTuple(String f, String s) { + this.first = f; + this.second = s; + } + + public int hashCode() { + return this.first.hashCode(); + } + public boolean equals(Object o) { + if (o instanceof StringTuple) { + return this.first.equals(((StringTuple)o).first); + } else { + return false; + } + } + public String toString() { + return this.first; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SubtypingProber.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SubtypingProber.java new file mode 100644 index 000000000..9ff3c4f92 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/SubtypingProber.java @@ -0,0 +1,107 @@ +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.Logger; +import java.util.*; + +/** + * This class goes through the tree and tries to close all open Subtype lines. + * + * Makes heavy use of instance fields instead of parameters and return values. + * I justify that with the rather small size of this class. + * Because of this this class has to be reinitialized for each run. + * @author daniels + */ +class SubtypingProber extends RefinementMenuCollector { + private static Logger nogger = Logger.getLogger(SubtypingProber.class.getName()); + /** + * how many undos are needed to clean up behind this probing + */ + protected int undoSteps = 0; + /** + * the GF AST line by line + */ + protected String[] treeArray = new String[0]; + /** + * the pointer to the line, that has been read last + */ + protected int currentLine; + + /** + * Standard fill-in-the-parameters constructor + * @param gfCapsule The encapsulation of GF + */ + public SubtypingProber(GfCapsule gfCapsule) { + super(gfCapsule); + this.currentLine = 0; + } + + /** + * stores the read GF AST in treeArray + */ + protected void readTree() { + String treeString = gfCapsule.readTree(); + this.treeArray = treeString.split("\\n"); + } + + /** + * looks at the refinement menu for node number lineNumber in the AST + * and if there is only one refinement command offered, does + * execute this. + * @param lineNumber + */ + protected void checkLine(int lineNumber) { + String command = "mp [] ;; > " + lineNumber; + this.undoSteps += 2; + send(command); + readGfedit(); + Vector commands = new Vector(); + for (Iterator it = this.refinementMenuContent.iterator(); it.hasNext(); ) { + StringTuple next = (StringTuple)it.next(); + if (next.first.startsWith("r")) { + commands.add(next); + } + } + if (commands.size() == 0) { + //nothing can be done + nogger.fine("no refinement for '" + this.treeArray[lineNumber] + "'"); + } else if (commands.size() == 1) { + StringTuple nextCommand = (StringTuple)commands.lastElement(); + nogger.fine("one refinement for '" + this.treeArray[lineNumber] + "'"); + send(nextCommand.first); + this.undoSteps += 1; + // now new things are in the state, + // but since we assume that nothing above lineNumber changes, + // that is wanted. + readGfedit(); + } else { + nogger.fine(commands.size() + " refinements for '" + this.treeArray[lineNumber] + "'"); + } + } + + /** + * Asks GF for the AST and tries to hunt down all unrefined + * Subtype witnesses. + * @return the number of undo steps this run needed + */ + public int checkSubtyping() { + //solve to try to eliminate many unrefined places + send("c solve ;; mp []"); //focus at the top where '*' does not disturb + readGfedit(); + this.undoSteps += 2; + for (int i = 3; i < this.treeArray.length; i++) { + //the condition gets rechecked every run, and length will only grow + //We start at 3 since the witness is the third argument of coerce, + //before nothing can happen + //(sth. like "n core.Subtype" does not count! + //(starting with new category Subtype) ) + if (this.treeArray[i].indexOf(": Subtype") > -1) { + if (!this.treeArray[i - 2].startsWith("?") //both Class arguments refined + && !this.treeArray[i - 1].startsWith("?")) { + checkLine(i); + } + } + } + nogger.fine(this.undoSteps + " individual commands sent"); + return this.undoSteps; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ToolTipCellRenderer.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ToolTipCellRenderer.java new file mode 100644 index 000000000..b05aeeb87 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/ToolTipCellRenderer.java @@ -0,0 +1,71 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.awt.Component; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +/** + * A cell renderer, that returns JLables, that put everything after the first + * '$' character into their tooltip + * @author daniels + */ +public class ToolTipCellRenderer extends JLabel implements ListCellRenderer { + + /** + * Returns a JLabel with a tooltip, which is given by the GFCommand + * @param list Well, the list this cell belongs to + * @param value value to display + * @param index cell index + * @param isSelected is the cell selected + * @param cellHasFocus the list and the cell have the focus + * @return a suiting JLabel + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } + else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + setEnabled(list.isEnabled()); + setFont(list.getFont()); + setOpaque(true); + + + if (value == null) { + setText("Null-Value!!! Something went terribly wrong here!"); + } else if (value instanceof GFCommand){ + GFCommand gfc = (GFCommand)value; + String disText = gfc.getDisplayText(); + if (gfc instanceof LinkCommand) { + //italic font could be an alternative + disText = "-> " + disText; + } + setText(disText); + setToolTipText(gfc.getTooltipText()); + } else { + setText(value.toString()); + setToolTipText("Strange thing of class '" + value.getClass() + "'"); + } + return this; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalyser.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalyser.java new file mode 100644 index 000000000..5edf2a16b --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalyser.java @@ -0,0 +1,387 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.tree.DefaultMutableTreeNode; + +/** + * Goes through the AST and: + * Labels node according to the following: + * hidden, if they are a coerce without a constraint + * colored, if they are a coerce with a constraint + * Saves a reference to the currently selected node + * Finds out + * if attributes of self should be given an easy access, + * if the refinement menu below a coerce should be reduces, + * if it should be probed, if self and result are superfluous + * in the refinement menu. + * if a coerce should be introduced automatically. + * Takes a tree and hides the nodes labelled as hidden in another stage. + * @author hdaniels + */ +class TreeAnalyser { + /** + * debug stuff for the tree + */ + private static Logger treeLogger = Logger.getLogger(TreeAnalyser.class.getName()); + /** + * dealing with coerce, when it is inserted and so on + */ + private static Logger coerceLogger = Logger.getLogger(TreeAnalyser.class.getName() + ".coerce"); + + /** + * if coerce should get hidden, if all their arguments are refined + */ + private boolean hideCoerce; + /** + * if coerce should always be hidden, + */ + private boolean hideCoerceAggressive; + /** + * if the refinement menu should get condensed at all + */ + private boolean coerceReduceRM; + /** + * if coerce should get introduced automatically at all + */ + private boolean autoCoerce; + /** + * if result and self should be shown always + */ + private boolean showSelfResult; + /** + * if properties of self should be probed for + */ + private boolean easyAttributes; + /** + * If coerces whith both Class arguments + */ + private boolean highlightSubtypingErrors; + + /** + * @param autoCoerce if coerce should get introduced automatically at all + * @param coerceReduceRM if the refinement menu should get condensed at all + * @param easyAttributes if properties of self should be probed for + * @param hideCoerce if coerce should get hidden, if all their arguments are refined + * @param hideCoerceAggressive if coerce should always be hidden, + * unless there is a GF constraint + * @param highlightSubtypingErrors If coerces whith both Class arguments + * refined, but not with the Subtype argument should get marked + * @param showSelfResult if result and self should be shown always + */ + public TreeAnalyser(boolean autoCoerce, boolean coerceReduceRM, boolean easyAttributes, boolean hideCoerce, boolean hideCoerceAggressive, boolean highlightSubtypingErrors, boolean showSelfResult) { + this.autoCoerce = autoCoerce; + this.coerceReduceRM = coerceReduceRM; + this.easyAttributes = easyAttributes; + this.hideCoerce = hideCoerce; + this.hideCoerceAggressive = hideCoerceAggressive; + this.highlightSubtypingErrors = highlightSubtypingErrors; + this.showSelfResult = showSelfResult; + } + + + /** + * Takes the rootNode of the AST and does some small analysis on it: + * Check for missing Subtype witnesses, + * check if the Instance menu of a Coerce can be reduced + * @param topNode The root or top node of the AST + * @return an object that contains the result of this analysis. + * Currently this applies only to the selected node. + * @see TreeAnalysisResult + */ + TreeAnalysisResult analyseTree(DefaultMutableTreeNode topNode) { + //just the initial values + String resultCommand = null; + int resultUndoSteps = -1; + boolean resultReduceCoerce = false; + boolean resultProbeSelfResult = false; + boolean resultDeleteAlsoAbove = false; + boolean resultEasyAttributes = false; + TreeAnalysisResult tar = new TreeAnalysisResult(resultCommand, resultUndoSteps, resultReduceCoerce, resultProbeSelfResult, resultDeleteAlsoAbove, resultEasyAttributes, null, null); + + //doing it depth first, because we have to know the subtypingStatus + //of the children of coerce before we analyze coerce itself + for (Enumeration e = topNode.depthFirstEnumeration() ; e.hasMoreElements() ;) { + DefaultMutableTreeNode currNode = (DefaultMutableTreeNode)e.nextElement(); + analyseTreeNode(currNode, tar); + } + AstNodeData and = (AstNodeData)tar.selectedNode.getUserObject(); + if ((and.showInstead != -1) && (tar.command == null)) { + //if the current node is hidden, move up in the tree, + //until a visible node is found + DefaultMutableTreeNode tn = (DefaultMutableTreeNode)tar.selectedNode.getParent(); + AstNodeData dand = null; + while (tn != null) { + dand = (AstNodeData)tn.getUserObject(); + if (dand.showInstead == -1) { + //found a visible node + break; + } + tn = (DefaultMutableTreeNode)tn.getParent(); + } + if (dand != null) { + tar.command = "[tr] mp " + dand.position; + tar.undoSteps = 1; + } //otherwise give up, can only occur, if coerce is the top node. + //And for that, one would have to do a "n Instance" first, + //which GF does not even offer. + } + return tar; + } + + /** + * Takes the rootNode of the AST and does some small analysis on it: + * Check for missing Subtype witnesses, + * check if the Instance menu of a Coerce can be reduced + * @param nodeToCheck The node that is to be analysed + * @param tar The result, that gets modified + * @see TreeAnalysisResult + */ + private void analyseTreeNode(DefaultMutableTreeNode nodeToCheck, TreeAnalysisResult tar) { + AstNodeData and = (AstNodeData)nodeToCheck.getUserObject(); + DefaultMutableTreeNode parent = (DefaultMutableTreeNode)nodeToCheck.getParent(); + Printname parentPrintname = null; + if ((parent != null) && (parent.getUserObject() != null) && (parent.getUserObject() instanceof AstNodeData)) { + AstNodeData parentAnd = (AstNodeData)parent.getUserObject(); + parentPrintname = parentAnd.getPrintname(); + } + + if (and.selected) { + tar.selectedNode = nodeToCheck; + tar.currentNode = and.node; + //set the focusPosition to a preliminary value + tar.focusPosition = new LinPosition(and.position, true); + //rather check too much for null + if (this.autoCoerce + && (and.node != null) + && and.node.isMeta() + && (parent != null) + && (parent.getUserObject() != null) + && (and.node.getType() != null) + && (and.node.getType().startsWith("Instance"))) { + //check, if a coerce is needed + GfAstNode parentNode = ((AstNodeData)parent.getUserObject()).node; + if (parentPrintname.getParamAutoCoerce(parent.getIndex(nodeToCheck))) { + coerceLogger.fine("Coerceable fun found: " + and.node + " + " + parentNode); + //refine with coerce. Do not allow another GF run, so [r] + tar.command = "[tr] r core.coerce ;; mp " + LinPosition.calculateChildPosition(and.position, 3); + tar.undoSteps = 2; //move there is also sth. to be undone + } else if ((parentNode.getFun().indexOf("coerce") > -1) + //to avoid getting stuck below a coerce with wrong type arguments + //the coerce above is deleted and rerefined. + + //coerce below a coerce is never done automatically, so the else if is justified, + //meaning, that introduced a coerce, we do not have to delete it right a away + && parent.getParent() != null + && (parent.getParent() instanceof DefaultMutableTreeNode)) { + DefaultMutableTreeNode grandParent = (DefaultMutableTreeNode)parent.getParent(); + if (grandParent != null) { + AstNodeData grandParentAnd = (AstNodeData)grandParent.getUserObject(); + Printname grandParentPrintname = grandParentAnd.getPrintname(); + + if (grandParentPrintname.getParamAutoCoerce(grandParent.getIndex(parent))) { + coerceLogger.fine("Auto-Coerce to be un- and redone: " + + and.node + " + " + parentNode + + " -- " + tar.focusPosition.position); + tar.command = "[tr] mp " + tar.focusPosition.parentPosition() + + " ;; d ;; mp " + tar.focusPosition.parentPosition() + + " ;; r core.coerce ;; mp " + tar.focusPosition.position; + tar.undoSteps = 6; + } + } + } + } + + if (coerceReduceRM + && (and.node != null) + && (and.node.getType() != null) + && (parent != null) + && (parent.getUserObject() != null) + && (((AstNodeData)parent.getUserObject()).getPrintname() != null) + && (((AstNodeData)parent.getUserObject()).getPrintname().fun.endsWith("coerce")) + && (and.node.getType().startsWith("Instance")) //if coerce, than we are the Instance argument + && (((DefaultMutableTreeNode)(parent.getChildAt(2))).getUserObject() != null) + && (parent.getChildAt(2) != null) + && ((AstNodeData)((DefaultMutableTreeNode)(parent.getChildAt(2))).getUserObject()).node.isMeta()) { + AstNodeData superTypeAnd = ((AstNodeData)((DefaultMutableTreeNode)(parent.getChildAt(1))).getUserObject()); + if (!superTypeAnd.node.isMeta() && (superTypeAnd.node.getFun().indexOf("OclAnyC") == -1)) { + //in these cases, everything goes. No sense in dozends of expensive GF runs then. + tar.reduceCoerce = true; + } + coerceLogger.fine("candidate for coerce reduction found: " + and.node + " + " + parent); + } + + if (showSelfResult + && (and.node != null) + && (and.node.getType() != null) + && (and.node.getType().startsWith("Instance")) + && (tar.reduceCoerce //not everything is allowed here + // if not below a coerce (covered above) and no constraints + || (and.node.getType().indexOf("{") == -1)) + ){ + //if there are constraints present, there is no point in probing, since + //then either no or every instance is offered. + //We do not have to probe then. + tar.probeSelfResult = true; + } + + if (easyAttributes + && (and.node != null) + && (and.node.getType() != null) + && (and.node.isMeta()) + && (and.node.getType().startsWith("Instance")) + ) { + //not much to check here + tar.easyAttributes = true; + } + } + + //check for subtyping errors + if (highlightSubtypingErrors + && (and.node != null) + && (and.node.getType() != null) + && (parent != null) + && (and.node.isMeta()) //otherwise GF would complain + && (and.node.getType().startsWith("Subtype")) //if coerce, than we are the Subtype argument + ) { + AstNodeData subtypeAnd = (AstNodeData)(((DefaultMutableTreeNode)(parent.getChildAt(0))).getUserObject()); + AstNodeData supertypeAnd = (AstNodeData)(((DefaultMutableTreeNode)(parent.getChildAt(1))).getUserObject()); + if ((subtypeAnd != null) && (supertypeAnd != null)) { + if (!supertypeAnd.node.isMeta() && !subtypeAnd.node.isMeta()) { + //if one of them is meta, then the situation is not fixed yet, + //so don't complain. + and.subtypingStatus = false; + } + } + } + //hide coerce if possible + //if coere is completely filled in (all children not meta), + //it will be replaced by child 3. + if (hideCoerce + && (and.node != null) + && (and.node.getType() != null) + && (and.node.getFun().endsWith("coerce")) + ) { + /** + * if true, then something is unfinished or constrained. + * So don't hide that node. + */ + boolean metaChild = false; + //check if constraints hold for this node + if ((and.constraint != null) && (and.constraint.length() > 0)) { + //some constraint holds here. + //We must not shroud a possible source for that. + metaChild = true; + } + //This search will only be run once for each coerce: + for (int i = 0; i < 3 && !metaChild; i++) { + //This is for the more complicated collection + //subtyping witnesses. + //we do a depthFirst search to find meta nodes. + //If they exist, we know that we shouldn't hide this node. + for (Enumeration e = ((DefaultMutableTreeNode)nodeToCheck.getChildAt(i)).depthFirstEnumeration() ; e.hasMoreElements() ;) { + DefaultMutableTreeNode currNode = (DefaultMutableTreeNode)e.nextElement(); + AstNodeData dand = (AstNodeData)currNode.getUserObject(); + if (!dand.subtypingStatus + //hideCoerceAggressive means that just incomplete type arguments are no reason to not hide the node + //only subtypingStatus is one because then surely there is an error + || (!hideCoerceAggressive && dand.node.isMeta())) { + metaChild = true; + break; //no need to go further + } + } + if (metaChild) { + break; + } + } + //For the Instance argument, we do not have do to a deep search + AstNodeData childAnd = (AstNodeData)(((DefaultMutableTreeNode)(nodeToCheck.getChildAt(3))).getUserObject()); + if (!hideCoerceAggressive && childAnd.node.isMeta()) { + //see reasons for hideCoerceAggressive above + metaChild = true; + } + + if (!metaChild) { + and.showInstead = 3; + //now label the type nodes as hidden + for (int i = 0; i < 3 && !metaChild; i++) { + //This is for the more complicated collection + //subtyping witnesses. + for (Enumeration e = ((DefaultMutableTreeNode)nodeToCheck.getChildAt(i)).depthFirstEnumeration() ; e.hasMoreElements() ;) { + DefaultMutableTreeNode currNode = (DefaultMutableTreeNode)e.nextElement(); + AstNodeData dand = (AstNodeData)currNode.getUserObject(); + dand.showInstead = -2; // tag for hidden without replacement + } + } + + } + + //if we are at a coerce above the selected Instance node, + //we want to mark that, so that the d command can be modified + if ((and.node != null) + && (and.node.getFun().endsWith("coerce")) + && (and.showInstead > -1) //only hidden coerce + && (((AstNodeData)((DefaultMutableTreeNode)nodeToCheck.getChildAt(3)).getUserObject()).selected) + ) { + tar.deleteAlsoAbove = true; + } + } + } + /** + * Removes nodes from the tree that has topNode as its root. + * Affected are nodes in which the field showInstead in their + * AstNodeData is greater than -1 + * @param topNode The root of the tree from which nodes should + * be removed. + * @return The root of the transformed tree. + * This might not be topNode, since that node might as well be + * removed. + */ + protected static DefaultMutableTreeNode transformTree(DefaultMutableTreeNode topNode) { + DefaultMutableTreeNode nextNode = topNode; + while (nextNode != null) { + AstNodeData and = (AstNodeData)nextNode.getUserObject(); + if (and.showInstead > -1) { + DefaultMutableTreeNode parent = (DefaultMutableTreeNode)nextNode.getParent(); + if (parent == null) { + topNode = (DefaultMutableTreeNode)nextNode.getChildAt(and.showInstead); + if (treeLogger.isLoggable(Level.FINE)) { + //yeah, I know, variable naming is messed up here because of the assignment above + treeLogger.fine("hiding topNode ###" + nextNode + "###, showing instead ###" + topNode + "###"); + } + nextNode = topNode; + } else { + final int index = parent.getIndex(nextNode); + parent.remove(index); + DefaultMutableTreeNode instead = (DefaultMutableTreeNode)nextNode.getChildAt(and.showInstead); + parent.insert(instead, index); + if (treeLogger.isLoggable(Level.FINE)) { + treeLogger.fine("hiding node ###" + nextNode + "###, showing instead ###" + instead + "###"); + } + nextNode = instead; + } + } else { + nextNode = nextNode.getNextNode(); + } + } + return topNode; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalysisResult.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalysisResult.java new file mode 100644 index 000000000..1bcae3421 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TreeAnalysisResult.java @@ -0,0 +1,92 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import javax.swing.tree.DefaultMutableTreeNode; + +/** + * A class to store the result of the tree analysis done in formTree + * @author daniels + */ +class TreeAnalysisResult { + /** + * The command, that is to be executed next automatically + */ + String command; + /** + * the number of undo steps needed to undo command + */ + int undoSteps; + /** + * reduceCoerce Whether the mechanism to produce a reduced + * refinement menu for coerce's 4th argument should kick in or not. + */ + boolean reduceCoerce; + /** + * If the editor should ask GF if self an result are applicable here or not + */ + boolean probeSelfResult; + /** + * If we at the the Instance Argument of a hidden + * coerce, we mark that (to change the d command) + */ + boolean deleteAlsoAbove; + /** + * if the attributes of self should be added to the refinement menu. + */ + boolean easyAttributes; + DefaultMutableTreeNode selectedNode = null; + /** + * The currently selected node + */ + GfAstNode currentNode; + /** + * Where the cursor in GF is. + * Correct is not yet known and thus always true. + */ + LinPosition focusPosition; + + /** + * Just sets both values. + * @param command The command, that is to be executed next automatically + * @param undoSteps the number of undo steps needed to undo command + * @param reduceCoerce Whether the mechanism to produce a reduced + * refinement menu for coerce's 4th argument should kick in or not. + * @param probeSelfResult If the editor should ask GF if self an result + * are applicable here or not + * @param deleteAlsoAbove If we at the the Instance Argument of a hidden + * coerce, we mark that (to change the d command) + * @param easyAttributes if the attributes of self should be added to the + * refinement menu. + * @param currentNode The currently selected node + * @param focusPosition Where the cursor in GF is. + * Correct is not yet known and thus always true. + */ + public TreeAnalysisResult(String command, int undoSteps, boolean reduceCoerce, boolean probeSelfResult, boolean deleteAlsoAbove, boolean easyAttributes, GfAstNode currentNode, LinPosition focusPosition) { + this.command = command; + this.undoSteps = undoSteps; + this.reduceCoerce = reduceCoerce; + this.probeSelfResult = probeSelfResult; + this.deleteAlsoAbove = deleteAlsoAbove; + this.currentNode = currentNode; + this.easyAttributes = easyAttributes; + this.focusPosition = focusPosition; + } + + public String toString() { + return this.command + "|" + this.reduceCoerce + "|" + this.undoSteps + "|" + this.probeSelfResult; + } +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TypesLoader.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TypesLoader.java new file mode 100644 index 000000000..5cf5c4bd5 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/TypesLoader.java @@ -0,0 +1,120 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.logging.*; + +/** + * @author daniels + * If the entries of the refinement menu should have to appear there with + * type information appended to them, then the printnames have to get this + * knowledge at the time of their creation. + * When the entries are displayed, the display text line of GF is *not* looked + * at. And even if it would be, it would mess up the architecture, that the + * printnames, and only they, are responsible for their linearization. + * Appending their type later on would break that architecture. + * So they have to be prepared. + */ +public class TypesLoader extends AbstractProber { + /** + * The hash in which the funs as keys and + * types as values get saved. Both are Strings. + */ + protected final Hashtable hashtable; + private static Logger nogger = Logger.getLogger(TypesLoader.class.getName()); + /** + * @param gfCapsule the read/write encapsulation of the running GF + * @param myHashMap The hash in which the funs as keys and + * types as values get saved. Both are Strings. + */ + public TypesLoader(GfCapsule gfCapsule, Hashtable myHashMap) { + super(gfCapsule); + this.hashtable = myHashMap; + } + + /** + * Reads the tree child of the XML from beginning to end. + * Sets autocompleted to false, if the focus position is open. + */ + protected void readMessage() { + try { + String result = gfCapsule.fromProc.readLine(); + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("7 " + result); + } + //first read line is <message>, but this one gets filtered out in the next line + while (result.indexOf("/message")==-1){ + result = result.trim(); + if (result.startsWith("fun ")) { + //unescape backslashes. Probably there are more + readType(result); + } + + result = gfCapsule.fromProc.readLine(); + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("7 " + result); + } + } + if (nogger.isLoggable(Level.FINER)) { + nogger.finer("finished loading printnames"); + } + } catch(IOException e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + } + + } + + /** + * asks GF to print a list of all available printnames and + * calls the registered PrintnameManager to register those. + */ + public void readTypes() { + //prints the last loaded grammar, + String sendString = "gf pg"; + if (nogger.isLoggable(Level.FINE)) { + nogger.fine("collecting types :" + sendString); + } + send(sendString); + readGfedit(); + } + + /** + * Reads a fun line from pg and puts it into hashMap with the fun + * as the key and the type as the value + * @param line One line which describes the signature of a fun + */ + private void readType(String line) { + final int funStartIndex = 4; //length of "fun " + final String fun = line.substring(funStartIndex, line.indexOf(" : ")); + final int typeStartIndex = line.indexOf(" : ") + 3; + final int typeEndIndex = line.lastIndexOf(" = "); + try { + final String type = line.substring(typeStartIndex, typeEndIndex); + this.hashtable.put(fun, type); + } catch (StringIndexOutOfBoundsException e) { + System.err.println("line: '" + line + "'"); + System.err.println("fun: '" + fun + "'"); + System.err.println("typeStartIndex: " + typeStartIndex); + System.err.println("typeEndIndex: " + typeEndIndex); + + System.err.println(e.getLocalizedMessage()); + } + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/UnrefinedAstNodeData.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/UnrefinedAstNodeData.java new file mode 100644 index 000000000..0c12fc0fb --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/UnrefinedAstNodeData.java @@ -0,0 +1,64 @@ +//Copyright (c) Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + +package de.uka.ilkd.key.ocl.gf; + +import java.util.logging.*; + +/** + * @author daniels + * + * represents an open, unrefined node in the AST. + * It knows, how it is called and described (tooltip). + */ +public class UnrefinedAstNodeData extends AstNodeData { + /** + * The tooltip that this node as a parameter should get + */ + protected final String paramTooltip; + + /** + * For a child we have to know its name, its type and the tooltip + * @param pTooltip The tooltip that this node as a parameter should get + * @param node The GfAstNode for the current AST node, for which + * this AstNodeData is the data for. + * @param pos The position in the GF AST of this node in Haskell notation + * @param selected if this is the selected node in the GF AST + * @param constraint A constraint from a parent node, that also + * applies for this node. + */ + public UnrefinedAstNodeData(String pTooltip, GfAstNode node, String pos, boolean selected, String constraint) { + super(node, pos, selected, constraint); + this.paramTooltip = pTooltip; + if (logger.isLoggable(Level.FINEST)) { + logger.finest(this.toString() + " - " + position); + } + } + /** + * @return no refinement, no printname, thus null + */ + public Printname getPrintname() { + return null; + } + + /** + * @return the parameter tooltip that this node has as a child + * of its parent (who gave it to it depending on its position) + */ + public String getParamTooltip() { + return this.paramTooltip; + } + +} diff --git a/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Utils.java b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Utils.java new file mode 100644 index 000000000..076cc2308 --- /dev/null +++ b/src/JavaGUI2/de/uka/ilkd/key/ocl/gf/Utils.java @@ -0,0 +1,243 @@ +//Copyright (c) Kristofer Johanisson 2005, Hans-Joachim Daniels 2005 +// +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; either version 2 of the License, or +//(at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You can either finde the file LICENSE or LICENSE.TXT in the source +//distribution or in the .jar file of this application + + +package de.uka.ilkd.key.ocl.gf; + + +import java.io.File; +import java.util.logging.*; +import javax.swing.ProgressMonitor; +import java.util.Vector; + +/** + * consists of a bunch of static methods, mostly for String + * @author daniels + * + */ +public class Utils { + private static Logger timeLogger = Logger.getLogger(Utils.class.getName() + ".timer"); + private static Logger deleteLogger = Logger.getLogger(Utils.class.getName() + ".delete"); + private static Logger stringLogger = Logger.getLogger(Utils.class.getName() + ".string"); + + private Utils() { + //non-instantiability enforced + } + + public static final String gf = "gf"; + public static final String gfcm = "gfcm"; + + /** + * Get the extension of a file. + */ + public static String getExtension(File f) { + String ext = null; + String s = f.getName(); + int i = s.lastIndexOf('.'); + + if (i > 0 && i < s.length() - 1) { + ext = s.substring(i+1).toLowerCase(); + } + return ext; + } + /** + * Sets the progress on the given ProgressMonitor and logs the current time. + * @param pm the monitor which is to be updated. If null, only logging is done + * @param progress The progress in absolute ticks + * @param note The note that is to be displayed above the progress monitor + */ + public static void tickProgress(ProgressMonitor pm, int progress, String note) { + if (note != null) { + if (timeLogger.isLoggable(Level.FINER)) { + timeLogger.finer(System.currentTimeMillis() + " : " + note); + } + } + if (pm == null) { + return; + } + pm.setProgress(progress); + if (note != null) { + pm.setNote(note); + } + } + + /** + * schedules all Eng, OCL and Ger grammar files for deletion. + * @param grammarsDir The directory where those files are + */ + public static void cleanupFromUMLTypes(String grammarsDir) { + String[] endings = {"Eng.gf", "Eng.gfc", "Ger.gf", "Ger.gfc", "OCL.gf", "OCL.gfc", ".gf", ".gfc"}; + for (int i = 0; i < endings.length; i++) { + String filename = grammarsDir + File.separator + GFEditor2.modelModulName + endings[i]; + File file = new File(filename); + file.deleteOnExit(); + if (deleteLogger.isLoggable(Level.FINER)) { + deleteLogger.fine("scheduled for deletion: " + filename); + } + } + File file = new File(grammarsDir); + file.deleteOnExit(); + file = file.getParentFile(); + file.deleteOnExit(); + } + + /** + * Searches for the first occurace not escaped with '\' of toBeFound in s. + * Works like String::indexOf otherwise + * @param s the String to search in + * @param toBeFound the String to search for + * @return the index of toBeFound, -1 if not found (or only escaped) + */ + public static int indexOfNotEscaped(String s, String toBeFound) { + return indexOfNotEscaped(s, toBeFound, 0); + } + + /** + * Searches for the first occurace not escaped with '\' of toBeFound in s. + * Works like String::indexOf otherwise + * @param s the String to search in + * @param toBeFound the String to search for + * @param startIndex the index in s, from which the search starts + * @return the index of toBeFound, -1 if not found (or only escaped) + */ + public static int indexOfNotEscaped(String s, String toBeFound, int startIndex) { + for (int from = startIndex; from < s.length();) { + int i = s.indexOf(toBeFound, from); + if (i <= 0) { + //-1 is not found at all, 0 can't have a '\' before + return i; + } else if (s.charAt(i-1) != '\\') { + return i; + } else { + from = i + 1; + } + } + return -1; + } + + /** + * a simple replaceAll replacement, that uses NO regexps + * and thus needs no freaking amount of backslashes + * @param original The String in which the replacements should take place + * @param toReplace the String literal that is to be replaced + * @param replacement the replacement string + * @return original, but with replacements + */ + public static String replaceAll(String original, String toReplace, String replacement) { + StringBuffer sb = new StringBuffer(original); + for (int i = sb.indexOf(toReplace); i >= 0; i = sb.indexOf(toReplace, i + replacement.length())) { + sb.replace(i, i + toReplace.length(), replacement); + } + return sb.toString(); + } + + /** + * Removes all parts, that are inside "quotation marks" from s. + * Assumes no nesting of those, like in Java. + * " escaped with \ like \" do not count as quotation marks + * @param s The String, that possibly contains quotations + * @return a String described above, s without quotations. + */ + public static String removeQuotations(String s) { + if (s.indexOf('"') == -1) { + return s; + } + for (int begin = indexOfNotEscaped(s, "\""); begin > -1 ; begin = indexOfNotEscaped(s, "\"", begin)) {//yes, I want an unescaped '"'! + int end = indexOfNotEscaped(s, "\"", begin + 1); + if (end > -1) { + s = s.substring(0, begin) + s.substring(end + 1); + } else { + stringLogger.info("Strange String given: '" + s + "'"); + s = s.substring(0, begin); + } + } + return s; + } + + /** + * counts the occurances of toSearch in s + * @param s The String in which the search shall take place + * @param toSearch The String that should be counted + * @return the number of occurances, 0 if s is null + */ + public static int countOccurances(String s, String toSearch) { + if (s == null) { + return 0; + } + int result = 0; + for (int i = s.indexOf(toSearch); i > -1; i = s.indexOf(toSearch, i)) { + result++; + i += toSearch.length(); + } + return result; + } + + /** + * Takes an arbitrary Vector and executes toString on each element and + * puts these into a String[] of the same size as the Vector + * @param strings A Vector of Object, preferably String + * @return The Vector as a String[] + */ + public static String[] vector2Array(Vector strings) { + String[] result = new String[strings.size()]; + for (int i = 0; i < strings.size(); i++) { + result[i] = strings.get(i).toString(); + } + return result; + } + /** + * just replace sequences of spaces with one space + * @param s The string to be compacted + * @return the compacted result + */ + static String compactSpaces(String s) { + String localResult = new String(); + boolean spaceIncluded = false; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c != ' ') { // include all non-spaces + localResult += String.valueOf(c); + spaceIncluded = false; + } else {// we have a space + if (!spaceIncluded) { + localResult += " "; + spaceIncluded = true; + } // else just skip + } + } + return localResult; + } + /** + * Replaces all occurances of toBeReplaced, that are not escaped by '\' + * with replacement + * @param working the String in which substrings should be replaced + * @param toBeReplaced The substring, that should be replaced by replacement + * @param replacement well, the replacement string + * @return The String with the replaced parts + */ + public static String replaceNotEscaped(String working, String toBeReplaced, String replacement) { + StringBuffer w = new StringBuffer(working); + for (int i = w.indexOf(toBeReplaced); i > -1 && i < w.length(); i = w.indexOf(toBeReplaced, i)) { + if (i == 0 || w.charAt(i - 1) != '\\') { + w.replace(i, i + toBeReplaced.length(), replacement); + i += replacement.length(); + } else { + i += 1; + } + } + return w.toString(); + } +} |
