Użycie interfejsu bez użycia jego deklaracji

0

Wiem, że jest sobie taki interfejs:

com.xxx.Listener{
	void eventHandlerA();
	void eventHandlerB();
}

którego implementacja jest używana przez bliżej nieznaną klasę:

com.yyy.BaseClass{
	addXXXListener(com.xxx.Listener listener);
	removeXXXListener(com.xxx.Listener listener);
	//...
}

Jest też sobie klasa com.zzz.DerivedClass extends BaseClass {/*...*/}, do obiektu której mam faktyczny dostęp.
Do żadnego źródła tych klas, ani interfejsów nie mam dostępu. Wszystko co jest mi znane, to z dokumentacji i wyciągania przez RTTI.

Potrzebuję wywoływać metody addXXXListener i removeXXXListener, ale problem jest z argumentem. Nie mogę jawnie użyć obiektu implementującego com.xxx.Listener. Jednak muszę mieć obiekt, który będzie ten interfejs implementował.
Na razie wykombinowałem coś takiego co faktycznie działa:

@SuppressWarnings("MarkerInterface")
private interface ProxyListener extends com.xxx.Listener {} //tu leci warning

ProxyListener proxyListener = new ProxyListener(){
	@Override void eventHandlerA() {/*...*/}
	@Override void eventHandlerB() {/*...*/}
}

Ale nadal kompilator irytuje mnie tym jednym pozostałym ostrzeżeniem o tym, że próbuję wrednie użyć prywatnego API. :)
Próbowałem kombinować z dynamic proxy i definicją własnego InvocationHandler'a, ale wszystkie przykłady użycia które widziałem zawsze gdzieś jawnie wymuszają użycie w kodzie com.xxx.Listener. A ja szukam sposobu aby nazwa tego interfejsu była zapisana najlepiej w jakimś stringu, a nie w kodzie, no i oczywiście aby uzyskać obiekt, który będzie mógł zostać łyknięty przez metody wywoływane też przez refleksję z DerivedClass.
Wywołuję (w uproszczeniu) w taki sposób:

private final Method addYYYListener = getMethod(getDerivedClassObject(),
	"addXXXListener", "com.xxx.Listener");
private final Method removeYYYListener = getMethod(getDerivedClassObject(),
	"removeXXXListener", "com.xxx.Listener"),
//...
	if(!addYYYListener.isAccessible())
		addYYYListener.setAccessible(true);
	addYYYListener.invoke(getDerivedClassObject(), proxyListener);
//...
	if(!removeYYYListener.isAccessible())
		removeYYYListener.setAccessible(true);
	removeYYYListener.invoke(getDerivedClassObject(), proxyListener);

Wszystko mi działa, ale jednak chciałbym wyeliminować z proxyListener użycie interfejsu com.xxx.Listener, a jednocześnie mieć ten kontrolowany przez siebie obiekt, który go implementuje.
Takie mam zboczenie, że zawsze piszę kod "0 warnings", a ten jeden którego nie da się nawet wyłączyć (no i nie powinno) trochę mnie męczy...
Poza tym w niektórych przypadkach nie będę mógł użyć tego myku z dziedziczeniem po interfejsie takim jak com.xxx.Listener bo prawdopodobnie nie będzie on dostępny.
Jakieś pomysły?

ps. Gdyby ktoś chciał mnie przekonywać, żebym to olał, to niech sobie daruje. Problem nie dotyczy tylko jednego przypadku bo to tylko pierwszy z wielu.

0

Może spróbuj z własnym ClassLoaderem? Tzn najpierw zrób kopię interfejsu i ją rozszerzaj, a następnie zrób ClassLoadera który przekierowuje ładowanie definicji kopii interfejsu do ładowania oryginału interfejsu.

0

Wszedzie tam, gdzie masz podac com.xxx.SomeListener.class mozesz zrobic Class.forName("com.xxx.SomeListener") - nie wystarczy to?

0

A nie możesz "podmienić" sobie tej klasy? Tzn napisać własną klasę która będzie miała taką samą sygnaturę?

0

Jak nie ma dostepu do klasy w zadnej formie, to co sie dzieje podczas runtime? Przeciez ta klasa musi najpozniej w runtime byc dostepna, a wiec jej Class.forName() wzglednie jakis fancy ClassLoader.forName musi ta klase zwrocic. Inaczej jvm sie wywali z ClassNotFoundError czy jakos tak. Hm?

1
Shalom napisał(a):

A nie możesz "podmienić" sobie tej klasy? Tzn napisać własną klasę która będzie miała taką samą sygnaturę?

Wydaje mi się, że bez zabaw z classloaderem straci się możliwość sprawdzenia czy ten prywatny interfejs istniał przed podmianą i straci się możliwość sprawdzenia czy jest on identyczny jak ten oryginalny. To jest prywatne API więc zarówno interfejs może sobie polecieć w niebyt jak i się zmienić, więc pasowałoby coś takiego wykryć, moim zdaniem.

0

Dzięki wszystkim za pomoc.
Problem faktycznie okazał się trywialny, a ja miałem po prostu pomroczność jasną. Z drugiej strony używanie refleksji, to jak pisanie skryptów. Jeden głupi błąd i nic nie działa. :)
Po tym wszystkim sprawdziłem sobie rzeczywiście czy to co mam mi działa i jak na razie działa dla wszystkich przypadków, które jak na razie sprawdziłem.

A tu przykładzik odpalenia dla przypadkowo trafiających :) do tego wątku:

package Inne.OtherPackage;

public interface Example
{
	int getAnInt(String in);
	String getAString(String in);
}

package Inne;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ImplementInterfaceWithReflection
{
	public static void main(String[] args) throws Exception
	{
		final String interfaceName = "Inne.OtherPackage.Example";
		System.out.println("Interface name: " + interfaceName);
		final Object proxyInstance = implementedInterface(interfaceName,
			new ExampleImplementation());
		final Inne.OtherPackage.Example example =
			(Inne.OtherPackage.Example)proxyInstance;
		System.out.println("Wynik: " + example.getAnInt("599"));
		System.out.println("Wynik: " + example.getAString("Blabla"));
	}

	/**
	 * Zwraca obiekt implementujący interfejs {@literal interfaceName} za
	 * pomocą podanej implementacji ({@literal implementation)
	 * @param interfaceName pełna nazwa paietowa interfejsu
	 * @param implementation implementacja
	 * @return obiekt który można rzutować do interfaceName
	 * @throws ClassNotFoundException gdy się sypnie
	 */
	private static Object implementedInterface(String interfaceName,
		InvocationHandler implementation) throws ClassNotFoundException
	{
		final Class<?> c = Class.forName(interfaceName);
		return Proxy.newProxyInstance(c.getClassLoader(),
			new Class<?>[] {c}, implementation);
	}

	private static class ExampleImplementation implements InvocationHandler
	{
		@Override public Object invoke(Object proxy, Method m, Object[] args)
		{
			System.out.println("Args: " + (args != null ? args.length :
				"none"));
			System.out.println("Method called: " + m);
			System.out.println("Method name: " + m.getName());
			switch(m.getName())
			{
			case "getAnInt":
				if(args.length > 0 && args[0] instanceof String)
				{
					final String arg = (String)args[0];
					return Integer.parseInt(arg);
				}
				return 98;
			case "getAString":
				if(args.length > 0 && args[0] instanceof String)
				{
					final String arg = (String)args[0];
					return "Na pewno coś ważnego: " + arg;
				}
				return "Jakiś String";
			default:
				return "unknown method";
			}
		} //public Object invoke(Object proxy, Method method, Object[] args)
	}
}

No i wynik:

Interface name: Inne.OtherPackage.Example
Args: 1
Method called: public abstract int Inne.OtherPackage.Example.getAnInt(java.lang.String)
Method name: getAnInt
Wynik: 599
Args: 1
Method called: public abstract java.lang.String Inne.OtherPackage.Example.getAString(java.lang.String)
Method name: getAString
Wynik: Na pewno coś ważnego: Blabla
0

Czy chodzi tylko o to, aby nie było warningu w czasie kompilacji?

Może zadziała anotacja SuppressWarnings (trzeba jednak znać typ tego warninga)?
http://docs.oracle.com/javase/7/docs/api/java/lang/SuppressWarnings.html

0

Nie, nie tylko. Kolejne przypadki jakie miałem do przeskoczenia to były przypadki bez interfejsów, a nawet bez informacji o nich (bez jawnej ścieżki kanonicznej). Teraz mogę sobie je sam skombinować, więc problem zniknął. :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1