Handle custom URL schemes in an OS X Java application - java

The Info.plist of our Java-based application contains following entries:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
<plist version="0.9">
<dict>
...
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>myApp handler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
...
</dict>
</plist>
It should handle an URL like myapp://foobar/bazz. Opening the application works fine, but how the application should obtain the clicked URL?

For Objective C the answer can be found here: When an OS X app is launched by a registered URL scheme, how do you access the full URL?
The solution for Java is to rewrite the ObjC code into plain C, then translate that into Java, with the help of a set of classes under org.eclipse.swt.internal.cocoa.*.
As a reference for the ObjC-to-C translation, we need Apple's Objective-C Runtime Reference.
Plain C version
First, let's translate
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:targetObject
andSelector:#selector(handleAppleEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
into plain C. To invoke a ObjC function in plain C, we use objc_msgSend(). Furthermore, #selector(method_footprint) is substituted by sel_registerName("method_footprint"), and classes are looked up with objc_getClass(). The types id and SEL are equivalent to a pointer (such as void*) or a full-size int (i.e. same size as a void*).
The result:
// id mgr = [NSAppleEventManager sharedAppleEventManager]
SEL sel_ sharedAppleEventManager = sel_registerName("sharedAppleEventManager");
id mgr = objc_msgSend (objc_getClass("NSAppleEventManager"), sharedAppleEventManager);
// and the rest
SEL sel_setEventHandler = sel_registerName("setEventHandler:andSelector:forEventClass:andEventID:");
SEL sel_handleAppleEvent = sel_registerName("handleAppleEvent:withReplyEvent:");
objc_msgSend (mgr, sel_setEventHandler, targetObject, sel_handleAppleEvent, kInternetEventClass, kAEGetURL);
As you can see, we have two subroutine invocations here: The first calls the sharedAppleEventManager message of the NSAppleEventManager class, retrieving a singleton object from that class. The second call is sending the setEventHandler... message to that object, passing 4 arguments (target object, target message, and two event specifiers).
The callback function's declaration, originally:
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
looks like this in plain C:
void handleAppleEvent (id self, SEL selector, NSAppleEventDescriptor* event, NSAppleEventDescriptor* replyEvent)
This means that when the function gets called, it gets sent not only its object reference (id) but also its method footprint (selector).
The callback code looks like this in ObjC to get to the URL:
NSString url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
And in plain C:
id desc_id = objc_msgSend (event_id, sel_registerName("paramDescriptorForKeyword:"), '----');
id url_id = objc_msgSend (desc_id, desc_id, sel_registerName("stringValue"));
One part is still missing:
targetObject needs to be initialized before invoking the code above, and a method matching the handleAppleEvent:withReplyEvent: footprint needs to be created in that target object, and then linked to our plain C event handler (handleAppleEvent()).
This means that we have to create an Objective C class, add a method to it, and then create an object instance of it:
// create an NSObject subclass for our target object
char objcClassName[] = "ObjCAppleEventHandler";
id objcClass = objc_allocateClassPair (objc_getClass("NSObject"), objcClassName);
// add the callback method to the class
SEL sel_handleAppleEvent = sel_registerName("handleAppleEvent:withReplyEvent:");
class_addMethod (objcClass, sel_handleAppleEvent, handleAppleEvent, "i#:##");
// register the class
objc_registerClassPair (objcClass)
// create an object instance
id targetObject = class_createInstance (objcClass, 0);
// ... here follows the above code with the setEventHandler invocation
// (note: `SEL sel_handleAppleEvent` appears twice - the 2nd one can be removed)
This concludes the plain C version.
(Note: The above code was written without testing it, so it may contain errors. The Java code below, however, has been tested.)
Java version
Translation from Plain C to Java is now fairly straight-forward.
The aforementioned ObjC Runtime functions are all available from org.eclipse.swt.internal.cocoa.OS.
First, some presets:
static final long class_NSAppleEventManager = OS.objc_getClass("NSAppleEventManager");
static final long sel_sharedAppleEventManager = OS.sel_registerName("sharedAppleEventManager");
static final long sel_setEventHandler = OS.sel_registerName("setEventHandler:andSelector:forEventClass:andEventID:");
static final long sel_handleAppleEvent = OS.sel_registerName("handleAppleEvent:withReplyEvent:");
static final long sel_paramDescriptorForKeyword = OS.sel_registerName("paramDescriptorForKeyword:");
static final long sel_stringValue = OS.sel_registerName("stringValue");
static final long kInternetEventClass = 0x4755524C; // 'GURL'
static final long kAEGetURL = 0x4755524C; // 'GURL'
static final long kCoreEventClass = 0x61657674; // 'aevt'
static final long kAEOpenApplication = 0x6F617070; // 'oapp'
static final long kAEReopenApplication = 0x72617070; // 'rapp'
static final long keyDirectObject = 0x2d2d2d2d; // '----'
The callback function:
static long handleAppleEvent (long id, long sel, long event_id, long reply_id) {
// This is a handler for AppleEvents that are registered with [NSAppleEventManager setEventHandler:...]
// It matches this selector (footprint):
// - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)reply
// Invoke [[event paramDescriptorForKeyword:keyDirectObject] stringValue] to get the direct object containing the URL
long direct_desc_id = OS.objc_msgSend (event_id, sel_paramDescriptorForKeyword, keyDirectObject);
long direct_str_id = OS.objc_msgSend (direct_desc_id, sel_stringValue);
NSString nsStr = new NSString (direct_str_id);
String str = nsStr.getString();
// now 'str' contains the URL
System.out.println ("handleAppleEvent invoked -- argument: "+url);
return 0;
}
And the code to register the callback function:
// Get access to a callback function for receiving the sel_handleAppleEvent message
aeCallback = new Callback(Main.class, "handleAppleEvent", 4);
long aeProc = aeCallback.getAddress();
// Create a ObjC class that provides a method with the sel_handleAppleEvent footprint
String objcClassName = "ObjCAppleEventHandler";
long objcClass = OS.objc_allocateClassPair(OS.class_NSObject, objcClassName, 0);
OS.class_addMethod(objcClass, sel_handleAppleEvent, aeProc, "i#:##");
OS.objc_registerClassPair(objcClass);
long objcHandlerInstance = OS.class_createInstance (objcClass, 0);
// Invoke [[NSAppleEventManager sharedAppleEventManager] setEventHandler:objcHandlerInstance andSelector:sel_handleAppleEvent forEventClass:kInternetEventClass andEventID:kAEGetURL]
long sharedAppleEventMgr = OS.objc_msgSend (class_NSAppleEventManager, sel_sharedAppleEventManager);
OS.objc_msgSend (sharedAppleEventMgr, sel_setEventHandler, objcHandlerInstance, sel_handleAppleEvent, kInternetEventClass, kAEGetURL);
What's left to do is to build an app bundle from this code and then add the CFBundleURLTypes entries to its Info.plist.
A complete sample source file can be downloaded here: http://files.tempel.org/Various/ObjectiveC-bridging.java.zip

With Java 9, this is easy quite easy, and no longer requires Apple's EAWT classes or any ObjC hackery.
Desktop.getDesktop().setOpenURIHandler((event) -> {
System.out.println("Open URI: " + event.getURI());
// do something with the URI
});
The application needs to be bundled, and the CFBundleURLTypes key must be set.
<!-- Open URIs with scheme example:// -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>example</string>
</array>
<key>CFBundleURLName</key>
<string></string>
</dict>
</array>
Unfortunately this only captures the URI if the application is already running. If the application was launched by opening a URI, the event is not delivered (see comments on ed22's answer).

In case anyone wanted a version using com.apple.eawt.*
This also uses reflection, so it will compile on any platform (Windows etc.). Make sure not to call the method registering the event handler on any non-Apple system ;)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
interface OpenUriAppleEventHandler {
public void handleURI(URI uri);
}
class OpenURIEventInvocationHandler implements InvocationHandler {
private OpenUriAppleEventHandler urlHandler;
public OpenURIEventInvocationHandler(OpenUriAppleEventHandler urlHandler) {
this.urlHandler = urlHandler;
}
#SuppressWarnings({ "rawtypes", "unchecked"})
public Object invoke(Object proxy, Method method, Object[] args) {
if (method.getName().equals("openURI")) {
try {
Class openURIEventClass = Class.forName("com.apple.eawt.AppEvent$OpenURIEvent");
Method getURLMethod = openURIEventClass.getMethod("getURI");
//arg[0] should be an instance of OpenURIEvent
URI uri = (URI)getURLMethod.invoke(args[0]);
urlHandler.handleURI(uri);
} catch (Exception ex) {
ex.printStackTrace();
}
}
return null;
}
}
public class OSXAppleEventHelper {
/**
* Call only on OS X
*/
#SuppressWarnings({ "unchecked", "rawtypes" })
public static void setOpenURIAppleEventHandler(OpenUriAppleEventHandler urlHandler) {
try {
Class applicationClass = Class.forName("com.apple.eawt.Application");
Method getApplicationMethod = applicationClass.getDeclaredMethod("getApplication", (Class[])null);
Object application = getApplicationMethod.invoke(null, (Object[])null);
Class openURIHandlerClass = Class.forName("com.apple.eawt.OpenURIHandler", false, applicationClass.getClassLoader());
Method setOpenURIHandlerMethod = applicationClass.getMethod("setOpenURIHandler", openURIHandlerClass);
OpenURIEventInvocationHandler handler = new OpenURIEventInvocationHandler(urlHandler);
Object openURIEvent = Proxy.newProxyInstance(openURIHandlerClass.getClassLoader(), new Class[] { openURIHandlerClass }, handler);
setOpenURIHandlerMethod.invoke(application, openURIEvent);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Use it like this:
//if(isOSX){
OSXAppleEventHelper.setOpenURIAppleEventHandler(new OpenUriAppleEventHandler() {
#Override
public void handleURI(URI url) {
/* do something with the url */
}
});

Related

Accessing COM Interface with JNA

I'm trying to access the IDesktopWallpaper interface with JNA, but I've hit a wall.
I went through ShOljIdl_core.idl (from Windows 10 SDK) and discovered the GUID of the interface as follows
// IDesktopWallpaper
[
uuid(B92B56A9-8B55-4E14-9A89-0199BBB6F93B),
object
]
and the GUID of the concrete class that implements the interface
// CLSID_DesktopWallpaper
[uuid(C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD)] coclass DesktopWallpaper { interface IDesktopWallpaper; }
So I followed the official example in the JDA github and wrote the following
#ComObject(clsId="{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}")
public interface DesktopWallpaper extends IUnknown{
}
and in Main
Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
try {
Factory factory = new Factory();
try {
DesktopWallpaper dw = factory.createObject(DesktopWallpaper.class);
} finally {
factory.disposeAll();
factory.getComThread().terminate(1 * 1000);
}
} finally {
Ole32.INSTANCE.CoUninitialize();
}
But the factory.createObject(DesktopWallpaper.class) throws No such interface supported(HRESULT: 80004002) (puArgErr=)and I don't know how to get around this or why it is happening.
Can any experts enlighten me on what's happening? (I am a complete noob) I will provide any further info that's necessary. Can JNA achieve what I want or do I have to use something else like Com4j?
TL;DR
After a lot of googling, I finally got it to work. The problem (at least to my current understanding) is that the current JNA helpers only work with interfaces that inherit from IDispatch. So if the interface in question such as IDesktopWallpaper does not inherit from IDispatch, then one should use vtable for function calls. I got this information from this amazing Google forum post in which the poster also provided a code sample that got me started.
Here is some working code for the SetWallpaper() function:
public class DesktopWallpaperHandler extends Unknown{
private static final GUID CLSID_DesktopWallpaper = new GUID("{C2CF3110-460E-4fc1-B9D0-8A1C0C9CC4BD}");
private static final GUID IID_IDesktopWallpaper = new GUID("{B92B56A9-8B55-4E14-9A89-0199BBB6F93B}");
private DesktopWallpaperHandler(Pointer pvInstance) {
super(pvInstance);
}
public static DesktopWallpaperHandler create(){
PointerByReference p = new PointerByReference();
WinNT.HRESULT hr = Ole32.INSTANCE.CoCreateInstance(CLSID_DesktopWallpaper, null, WTypes.CLSCTX_SERVER, IID_IDesktopWallpaper, p);
COMUtils.checkRC(hr);
DesktopWallpaperHandler handler = new DesktopWallpaperHandler(p.getValue());
return handler;
}
public void SetWallpaper(WTypes.LPWSTR monitor, WTypes.LPWSTR wallpaper){
int result = this._invokeNativeInt(3, new Object[]{this.getPointer(), monitor, wallpaper});
COMUtils.checkRC(new HRESULT(result));
}
}
And then in Main:
Ole32.INSTANCE.CoInitializeEx(Pointer.NULL, Ole32.COINIT_MULTITHREADED);
try {
WTypes.LPWSTR path = new LPWSTR("C:\\Users\\Harry\\Desktop\\1.jpg");
DesktopWallpaperHandler handler = DesktopWallpaperHandler.create();
handler.SetWallpaper(null, path);
} finally {
Ole32.INSTANCE.CoUninitialize();
}
The original motive to use IDesktopWallpaper was to access the fade in transition effect, and now that can be done by adding the following:
User32.INSTANCE.SendMessageTimeout(User32.INSTANCE.FindWindow("Progman", null), 0x52c, 0, 0, 0, 500, null);

Multithreading a java agent

A java agent has to upload a file in the background and return the Url of the uploaded file. While uploading, the agent has to report its progress.
I marked the agent to be "Run in background client thread.
I am stuck in the following dilemma:
I can run the agent from Lotus Script and pass its parameters in an In-Memory file, but the client actually doesn't run in its own thread. Instead, it blocks the whole client.
I can run the agent from a formula, but then I can't pass any parameters!
If I use Lotus Script and take care of the threading on my own in Java, my thread doesn't even get started!
I've read that the Notes client doesn't support multithreading. But I can't make the agent RunOnServer because it is accessing a web server available only for the client.
This is related to another question of mine by the way.
Is there any better solution for this?
If you can't make the agent RunOnServer then you can use LS2J instead of agent. Create your own class with threading and use its properties.
Here is example with custom Java Class and Java Timer:
import java.util.Timer;
import java.util.TimerTask;
public class Test
{
private boolean _isOver;
private int _counter;
private Timer _timer;
private String _url;
public Test()
{
_timer = new Timer("Timer");
}
public void Start() //Add parameters here that you want to use in Java
{
_counter = 0;
_isOver = false;
_url = "";
TimerTask timerTask = new TimerTask()
{
public void run()
{
if (_counter++ == 9)
{
_isOver = true;
_timer.cancel();
_url = "http://stackoverflow.com/";
}
}
};
_timer.scheduleAtFixedRate(timerTask, 30, 5000);
}
public int getCounter() { return _counter; }
public boolean getIsOver() { return _isOver; }
public String getURL() { return _url; }
}
In LotusScript add global LS2J variables:
(Options)
Uselsx "*javacon"
Use "MyJavaLibrary"
(Declarations)
Dim jSession As JavaSession
Dim jClass As JavaClass
Dim jObject As JavaObject
Sub Initialize
Set jSession = New JavaSession()
Set jClass = jSession.GetClass("MyClass")
Set jObject = jClass.CreateObject
End Sub
To start Java object use (in LotusScript of Button):
Call jObject.Start() 'Call with parameters that you want to use in Java
To check state and show progress use (in LotusScript of Timer):
If jObject.getIsOver() Then
s$ = jObject.getURL()
'Show results
Else
i% = jObject.getCounter()
'Show progress
End If

Is it possible to import Java models on Objective c?

I have some Java model on the server but in my iOS app I'm using JSON without a serializator because my server hasn't an Objective-C model generator.
Is it possible to integrate Java model class in Objective-C?
How?
Yes you definitely can but you will see you are much better off "translating".
These are the step by step instructions (source: http://hints.macworld.com/article.php?story=20040321163154226).
Create a new Foundation project called "HelloBridge"
Create a new Pure Java Package target called "JavaClasses"
Create a new Java class called "HelloBridge.java" an add it to target
"JavaClasses":
public class HelloBridge {
private String string = "Hello";
public void setString(String string) {
this.string = string;
}
public String getString() {
return this.string;
}
public void printString() {
System.out.println(this.string);
}
}
Add "HelloBridge.java" to the "Sources" build phase in target
"JavaClasses"
Create a new empty file called "JavaInterfaces.h" and add it to
target "HelloBridge":
// Provide Objective-C interfaces for the Java classes
// Not only good practice, it provides Code Sense
#interface java_util_Vector : NSObject
{}
- (void)add:(id)anObject;
- (id)get:(int)index;
#end
#interface HelloBridge : NSObject
{}
- (void)setString:(NSString *)string;
- (NSString *)getString;
- (void)printString;
#end
Modify "main.m":
#import <Foundation/Foundation.h>
#import "JavaInterfaces.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Load the Java VM
id vm = NSJavaSetupVirtualMachine();
// Start the Java class loader
NSJavaClassesFromPath(nil, nil, YES, &vm);
// Load a new instance of the java.util.Vector Java class into an Objective-C pointer
java_util_Vector * vector = NSJavaObjectNamedInPath(#"java.util.Vector", nil);
[vector add:#"one item!"];
NSLog(#"item 1=%#",[vector get:0]);
[vector release];
// Load a new instance of our custom HelloBridge Java class into an Objective-C pointer
HelloBridge * hello = NSJavaObjectNamedInPath(#"HelloBridge", nil);
NSLog(#"item 1=%#",[hello getString]);
[hello setString:#"Test"];
NSLog(#"item 1=%#",[hello getString]);
[hello printString];
[hello release];
[pool release];
return 0;
}
Select "JavaClasses" from the targets drop box and Build it
Select "HelloBridge" from the targets drop box and Build/Run it

How to call a java shared script library from a Java agent in Xpages?

I have an agent that is set to run every day at 8:00
I want to write a java code (in a shared library) and call that library from the agent with parameters.
For Example:
Agent code:
// ....
checkAndSendMail(email_1);
checkAndSendMail(email_2);
// ....
java library code:
public class Check{
public void checkAndSendMail(String email_param){
// ...
mail.send(email_param);
// ...
}
}
Can I call a java shared script library from a Java agent in Xpages?
if yes, then how to call?
The JVM in XPages and Domino Java Agents is separate so you can't share java code between them.
You can create java code if you go to script libraries section in the designer
not the Java/Jar section that is for XPages. And create a new Java Library that can be included inside a Java agent.
You can do this, but this is only possible with a lot of "overhead". Assuming you want to load a Java class in an Agent you could do the following:
Get the design note containing your class (f.e. with a special design view or the Java NAPI)
Export the note with DXL
Extract the content all "$ClassData" fields
Base64 decode the content
Skip the first 42 bytes , and load the resulting byte array with your own class loader (override the findClass method which does a defineClass call)
Now you can instantiate the class in your agent and access it via reflection
As you can see, it is possible, but for a higher effort than just "doubling" the libraries in the DDE.
EDIT:
Here is an example class loader for an agent. The Base64 encoded DXL is already added.
The agent instantiates the class ch.hasselba.demo.LoadedClass and calls the method printTime():
package ch.hasselba.demo;
public class LoadedClass {
public void printTime(){
System.out.println("Time: " + System.currentTimeMillis() );
}
}
The code of the agent (uses lwpd.commons.jar)
import lotus.domino.AgentBase;
import com.ibm.commons.util.io.base64.Base64;
import java.lang.reflect.Method;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
// trucated DXL string
String dataDXL = "YQAYAAAAAACqAgAAAQAAAAAAAAAAAAAAYAC8AgAAqgKqAgAAAAAAAAAAyv66vgAAADEALwcAAgEAFWNoL2hhc3NlbGJhL3hwYWdlcy9aWgcA";
// base64 decode the string
String b64 = Base64.decode(dataDXL);
byte[] b64Bytes = b64.getBytes();
byte[] classBytes = new byte[b64Bytes.length - 42];
// skip the first 42 bytes
System.arraycopy( b64Bytes, 42, classBytes, 0, b64Bytes.length - 42);
try {
// load the class
ByteClassLoader obj = new ByteClassLoader();
Class theClass = obj.findClass("ch.hasselba.demo.LoadedClass", classBytes);
// instantiate it
Object theInstance = theClass.newInstance();
// get the method printTime via Reflection & call it
Method theMethod = theInstance.getClass().getMethod("printTime", null);
theMethod.invoke( theInstance, null);
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// the class loader
public static class ByteClassLoader extends ClassLoader {
public Class findClass(String name, byte[] data) {
return defineClass(name, data, 0, data.length);
}
}
}
Mike, Fredrik is right - no sharing. Unless...
you package your shared code into a Jar and deploy that one to the jvm/lib/ext directory of your server and/or client. Your admin will not like that potentially. There was a patched version of the updatesite.ntf on OpenNTF that allowed to deploy plug-ins into the server OS. You could hack the script to deploy a jar into the ext directory. But please only with admin's consent.
:-) stw

read from class defined by variable

I have Java-related question:
I want to know is there a way to create path to class (in program) by using a variable(s).
Im making a program that will download pictures from certain sites and show them to a user. However, different sites have different forms, that's why I have to define a series of functions specific to each. They cannot be put in the same class because functions that preform same job (just for another site) would have to have same names. I'm trying to make adding support for another site later as simple as possible.
Anyway, the question is, could I call a function in program using a variable to determine its location.
For example: code.picturesite.functionINeed();
code is the package containing all of the coding, and picturesite is not a class but rather a variable containing the name of the desired class - that way I can only change value of the variable to call a different function (or the same function in a different class).
I don't really expect that to be possible (this was more for you to understand the nature of the problem), but is there another way to do what I'm trying to achieve here?
Yes, there is a way. It's called reflection.
Given a String containing the class name, you can get an instance like this:
Class<?> c = Class.forName("com.foo.SomeClass");
Object o = c.newInstance(); // assuming there's a default constructor
If there isn't a default constructor, you can get a reference to one via c.getConstructor(param1.getClass(), param2.getClass(), etc)
Given a String containing the method name and an instance, you can invoke that method like this:
Method m = o.getClass().getMethod("someMethod", param1.getClass(), param2.getClass(), etc);
Object result = m.invoke(o, param1, param2, etc);
I'm not immediately seeing anything in your question that couldn't be solved by, instead of having a variable containing a class name, having a variable containing an instance of that class -- to call a function on the class, you would have to know it implements that function, so you could put the function in an interface.
interface SiteThatCanFoo {
void foo();
}
And
class SiteA extends Site implements SiteThatCanFoo {
public void foo() {
System.out.println("Foo");
}
}
Then:
Site currentSite = getCurrentSite(); // or getSiteObjectForName(siteName), or similar
if (SiteThatCanFoo.isAssignableFrom(currentSite.class)) {
((SiteThatCanFoo)currentSite).foo();
}
So you want to do something like this (check ImageDownloader.getImageFrom method)
class SiteADownloader {
public static Image getImage(URI uri) {
System.out.println("invoking SiteADownloader on "+uri);
Image i = null;
// logic for dowlnoading image from siteA
return i;
}
}
class SiteBDownloader {
public static Image getImage(URI uri) {
System.out.println("invoking SiteBDownloader on "+uri);
Image i = null;
// logic for dowlnoading image from siteB
return i;
}
}
// MAIN CLASS
class ImageDownloader {
public static Image getImageFrom(String serverName, URI uri) {
Image i = null;
try {
// load class
Class<?> c = Class.forName(serverName + "Downloader");
// find method to dowload img
Method m = c.getDeclaredMethod("getImage", URI.class);
// invoke method and store result (method should be invoked on
// object, in case of static methods they are invoked on class
// object stored earlier in c reference
i = (Image) m.invoke(c, uri);
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace();
}
return i;
}
// time for test
public static void main(String[] args) {
try {
Image img = ImageDownloader.getImageFrom("SiteB", new URI(
"adress"));
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}

Categories