6

I'm trying to develop ORM for database synchronisation and decided to give Java reflection a go. I have a library that defines Synchronised annotation like this

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Synchronised {
    String tableName();
    SynchronisationType synchronisationType();
}

In android project I use this annotation to mark a model class

@Synchronised(tableName="UserAccounts", synchronisationType=SynchronisationType.DownloadOnly)
public class UserAccount {   
    @SynchronisedField(fieldName="CompanyFk")
    private int companyId;
    @SynchronisedField(fieldName="Id")
    private int userId;
    @SynchronisedField(fieldName="Username")
    private String username;
    @SynchronisedField(fieldName="Password")
    private String password;
    @SynchronisedField(fieldName="Salt")
    private String salt;
    @SynchronisedField(fieldName="IsLocked")
    private boolean isLocked;
    @SynchronisedField(fieldName="HasMobileAccess")
    private boolean hasMobileAccess;
}

After some investigation a "loader" method was finally written that allows discovering model classes in current apk. It should retrieve all classes that are marked as "Synchronised", but the issue here is that getAttribute(Synchronised.class) doesn't work. Manually iterating annotations and searching for the attribute (instanceof, etc.) doesn't work either. When debugging I noticed that annotation coming from reflection is actually a proxy (getClass() gives "class Proxy2", while annotation.AnnotationType() returns correct name) - this explains why non of my previous attempts succeeded. When trying to cast directly - a cast exception is thrown (understandably). Basically I'm at a loss here so any idea is welcomed.

Model loading method (in library project):

public static List<Class> getModels(Context context, String packageName)
        throws IOException, URISyntaxException, ClassNotFoundException,
            NameNotFoundException {

        String apkName = context.getPackageManager().getApplicationInfo(packageName, 0).sourceDir;
        DexFile dexFile = new DexFile(apkName);
        //PathClassLoader classLoader = new PathClassLoader(apkName, ClassLoader.getSystemClassLoader());       
        PathClassLoader classLoader = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());

        List<Class> classes = new ArrayList<Class>();
        Enumeration<String> entries = dexFile.entries();

        while (entries.hasMoreElements()) {

            String entry = entries.nextElement();
            // only check items that exist in source package and not in libraries, etc.
            if (entry.startsWith(packageName)) {
                Log.d(TAG, "Entry: " + entry);

                Class<?> entryClass = classLoader.loadClass(entry);//dexFile.loadClass(entry, classLoader);
                if (entryClass != null) {
                    Annotation[] annotations = entryClass.getAnnotations();
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof Synchronised) {
                            classes.add(entryClass);
                        }
                    }
                }
            }               
        }

        return classes;     
    }

[SOLUTION] The class loader needs to be chained like this:

PathClassLoader classLoader2 = new PathClassLoader(apkName, Thread.currentThread().getContextClassLoader());
DexClassLoader classLoader = new DexClassLoader(apkName, new ContextWrapper(context).getCacheDir().getAbsolutePath(), null, classLoader2);

If you still have no idea what I'm talking about, thanks for reading this lengthy post (-.

Audrius
  • 2,836
  • 1
  • 26
  • 35

1 Answers1

2

Get just annotation that you are interested in:

Annotation<Synchronised> annotation = entryClass.getAnnotation(Synchronised.class);

Update:

The problem is that DexFile.getClass() apparently returns a proxy. OP has found the anser and updated the question with a solution.

Peter Knego
  • 79,991
  • 11
  • 123
  • 154
  • Where have you found a generic version of getAnnotations? I can only see getAnotation(class) and getAnnotations(). – Audrius Feb 24 '11 at 15:08
  • Oh my bad: it does not return an array. But how could it - one class can have only one annotation of type Synchronized on it: http://developer.android.com/reference/java/lang/Class.html#getAnnotation(java.lang.Class) – Peter Knego Feb 24 '11 at 15:13
  • That's right, but this is the issue I'm having. getAnnotation(Synchronised.class) just returns null. If I use getAnnotations() and iterate through it I can see my annotation, but it looks like a dynamic proxy so that even comparing classes does not work. annotation.getClass().toString() gives me "class Proxy2", annotation.annotationType().toString() gives me a correct class name. So I assume that it is possible to retrieve my true annotation object from dynamic proxy, but I'm not sure how. – Audrius Feb 24 '11 at 15:19
  • Did you try entryClass.isAnnotationPresent(..)? – Peter Knego Feb 24 '11 at 15:32
  • Yes I did and it returns false. It looks like I'm not the only one having this [problem](http://android.bigresource.com/Track/android-PLHurxsub/) – Audrius Feb 24 '11 at 15:48
  • 2
    If DexClass really returns a proxy, try loading classes in a different way: http://stackoverflow.com/questions/3022454/how-to-load-a-java-class-dynamically-on-android-dalvik/3024261#3024261 – Peter Knego Feb 24 '11 at 16:26
  • You are right, after a couple of experiments I got it working. Updated question with the solution. Thanks! – Audrius Feb 24 '11 at 17:17