View Javadoc

1   /**
2    * Logback: the generic, reliable, fast and flexible logging framework for Java.
3    * 
4    * Copyright (C) 2000-2006, QOS.ch
5    * 
6    * This library is free software, you can redistribute it and/or modify it under
7    * the terms of the GNU Lesser General Public License as published by the Free
8    * Software Foundation.
9    */
10  package ch.qos.logback.core.joran.spi;
11  
12  import java.util.ArrayList;
13  import java.util.Iterator;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.Stack;
17  import java.util.Vector;
18  
19  import org.xml.sax.Attributes;
20  import org.xml.sax.Locator;
21  
22  import ch.qos.logback.core.Context;
23  import ch.qos.logback.core.joran.action.Action;
24  import ch.qos.logback.core.joran.action.ImplicitAction;
25  import ch.qos.logback.core.joran.event.BodyEvent;
26  import ch.qos.logback.core.joran.event.EndEvent;
27  import ch.qos.logback.core.joran.event.SaxEvent;
28  import ch.qos.logback.core.joran.event.StartEvent;
29  import ch.qos.logback.core.spi.ContextAwareImpl;
30  
31  /**
32   * <id>Interpreter</id> is Joran's main driving class. It extends SAX
33   * {@link org.xml.sax.helpers.DefaultHandler DefaultHandler} which invokes
34   * various {@link Action actions} according to predefined patterns.
35   * 
36   * <p>
37   * Patterns are kept in a {@link RuleStore} which is programmed to store and
38   * then later produce the applicable actions for a given pattern.
39   * 
40   * <p>
41   * The pattern corresponding to a top level &lt;a&gt; element is the string
42   * <id>"a"</id>.
43   * 
44   * <p>
45   * The pattern corresponding to an element &lt;b&gt; embedded within a top level
46   * &lt;a&gt; element is the string <id>"a/b"</id>.
47   * 
48   * <p>
49   * The pattern corresponding to an &lt;b&gt; and any level of nesting is
50   * "&#42;/b. Thus, the &#42; character placed at the beginning of a pattern
51   * serves as a wildcard for the level of nesting.
52   * 
53   * Conceptually, this is very similar to the API of commons-digester. Joran
54   * offers several small advantages. First and foremost, it offers support for
55   * implicit actions which result in a significant leap in flexibility. Second,
56   * in our opinion better error reporting capability. Third, it is self-reliant.
57   * It does not depend on other APIs, in particular commons-logging which is too
58   * unreliable. Last but not least, Joran is quite tiny and is expected to remain
59   * so.
60   * 
61   * @author Ceki G&uuml;lcu&uuml;
62   * 
63   */
64  public class Interpreter {
65    private static List EMPTY_LIST = new Vector(0);
66  
67    final private RuleStore ruleStore;
68    final private InterpretationContext interpretationContext;
69    final private ArrayList<ImplicitAction> implicitActions;
70    final private CAI_WithLocatorSupport cai;
71    Pattern pattern;
72    Locator locator;
73    EventPlayer player;
74  
75    /**
76     * The <id>actionListStack</id> contains a list of actions that are executing
77     * for the given XML element.
78     * 
79     * A list of actions is pushed by the {link #startElement} and popped by
80     * {@link #endElement}.
81     * 
82     */
83    Stack<List> actionListStack;
84  
85    /**
86     * If the skip nested is set, then we skip all its nested elements until it is
87     * set back to null at when the element's end is reached.
88     */
89    Pattern skip = null;
90  
91    public Interpreter(Context context, RuleStore rs, Pattern initialPattern) {
92      this.cai = new CAI_WithLocatorSupport(this);
93      this.cai.setContext(context);
94      ruleStore = rs;
95      interpretationContext = new InterpretationContext(context, this);
96      implicitActions = new ArrayList<ImplicitAction>(3);
97      this.pattern = initialPattern;
98      actionListStack = new Stack<List>();
99      player = new EventPlayer(this);
100   }
101   
102   public void setInterpretationContextPropertiesMap(Map<String, String> propertiesMap) {
103     interpretationContext.setPropertiesMap(propertiesMap);
104   }
105   /**
106    * @deprecated replaced by {@link #getInterpretationContext()}
107    */
108   public InterpretationContext getExecutionContext() {
109     return getInterpretationContext();
110   }
111   
112   public InterpretationContext getInterpretationContext() {
113     return interpretationContext;
114   }
115 
116   public void startDocument() {
117   }
118 
119   public void startElement(StartEvent se) {
120     setDocumentLocator(se.getLocator());
121     startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
122   }
123 
124   private void startElement(String namespaceURI, String localName,
125       String qName, Attributes atts) {
126 
127     String tagName = getTagName(localName, qName);
128     pattern.push(tagName);
129 
130     if (skip != null) {
131       // every startElement pushes an action list
132       pushEmptyActionList();
133       return;
134     }
135 
136     List applicableActionList = getApplicableActionList(pattern, atts);
137     if (applicableActionList != null) {
138       actionListStack.add(applicableActionList);
139       callBeginAction(applicableActionList, tagName, atts);
140     } else {
141       // every startElement pushes an action list
142       pushEmptyActionList();
143       String errMsg = "no applicable action for [" + tagName
144           + "], current pattern is [" + pattern + "]";
145       cai.addError(errMsg);
146     }
147   }
148   
149   /**
150    * This method is used to 
151    */
152   private void pushEmptyActionList() {
153     actionListStack.add(EMPTY_LIST);
154   }
155 
156   public void characters(BodyEvent be) {
157 
158     setDocumentLocator(be.locator);
159 
160     String body = be.getText();
161     List applicableActionList = (List) actionListStack.peek();
162 
163     if (body != null) {
164       body = body.trim();
165     }
166     if (body.length() > 0) {
167       // System.out.println("calling body method with ["+body+ "]");
168       callBodyAction(applicableActionList, body);
169     }
170   }
171 
172   public void endElement(EndEvent endEvent) {
173     setDocumentLocator(endEvent.locator);
174     endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
175   }
176 
177   private void endElement(String namespaceURI, String localName, String qName) {
178     // given that an action list is always pushed for every startElement, we need
179     // to always pop for every endElement
180     List applicableActionList = (List) actionListStack.pop();
181    
182     if (skip != null) {
183       if (skip.equals(pattern)) {
184         skip = null;
185       }
186     } else if (applicableActionList != EMPTY_LIST) {
187       callEndAction(applicableActionList, getTagName(localName, qName));
188     }
189 
190     // given that we always push, we must also pop the pattern
191     pattern.pop();
192   }
193 
194   public Locator getLocator() {
195     return locator;
196   }
197 
198   public void setDocumentLocator(Locator l) {
199     locator = l;
200   }
201 
202   String getTagName(String localName, String qName) {
203     String tagName = localName;
204 
205     if ((tagName == null) || (tagName.length() < 1)) {
206       tagName = qName;
207     }
208 
209     return tagName;
210   }
211 
212   public void addImplicitAction(ImplicitAction ia) {
213     implicitActions.add(ia);
214   }
215 
216   /**
217    * Check if any implicit actions are applicable. As soon as an applicable
218    * action is found, it is returned. Thus, the returned list will have at most
219    * one element.
220    */
221   List lookupImplicitAction(Pattern pattern, Attributes attributes,
222       InterpretationContext ec) {
223     int len = implicitActions.size();
224 
225     for (int i = 0; i < len; i++) {
226       ImplicitAction ia = (ImplicitAction) implicitActions.get(i);
227 
228       if (ia.isApplicable(pattern, attributes, ec)) {
229         List<Action> actionList = new ArrayList<Action>(1);
230         actionList.add(ia);
231 
232         return actionList;
233       }
234     }
235 
236     return null;
237   }
238 
239   /**
240    * Return the list of applicable patterns for this
241    */
242   List getApplicableActionList(Pattern pattern, Attributes attributes) {
243     List applicableActionList = ruleStore.matchActions(pattern);
244 
245     // logger.debug("set of applicable patterns: " + applicableActionList);
246     if (applicableActionList == null) {
247       applicableActionList = lookupImplicitAction(pattern, attributes, interpretationContext);
248     }
249 
250     return applicableActionList;
251   }
252 
253   void callBeginAction(List applicableActionList, String tagName,
254       Attributes atts) {
255     if (applicableActionList == null) {
256       return;
257     }
258 
259     Iterator i = applicableActionList.iterator();
260     while (i.hasNext()) {
261       Action action = (Action) i.next();
262       // now let us invoke the action. We catch and report any eventual
263       // exceptions
264       try {
265         action.begin(interpretationContext, tagName, atts);
266       } catch (ActionException e) {
267         skip = (Pattern) pattern.clone();
268         cai.addError("ActionException in Action for tag [" + tagName + "]", e);
269       } catch (RuntimeException e) {
270         skip = (Pattern) pattern.clone();
271         cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
272       }
273     }
274   }
275 
276   private void callBodyAction(List applicableActionList, String body) {
277     if (applicableActionList == null) {
278       return;
279     }
280     Iterator i = applicableActionList.iterator();
281 
282     while (i.hasNext()) {
283       Action action = (Action) i.next();
284       try {
285         action.body(interpretationContext, body);
286       } catch (ActionException ae) {
287         cai
288             .addError("Exception in end() methd for action [" + action + "]",
289                 ae);
290       }
291     }
292   }
293 
294   private void callEndAction(List applicableActionList, String tagName) {
295     if (applicableActionList == null) {
296       return;
297     }
298 
299     // logger.debug("About to call end actions on node: [" + localName + "]");
300     Iterator i = applicableActionList.iterator();
301 
302     while (i.hasNext()) {
303       Action action = (Action) i.next();
304       // now let us invoke the end method of the action. We catch and report
305       // any eventual exceptions
306       try {
307         action.end(interpretationContext, tagName);
308       } catch (ActionException ae) {
309         // at this point endAction, there is no point in skipping children as
310         // they have been already processed
311         cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
312       } catch (RuntimeException e) {
313         // no point in setting skip
314         cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
315       }
316     }
317   }
318 
319   public RuleStore getRuleStore() {
320     return ruleStore;
321   }
322 
323   public void play(List<SaxEvent> eventList) {
324     player.play(eventList);
325   }
326 
327   public void addEventsDynamically(List<SaxEvent> eventList) {
328     if (player != null) {
329       player.addEventsDynamically(eventList);
330     }
331   }
332 }
333 
334 /**
335  * When {@link Interpreter} class is used as the origin of an
336  * {@link ContextAwareImpl} instance, then XML locator information is lost. This
337  * class preserves locator information (as a string).
338  * 
339  * @author ceki
340  */
341 class CAI_WithLocatorSupport extends ContextAwareImpl {
342 
343   CAI_WithLocatorSupport(Interpreter interpreter) {
344     super(interpreter);
345   }
346 
347   @Override
348   protected Object getOrigin() {
349     Interpreter i = (Interpreter) super.getOrigin();
350     Locator locator = i.locator;
351     if (locator != null) {
352       return Interpreter.class.getName() + "@" + locator.getLineNumber() + ":"
353           + locator.getColumnNumber();
354     } else {
355       return Interpreter.class.getName() + "@NA:NA";
356     }
357   }
358 }