package org.commcare.models.database; import android.content.Context; import org.commcare.models.AndroidPrototypeFactory; import org.javarosa.core.util.externalizable.Externalizable; import org.javarosa.core.util.externalizable.PrototypeFactory; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import dalvik.system.DexFile; /** * @author Phillip Mates (pmates@dimagi.com) */ public class AndroidPrototypeFactorySetup { private static final String[] packageNames = new String[]{"org.javarosa", "org.commcare", "org.odk.collect"}; private static PrototypeFactory factory; /** * Basically this is our PrototypeManager for Android */ public static PrototypeFactory getPrototypeFactory(Context c) { if (factory != null) { return factory; } try { factory = new AndroidPrototypeFactory(new HashSet<>(getClasses(c))); } catch (Exception e) { throw new RuntimeException(e); } return factory; } public static void setDBUtilsPrototypeFactory(PrototypeFactory factory) { AndroidPrototypeFactorySetup.factory = factory; } /** * Scans all classes accessible from the context class loader which belong to the given package and subpackages. */ private static List<String> getClasses(Context c) throws IOException { ArrayList<String> classNames = new ArrayList<>(); String zpath = c.getApplicationInfo().sourceDir; if (zpath == null) { zpath = "/data/app/org.commcare.android.apk"; } DexFile df = new DexFile(new File(zpath)); for (Enumeration<String> en = df.entries(); en.hasMoreElements(); ) { String cn = en.nextElement(); loadClass(cn, classNames); } return classNames; } public static void loadClass(String cn, List<String> classNames) { try { for (String packageName : packageNames) { if (cn.startsWith(packageName)) { //TODO: These optimize by preventing us from statically loading classes we don't need, but they take a _long_ time to run. //Maybe we should skip this and/or roll it into initializing the factory itself. Class prototype = Class.forName(cn); if (prototype.isInterface()) { continue; } boolean emptyc = false; for (Constructor<?> cons : prototype.getConstructors()) { if (cons.getParameterTypes().length == 0) { emptyc = true; } } if (!emptyc) { continue; } if (Externalizable.class.isAssignableFrom(prototype)) { classNames.add(cn); } } } } catch (Error | Exception e) { e.printStackTrace(); } } }