Reputation:
I have the following auto-generated code:
EDIT: It's auto-generated so I'm not allowed to modify it. (If only it were that easy...)
abstract class Foo {
}
class Fuwa extends Foo {
String chocolate() {...}
String muffin() {...}
}
class Fuko extends Foo {
String chocolate() {...}
String cookie() {...}
}
The chocolate() method in both the child classes are literally (aside from variable names) line-for-line identical and essentially interchanable.
Based on client input, I want test the specified children of Foo and validate the response. Currently I have:
class FooFactory {
Foo createFoo(name) {
if (name.equals("fuwa")) {
...
// returns a Fuwa object
} else if (name.equals("fuko")) {
...
// returns Fuko object
}
}
}
class MuffinTester extends FooTester {
boolean test(Foo inputFoo) {
Result x = ((Fuwa) inputFoo).muffin();
return validate(x);
}
private validate(x) {...}
}
class CookieTester extends FooTester {
boolean test(Foo inputFoo) {
Result x = ((Fuko) inputFoo).cookie();
return validate(x);
}
private validate(x) {...}
}
class TesterFactory {
FooTester createTest(name) {
if (name.equals("muffin")) {
...
// returns MuffinTester object
} else if (name.equals("cookie")) {
...
// returns CookieTester object
} else if (name.equals("chocolate")) {
...
// returns ChocolateTester object
}
}
}
The client specifies the Foo and method to be tested and the FooFactory and TestFactory (respectively) instantiate the required objects (there is logic to make sure the request is valid and the Foo contains the method, eg. no testing cookie() on Fuwa).
The problem arises when I try to code ChocolateTester:
class ChocolateTester extends FooTester {
boolean test(Foo inputFoo) {
Result x = ((???) inputFoo).chocolate();
return validate(x);
}
private validate(x) {...}
}
I can't leave inputFoo as just Foo since the compiler doesn't like that it doesn't have a chocolate() method. And I can't cast inputFoo to Fuwa or Fuko since whichever one I don't cast it to gets annoyed that they're being confused with their sibling (even though they're identical when exposed to chocolate for all intents and purposes). It would be great if I could modify Foo, but since it's auto-generated I can't touch it.
The best I could come up with is a bunch of if/else statements:
class ChocolateTester extends FooTester {
boolean test(Foo inputFoo) {
Result x;
if (inputFoo instanceof Fuwa) {
x = ((Fuwa) inputFoo).chocolate();
} else if (inputFoo instanceof Fuko) {
x = ((Fuko) inputFoo).chocolate();
}
return validate(x);
}
private validate(x) {...}
}
But feels really hacky when there are some 15 or so Foo and I have to duplicate the giant if/else block in for other methods the children Foo have in common, say a cake() method. Moreover, this sounds like a maintenance nightmare when a new Foo named Futaro joins in and I have to update the if/else blocks in not only FooFactory but also in ChocolateTester and CakeTester and in any other common methods.
So...
I'm sorry for being so long winded, but basically I want to ask is there a better way to do this (that is not too hacky/unmaintainable)? Is there a simple annotation to force method call to a method which doesn't exist, or a way to cast an object to its actual type, or a use of reflection which can solve this?
Update: I ultimately decided to use reflection with method invocation, see below.
Upvotes: 0
Views: 151
Reputation:
I posed this question to my boss and what he suggested was that I use reflection to invoke the method.
So in this case:
class ChocolateTester extends FooTester {
boolean test(Foo inputFoo) {
Method chocolateMethod = inputFoo.getClass().getMethod("chocolate");
Result x = chocolateMethod.invoke(inputFoo);
return validate(x);
}
private validate(x) {...}
}
I would need to add some code validate that inputFoo indeed had a chocolate() method and to catch all the exceptions, but this seems like the best solution given the constraint that I cannot modify the auto-generated code.
Upvotes: 0
Reputation: 25399
After automatically generating the code, you could post-process it to add a suitable interface to the child classes. The exact method you'd use to do this would depend on what build automation you're using already. For example, if you're using ant
to run the axis2 code generator, then it'd be straightforward to add some additional steps to the code-generation target that changed each of the generated files.
Upvotes: 0
Reputation: 49
Yes there is!
You can create abstract methods inside of Foo like this:
abstract class Foo {
abstract String Chocolate();
}
OR you turn Foo into an Interface, forcing any implementing classes to have a chocolate() method:
interface Foo {
String chocolate();
}
class Fuwa implements Foo {
String chocolate() {...}
String muffin() {...}
}
class Fuko implements Foo {
String chocolate() {...}
String cookie() {...}
}
Upvotes: 2