2
\$\begingroup\$

In my library Transfuse I use a code generator to build a handful of classes that look up resources. Each of these look up classes are proxied by a static utility class so they may be referenced before the generated class is built. For instance, the Parcels class can be used to wrap an object with a generated Parcelable class:

ExampleParcel parcel = new ExampleParcel();
Parcelable parcelable = Parcels.wrap(parcel);
// ... load into bundle as extra, etc

Parcels proxies a generated class called Transfuse$Parcels.

Parcels:

public final class Parcels {

    public static final String PARCELS_NAME = "Parcels";
    public static final String PARCELS_REPOSITORY_NAME = "Transfuse$Parcels";
    public static final String PARCELS_PACKAGE = "org.androidtransfuse";

    private static ParcelRepository instance;

    static{
        try{
            Class injectorClass = Class.forName(PARCELS_PACKAGE + "." + PARCELS_REPOSITORY_NAME);
            instance = (ParcelRepository) injectorClass.newInstance();
        } catch (ClassNotFoundException e) {
            instance = null;
        } catch (InstantiationException e) {
            throw new TransfuseRuntimeException("Unable to instantiate generated ParcelRepository", e);
        } catch (IllegalAccessException e) {
            throw new TransfuseRuntimeException("Unable to access generated ParcelRepository", e);
        }
    }

    private Parcels(){
        // private utility class constructor
    }

    public static Parcelable wrap(Object input) {
        if(instance == null){
            throw new TransfuseRuntimeException("Unable to find " + PARCELS_REPOSITORY_NAME + " class");
        }
        return instance.wrap(input);
    }
}

Transfuse$Parcels:

@Generated(value = "org.androidtransfuse.TransfuseAnnotationProcessor", date = "01/12/2013 16:56:02 MST")
public class Transfuse$Parcels
    implements ParcelRepository
{

    private final Map<Class, ParcelableFactory> parcelWrappers = new HashMap<Class, ParcelableFactory>();

    public Transfuse$Parcels() {
        parcelWrappers.put(...);
    }

    @Override
    public Parcelable wrap(Object input) {
        return parcelWrappers.get(input.getClass()).buildParcelable(input);
    }
    // Define ParcelableFactory classes...
}

Is this technique of loading the generated class using the static initialization block optimal? As you can see, if the class is not found (not generated possibly) then the ParcelRepository instance ends up being null and wrap() would always return null. Should wrap() throw a runtime exception if an instance is not found instead?

If you take a look at the library, the code above is a simplification for example purposes: Parcels

\$\endgroup\$
5
  • \$\begingroup\$ Is there a reason why Parcels and Transfuse$Parcels can not be in the same jar? \$\endgroup\$ Commented Jan 24, 2013 at 9:30
  • \$\begingroup\$ Yes, because Parcels is the public API, whereas Transfuse$Parcels is generated per client code configuration. Parcels is readily available while Transfuse$Parcels may not even exist until compile time. \$\endgroup\$ Commented Jan 24, 2013 at 16:16
  • \$\begingroup\$ What are your criteria for optimal? \$\endgroup\$ Commented Jan 24, 2013 at 17:03
  • \$\begingroup\$ Good question. "Optimal" is rather subjective. Personally, I prefer to only use static if absolutely necessary, so if static is not required, then that solution would be preferred. Also, are all the exceptions thrown necessary and correct? \$\endgroup\$ Commented Jan 24, 2013 at 18:33
  • \$\begingroup\$ I encourage you to take a look at the real code in the library as well: github.com/johncarl81/transfuse/blob/master/transfuse-api/src/… \$\endgroup\$ Commented Jan 24, 2013 at 18:34

1 Answer 1

2
\$\begingroup\$

I ended up taking a bit of a different route with this problem. It seems I was not adding additional proxied resources if they were not contained within the current compiled package... as in, they were included in included libraries. So, my proxy utility looks like the following:

public abstract class GeneratedCodeRepository<T> {

    private ConcurrentMap<Class, T> generatedMap = new ConcurrentHashMap<Class, T>();

    public GeneratedCodeRepository(String repositoryPackage, String repositoryName) {
        loadRepository(getClass().getClassLoader(), repositoryPackage, repositoryName);
    }

    public T get(Class clazz){
        T result = generatedMap.get(clazz);
        if (result == null) {
            T value = findClass(clazz);
            if(value == null){
                return null;
            }
            result = generatedMap.putIfAbsent(clazz, value);
            if (result == null) {
                result = value;
            }
        }

        return result;
    }

    public abstract T findClass(Class clazz);

    /**
     * Update the repository class from the given classloader.  If the given repository class cannot be instantiated
     * then this method will throw a TransfuseRuntimeException.
     *
     * @throws TransfuseRuntimeException
     * @param classLoader
     */
    public final void loadRepository(ClassLoader classLoader, String repositoryPackage, String repositoryName){
        try{
            Class repositoryClass = classLoader.loadClass(repositoryPackage + "." + repositoryName);
            Repository<T> instance = (Repository<T>) repositoryClass.newInstance();
            generatedMap.putAll(instance.get());

        } catch (ClassNotFoundException e) {
            //nothing
        } catch (InstantiationException e) {
            throw new TransfuseRuntimeException("Unable to instantiate generated Repository", e);
        } catch (IllegalAccessException e) {
            throw new TransfuseRuntimeException("Unable to access generated Repository", e);
        }
    }
}

findClass() then can be defined to lookup the given repository class using reflection.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.