0

My goal is to create an instance from a class that implements an interface and extends another class.

...Entity annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Entity {

    String visibileName();

}

...implementsIEventDesignDialog

      public class EventDesignDialog implements IEventDesignDialog{

        private String show;
        private String dateAndTimeDisplayFormat;
        private String eventType;


        @Entity(visibileName = "Show")
        public String getShow() {
            return this.show;
        }

        @Entity(visibileName = "Date And Time display format")
        public String getDateAndTimeDisplayFormat() {
            return this.dateAndTimeDisplayFormat;
        }

        @Entity(visibileName = "Event Type")
        public String getEventType() {
            System.out.println("get event type method invokde successfully");
            return this.eventType;
        }
}

IEventDesignDialog interface:

public interface IEventDesignDialog extends IPage{

    public String getShow();

    public String getDateAndTimeDisplayFormat();

    public String getEventType();


}

IPage interface:

public interface IPage {

}

Dynamic proxy implementation:

public class IPageProxy implements InvocationHandler {
    private List<Method> entityMethods;



    private Class <? extends IPage> screenClazz;

    public IPageProxy(final Class <? extends IPage> screenClazz) {
        entityMethods = new ArrayList<>();
        getEntityAnnotatedMethods(screenClazz);
        // Accept the real implementation to be proxied
        this.screenClazz = screenClazz;
    }


    /**
     * create an page instance
     * @param type
     * @param
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static IPage getInstance(final Class<? extends IPage> type)
            throws InstantiationException, IllegalAccessException {

        List<Class<?>> interfaces = new ArrayList<>();
        interfaces.addAll(Arrays.asList(type.getInterfaces()));

        return (IPage) Proxy.newProxyInstance(
                type.getClassLoader(),
                findInterfaces(type),
                new IPageProxy(type)
             );

        /*return (IPage) Proxy.newProxyInstance(type.getClassLoader(),
               interfaces.toArray(new Class<?>[interfaces.size()])
                , new IPageProxy(type));*/
    }


    /**
     * get all methods that annotated with @Entity annotation
     * and add it for entityMethods array List
     * @param screenClazz
     */
    private void getEntityAnnotatedMethods(final Class <? extends IPage>  screenClazz) {
        // Scan each interface method for the specific annotation
        // and save each compatible method
        for (final Method m : screenClazz.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Entity.class)) {
                entityMethods.add(m);
            }
        }
    }


    static Class<?>[] findInterfaces(final Class<? extends IPage> type) {
        Class<?> current = type;

        do {
            final Class<?>[] interfaces = current.getInterfaces();

            if (interfaces.length != 0) {
                return interfaces;
            }
        } while ((current = current.getSuperclass()) != Object.class);

        throw new UnsupportedOperationException("The type does not implement any interface");
    }



    @Override
    public Object invoke(
            final Object proxy,
            final Method method,
            final Object[] args) throws InvocationTargetException, IllegalAccessException {
        // A method on MyInterface has been called!
        // Check if we need to go call it directly or if we need to
        // execute something else before!


        if (entityMethods.contains(method)) {
            // The method exist in our to-be-proxied list
            // Execute something and the call it
            // ... some other things
            System.out.println("Something else");
        }

        // Invoke original method
        return method.invoke(screenClazz, args);
    }

}

Main class:

public class Main {

    public static void main(String[] args) {
        try {

            ((EventDesignDialog)getInstance(EventDesignDialog.class)).getEventType();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    @SuppressWarnings("unchecked")
    public static <T extends IPage> T getInstance(final Class<? extends IPage> type) throws InstantiationException, IllegalAccessException {
        return (T) IPageProxy.getInstance(type);
    }

}

The following exception is thrown:

  Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy2 cannot be cast to abc.EventDesignDialog
    at abc.Main.main(Main.java:8)
Ali Taha
  • 199
  • 1
  • 2
  • 16
  • The proxy that is created is not a `Screen` but rather a class that implements all of `Screen`s `interface`s, so they are not equal. That's why you get that exception – Lino Mar 25 '19 at 15:29
  • I remember we discussed about Dynamic Proxies a while ago, I see you're using them frequently! – LppEdd Mar 25 '19 at 15:49
  • yes, I'm investigating this type of code writing. actually, I'm learning how to use this feature. – Ali Taha Mar 25 '19 at 15:52
  • @AliTaha that's good, it's an interesting topic. Let me know if you need other clarifications. – LppEdd Mar 25 '19 at 15:53

1 Answers1

2

You're extending Screen, which means it isn't an interface.
Dynamic Proxies work only if a base interface is present in the hierarchy.

interfaces.size() == 0

Thus the proxy can't implement any interface, and obviously it isn't part of the Screen hierarchy.


If Screen was an interface, your method is still too complex. This

public static Screen getInstance(Class<? extends Screen> type)

is sufficient.


You still receive an exception because

Class#getInterfaces

returns the interfaces which are implemented by this class.

That means if you invoke it on EventDesignDialog.class, it will return an empty array.
That means if you invoke it on EntityDesignDialog.class, still it will return an empty array.
When invoking it on Screen.class, it will return

[IPage.class]

You need to loop the hierarchy with

Class#getSuperclass

until you find a suitable interface.

A possible implementation might look like

static Class<?>[] findInterfaces(final Class<?> type) {
    Class<?> current = type;

    do {
        final Class<?>[] interfaces = current.getInterfaces();

        if (interfaces.length != 0) {
            return interfaces;
        }
    } while ((current = current.getSuperclass()) != Object.class);

    throw new UnsupportedOperationException("The type does not implement any interface");
}

which means you need to change your code to

return (IPage) Proxy.newProxyInstance(
              type.getClassLoader(),
              findInterfaces(type),
              new IPageProxy(type)
           );

But, being that you already know the result will be an IPage proxy, you can just

return (IPage) Proxy.newProxyInstance(
              type.getClassLoader(),
              new Class[] { IPage.class },
              new IPageProxy(type)
           );

Here

public static IPage getInstance(final Class<? extends IPage> type)

you're returning an IPage, but here

((EventDesignDialog)getInstance(EventDesignDialog.class))

you're trying to downcast it, which means you're trying to cast it to a more specific type. This isn't possible as the Proxy isn't of the type EventDesignDialog, but it just implements your IPage interface.

Being that Dynamic Proxies are interface-based, you'll be forced to deal with interfaces.
Trying to cast to concrete classes will always throw an exception.

If you need an IEventDesignDialog, you need a new Proxy specifically for it.

LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • Screen class implements IPage interface, now that is its signature: public static IPage getInstance(Class extends IPage> type) throws InstantiationException, IllegalAccessException same exception thrown : java.lang.ClassCastException: com.sun.proxy.$Proxy11 cannot be cast to com.abc.IPage – Ali Taha Mar 26 '19 at 08:19
  • @AliTaha btw, if you already know that IPage is the wanted interface. Just pass it directly with `new Class[] { IPage.class }` – LppEdd Mar 26 '19 at 10:54
  • I need to create instances from classes that implement Ipage :) – Ali Taha Mar 26 '19 at 11:57
  • I added full code implementation above, it still throws an exception, can you please show how to fix it? – Ali Taha Mar 26 '19 at 12:48
  • @AliTaha see updared answer. – LppEdd Mar 26 '19 at 13:03