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