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