Custom Ant Types with reference result in empty class

倾然丶 夕夏残阳落幕 提交于 2019-12-25 02:24:24

问题


I am using Apache Ant as a tool for tedious data collection and calculation tasks I have to do over and over again. I have defined some custom Ant Tasks and they work really well.

However, now I want to create new data-types using the <typedef> tag. I want to define some data in the beginning of my build.xml which I can reference to later, much like the following example from a regular build file from one of my Java projects:

<path id="classpath.build">
    <fileset dir="${dir.lib}">
        <include name="**/*.jar" />
        <exclude name="**/junit*" />
    </fileset>
</path>

So I created a simple HelloWorld example like follows:

<sampledata data="LOL" id="someid" />

and in a custom ant task I would like to refer to this data type:

<customtask dataref="someid" />

This seems reasonable simple, so after digging in the API docs I found out that my class has to extend org.apache.tools.ant.types.DataType and has to have the method setRefid(org.apache.tools.ant.types.Reference r).

My custom Ant Task customtask uses the following code for the dataref attribute:

public class CustomTask extends Task {

     private SampleData data;

     public void setDataref(Reference r) {
        data = new SampleData(getProject());
        data.setRefid(r);
     }

     public void execute() {
          System.out.println(data.getData());
     }
}

And my SampleData implementation is like follows:

public class SampleData extends DataType {

     private String data;

     public SampleData(Project project) {
         setProject(project);
     }

     public void setData(String data) {
         this.data = data;
     }

     public String getData() {
         return this.data;
     }

     public void setRefid(Reference r) {
          super.setRefid(r);
     }

 }

Mind you, this is all based on the sources from org.apache.tools.ant.types.Path which shows the behavior I want.

However, after creating a target with the customtask as defined above, the output is null. So SampleData is instantiated but the reference is not set correctly. When I debug I find out that SampleData is correctly instantiated in my ant file with the data LOL and even the refence is set to someid. Also, the CustomTask class setDataref method indeed is passed a Reference named someid, so it all goes wrong in the setDataref method, but I have no clue what I have to do and the manual is lacking (or I am missing an important part).

I have the feeling I don't completely grasp the lifecycle of custom datatypes with id's.

EDIT 23-11-2012 9:24 :

After some more fiddling and looking in the source of org.apache.tools.ant.types.Path I followed some of the methods there and changed my SampleData.getData to the following:

public String getData() {
    if(isReference()) {
        return ((SampleData)getCheckedRef()).getData();
    }
    return this.data;
}

I am a little bit further, however now I get the following Ant error in my build.xml :

 /home/arjan/dev/so-demo/build.xml:9: someid doesn't denote a SampleData

However when I check the class encapsulated by the Reference object it is the correct type. I am getting pretty fed up by this now. Any more tips?

EDIT 23-11-2012 11:46 :

I created a Gist with a clear testcase. My Ant version is 1.8.4. Hopefully someone will come with a solution, because I've looked in other libraries like Sonatype Aether Antlib and followed their way of reasoning.

It all goes wrong at the getCheckedRef method, specifically in the Ant sourcefile src\main\org\apache\tools\ant\types\DataType.java:250:

if (!(requiredClass.isAssignableFrom(o.getClass()))) {
    log("Class " + o.getClass() + " is not a subclass of " + requiredClass,
            Project.MSG_VERBOSE);
    String msg = ref.getRefId() + " doesn\'t denote a " + dataTypeName;
    throw new BuildException(msg);
}

What is going on? This is the simples testcase I could come up with.


回答1:


I believe that this might fix your issue, I was running into similar errors:

You are missing the loaderRef directive for your custom task and type. See here: If you are defining tasks or types that share the same classpath with multiple taskdef or typedef tasks, the corresponding classes will be loaded by different Java ClassLoaders. Two classes with the same name loaded via different ClassLoaders are not the same class from the point of view of the Java VM, they don't share static variables and instances of these classes can't access private methods or attributes of instances defined by "the other class" of the same name. They don't even belong to the same Java package and can't access package private code, either.

So when you define your custom task and custom type via typedef and taskdef use the loaderRef attribute - this can be anything (like customTaskLoader) as long as it is the same between your task and type definition.

From there you can simplify your code even further:

public class CustomTask extends Task {

 private SampleData data;

 public void execute() {
      System.out.println(data.getData());
 }

}

and

public class SampleData extends DataType {

 private String data;

 public SampleData(Project project) {
     setProject(project);
 }

 public void setData(String data) {
     this.data = data;
 }

 public String getData() {
     if(isReference()) {
         return ((SampleData)getCheckedRef()).getData();
     }
     return this.data;
 }

}




回答2:


I think the issue is you have not provided setter for Data in your class... Which is mandatory for CustomTask...

public class CustomTask extends Task {
    private SampleData data;
     public void setDataref(Reference r) {
        data = new SampleData(getProject());
        data.setRefid(r);
     }
    public void setData(SampleData data){
        this.data = data;
    }
     public void execute() {
          System.out.println(data.getData());
     }
}

Hope this helps..




回答3:


I solved it using the Gist above! The problem is two-fold, however I don't know how to solve the second one.

Problem 1

Somehow the classpathref argument for the typedef and taskdef tags is not the way to go. I am directing them to my compiled classes in the cls directory.

So I decided to jar all my files and place them in the {ant.home}/lib directory like follows:

<jar basedir="cls" destfile="${ant.home}/lib/demo.jar" />
<delete dir="cls" />

This way I could remove the classpathref arguments. I thought that would solve it maybe... but I was wrong, however it is the actual solution (if you might call it that) that works.

Problem 2

Eclipse... this program is using its own Ant distribution and somehow my own generated jar is not added to the classpath when running Eclipse. This results in the following error:

typedef class types.DemoType cannot be found

Running from the command-line proved to be no problem.

Bottomline

The problem is solved by rarring my types and tasks and running it from the commandline. Using classpathref with typedef or taskdef resulted in the someid doesn't denote a SampleData error. Using Eclipse resulted in the class not found error.

Odd behavior I must say and I want to know how to make Eclipse properly work with custom tasks, because that must possible.

Well... that has cost me a few hours.



来源:https://stackoverflow.com/questions/13514821/custom-ant-types-with-reference-result-in-empty-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!