tags:

views:

713

answers:

8

I don't want to discuss the merits of this approach, just if it is possible. I believe the answer to be "no". But maybe someone will surprise me!

Imagine you have a core widget class. It has a method calculateHeight(), that returns a height. The height is too big - this result in buttons (say) that are too big. You can extend DefaultWidget to create your own NiceWidget, and implement your own calculateHeight() to return a nicer size.

Now a library class WindowDisplayFactory, instantiates DefaultWidget in a fairly complex method. You would like it to use your NiceWidget. The factory class's method looks something like this:

public IWidget createView(Component parent) {
    DefaultWidget widget = new DefaultWidget(CONSTS.BLUE, CONSTS.SIZE_STUPIDLY);

    // bunch of ifs ...
    SomeOtherWidget bla = new SomeOtherWidget(widget);
    SomeResultWidget result = new SomeResultWidget(parent);
    SomeListener listener = new SomeListener(parent, widget, flags);

    // more widget creation and voodoo here

    return result;
}

That's the deal. The result has the DefaultWidget deep within a hierarchy of other objects. The question - how to get this factory method to use my own NiceWidget? Or at least get my own calculateHeight() in there. Ideally, I'd like to be able to monkey patch the DefaultWidget so that its calculateHeight did the right thing...

public class MyWindowDisplayFactory {
    public IWidget createView(Component parent) {
        DefaultWidget.class.setMethod("calculateHeight", myCalculateHeight);
        return super.createView(parent);
    }
}

Which is what I could do in Python, Ruby, etc. I've invented the name setMethod() though. The other options open to me are:

  • Copying and pasting the code of the createView() method into my own class that inherits from the factory class
  • Living with widgets that are too big

The factory class can't be changed - it is part of a core platform API. I tried reflection on the returned result to get to the widget that (eventually) got added, but it is several widget-layers down and somewhere it gets used to initialize other stuff, causing odd side effects.

Any ideas? My solution so far is the copy-paste job, but that's a cop out that requires tracking the changes in the parent factory class when upgrading to newer versions of the platform, and I'd be interested to hear other options.

A: 

One ugly solution would be to put your own implementation of DefaultWidget (with same FQCN) earlier on the Classpath than the normal implementation. It's a terrible hack, but every other approach that I can think of is even worse.

Joachim Sauer
Sadly I'd end up wanting to use 99% of the *real* DefaultWidget with no way to access it.
rq
A: 

The object-oriented way of doing this would be to create a wrapper implementing IWidget, delegating all calls to the actual widget, except calculateHeight, something like:

class MyWidget implements IWidget {
    private IWidget delegate;
    public MyWidget(IWidget d) {
        this.delegate = d;
    }
    public int calculateHeight() {
        // my implementation of calculate height
    }
    // for all other methods: {
    public Object foo(Object bar) {
        return delegate.foo(bar);
    }
}

For this to work, you need to intercept all creations of the widget you want to replace, which probably means creating a similar wrapper for the WidgetFactory. And you must be able to configure which WidgetFactory to use.

It also depends on no client trying to cast the IWidget back to DefaultWidget...

Rolf Rander
This is the stategy design pattern, and a classic Java tool. Nothing to do with monkey patch. You can like monkey patch or not, but it's an anwser to a very special problem.
e-satis
Absolutely, I am just posting this as a possible option. There are lots of alternatives here, which is best suited depends on the actual scenario: the extension possibilities of the API in question, flexibility, platform, need to be forward/backward compatible etc.
Rolf Rander
A: 

Only suggestions I can think of:

  1. Dig through the library API to see if there's some way of overriding the defaults and sizing. Sizing can be confusing in swing (at least to me) , setMinimum, setMaximum, setdefault, setDefaultOnThursday, ... . It's possible there's a way. If you can contact the library designer(s) you might find an answer that will alleviate the need for unpleasant hacking.

  2. Perhaps extend the factory only overriding some default sizing parameter? depends on the factory but it might be possible.

Creating a class with the same name might be the only other option, as others have pointed out it's ugly and you're liable to forget it and break stuff when you update the api library or deploy in a different environment and forget why you had the classpath set up that way.

Steve B.
I keep looking for the "setMinimumNoReallyIMeanItStopIgnoringMe" method.
Paul Tomblin
Heh - it's actually far worse than this - the code in question isn't even Swing. Looking at the API, it begs for dependency injection, but I can see why it doesn't have this (to keep things simple). Ho hum!
rq
+5  A: 

Perhaps you could use Aspect Oriented Programming to trap calls to that function and return your own version instead?

Spring offers some AOP functionality but there are other libraries that do it as well.

Andy
Sounds good and possibly the closest real solution. Perhaps a bit overkill to add a Spring dependency where none existed to the app just for this fixup though. A custom class loader might not play nicely with Eclipse RCP, which I also use...
rq
You don't need Spring to do AOP, just get AspectJ. There is also an eclipse plugin: http://www.eclipse.org/aspectj/
Jason Day
+1  A: 

Just my concept idea,

It is possible that use AOP, with bytecode engineering way, to inject a aspect to the calculateHeight method.

Then, you may enable you patch by ThreadLocal or else variable.

Dennis Cheung
A: 

Well, I keep trying to post suggestions, and then I see that they won't work or that you've already mentioned you tried them.

The best solution I can think of is to subclass WindowDisplayFactory, then in the subclass's createView() method, first call super.createView(), then modify the object returned to completely throw out the widget and replace it with an instance of the subclass that does what you want. But the widget is used to initialize stuff, so you'd have to go change all of those.

Then I think of using reflection on the returned object from createView() and trying to fix things up that way, but again, that's hairy because so much stuff was initialized with the widget. I think I would try to use that approach, though, if it was simple enough to justify it over copying and pasting.

I'll be watching this, plus thinking to see if I can come up with any other ideas. Java Reflection sure is nice, but it can't beat the dynamic introspection I've seen available in languages such as Perl and Python.

skiphoppy
Yeah, there's a fair amount of stuff done in the extra new'd classes in the createView - in the end the reflection + fix ups would probably have been more fragile (harder to maintain with new versions of the WidgetFactory) than just copy-pasting.
rq
+2  A: 

cglib is a Java library that can do some things similar to monkey patching - it can manipulate bytecode at runtime to change certain behaviours. I'm not sure if it can do exactly what you need, but it's worth a look...

Dan Vinton
Other tools for manipulating byte-code:http://jakarta.apache.org/bcel/http://asm.objectweb.org/
Rolf Rander
A: 

Perhaps, you could write a custom class loader.

http://en.wikipedia.org/wiki/Classloader

http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

eel ghEEz