dexmaker icon indicating copy to clipboard operation
dexmaker copied to clipboard

Example for declare(FieldId<?, ?> fieldId, int flags, Object staticValue)

Open hamada147 opened this issue 7 years ago • 9 comments

I would like to create a norma modal with all its fields are public but I can't figure our how to use the provided function declare(FieldId, ?> fieldId, int flags, Object staticValue). Can someone please provide me with a working example

I want to create something like that

public class ModelA {
    public String value1;
    public String value2;
    public String value3;
}

My issue is with FieldId value for the declare function.

this.dexMaker.declare(???, Modifier.PUBLIC, "");

hamada147 avatar Jul 02 '18 14:07 hamada147

I tried to my best knowledge and here is what I managed to make so far The following is the main package com.kn.testcreatingaclassinruntime CreateClassInRunTimeWrapper is another package inside of it

package com.kn.testcreatingaclassinruntime.CreateClassInRunTimeWrapper;

import com.android.dx.DexMaker;
import com.android.dx.FieldId;
import com.android.dx.TypeId;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

/**
 * This is a DexMaker wrapper to create classes in run time
 * because the existing code is way too big and confusing for me
 * @author Ahmed Moussa <email>[email protected]</email>
 * @version 1.0
 * Created by Ahmed Moussa on 7/2/18.
 */
public final class DexMakerWrapper {

    // ClassName
    private String ClassName;
    // dexMaker used to create the class
    private DexMaker dexMaker;
    // class declaration after finishing
    private Class<?> createdClass;

    // make and entry point to the train methodology
    public static DexMakerWrapper Constructor() {
        return new DexMakerWrapper();
    }
    
    // use the it to initiate an object of the dex maker class
    private DexMakerWrapper() {
        this.dexMaker = new DexMaker();
    }

    // set class name as well as start class declaration
    public DexMakerWrapper setClassName(String ClassName) {
        this.ClassName = ClassName;
        TypeId<?> classDeclaration = TypeId.get("L" + this.ClassName + ";");
        this.dexMaker.declare(classDeclaration, this.ClassName + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
        return this;
    }

    // set class fields with data type String for all of them
    public DexMakerWrapper setStringFields(ArrayList<String> Fields) {
        TypeId<System> systemType = TypeId.get(System.class);
        TypeId<String> DataType = TypeId.get(String.class);

        for (String field : Fields) {
            FieldId<System, String>FieldDec = systemType.getField(DataType, field);
            this.dexMaker.declare(FieldDec, Modifier.PUBLIC, null);
        }
        return this;
    }

    // creating the class itself 
    public Class<?> createClass() {
        File outputDir = new File(".");
        try {
            ClassLoader loader = this.dexMaker.generateAndLoad(DexMakerWrapper.class.getClassLoader(), outputDir);
            this.createdClass = loader.loadClass(this.ClassName);
            return this.createdClass;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

Here is an example of my use of previous code

val fields = ArrayList<String>()
fields.add("A")
fields.add("B")
fields.add("C")
fields.add("D")
val dex: DexMakerWrapper = DexMakerWrapper.Constructor().setClassName("ModelA").setStringFields(fields)
val createdClass = dex.createClass()

I'm getting a crash java.lang.RuntimeException

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.kn.testcreatingaclassinruntime/com.kn.testcreatingaclassinruntime.MainActivity}: java.lang.IllegalStateException: Undeclared type Ljava/lang/System; declares members: [Ljava/lang/System;.A, Ljava/lang/System;.B, Ljava/lang/System;.C, Ljava/lang/System;.D] []

The crash is happening inside your code DexMaker class inside generate function

Any help would be appreciated.

hamada147 avatar Jul 03 '18 10:07 hamada147

Another update. For some reason or another I'm unable to figure out the reason for the crash except the following information. Ljava/lang/system is no declared. in TypeDeclaration class declared variable value is always false on it why no idea. I'm also clueless as of what changes this value or what set it. So I changed my Wrapper

package com.kn.testcreatingaclassinruntime.CreateClassInRunTimeWrapper;

import android.content.ContextWrapper;

import com.android.dx.DexMaker;
import com.android.dx.FieldId;
import com.android.dx.TypeId;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

/**
 * This is a DexMaker wrapper to create classes in run time
 * because the existing code is way too big and confusing for me
 * @author Ahmed Moussa <email>[email protected]</email>
 * @version 1.0
 * Created by Ahmed Moussa on 7/2/18.
 */
public final class DexMakerWrapper {

    // ClassName
    private String ClassName;
    // dexMaker used to create the class
    private DexMaker dexMaker;
    // class declaration code
    private TypeId<?> classDeclaration;
    // class declaration after finishing
    private Class<?> createdClass;

    // make and entry point to the train methodology
    public static DexMakerWrapper Constructor() {
        return new DexMakerWrapper();
    }

    // use the it to initiate an object of the dex maker class
    private DexMakerWrapper() {
        this.dexMaker = new DexMaker();
    }

    // set class name as well as start class declaration
    public DexMakerWrapper setClassName(String ClassName) {
        this.ClassName = ClassName;
        this.classDeclaration = TypeId.get("Lcom/kn/testcreatingaclassinruntime/CreateClassInRunTimeWrapper/" + this.ClassName + ";");
        this.dexMaker.declare(classDeclaration, this.ClassName + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
        return this;
    }

    // set class fields with data type String for all of them
    public DexMakerWrapper setStringFields(ArrayList<String> Fields) {
        // TypeId<System> systemType = TypeId.get(System.class);
        TypeId<String> DataType = TypeId.get(String.class);

        for (String field : Fields) {
            FieldId<System, String> FieldDec = (FieldId<System, String>) this.classDeclaration.getField(DataType, field); //systemType.getField(DataType, field);
            this.dexMaker.declare(FieldDec, Modifier.PUBLIC, null);
        }
        return this;
    }

    // creating the class itself
    public Class<?> createClass(File f) {

        File outputDir = f;
        try {
            ClassLoader loader = this.dexMaker.generateAndLoad(DexMakerWrapper.class.getClassLoader(), outputDir);
            this.createdClass = loader.loadClass(this.ClassName);
            return this.createdClass;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

And my usage

val fields = ArrayList<String>()
        fields.add("A")
        fields.add("B")
        fields.add("C")
        fields.add("D")

        var dir = this.dataDir
        val createdClass = DexMakerWrapper.Constructor().setClassName("ModelA").setStringFields(fields).createClass(dir)
        for (f in createdClass!!.declaredFields) {
            println(f.name)
        }

Also had to add in the manifest file.

Now I'm getting java.lang.ClassNotFoundException: Didn't find class "ModelA" on path: DexPathList[[zip file "/data/user/0/com.kn.testcreatingaclassinruntime/Generated_-1884772861.jar"],nativeLibraryDirectories=[/system/lib, /vendor/lib]]

Now what did I do wrong here?

hamada147 avatar Jul 03 '18 11:07 hamada147

Example code itself is not running?

hamada147 avatar Jul 03 '18 11:07 hamada147

Guys, Any help would be deeply appreciated. @drewhannay @moltmann

hamada147 avatar Jul 04 '18 08:07 hamada147

Managed to test a fix.

// this line should include the absolute path of the class
this.createdClass = loader.loadClass("com.kn.testcreatingaclassinruntime.CreateClassInRunTimeWrapper." + this.ClassName);

hamada147 avatar Jul 04 '18 10:07 hamada147

So Far I'm able to create the Class in run time as shown in the code. With the wanted structure

public class ModelA {
    public String value1;
    public String value2;
    public String value3;
}

But I'm not able to create new instant out of it and therefore I'm not able to set fields value which is what I want

I need to make an ArrayList of this newly generated Class and set the values inside their fields.

@drewhannay @moltmann

hamada147 avatar Jul 05 '18 07:07 hamada147

@abti

hamada147 avatar Jul 05 '18 09:07 hamada147

@swankjesse

hamada147 avatar Jul 05 '18 09:07 hamada147

@allight

hamada147 avatar Jul 05 '18 09:07 hamada147