AS3: call a static class method - class and method names are strings - apache-flex

I have an ugly problem. I have two string variables (className and staticMethod) store the name of a class and it's static method I have to call:
package {
import flash.display.Sprite;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
public class ClassPlay extends Sprite {
public function ClassPlay() {
new Foo();
var className:String = 'Foo';
var staticMethod:String = 'bar';
var classClass:Class = getDefinitionByName(className) as Class;
try {
classClass[staticMethod]();
} catch (e:Error) {}
}
}
}
This is the subject class:
package {
public class Foo {
public static function bar():void {trace('Foo.bar() was called.');}
}
}
It works just perfectly. The problem when you comment out this (9th) line:
// new Foo();
Without this line it exits with an exception:
ReferenceError: Error #1065: Variable Foo is not defined.
How could I do this without that instantiation? If that is impossible, is there a way to instantiate the class from the string variable? Or if it's still a bad practice, how would you do that? (I have to work with those two unknown string variable.)
Thanks in advance.

The reason is that the compiler will strip out unnecessary classes - if you don't have an explicit reference to the class Foo somewhere, it won't be present in your final application.
You could the reference elsewhere and still force it to be loaded - for example, a static array of references to the classes.

It should work if you just throw in a trace(classClass) - that should give you the reference you need, if I remember this stuff correctly.

Related

Minecraft modding block constructer error

I'm making a mod, and I am getting an error(no duh) and I have tried searching it up but I want an answer specific to my problem because I am not very good at this. I am getting this error in my block class.
Implicit super constructor Block() is undefined for default constructor. Must define an explicit constructor
and I don't know how to fix it. Please Help its for a project.
block class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
public class BlockCbBlock extends Block {
protected BlockCbBlock(Material material) {
super(material);
}
}
mod class:
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
#Mod(modid = "fc", name = "Fandomcraft", version = "1.0")
public class fandomcraft {
public static Item itemCbIngot;
public static Block blockCbBlock;
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Item/Block initialization and registering
//Config Handling
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot"); //item.itemCbIngot.name
blockCbBlock = new BlockCbBlock(Material.iron);
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}
#EventHandler
public void init(FMLInitializationEvent event){
//Proxy, TileEntity, entity, GUI and Packet Registering
}
#EventHandler
public void postInit(FMLPostInitializationEvent event) {
}
}
This error pertains to all of java, not just minecraft forge. Check this for some more reference. There are a couple possible reasons for this error. It is most likely 1, but 2 and 3 can be a contributing factor to the error.
Your BlockCbBlock Class declares a constructor that is not the default, no-argument constructor that the compiler would otherwise provide (that is, if the Block class doesn't have a constructor) and, if in fact the Block class is using the default constructor, then you can't call super() on the arguements because the Block class uses a constructor with no arguments. Because of this, if you wanted to modify the Block constructor, it would be safier and easier to create a custom construcotr inside of the BlockCbBlock class itself.
You are trying to inherit the constructor of Block, but you have declared it as protected, when the constructor in your class should be public to match the inherited .
If you're using Eclipse, it can give this error when you have your project setup incorrectly (system configuration mismatch)
Probably not directly realted to this specific error, but a possible cause of other errors in the near future; you are using the annotation #EventHandler, but you have not actually declared the forge event handler.
You don't actually register the block for some reason. Even if you're using the block as a recipe item, you still need to register it
To fix potential problems 1, 2, and 4, try this (obtained from here):
package GDMCrocknrollkid.fandomcraft;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
private final String name = "BlockCbBlock";
public class BlockCbBlock extends Block {
public BlockCbBlock() {
super(Material.iron);
GameRegistry.registerBlock(this, name);
setUnlocalizedName(Reference.MODID + "_" + name);
setCreativeTab(CreativeTabs.tabBlock);
}
public String getName() {
return name;
}
}
This way, you'll declare its UnlocalizedName, Material, and CreativeTab ahead of time. This method might be unnecessary, but its a good precaution to help prevent the error. Now, all you have to do is declare it like this:
//You need to make your own EventHandler class. Search online for that.
FCEventHandler handler = new FCEventHandler();
#EventHandler
public void preInit(FMLPreInitializationEvent event){
//Config Handling
//event handler registry
FMLCommonHandler.instance().bus().register(handler);
MinecraftForge.EVENT_BUS.register(handler);
//the same thing can be similarly done with this if you wish
itemCbIngot = new ItemCbIngot().setUnlocalizedName("ItemCbIngot").setTextureName("fc:itemCbIngot");
blockCbBlock = new BlockCbBlock();
GameRegistry.registerItem(itemCbIngot, itemCbIngot.getUnlocalizedName().substring(5));
}

flexunit: Parametrized tests

I am trying to run a parametrized tests... Was trying to implement it like it explained here:
http://docs.flexunit.org/index.php?title=Parameterized_Test_Styles
Here is what my test case looking
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
[Paremeters]
public static var stackProvider:Array = [new ArrayBasedStack(), new LinkedListBasedStack()] ;
private var _stack:IStack;
public function ArrayBasedStackTests(param:IStack)
{
_stack = param;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stackProvider" )]
public function isEmptyStackPositiveTest():void
{
var stack:IStack = _stack;
assertEquals( true, stack.isEmpty() );
}
But this code throws following initializing Error:
Error: Custom runner class org.flexunit.runners.Parameterized should
be linked into project and implement IRunner. Further it needs to have
a constructor which either just accepts the class, or the class and a
builder.
Need help to fix it
UPDATE
I've updated the code so it looks like this
[RunWith("org.flexunit.runners.Parameterized")]
public class ArrayBasedStackTests
{
private var foo:Parameterized;
[Parameters]
public static function stacks():Array
{
return [ [new ArrayBasedStack()], [new LinkedListBasedStack()] ] ;
}
[Before]
public function setUp():void
{
}
[After]
public function tearDown():void
{
}
[Test ( description = "Checks isEmpty method of the stack. For empty stack", dataProvider="stacks")]
public function isEmptyStackPositiveTest(stack:IStack):void
{
assertEquals( true, _stack.isEmpty() );
}
It works. But the result is a bit strange. I have 4 test executed instead of 2. (I have 2 items in data provider, so cant get why do I have 4 tests).
Output
http://screencast.com/t/G8DHbcjDUkJ
The [Parameters] meta-data specifies that the parameters are passed to the constructor of the test - so the test class is called for each parameter. You also have the dataProvider set for the specific test method, so the test method is also called once for each parameter. Two calls for the test, and two calls to the method, ends up running four tests.
The solution is to either use [Parameters] meta-tag which specifies the data to use for the whole test class, or use the dataProvider for each test method, but not both with the same data at the same time.
You're missing the static reference to Paramaterized, as shown here:
import org.flexunit.runners.Parameterized;
[RunWith("org.flexunit.runners.Parameterized")]
public class MyTestNGTest
{
private var foo:Parameterized;
...
Basically, that error means that the [Runner] defined isn't available at runtime, which occurs if there is no static reference in the class to cause it to get linked in.
In FlexUnit 4.5.1, this approach changed to using [Rule]'s like so:
public class MyTestNGTest
{
[Rule]
public function paramaterizedRule:ParamaterizedRule = new ParamaterizedRule();
...
}
However, I can't seem to see an actual implementation of IMethodRule for paramaterized tests (that example is fictional).

Flex/Actionscript : passing data from custom class to main application?

Ok this probably sounds dumb but Im a complete beginner in Flex programming.
I have an application with a main .mxml file, and a certain class Foo that I call from the .mxml
In Foo, I make a URLRequest and listen for the Complete event. Then I found myself with the returned data in a Foo function, but I have no idea how to communicate it to the .mxml part of the applicaton ! I looked into ArrayCollections but I can't seem to understand how they work and whether it might help. Isn't there a way to modify, from inside the class, a variable with a global scope ?
This sounds like a small application, but if it's a large application you might want to look at a micro-framework like RobotLegs
If you have your Foo class extend EventDispatcher then it will be able to send events and have the main MXML app listen for said events.
package com.example
{
import flash.events.EventDispatcher;
import com.example.events.MyEvent;
public class Foo extends EventDispatcher
{
public function doAction():void
{
var someData:String = "blah";
dispatchEvent(new MyEvent(MyEvent.SOMETHING_HAPPENED, someData));
}
}
}
A Custom event with a payload (in this case a string)
package com.example.events
{
import flash.events.Event;
public class MyEvent extends Event
{
static public const SOMETHING_HAPPENED:String = "somethingHappened";
private var _myData:String;
public function get myData():String
{
return _myData;
}
public function MyEvent(type:String, myData:String, bubbles:Boolean=false, cancelable:Boolean=false)
{
_myData = myData;
super(type, bubbles, cancelable);
}
override public function clone():Event
{
return new MyEvent(type, myData, bubbles, cancelable);
}
}
}
Working with your Foo class from the main file:
public function EventDispatcherExample() {
var foo:Foo = new Foo();
foo.addEventListener(MyEvent.SOMETHING_HAPPENED, actionHandler);
foo.doAction();
}
private function actionHandler(e:MyEvent):void {
trace("my data is: " + e.myData);
}
import mx.core.FlexGlobals;
FlexGlobals.toplevelApplication.varName;
Your Foo class can dispatch an event and have something in you main.mxml listen for that event. I am sure that I could create an example. I think it is under customer events in Flex documentation. This is assuming I understand the question.
As John said, an event is your best choice.
If you'd like some example code, I provided some for a similar question here: Data from Popup to Main Application?
An event might be the best way to do it as it has been stated.
Another approach is to dispatch an event like this
dispatchEvent(new Event('somethingHappened'));
and also create a get method in your class for the data you need to get.
Then all you have to do in your main app is this
var foo:Foo = new Foo();
foo.addEventListener('somethingHappened', actionHandler);
private function actionHandler(e:Event):void
{
trace(foo.memberData);
}
This way might be more suitable if the data should be a class member anyway and if you would like to avoid creating a new event class.

Java: How do I override a method of a class dynamically (class is eventually NOT in classpath)?

How do I call a method of a class dynamically + conditionally?
(Class is eventually not in classpath)
Let's say, I need the class NimbusLookAndFeel, but on some systems it's not available (i.e. OpenJDK-6).
So I must be able to:
Get to know it that class is available (at runtime),
If it's not the case, skip the whole thing.
How do I manage to override a method of a dynamically-loaded class
(thus creating an anonymous inner sub-class of it)?
Code example
public static void setNimbusUI(final IMethod<UIDefaults> method)
throws UnsupportedLookAndFeelException {
// NimbusLookAndFeel may be now available
UIManager.setLookAndFeel(new NimbusLookAndFeel() {
#Override
public UIDefaults getDefaults() {
UIDefaults ret = super.getDefaults();
method.perform(ret);
return ret;
}
});
}
EDIT:
Now I edited my code, as it was suggested, to intercept NoClassDefFoundError using try-catch. It fails. I don't know, if it's OpenJDK's fault. I get InvocationTargetException, caused by NoClassDefFoundError. Funny, that I can't catch InvocationTargetException: It's thrown anyway.
EDIT2::
Cause found: I was wrapping SwingUtilities.invokeAndWait(...) around the tested method, and that very invokeAndWait call throws NoClassDefFoundError when loading Nimbus fails.
EDIT3::
Can anyone please clarify where NoClassDefFoundError can occur at all? Because it seems that it's always the calling method, not the actual method which uses the non-existing class.
Get to know it that class is available (at runtime)
Put the usage in a try block ...
If it's not the case, skip the whole thing
... and leave the catch block empty (code smell?!).
How do I manage to override a method of a dynamically-loaded class
Just do it and make sure the compile-time dependency is satisfied. You are mixing things up here. Overriding takes place at compile time while class loading is a runtime thing.
For completeness, every class you write is dynamically loaded by the runtime environment when it is required.
So your code may look like:
public static void setNimbusUI(final IMethod<UIDefaults> method)
throws UnsupportedLookAndFeelException {
try {
// NimbusLookAndFeel may be now available
UIManager.setLookAndFeel(new NimbusLookAndFeel() {
#Override
public UIDefaults getDefaults() {
final UIDefaults defaults = super.getDefaults();
method.perform(defaults);
return defaults;
}
});
} catch (NoClassDefFoundError e) {
throw new UnsupportedLookAndFeelException(e);
}
}
Use BCEL to generate your dynamic subclass on the fly.
http://jakarta.apache.org/bcel/manual.html
The follow code should solve your problem. The Main class simulates your main class. Class A simulates the base class you want to extend (and you have no control of). Class B is the derived class of class A. Interface C simulates "function pointer" functionality that Java does not have. Let's see the code first...
The following is class A, the class you want to extend, but have no control of:
/* src/packageA/A.java */
package packageA;
public class A {
public A() {
}
public void doSomething(String s) {
System.out.println("This is from packageA.A: " + s);
}
}
The following is class B, the dummy derived class. Notice that, since it extends A, it must import packageA.A and class A must be available at the compile time of class B. A constructor with parameter C is essential, but implementing interface C is optional. If B implements C, you gain the convenience to call the method(s) on an instance of B directly (without reflection). In B.doSomething(), calling super.doSomething() is optional and depends on whether you want so, but calling c.doSomething() is essential (explained below):
/* src/packageB/B.java */
package packageB;
import packageA.A;
import packageC.C;
public class B extends A implements C {
private C c;
public B(C c) {
super();
this.c = c;
}
#Override
public void doSomething(String s) {
super.doSomething(s);
c.doSomething(s);
}
}
The following is the tricky interface C. Just put all the methods you want to override into this interface:
/* src/packageC/C.java */
package packageC;
public interface C {
public void doSomething(String s);
}
The following is the main class:
/* src/Main.java */
import packageC.C;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) {
doSomethingWithB("Hello");
}
public static void doSomethingWithB(final String t) {
Class classB = null;
try {
Class classA = Class.forName("packageA.A");
classB = Class.forName("packageB.B");
} catch (ClassNotFoundException e) {
System.out.println("packageA.A not found. Go without it!");
}
Constructor constructorB = null;
if (classB != null) {
try {
constructorB = classB.getConstructor(C.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
C objectB = null;
if (constructorB != null) {
try {
objectB = (C) constructorB.newInstance(new C() {
public void doSomething(String s) {
System.out.println("This is from anonymous inner class: " + t);
}
});
} catch (ClassCastException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
if (objectB != null) {
objectB.doSomething("World");
}
}
}
Why does it compile and run?
You can see that in the Main class, only packageC.C is imported, and there is no reference to packageA.A or packageB.B. If there is any, the class loader will throw an exception on platforms that don't have packageA.A when it tries to load one of them.
How does it work?
In the first Class.forName(), it checks whether class A is available on the platform. If it is, ask the class loader to load class B, and store the resulting Class object in classB. Otherwise, ClassNotFoundException is thrown by Class.forName(), and the program goes without class A.
Then, if classB is not null, get the constructor of class B that accepts a single C object as parameter. Store the Constructor object in constructorB.
Then, if constructorB is not null, invoke constructorB.newInstance() to create a B object. Since there is a C object as parameter, you can create an anonymous class that implements interface C and pass the instance as the parameter value. This is just like what you do when you create an anonymous MouseListener.
(In fact, you don't have to separate the above try blocks. It is done so to make it clear what I am doing.)
If you made B implements C, you can cast the B object as a C reference at this time, and then you can call the overridden methods directly (without reflection).
What if class A does not have a "no parameter constructor"?
Just add the required parameters to class B, like public B(int extraParam, C c), and call super(extraParam) instead of super(). When creating the constructorB, also add the extra parameter, like classB.getConstructor(Integer.TYPE, C.class).
What happens to String s and String t?
t is used by the anonymous class directly. When objectB.doSomething("World"); is called, "World" is the s supplied to class B. Since super can't be used in the anonymous class (for obvious reasons), all the code that use super are placed in class B.
What if I want to refer to super multiple times?
Just write a template in B.doSomething() like this:
#Override
public void doSomething(String s) {
super.doSomething1(s);
c.doSomethingAfter1(s);
super.doSomething2(s);
c.doSomethingAfter2(s);
}
Of course, you have to modify interface C to include doSomethingAfter1() and doSomethingAfter2().
How to compile and run the code?
$ mkdir classes
$
$
$
$ javac -cp src -d classes src/Main.java
$ java -cp classes Main
packageA.A not found. Go without it!
$
$
$
$ javac -cp src -d classes src/packageB/B.java
$ java -cp classes Main
This is from packageA.A: World
This is from anonymous inner class: Hello
In the first run, the class packageB.B is not compiled (since Main.java does not have any reference to it). In the second run, the class is explicitly compiled, and thus you get the result you expected.
To help you fitting my solution to your problem, here is a link to the correct way to set the Nimbus Look and Feel:
Nimbus Look and Feel
You can use Class class to do that.
I.E.:
Class c = Class.forName("your.package.YourClass");
The sentence above will throw a ClassNotFoundException if not found on current classpath. If the exception is not thrown, then you can use newInstance() method in c to create objects of your.package.YourClass class. If you need to call a specific constructor, you can use getConstructors method to get one and use it to create a new instance.
Erm, can't you put the class you want to extend into the compile time class path, write your subclass as usual, and at runtime, explicitly trigger loading the subclass, and handle any exception thrown by the linker that indicates that the superclass is missing?

Strange behavior from getDefinitionByName

I've created a class that loads it's subclasses based on a name passed to it. The function uses getDefinitionByName, gets the class type, and instantiates it, and returns it if the class is a subtype of the class that owns this method. The subtypes are all mxml files that extend the base class, in order to simplify instantiating controls.
However, in the case where I pass it a fully qualified name, it works in my unit tests but fails when I execute it in the context of my application. Is there a gotcha in getDefinitionByName that makes it behave differently in different execution contexts? Is there a simpler way to load classes by their qualified name?
static public function loadDisplay(className:String, extendedClassName:String = null):FeatureDisplay
{
try
{
trace("Loading", className);
var cls:Class = getDefinitionByName(className) as Class;
var display:FeatureDisplay = new cls() as FeatureDisplay;
if(display)
{
return display;
}
else
{
trace(className, "is not a subclass of FeatureDisplay");
return null;
}
}
catch(error:Error)
{
trace("Error loading", className);
trace("Error:", error.message);
}
return null;
}
My first question is are you explicitly using any of the classes anywhere? If you do not actually use a class, even if it is imported, ActionScript may not end up keeping a copy of the class's definition in the swf.
That said, you're better off avoiding getDefinitionByName, describeType, getQualifiedClassName or getQualifiedSuperclassName if you can possibly avoid them. They are memory hogs and it is generally best to avoid them. (unless you do not have control over which classes will be used at run time and they HAVE to be used through getDefinitionByName).
My suggestion is that you replace getQualifiedClassName with a swtich...case:
// Import the subclasses.
import path.to.SpriteFeatureDisplay;
import path.to.OtherFeatureDisplay;
class FeatureDisplay extends Sprite{
//Make one public static const per class.
public static const SPRITE_FEATURE_DISPLAY:String = "sprite_feature_display";
public static const OTHER_FEATURE_DISPLAY:String = "other_feature_display";
public static function loadDisplay( className:String,
extName:String = null ):FeatureDisplay
{
trace("Loading", className);
// This will ensure that each of the classes is stored in the swf
// it will behave faster, and it is less prone to errors (note that
// try...catch is not needed).
swtich( className )
{
case SPRITE_FEATURE_DISPLAY:
return new SpriteFeatureDisplay();
case OTHER_FEATURE_DISPLAY:
return new OtherFeatureDisplay();
default:
trace( "Requested class " + className + " could not be created..." +
"\nPlease make sure that it is a subclass of FeatureDisplay" );
return null;
}
return null;
}
}
FYI, I've seen the following method of keeping classes used in Flex's source code:
// References.cs
// notice the double reference: one to import, the other to reference
import package.to.ClassA; ClassA;
import package.to.ClassB; ClassB;
import package.to.ClassC; ClassC;
Of course, you still have to reference the "References" class somewhere.

Resources