View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.chainsaw;
19  
20  import org.apache.log4j.chainsaw.filter.FilterModel;
21  import org.apache.log4j.rule.RuleFactory;
22  import org.apache.log4j.spi.LoggingEventFieldResolver;
23  
24  import javax.swing.*;
25  import javax.swing.text.JTextComponent;
26  import java.awt.*;
27  import java.awt.event.*;
28  
29  /**
30   * A popup menu which assists in building expression rules.  Completes event keywords, operators and
31   * context if available.
32   *
33   * @author Scott Deboy <sdeboy@apache.org>
34   */
35  public class ExpressionRuleContext extends KeyAdapter {
36      RuleFactory factory = RuleFactory.getInstance();
37      LoggingEventFieldResolver resolver = LoggingEventFieldResolver.getInstance();
38      JPopupMenu contextMenu = new JPopupMenu();
39      JList list = new JList();
40      FilterModel filterModel;
41      JScrollPane scrollPane = new JScrollPane(list);
42      final JTextComponent textComponent;
43      private DefaultListModel fieldModel = new DefaultListModel();
44      private DefaultListModel operatorModel = new DefaultListModel();
45  
46      public ExpressionRuleContext(
47          final FilterModel filterModel, final JTextComponent textComponent) {
48          this.filterModel = filterModel;
49          this.textComponent = textComponent;
50          fieldModel.addElement("LOGGER");
51          fieldModel.addElement("LEVEL");
52          fieldModel.addElement("CLASS");
53          fieldModel.addElement("FILE");
54          fieldModel.addElement("LINE");
55          fieldModel.addElement("METHOD");
56          fieldModel.addElement("MSG");
57          fieldModel.addElement("NDC");
58          fieldModel.addElement("EXCEPTION");
59          fieldModel.addElement("TIMESTAMP");
60          fieldModel.addElement("THREAD");
61          fieldModel.addElement("PROP.");
62  
63          operatorModel.addElement("&&");
64          operatorModel.addElement("||");
65          operatorModel.addElement("!");
66          operatorModel.addElement("!=");
67          operatorModel.addElement("==");
68          operatorModel.addElement("~=");
69          operatorModel.addElement("LIKE");
70          operatorModel.addElement("EXISTS");
71          operatorModel.addElement("<");
72          operatorModel.addElement(">");
73          operatorModel.addElement("<=");
74          operatorModel.addElement(">=");
75  
76          //make long to avoid scrollbar
77          list.setVisibleRowCount(13);
78  
79          PopupListener popupListener = new PopupListener();
80          textComponent.addMouseListener(popupListener);
81  
82          list.addKeyListener(
83              new KeyAdapter() {
84                  public void keyPressed(KeyEvent e) {
85                      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
86                          String value = list.getSelectedValue().toString();
87                          String contextKey = getContextKey();
88                          if (contextKey != null && (!(contextKey.endsWith(".")))) {
89                              value = "'" + value + "'";
90                          }
91  
92                          updateField(value);
93                          contextMenu.setVisible(false);
94                      }
95                  }
96              });
97  
98          list.addMouseListener(
99              new MouseAdapter() {
100                 public void mouseClicked(MouseEvent e) {
101                     if (e.getClickCount() == 2) {
102                         String value = list.getSelectedValue().toString();
103                         String contextKey = getContextKey();
104                         if (contextKey != null && (!(contextKey.endsWith(".")))) {
105                             value = "'" + value + "'";
106                         }
107 
108                         updateField(value);
109                         contextMenu.setVisible(false);
110                     }
111                 }
112             });
113 
114         contextMenu.insert(scrollPane, 0);
115     }
116 
117     private void updateField(String value) {
118         if (textComponent.getSelectedText() == null) {
119             if (!(value.endsWith("."))) {
120                 value = value + " ";
121             }
122         }
123 
124         textComponent.replaceSelection(value);
125     }
126 
127     public void keyPressed(KeyEvent e) {
128         if (
129             (e.getKeyCode() == KeyEvent.VK_SPACE)
130                 && (e.getModifiers() == InputEvent.CTRL_MASK)) {
131             displayContext();
132         }
133     }
134 
135     public void displayContext() {
136         String lastField = getContextKey();
137 
138         if (lastField != null) {
139             ListModel model = filterModel.getContainer().getModel(lastField);
140             if (model == null) {
141                 return;
142             }
143             list.setModel(model);
144             list.setSelectedIndex(0);
145 
146             Point p = textComponent.getCaret().getMagicCaretPosition();
147             contextMenu.doLayout();
148             contextMenu.show(textComponent, p.x, (p.y + (textComponent.getHeight() - 5)));
149             list.requestFocus();
150         } else {
151             if (isOperatorContextValid()) {
152                 list.setModel(operatorModel);
153                 list.setSelectedIndex(0);
154 
155                 Point p = textComponent.getCaret().getMagicCaretPosition();
156                 contextMenu.doLayout();
157                 contextMenu.show(textComponent, p.x, (p.y + (textComponent.getHeight() - 5)));
158                 list.requestFocus();
159             } else if (isFieldContextValid()) {
160                 list.setModel(fieldModel);
161                 list.setSelectedIndex(0);
162 
163                 Point p = textComponent.getCaret().getMagicCaretPosition();
164 
165                 if (p == null) {
166                     p = new Point(
167                         textComponent.getLocation().x,
168                         (textComponent.getLocation().y - textComponent.getHeight() + 5));
169                 }
170                 contextMenu.doLayout();
171                 contextMenu.show(textComponent, p.x, (p.y + (textComponent.getHeight() - 5)));
172                 list.requestFocus();
173             }
174         }
175     }
176 
177     private boolean isFieldContextValid() {
178         String text = textComponent.getText();
179         int currentPosition = textComponent.getSelectionStart();
180 
181         return ((currentPosition == 0)
182             || (text.charAt(currentPosition - 1) == ' '));
183     }
184 
185     private String getContextKey() {
186         String field = getField();
187 
188         if (field == null) {
189             field = getSubField();
190         }
191 
192         return field;
193     }
194 
195     private boolean isOperatorContextValid() {
196         String text = textComponent.getText();
197 
198         int currentPosition = textComponent.getSelectionStart();
199 
200         if ((currentPosition < 1) || (text.charAt(currentPosition - 1) != ' ')) {
201             return false;
202         }
203 
204         int lastFieldPosition = text.lastIndexOf(" ", currentPosition - 1);
205 
206         if (lastFieldPosition == -1) {
207             return false;
208         }
209 
210         int lastFieldStartPosition =
211             Math.max(0, text.lastIndexOf(" ", lastFieldPosition - 1));
212         String field =
213             text.substring(lastFieldStartPosition, lastFieldPosition).toUpperCase()
214                 .trim();
215 
216         return resolver.isField(field);
217 
218     }
219 
220     //returns the currently active field which can be used to display a context menu
221     //the field returned is the left hand portion of an expression (for example, logger == )
222     //logger is the field that is returned
223     private String getField() {
224         String text = textComponent.getText();
225 
226         int currentPosition = textComponent.getSelectionStart();
227 
228         if ((currentPosition < 1) || (text.charAt(currentPosition - 1) != ' ')) {
229             return null;
230         }
231 
232         int symbolPosition = text.lastIndexOf(" ", currentPosition - 1);
233 
234         if (symbolPosition < 0) {
235             return null;
236         }
237 
238         int lastFieldPosition = text.lastIndexOf(" ", symbolPosition - 1);
239 
240         if (lastFieldPosition < 0) {
241             return null;
242         }
243 
244         int lastFieldStartPosition =
245             Math.max(0, text.lastIndexOf(" ", lastFieldPosition - 1));
246         String lastSymbol =
247             text.substring(lastFieldPosition + 1, symbolPosition).trim();
248 
249         String lastField =
250             text.substring(lastFieldStartPosition, lastFieldPosition).trim();
251 
252         if (
253             factory.isRule(lastSymbol)
254                 && filterModel.getContainer().modelExists(lastField)) {
255             return lastField;
256         }
257 
258         return null;
259     }
260 
261     //subfields allow the key portion of a field to provide context menu support
262     //and are available after the fieldname and a . (for example, PROP.)
263     private String getSubField() {
264         int currentPosition = textComponent.getSelectionStart();
265         String text = textComponent.getText();
266 
267         if (text.substring(0, currentPosition).toUpperCase().endsWith("PROP.")) {
268             return "PROP.";
269         }
270         return null;
271     }
272 
273     class PopupListener extends MouseAdapter {
274         PopupListener() {
275         }
276 
277         public void mousePressed(MouseEvent e) {
278             checkPopup(e);
279         }
280 
281         public void mouseReleased(MouseEvent e) {
282             checkPopup(e);
283         }
284 
285         private void checkPopup(MouseEvent e) {
286             if (e.isPopupTrigger()) {
287                 displayContext();
288             }
289         }
290     }
291 }