As I said in my previous article, it's a bit of a slow day in the office today, so I'm just sitting here hitting ColdFusion interfaces with a sledgehammer to see what happens. I was trying to be clever, and have failed miserably. Oh well.
When I was fiddling around trying to work out "WTF" in that previous article, I ended up with this code.
A vanilla interface:
// TestInterface.cfc
interface {
public numeric function length(required string s);
}
A CFC which implements it:
// Impl.cfc
component implements="TestInterface" {
public numeric function length(required string s) {
return len(s);
}
}
And some code that mungs around with it:
writeOutput("Initial state<br>");
impl = new Impl();
writeOutput('isInstanceOf(impl, "TestInterface"): ' & isInstanceOf(impl, "TestInterface") & "<br>");
writeOutput("<hr>");
writeOutput("After clearance<br>");
impl = new Impl();
structClear(impl);
writeOutput('isInstanceOf(impl, "TestInterface"): ' & isInstanceOf(impl, "TestInterface") & "<br>");
writeOutput("<hr>");
writeOutput("Restored to working order<br>");
impl = new Impl();
public numeric function length(required string s) {
return len(s);
}
impl.length = length;
writeOutput('isInstanceOf(impl, "TestInterface"): ' & isInstanceOf(impl, "TestInterface") & "<br>");
writeOutput("<hr>");
All I'm doing here is:
- creating an Impl object, checking to see if it passes isInstanceOf() for a TestInterface;
- deleting the length() method from the Impl instance, and checking if it still passes the isInstanceOf() test;
- pops an appropriate function back into the Impl instance, and checking again if it passes isInstanceOf() for a TestInterface.
Initial state
isInstanceOf(impl, "TestInterface"): YES
After clearance
isInstanceOf(impl, "TestInterface"): NO
Restored to working order
isInstanceOf(impl, "TestInterface"): YES
So - as I kinda expected - I can "break" and "fix" the object when taking methods in and out at runtime. I demonstrated last time that this is not 100% reliable, but still: worth playing with.
And this got me thinking... if I can remove and add methods and it'll change whether it adheres to the interface... can I actually change a whole object to be implementing an interface at runtime? Basically I'll create an instance of a CFC which doesn't implement an interface, and then alter its metadata and tell it it does implement the interface. And see how it behaves.
Here's my test code. First I get the metadata for TestInterface.cfc and Imp.cfc:
metaDataI = getComponentMetadata("TestInterface");
metaDataImpl = getComponentMetadata("Impl");
writeDump({
metaDataI = metaDataI,
metaDataImpl = metaDataImpl
});
Which is as follows:
struct | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
METADATAI |
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
METADATAIMPL |
|
Note how basically Impl.cfc has a copy of TestInterface.cfc's metadata in there. And if we create an instance of Impl.cfc and get its metadata, it's the same (to save space I'll leave it out... take my word for it).
So I figure, OK, what if that is what makes an object implement an interface: having the relevant metadata in their to say so.
So I toy with this. I have a completely empty CFC:
// Empty.cfc
component {
}
And continuing my code from above, I stick TestInterface.cfc's metadata into Empty.cfc's metadata in the appropriate place:
metaDataEmpty = getComponentMetadata("Empty");
writeDump({metaDataEmpty=metaDataEmpty});
metaDataEmpty.implements = {
"TestInterface" = metaDataI
};
writeOutput("<hr>");
empty = new Empty();
public numeric function length(required string s) {
return len(s);
}
empty.length = length;
metadataEmpty = getMetadata(empty);
writeDump({metadataEmpty=metadataEmpty});
This yields:
struct | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
METADATAEMPTY |
|
struct | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
METADATAEMPTY |
|
So the metadata for the object instance of Empty.cfc looks the part! So... does it work? (I've kinda already given away the answer to this).
writeOutput('isInstanceOf(empty, "TestInterface"): ' & isInstanceOf(empty, "TestInterface") & "<br>");
writeOutput("<hr>");
try {
boolean function inboundTester(required TestInterface o){
return true;
}
writeOutput("Test passing into function<br>");
inboundTester(empty);
writeOutput("inboundTester(empty): OK<br>");
}
catch (any e){
writeDump(var=e, label="e");
}
try {
TestInterface function outboundTester(required any o){
return o;
}
writeOutput("Test passing back from function<br>");
outboundTester(empty);
writeOutput("outboundTester(empty): OK<br>");
}
catch (any e){
writeDump(var=e, label="e");
}
Here I just do the isInstanceOf() test again, and then go ahead and try to see if my Empty instance will work where a TestInterface is needed, both for an argument being passed into a function, or as a value being returned from a function. Results:
isInstanceOf(empty, "TestInterface"): NO
Test passing into function
e - struct | |
---|---|
Message | coldfusion.runtime.Struct cannot be cast to coldfusion.runtime.AttributeCollection |
StackTrace | |
TagContext | |
Type | java.lang.ClassCastException |
e - struct | |
---|---|
Message | coldfusion.runtime.Struct cannot be cast to coldfusion.runtime.AttributeCollection |
StackTrace | |
TagContext | |
Type | java.lang.ClassCastException |
So... err... no. That didn't work. And I seemed to have really confused ColdFusion, as it's not saying "that is not a TestInterface", as one would expect, it's bleating about struct not being attributesCollections. Shrug.
On Railo it doesn't work either, but at least I get a sensible error message:
invalid call of the function inboundTester (C:\Apps\railo-express-jre-win64\webapps\railo\www.scribble.local\shared\git\blogExamples\interfaces\buildfromscratch\blankslate.cfm), first Argument (o) is of invalid type, can't cast Object type [Component www.scribble.local.shared.git.blogExamples.interfaces.buildfromscratch.Empty] to a value of type [testinterface]
And:
the function outboundTester has an invalid return value , can't cast Object type [Component www.scribble.local.shared.git.blogExamples.interfaces.buildfromscratch.Empty] to a value of type [TestInterface]
Oh well. Wishful thinking.
I think given CF is quite happy with things being poked in and prodded about the place, then - even if not via this rather simplistic mechanism - one should be able to do what I set out to do. If I can construct something at runtime that would pass the interface contract if it was written out in code, then that should be cool.
But we're not. OK.
--
Adam