JapeViewer.java
001 package gate.gui.jape;
002 
003 import gate.Resource;
004 import gate.creole.ANNIEConstants;
005 import gate.creole.AbstractVisualResource;
006 import gate.creole.Transducer;
007 import gate.event.ProgressListener;
008 import gate.jape.parser.ParseCpslConstants;
009 import gate.jape.parser.ParseCpslTokenManager;
010 import gate.jape.parser.SimpleCharStream;
011 import gate.jape.parser.Token;
012 import gate.util.BomStrippingInputStreamReader;
013 import gate.util.GateRuntimeException;
014 
015 import java.awt.BorderLayout;
016 import java.awt.Color;
017 import java.io.BufferedReader;
018 import java.io.IOException;
019 import java.io.InputStreamReader;
020 import java.io.Reader;
021 import java.io.StringReader;
022 import java.net.MalformedURLException;
023 import java.net.URL;
024 import java.util.ArrayList;
025 import java.util.HashMap;
026 import java.util.List;
027 import java.util.Map;
028 
029 import javax.swing.JScrollPane;
030 import javax.swing.JTextPane;
031 import javax.swing.JTree;
032 import javax.swing.event.TreeSelectionEvent;
033 import javax.swing.event.TreeSelectionListener;
034 import javax.swing.text.Style;
035 import javax.swing.text.StyleConstants;
036 import javax.swing.text.StyledDocument;
037 import javax.swing.tree.DefaultMutableTreeNode;
038 import javax.swing.tree.DefaultTreeModel;
039 import javax.swing.tree.TreeSelectionModel;
040 
041 /**
042  * A JAPE viewer that allows access to all phases of the grammar and
043  * provides syntax highlighting. Future versions may allow editing and
044  * reload of JAPE files.
045  *
046  @author Mark A. Greenwood
047  */
048 public class JapeViewer extends AbstractVisualResource implements
049                                                       ANNIEConstants,
050                                                       ProgressListener {
051 
052   /**
053    * The text area where the JAPE source will be displayed
054    */
055   private JTextPane textArea;
056 
057   /**
058    * The tree in which the phases of the grammar will be shown
059    */
060   private JTree treePhases;
061 
062   private JScrollPane treeScroll;
063 
064   /**
065    * A flag so we can know if we are currently reading a highlighting a
066    * JAPE source file
067    */
068   private boolean updating = false;
069 
070   /**
071    * The JAPE transducer for which we need to show the JAPE source
072    */
073   private Transducer transducer;
074 
075   /**
076    * A map that associates the syntactic elements of JAPE files with a
077    * colour for performing syntax highlighting
078    */
079   private Map<Integer, Style> colorMap = new HashMap<Integer, Style>();
080 
081   /**
082    * The default style used by the text area. This is used so that we
083    * can ensure that normal text is displayed normally. This fixes a
084    * problem where sometime the highlighting goes screwy and shows
085    * everything as a comment.
086    */
087   private Style defaultStyle;
088 
089   @Override
090   public Resource init() {
091     initGuiComponents();
092     return this;
093   }
094 
095   private void initGuiComponents() {
096     setLayout(new BorderLayout());
097     textArea = new JTextPane();
098     textArea.setEditable(false);
099     JScrollPane textScroll = new JScrollPane(textArea,
100             JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
101             JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
102     add(textScroll, BorderLayout.CENTER);
103 
104     treePhases = new JTree();
105     treeScroll = new JScrollPane(treePhases,
106             JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
107             JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
108     add(treeScroll, BorderLayout.WEST);
109     treePhases.getSelectionModel().setSelectionMode(
110             TreeSelectionModel.SINGLE_TREE_SELECTION);
111     treePhases.addTreeSelectionListener(new TreeSelectionListener() {
112       public void valueChanged(TreeSelectionEvent e) {
113         if(updatingreturn;
114         if(e.getPath().getLastPathComponent() == nullreturn;
115 
116         try {
117           readJAPEFileContents(new URL(transducer.getGrammarURL(), e.getPath()
118                   .getLastPathComponent()
119                   ".jape"));
120         }
121         catch(MalformedURLException mue) {
122           mue.printStackTrace();
123         }
124       }
125     });
126 
127     // if we want to set the jape to be monospaced (like most code
128     // editors) then
129     // do this...
130     /*
131      * MutableAttributeSet attrs = textArea.getInputAttributes();
132      * StyleConstants.setFontFamily(attrs, "monospaced"); StyledDocument
133      * doc = textArea.getStyledDocument(); doc.setCharacterAttributes(0,
134      * doc.getLength() + 1, attrs, false);
135      */
136     defaultStyle = textArea.addStyle("default"null);
137 
138     Style style = textArea.addStyle("brackets"null);
139     StyleConstants.setForeground(style, Color.red);
140     colorMap.put(ParseCpslConstants.leftBrace, style);
141     colorMap.put(ParseCpslConstants.rightBrace, style);
142     colorMap.put(ParseCpslConstants.leftBracket, style);
143     colorMap.put(ParseCpslConstants.rightBracket, style);
144     colorMap.put(ParseCpslConstants.leftSquare, style);
145     colorMap.put(ParseCpslConstants.rightSquare, style);
146 
147     style = textArea.addStyle("keywords"null);
148     StyleConstants.setForeground(style, Color.blue);
149     colorMap.put(ParseCpslConstants.rule, style);
150     colorMap.put(ParseCpslConstants.priority, style);
151     colorMap.put(ParseCpslConstants.macro, style);
152     colorMap.put(ParseCpslConstants.bool, style);
153     colorMap.put(ParseCpslConstants.phase, style);
154     colorMap.put(ParseCpslConstants.input, style);
155     colorMap.put(ParseCpslConstants.option, style);
156     colorMap.put(ParseCpslConstants.multiphase, style);
157     colorMap.put(ParseCpslConstants.phases, style);
158 
159     style = textArea.addStyle("strings"null);
160     StyleConstants.setForeground(style, new Color(0128128));
161     colorMap.put(ParseCpslConstants.string, style);
162 
163     style = textArea.addStyle("comments"null);
164     StyleConstants.setForeground(style, new Color(01280));
165     colorMap.put(ParseCpslConstants.singleLineCStyleComment, style);
166     colorMap.put(ParseCpslConstants.singleLineCpslStyleComment, style);
167     colorMap.put(ParseCpslConstants.commentStart, style);
168     colorMap.put(ParseCpslConstants.commentChars, style);
169     colorMap.put(ParseCpslConstants.commentEnd, style);
170     colorMap.put(ParseCpslConstants.phasesSingleLineCStyleComment, style);
171     colorMap.put(ParseCpslConstants.phasesSingleLineCpslStyleComment, style);
172     colorMap.put(ParseCpslConstants.phasesCommentStart, style);
173     colorMap.put(ParseCpslConstants.phasesCommentChars, style);
174     colorMap.put(ParseCpslConstants.phasesCommentEnd, style);
175   }
176 
177   @Override
178   public void setTarget(Object target) {
179     if(target == null || !(target instanceof Transducer)) {
180       throw new IllegalArgumentException(
181               "The GATE jape viewer can only be used with a GATE jape transducer!\n"
182                       + target.getClass().toString()
183                       " is not a GATE Jape Transducer!");
184     }
185 
186     if(transducer != null) {
187       transducer.removeProgressListener(this);
188     }
189 
190     transducer = (Transducer)target;
191     URL japeFileURL = transducer.getGrammarURL();
192 
193     if(japeFileURL == null) {
194       textArea.setText("The source for this JAPE grammar is not available!");
195       remove(treeScroll);
196       return;
197     }
198 
199     String japePhaseName = japeFileURL.getFile();
200     japePhaseName = japePhaseName.substring(japePhaseName.lastIndexOf("/"1,
201             japePhaseName.length() 5);
202     treePhases.setModel(new DefaultTreeModel(new DefaultMutableTreeNode(
203             japePhaseName)));
204     treePhases.setSelectionRow(0);
205 
206     readJAPEFileContents(japeFileURL);
207     transducer.addProgressListener(this);
208   }
209 
210   private void readJAPEFileContents(URL url) {
211     if(treePhases.getLastSelectedPathComponent() == nullreturn;
212     updating = true;
213 
214     try {
215       Reader japeReader = null;
216       if(transducer.getEncoding() == null) {
217         japeReader = new BomStrippingInputStreamReader(url.openStream());
218       }
219       else {
220         japeReader = new BomStrippingInputStreamReader(url.openStream(), transducer
221                 .getEncoding());
222       }
223       BufferedReader br = new BufferedReader(japeReader);
224       String content = br.readLine();
225       StringBuilder japeFileContents = new StringBuilder();
226       List<Integer> lineOffsets = new ArrayList<Integer>();
227 
228       while(content != null) {
229         lineOffsets.add(japeFileContents.length());
230 
231         // replace tabs with spaces otherwise the highlighting fails
232         // TODO work out why this is needed and fix it properly
233         japeFileContents.append(content.replaceAll("\t""   ")).append("\n");
234         content = br.readLine();
235       }
236 
237       textArea.setText(japeFileContents.toString());
238       textArea.updateUI();
239       br.close();
240 
241       ParseCpslTokenManager tokenManager = new ParseCpslTokenManager(
242               new SimpleCharStream(
243                       new StringReader(japeFileContents.toString())));
244 
245       StyledDocument doc = textArea.getStyledDocument();
246 
247       doc.setCharacterAttributes(0, japeFileContents.length(), defaultStyle,
248               true);
249 
250       ((DefaultMutableTreeNode)treePhases.getSelectionPath()
251               .getLastPathComponent()).removeAllChildren();
252 
253       Token t;
254       while((t = tokenManager.getNextToken()).kind != 0) {
255 
256         Token special = t.specialToken;
257         while(special != null) {
258           Style style = colorMap.get(special.kind);
259           if(style != null) {
260             int start = lineOffsets.get(special.beginLine - 1)
261                     + special.beginColumn - 1;
262             int end = lineOffsets.get(special.endLine - 1+ special.endColumn
263                     1;
264             doc.setCharacterAttributes(start, end - start + 1, style, true);
265           }
266 
267           special = special.specialToken;
268         }
269 
270         Style style = colorMap.get(t.kind);
271 
272         if(style != null) {
273           int start = lineOffsets.get(t.beginLine - 1+ t.beginColumn - 1;
274           int end = lineOffsets.get(t.endLine - 1+ t.endColumn - 1;
275           doc.setCharacterAttributes(start, end - start + 1, style, true);
276         }
277 
278         if(t.kind == ParseCpslConstants.path) {
279           ((DefaultMutableTreeNode)treePhases.getSelectionPath()
280                   .getLastPathComponent()).add(new DefaultMutableTreeNode(t
281                   .toString()));
282         }
283       }
284     }
285     catch(IOException ioe) {
286       throw new GateRuntimeException(ioe);
287     }
288 
289     if(treePhases.getSelectionRows() != null
290             && treePhases.getSelectionRows().length > 0)
291       treePhases.expandRow(treePhases.getSelectionRows()[0]);
292 
293     updating = false;
294   }
295 
296   public void processFinished() {
297     setTarget(transducer);
298   }
299 
300   public void progressChanged(int progress) {
301 
302   }
303 }