package com.sissi.io.read.sax;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import com.sissi.commons.Trace;
import com.sissi.io.read.Collector;
import com.sissi.io.read.Counter;
import com.sissi.io.read.Mapping;
import com.sissi.protocol.Stream;
/**
* @author kim 2013-10-21
*/
public class SAXHandler extends DefaultHandler {
private final static String text = "text";
private final static Log log = LogFactory.getLog(SAXHandler.class);
@SuppressWarnings("serial")
private final static Set<String> reset = new HashSet<String>() {
{
add(Stream.NAME);
}
};
private final static Map<Class<?>, MethodFinder> methods = new HashMap<Class<?>, MethodFinder>();
private final Map<String, String> xmlns = new HashMap<String, String>();
private final LinkedList<Object> stack = new LinkedList<Object>();
private final SAXFuture future;
private final Mapping mapping;
private final Counter counter;
private Object current;
/**
* @param mapping
* @param future
* @param counter XMPP节长度校验器
*/
public SAXHandler(Mapping mapping, SAXFuture future, Counter counter) {
super();
this.future = future;
this.counter = counter;
this.mapping = mapping;
}
private SAXHandler xmlnCopy() {
if (this.current != null) {
for (String xmln : this.xmlns.keySet()) {
this.propertyCopy(this.current, xmln, this.xmlns.get(xmln));
}
}
return this;
}
private SAXHandler reset(String localName) {
if (reset.contains(localName.intern().trim())) {
this.stack.clear();
}
return this;
}
private MethodFinder find4Cached(Object ob) {
MethodFinder finder = methods.get(ob.getClass());
if (finder == null) {
log.debug("Create finder for " + ob.getClass());
methods.put(ob.getClass(), (finder = new MethodFinder()));
}
return finder;
}
private SAXHandler propertyCopy(Attributes attributes, Object element) {
for (int index = 0; index < attributes.getLength(); index++) {
this.propertyCopy(element, attributes.getLocalName(index), attributes.getValue(index));
}
return this;
}
private boolean propertyCopy(Object ob, String key, Object value) {
try {
return this.find4Cached(ob).invoke(ob, key, value);
} catch (Exception e) {
log.debug(e.toString());
Trace.trace(log, e);
return false;
}
}
private SAXHandler newElement(String uri, String localName) {
this.current = this.mapping.instance(uri, localName);
return this;
}
private boolean generateNode(Attributes attributes, String uri, String localName) {
this.reset(localName).newElement(uri, localName).xmlnCopy();
if (this.current == null) {
return false;
}
if (this.stack.isEmpty()) {
if (!this.future.push(this.propertyCopy(attributes, this.current).current)) {
log.warn("Queue none capacity");
}
} else {
Object first = this.stack.getFirst();
if (Collector.class.isAssignableFrom(first.getClass())) {
Collector.class.cast(first).set(localName, this.current);
}
}
this.stack.addFirst(this.current);
return true;
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
if (prefix != null && !prefix.isEmpty()) {
this.xmlns.put(prefix, uri);
}
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
this.counter.recount();
log.debug("Process uri: " + uri + " localName: " + localName);
if (this.generateNode(attributes, uri, localName)) {
this.propertyCopy(attributes, this.stack.getFirst());
}
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if (this.mapping.cached(uri, localName)) {
Object first = this.stack.size() == 1 ? this.stack.getFirst() : this.stack.removeFirst();
log.debug(first.getClass() + " will be remove");
if (this.stack.size() <= 1) {
log.debug(first.getClass() + " will be feed");
this.future.push(first);
}
}
}
public void characters(char ch[], int start, int length) throws SAXException {
String text = new String(ch, start, length).trim();
if (!text.isEmpty()) {
if (this.current != null) {
this.propertyCopy(this.stack.getFirst(), SAXHandler.text, text);
}
}
}
public void fatalError(SAXParseException e) throws SAXException {
log.debug(e.toString());
Trace.trace(log, e);
}
private static class MethodFinder extends HashMap<String, Method> {
private final static long serialVersionUID = 1L;
private final Set<String> ignores = new HashSet<String>();
private final static Class<?>[] types = new Class[] { String.class };
public boolean invoke(Object ob, String key, Object value) throws Exception {
if (this.ignores.contains(key)) {
return false;
}
this.cacheMethod(ob, key, this.get(key)).invoke(ob, value);
log.trace("Copy " + key + " / " + value + " on " + ob.getClass());
return true;
}
private Method cacheMethod(Object ob, String key, Method method) throws NoSuchMethodException {
if (method == null) {
try {
this.put(key, (method = ob.getClass().getMethod("set" + StringUtils.capitalize(key), types)));
} catch (NoSuchMethodException e) {
this.ignores.add(key);
}
}
return method;
}
}
}