All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.qos.logback.classic.android.ASaxEventRecorder Maven / Gradle / Ivy

There is a newer version: 1.1.1-6
Show newest version
/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2013, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.classic.android;

import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xml.sax.InputSource;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.LocatorImpl;
import org.xmlpull.v1.XmlPullParser;

import brut.androlib.res.decoder.AXmlResourceParser;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.event.SaxEventRecorder;
import ch.qos.logback.core.joran.spi.JoranException;

/**
 * SAX event recorder for compressed Android XML resource files.
 * Supports filtering to capture only the sub-events of an event
 * of interest in order to conserve memory usage.
 *
 * @author Anthony Trinh
 */
public class ASaxEventRecorder extends SaxEventRecorder {
  private int holderForStartAndLength[] = new int[2];
  private StatePassFilter filter = new StatePassFilter();
  private String elemNameToWatch = null;
  private Map elemAttrs = null;

  /**
   * Sets a filter so that only sub-elements of a specific element
   * are captured
   *
   * 

* For example, if the desired elements were inside *
*

{@code }
* and the input were *
*
{@code }
* the filter would pass *
*
{@code }
* * The call in this example would be: {@code setFilter("x", "y")}. * * @param names names of elements leading to the target elements; * use {@code null} to disable filtering (capture all events) */ public void setFilter(String... names) { filter = new StatePassFilter(names); } /** * Sets a "watch" for an element's attributes, which can be retrieved * with {@link #getAttributeWatchValues()}. During the parsing of the SAX * events, the START-elements are searched for the target element name. * If found, the element's attributes are stored. This checks all START- * elements, regardless of filtering. * * @param elemName name of the element */ public void setAttributeWatch(String elemName) { elemNameToWatch = elemName; } /** * Gets the attributes set by {@link #setAttributeWatch(String)} * * @return attributes (name to value) of the watched element; if the element * was not encountered, this returns null */ public Map getAttributeWatchValues() { return elemAttrs; } /** * Parses SAX events from a compressed Android XML resource * * @param src input source pointing to a compressed Android XML resource */ @Override public List recordEvents(InputSource src) throws JoranException { InputStream stream = src.getByteStream(); if (stream == null) { throw new IllegalArgumentException("Input source must specify an input stream"); } List events = null; try { AXmlResourceParser xpp = new AXmlResourceParser(stream); elemAttrs = null; int eventType = -1; while ((eventType = xpp.next()) > -1) { if (XmlPullParser.START_DOCUMENT == eventType) { filter.reset(); startDocument(xpp); } else if (XmlPullParser.END_DOCUMENT == eventType) { filter.reset(); endDocument(); break; } else if (XmlPullParser.START_TAG == eventType) { startElement(xpp); } else if (XmlPullParser.END_TAG == eventType) { endElement(xpp); } else if (XmlPullParser.TEXT == eventType) { characters(xpp); } } events = getSaxEventList(); } catch (Exception e) { addError(e.getMessage(), e); throw new JoranException("Can't parse Android XML resource", e); } return events; } /** * Processes the START_DOCUMENT event * * @param xpp parser that contains the event */ private void startDocument(XmlPullParser xpp) { super.startDocument(); super.setDocumentLocator(new LocatorImpl()); } /** * Processes the TEXT event * * @param xpp parser that contains the event */ private void characters(XmlPullParser xpp) { if (filter.passed()) { char ch[] = xpp.getTextCharacters(holderForStartAndLength); int start = holderForStartAndLength[0]; int length = holderForStartAndLength[1]; super.characters(ch, start, length); } } /** * Process the END_ELEMENT event * * @param xpp parser that contains the event */ private void endElement(XmlPullParser xpp) { String name = xpp.getName(); if (filter.checkEnd(name)) { endElement(xpp.getNamespace(), name, name); } } /** * Processes the START_ELEMENT event * * @param xpp parser that contains the event */ private void startElement(XmlPullParser xpp) { String name = xpp.getName(); if (filter.checkStart(name)) { AttributesImpl atts = new AttributesImpl(); for (int i = 0; i < xpp.getAttributeCount(); i++) { atts.addAttribute(xpp.getAttributeNamespace(i), xpp.getAttributeName(i), xpp.getAttributeName(i), xpp.getAttributeType(i), xpp.getAttributeValue(i)); } startElement(xpp.getNamespace(), name, name, atts); } // check for attributes (and ignore filter) checkForWatchedAttributes(xpp); } /** * Checks a START-element for the watched element, set by * {@link #setAttributeWatch(String)}. If the element * was already found, this does nothing. * * @param xpp parser that contains the START_ELEMENT event */ private void checkForWatchedAttributes(XmlPullParser xpp) { if (elemNameToWatch != null && elemAttrs == null && xpp.getName().equals(elemNameToWatch)) { Map map = new HashMap(); for (int i = 0; i < xpp.getAttributeCount(); i++) { // prefix namespace to element name String key = ""; String ns = xpp.getAttributeNamespace(i); if (ns.length() > 0) { // if namespace is a URL, get the last element int pos = ns.lastIndexOf("/"); if (pos > -1 && pos + 1 < ns.length()) { ns = ns.substring(pos+1); } key = ns + ":"; } key += xpp.getAttributeName(i); map.put(key, xpp.getAttributeValue(i)); } elemAttrs = map; } } /** * Filter that passes start-tags and end-tags within a specific * set of tags. * *

* This is useful in an XML pull parser for capturing XML sub-elements * of a specific element. For example, if the desired elements were in *
*

{@code }
* and the input were *
*
* the filter would pass *
*
{@code }
* * The initialization in this example would be: {@code new StatePassFilter("x", "y")}. */ static class StatePassFilter { private final String[] _states; private int _depth = 0; public StatePassFilter(String... states) { _states = states == null ? new String[0] : states; } /** * Checks if a start-tag element name passes the filter. If the * name matches the filter's current state, the filter depth * is advanced. * * @param name element name to check * @return {@true code} if passed; {@code false} otherwise */ public boolean checkStart(String name) { if (_depth == _states.length) { return true; } else if (name.equals(_states[_depth])) { _depth++; } return false; } /** * Checks if an end-tag element name passes the filter. If the * name matches the filter's current state, the filter depth * is decremented. * * @param name element name to check * @return {@true code} if passed; {@code false} otherwise */ public boolean checkEnd(String name) { if ((_depth > 0) && name.equals(_states[_depth - 1])) { _depth--; return false; } return _depth == _states.length; } /** * Gets the number of states in the filter * * @return state count */ public int size() { return _states.length; } /** * Gets the current depth (state) into the filter * * @return depth */ public int depth() { return _depth; } /** * Resets the depth (state) */ public void reset() { _depth = 0; } /** * Gets the pass state. Equivalent to {@code depth() == size()}. * * @return {@code true} if passed; {@code false} otherwise */ public boolean passed() { return _depth == _states.length; } } }
{@code }