package org.holoeverywhere.internal; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.HashMap; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.res.XmlResourceParser; import android.util.AttributeSet; import android.util.Xml; import android.view.InflateException; public abstract class GenericInflater<T, P extends GenericInflater.Parent<T>> { public interface Factory<T> { public T onCreateItem(String name, Context context, AttributeSet attrs); } private static class FactoryMerger<T> implements Factory<T> { private final Factory<T> mF1, mF2; FactoryMerger(Factory<T> f1, Factory<T> f2) { mF1 = f1; mF2 = f2; } @Override public T onCreateItem(String name, Context context, AttributeSet attrs) { T v = mF1.onCreateItem(name, context, attrs); if (v != null) { return v; } return mF2.onCreateItem(name, context, attrs); } } public interface Parent<T> { public void addItemFromInflater(T child); } protected static final HashMap<String, Constructor<?>> constructorMap = new HashMap<String, Constructor<?>>(); protected static final Class<?>[] constructorSignature = new Class<?>[] { Context.class, AttributeSet.class }; protected final Object[] constructorArgs = new Object[2]; protected final Context context; private String defaultPackage; private Factory<T> factory; private boolean factorySet; protected GenericInflater(Context context) { this.context = context; } protected GenericInflater(GenericInflater<T, P> original, Context newContext) { context = newContext; factory = original.factory; } public abstract GenericInflater<T, P> cloneInContext(Context newContext); @SuppressWarnings("unchecked") public final T createItem(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException { Constructor<?> constructor = GenericInflater.constructorMap.get(name); try { if (constructor == null) { Class<?> clazz = context.getClassLoader().loadClass( prefix != null ? prefix + name : name); constructor = findConstructor(clazz); GenericInflater.constructorMap.put(name, constructor); } Object[] args = constructorArgs; args[1] = attrs; return (T) constructor.newInstance(args); } catch (NoSuchMethodException e) { InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + (prefix != null ? prefix + name : name)); ie.initCause(e); throw ie; } catch (Exception e) { InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + constructor.toString()); ie.initCause(e); throw ie; } } private final T createItemFromTag(XmlPullParser parser, String name, AttributeSet attrs) { try { T item = factory == null ? null : factory.onCreateItem(name, context, attrs); if (item == null) { if (name.indexOf('.') < 0) { item = onCreateItem(name, attrs); } else { item = createItem(name, null, attrs); } } return item; } catch (InflateException e) { throw e; } catch (ClassNotFoundException e) { InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } catch (Exception e) { InflateException ie = new InflateException( attrs.getPositionDescription() + ": Error inflating class " + name); ie.initCause(e); throw ie; } } protected Constructor<?> findConstructor(Class<?> clazz) throws NoSuchMethodException { return clazz.getConstructor(GenericInflater.constructorSignature); } public Context getContext() { return context; } public String getDefaultPackage() { return defaultPackage; } public final Factory<T> getFactory() { return factory; } public T inflate(int resource, P root) { return inflate(resource, root, root != null); } public T inflate(int resource, P root, boolean attachToRoot) { XmlResourceParser parser = getContext().getResources().getXml(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } } public T inflate(XmlPullParser parser, P root) { return inflate(parser, root, root != null); } @SuppressWarnings("unchecked") public T inflate(XmlPullParser parser, P root, boolean attachToRoot) { synchronized (constructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); constructorArgs[0] = context; T result = (T) root; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { ; } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } T xmlRoot = createItemFromTag(parser, parser.getName(), attrs); result = (T) onMergeRoots(root, attachToRoot, (P) xmlRoot); rInflate(parser, result, attrs); } catch (InflateException e) { throw e; } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } return result; } } protected boolean onCreateCustomFromTag(XmlPullParser parser, T parent, final AttributeSet attrs) throws XmlPullParserException { return false; } protected T onCreateItem(String name, AttributeSet attrs) throws ClassNotFoundException { return createItem(name, defaultPackage, attrs); } protected P onMergeRoots(P givenRoot, boolean attachToGivenRoot, P xmlRoot) { return xmlRoot; } @SuppressWarnings("unchecked") private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser .getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } if (onCreateCustomFromTag(parser, parent, attrs)) { continue; } String name = parser.getName(); T item = createItemFromTag(parser, name, attrs); ((P) parent).addItemFromInflater(item); rInflate(parser, item, attrs); } } public void setDefaultPackage(String defaultPackage) { this.defaultPackage = defaultPackage; } public void setFactory(Factory<T> factory) { if (factorySet) { throw new IllegalStateException( "A factory has already been set on this inflater"); } if (factory == null) { throw new NullPointerException("Given factory can not be null"); } factorySet = true; if (this.factory == null) { this.factory = factory; } else { this.factory = new FactoryMerger<T>(factory, this.factory); } } }