I wanted to see the source code for an apk file created which I did by using dex2jar to transform the dex file in to a .class file, and then use a jar decompiler (such as the free jd-gui) to plain text java.
Now for viewing that source code I added the classes.dex.dex2jar as an external jar in the project and started viewing the .class files of the external jar added. Now the issue is, in the code there are some statements like:
1 local1 = new 1(this);
2 local2 = new 2(this);
3 local3 = new 3(this);
Can anyone help how to resolve this.
This is equivalent to creating 3 inner classes, like so:
public class test000021_innerclass {
void X() {
innerClass1 c1 = new innerClass1(this);
innerClass2 c2 = new innerClass2(this);
innerClass3 c3 = new innerClass3(this);
}
private class innerClass1 {
public innerClass1(test000021_innerclass test000021_innerclass) {
}
}
private class innerClass2 {
public innerClass2(test000021_innerclass test000021_innerclass) {
}
}
private class innerClass3 {
public innerClass3(test000021_innerclass test000021_innerclass) {
}
}
}
Related
I have MainClass
public class MainClass extends Application {
#Override
public void start(Stage primaryStage) {
try{
Image img = new Image(getClass().getResourceAsStream(".\\build\\resources\\main\\img\\h1.jpg"));
System.out.println("ok");
}
catch(Exception ex){
System.out.println(ex.getMessage());
}
}
public static void main(String[] args) {
launch(args);
}
}
and my image is in \build\resources\main\img\h1.jpg directory
my project files:
When I run project it gives Input stream must not be null exception.
Resource paths are not separated by \ Furthermore they start at the resource root. In this case the path "/img/h1.jpg" should do the trick assuming your IDE properly includes the resources in the classpath at runtime.
getResource(AsStream) does not access the data via file path; The data may not be available as file at all, but as entry in a JAR file. If you need to refer to a file that is not included in the classpath, use File's functionality to convert to a URI or use a FileInputStream:
new Image(new File(".\\build\\resources\\main\\img\\h1.jpg").toURI().toString())
Is there a Java equivalent for System.IO.Path.Combine() in C#/.NET? Or any code to accomplish this?
This static method combines one or more strings into a path.
Rather than keeping everything string-based, you should use a class which is designed to represent a file system path.
If you're using Java 7 or Java 8, you should strongly consider using java.nio.file.Path; Path.resolve can be used to combine one path with another, or with a string. The Paths helper class is useful too. For example:
Path path = Paths.get("foo", "bar", "baz.txt");
If you need to cater for pre-Java-7 environments, you can use java.io.File, like this:
File baseDirectory = new File("foo");
File subDirectory = new File(baseDirectory, "bar");
File fileInDirectory = new File(subDirectory, "baz.txt");
If you want it back as a string later, you can call getPath(). Indeed, if you really wanted to mimic Path.Combine, you could just write something like:
public static String combine(String path1, String path2)
{
File file1 = new File(path1);
File file2 = new File(file1, path2);
return file2.getPath();
}
In Java 7, you should use resolve:
Path newPath = path.resolve(childPath);
While the NIO2 Path class may seem a bit redundant to File with an unnecessarily different API, it is in fact subtly more elegant and robust.
Note that Paths.get() (as suggested by someone else) doesn't have an overload taking a Path, and doing Paths.get(path.toString(), childPath) is NOT the same thing as resolve(). From the Paths.get() docs:
Note that while this method is very convenient, using it will imply an assumed reference to the default FileSystem and limit the utility of the calling code. Hence it should not be used in library code intended for flexible reuse. A more flexible alternative is to use an existing Path instance as an anchor, such as:
Path dir = ...
Path path = dir.resolve("file");
The sister function to resolve is the excellent relativize:
Path childPath = path.relativize(newPath);
The main answer is to use File objects. However Commons IO does have a class FilenameUtils that can do this kind of thing, such as the concat() method.
platform independent approach (uses File.separator, ie will works depends on operation system where code is running:
java.nio.file.Paths.get(".", "path", "to", "file.txt")
// relative unix path: ./path/to/file.txt
// relative windows path: .\path\to\filee.txt
java.nio.file.Paths.get("/", "path", "to", "file.txt")
// absolute unix path: /path/to/filee.txt
// windows network drive path: \\path\to\file.txt
java.nio.file.Paths.get("C:", "path", "to", "file.txt")
// absolute windows path: C:\path\to\file.txt
I know its a long time since Jon's original answer, but I had a similar requirement to the OP.
By way of extending Jon's solution I came up with the following, which will take one or more path segments takes as many path segments that you can throw at it.
Usage
Path.combine("/Users/beardtwizzle/");
Path.combine("/", "Users", "beardtwizzle");
Path.combine(new String[] { "/", "Users", "beardtwizzle", "arrayUsage" });
Code here for others with a similar problem
public class Path {
public static String combine(String... paths)
{
File file = new File(paths[0]);
for (int i = 1; i < paths.length ; i++) {
file = new File(file, paths[i]);
}
return file.getPath();
}
}
To enhance JodaStephen's answer, Apache Commons IO has FilenameUtils which does this. Example (on Linux):
assert org.apache.commons.io.FilenameUtils.concat("/home/bob", "work\\stuff.log") == "/home/bob/work/stuff.log"
It's platform independent and will produce whatever separators your system needs.
Late to the party perhaps, but I wanted to share my take on this. I prefer not to pull in entire libraries for something like this. Instead, I'm using a Builder pattern and allow conveniently chained append(more) calls. It even allows mixing File and String, and can easily be extended to support Path as well. Furthermore, it automatically handles the different path separators correctly on both Linux, Macintosh, etc.
public class Files {
public static class PathBuilder {
private File file;
private PathBuilder ( File root ) {
file = root;
}
private PathBuilder ( String root ) {
file = new File(root);
}
public PathBuilder append ( File more ) {
file = new File(file, more.getPath()) );
return this;
}
public PathBuilder append ( String more ) {
file = new File(file, more);
return this;
}
public File buildFile () {
return file;
}
}
public static PathBuilder buildPath ( File root ) {
return new PathBuilder(root);
}
public static PathBuilder buildPath ( String root ) {
return new PathBuilder(root);
}
}
Example of usage:
File root = File.listRoots()[0];
String hello = "hello";
String world = "world";
String filename = "warez.lha";
File file = Files.buildPath(root).append(hello).append(world)
.append(filename).buildFile();
String absolute = file.getAbsolutePath();
The resulting absolute will contain something like:
/hello/world/warez.lha
or maybe even:
A:\hello\world\warez.lha
If you do not need more than strings, you can use com.google.common.io.Files
Files.simplifyPath("some/prefix/with//extra///slashes" + "file//name")
to get
"some/prefix/with/extra/slashes/file/name"
Here's a solution which handles multiple path parts and edge conditions:
public static String combinePaths(String ... paths)
{
if ( paths.length == 0)
{
return "";
}
File combined = new File(paths[0]);
int i = 1;
while ( i < paths.length)
{
combined = new File(combined, paths[i]);
++i;
}
return combined.getPath();
}
This also works in Java 8 :
Path file = Paths.get("Some path");
file = Paths.get(file + "Some other path");
This solution offers an interface for joining path fragments from a String[] array. It uses java.io.File.File(String parent, String child):
public static joinPaths(String[] fragments) {
String emptyPath = "";
return buildPath(emptyPath, fragments);
}
private static buildPath(String path, String[] fragments) {
if (path == null || path.isEmpty()) {
path = "";
}
if (fragments == null || fragments.length == 0) {
return "";
}
int pathCurrentSize = path.split("/").length;
int fragmentsLen = fragments.length;
if (pathCurrentSize <= fragmentsLen) {
String newPath = new File(path, fragments[pathCurrentSize - 1]).toString();
path = buildPath(newPath, fragments);
}
return path;
}
Then you can just do:
String[] fragments = {"dir", "anotherDir/", "/filename.txt"};
String path = joinPaths(fragments);
Returns:
"/dir/anotherDir/filename.txt"
Assuming all given paths are absolute paths. you can follow below snippets to merge these paths.
String baseURL = "\\\\host\\testdir\\";
String absoluteFilePath = "\\\\host\\testdir\\Test.txt";;
String mergedPath = Paths.get(baseURL, absoluteFilePath.replaceAll(Matcher.quoteReplacement(baseURL), "")).toString();
output path is \\host\testdir\Test.txt.
I want to repackage apache's httpclient lib to ship it with an android app (like https://code.google.com/p/httpclientandroidlib/ but with HttpClient 4.3.1)
Therefore, I downloaded the httpclient 4.3.1 jar (includes all its dependencies) by hand and used jarjar to repackage it:
x#x$: cd libs && for f in *.jar; do java -jar ../jarjar-1.4.jar process ../rules.txt $f out/my-$f; done
with rules.txt:
rule org.apache.http.** my.repackaged.org.apache.http.#1
Then I used ant to put the output together:
<project name="MyProject" default="merge" basedir=".">
<target name="merge">
<zip destfile="my-org-apache-httpclient-4.3.1.jar">
<zipgroupfileset dir="libs/out" includes="*.jar"/>
</zip>
</target>
</project>
I can use that file to develop and test my app, but if I deploy it on android, it throws an exception s/th like that it cannot find my.repackaged.org.apache.logging.log4j.something referenced by my.package.org.apache.logging.whatEver.
So, now I want to strip out any dependency on commons-logging by using bytecode manipulation. This has been done before: http://sixlegs.com/blog/java/dependency-killer.html
But I wonder how I actually do it? There are only dependencies on org.apache.commons.logging.Log:
x$x$: java -jar jarjar-1.4.jar find jar my-org-apache-httpclient-4.3.1.jar commons-logging-1.1.3.jar
my/http/impl/execchain/ServiceUnavailableRetryExec -> org/apache/commons/logging/Log
my/http/impl/execchain/RetryExec -> org/apache/commons/logging/Log
my/http/impl/execchain/RedirectExec -> org/apache/commons/logging/Log
my/http/impl/execchain/ProtocolExec -> org/apache/commons/logging/Log
...
I think the way to go is, to remove these dependencies and replace it with an own implementation like he did here https://code.google.com/p/httpclientandroidlib/ . Therefore, I made a new maven project with only one class with provided scope for the commons-logging that implements org.apache.commons.logging.Log interface and just delefates to the android.utils.Log:
MyLog implements org.apache.commons.logging.Log {}
in the package my.log and I packaged that in my-log-1.0.0.jar. I put that jar into the same folder as the repackaged httpclient-jars and used ant as mentioned above to package all together in my-org-apache-httpclient-4.3.1.jar.
Approach 1
I tried to use jarjar again:
java -jar jarjar-1.4.jar process rules2.txt my-org-apache-httpclient-4.3.1.jar my-org-apache-httpclient-4.3.1-without-logging-dep.jar
with rules2.txt:
rule my.repackaged.commons.logging.** my.log.#1
but that does not work. The exception that it cannot find my.repackaged.org.apache.logging.log4j.something referenced by my.package.org.apache.logging.whatEver is still thrown.
Approach 2
I also tried to delete the logging stuff from the final jar and/or repackage the my.repackaged.org.apache.log4j and logging to its original packages:
rules2.txt v2:
rule my.repackaged.org.apache.log4j.** org.apache.log4j.#1
rule my.repackaged.org.apache.logging.** org.apache.logging.#1
but that also is still throwing the excpetion: my.repackaged.org.apache.logging.log4j.something referenced by my.package.org.apache.logging.whatEver
QUESTION
How can I kill/replace that commons-logging dependencies and get rid of the Exception?
Introduction
If a program depends on a library it usually means that it uses methods of the library. Removing a dependency is therefore not a simple task. You effectively want to take away code that is - at least formally - required by the program.
There are three ways of removing dependencies:
Adapt the source code to not depend on the library and compile it from scratch.
Modify the bytecode to remove references to the library the project depends on.
Manipulate the runtime to not require the dependency. The easiest way is to recreate the required classes and to put them into the jar file.
None of these ways are really pretty. All of them can require a lot of work. None are guaranteed to work without side effects.
Solution
I will describe my solution by presenting the files and steps I used to solve the problem. To reproduce, you will need the following files (in a single directory):
lib/xxx-v.v.v.jar: The library jars (httpclient and dependencies, excluding commons-logging-1.1.3.jar)
jarjar-1.4.jar: Used for repackaging the jars
rules.txt: The jarjar rules
rule org.apache.http.** my.http.#1
rule org.apache.commons.logging.** my.logging.#1
build.xml: Ant build configuration
<project name="MyProject" basedir=".">
<target name="logimpl">
<javac srcdir="java/src" destdir="java/bin" target="1.5" />
<jar jarfile="out/logimpl.jar" basedir="java/bin" />
</target>
<target name="merge">
<zip destfile="httpclient-4.3.1.jar">
<zipgroupfileset dir="out" includes="*.jar"/>
</zip>
</target>
</project>
java/src/Log.java
package my.logging;
public interface Log {
public boolean isDebugEnabled();
public void debug(Object message);
public void debug(Object message, Throwable t);
public boolean isInfoEnabled();
public void info(Object message);
public void info(Object message, Throwable t);
public boolean isWarnEnabled();
public void warn(Object message);
public void warn(Object message, Throwable t);
public boolean isErrorEnabled();
public void error(Object message);
public void error(Object message, Throwable t);
public boolean isFatalEnabled();
public void fatal(Object message);
public void fatal(Object message, Throwable t);
}
java/src/LogFactory.java
package my.logging;
public class LogFactory {
private static Log log;
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String name) {
if(log == null) {
log = new Log() {
public boolean isWarnEnabled() { return false; }
public boolean isInfoEnabled() { return false; }
public boolean isFatalEnabled() { return false; }
public boolean isErrorEnabled() {return false; }
public boolean isDebugEnabled() { return false; }
public void warn(Object message, Throwable t) {}
public void warn(Object message) {}
public void info(Object message, Throwable t) {}
public void info(Object message) {}
public void fatal(Object message, Throwable t) {}
public void fatal(Object message) {}
public void error(Object message, Throwable t) {}
public void error(Object message) {}
public void debug(Object message, Throwable t) {}
public void debug(Object message) {}
};
}
return log;
}
}
do_everything.sh
#!/bin/sh
# Repackage library
mkdir -p out
for jf in lib/*.jar; do
java -jar jarjar-1.4.jar process rules.txt $jf `echo $jf | sed 's/lib\//out\//'`
done
# Compile logging implementation
mkdir -p java/bin
ant logimpl
# Merge jar files
ant merge
That's it. Open up a console and execute
cd my_directory && ./do_everything.sh
This will create a folder "out" containing single jar files and "httpclient-4.3.1.jar" which is the final, independent and working jar file. So, what did we just do?
Repackaged httpclient (now in my.http)
Modified the library to use my.logging instead of org.apache.commons.logging
Compiled required classes to be used by the library (my.logging.Log and my.logging.LogFactory).
Merged the repackaged libraries and the compiled classes into a single jar file, httpclient-4.3.1.jar.
Pretty simple, isn't it? Just read the shell script line by line to discover the single steps. To check whether all dependencies were removed you can run
java -jar jarjar-1.4.jar find class httpclient-4.3.1.jar commons-logging-1.1.3.jar
I tried the generated jar file with SE7 and Android 4.4, it worked in both cases (see below for remarks).
Class file version
Every class file has a major version and a minor version (both depend on the compiler). The Android SDK requires class files to have a major version less than 0x33 (so everything pre 1.7 / JDK 7). I added the target="1.5" attribute to the ant javac task so the generated class files have a major version of 0x31 and can therefore be included in your Android app.
Alternative (bytecode manipulation)
You're lucky. Logging is (almost always) a one-way operation. It barely causes side effects affecting the main program. That means that removing commons-logging should be possible as it won't affect the functionality of the program.
I chose the second way, bytecode manipulation, which you suggested in your question. The concept is basically just this (A is httpclient, B is commons-logging):
If the return type of a method of A is part of B, the return type will be changed to java.lang.Object.
If any argument of a method of A has a type that is part of B, the argument type will be changed to java.lang.Object.
Invocations of methods belonging to B are removed entirely. pop and constant instructions are inserted to repair the VM stack.
Types belonging to B are removed from descriptors of methods called from A. This requires the target class (the class containing the called method) to be processed. All object types belonging to B will be replaced with java.lang.Object.
Instructions that attempt to access fields of classes belonging to B are removed. pop and constant instructions are inserted to repair the VM stack.
If a method tries to access a field of a type that belongs to B, the field signature referenced by the instruction is changed to java.lang.Object. This requires the target class (the class containing the accessed field) to be processed.
Fields of a type contained in B but belonging to classes of A are modified so that their type is java.lang.Object.
As you can see, the idea behind this is to replace all referenced classes with java.lang.Object and to remove all accesses to class members belonging to commons-logging.
I don't know whether this is reliable and I did not test the library after applying the manipulator. But from what I saw (the disassembled class files and no VM errors while loading the class files) I am fairly sure the code works.
I tried to document almost everything the program does. It uses the ASM Tree API which provides rather simple access to the class file structure. And - to avoid unnecessary negative reviews - this is "quick 'n' dirty" code. I did not really test it a lot and I bet there are faster ways of bytecode manipulation. But this program seems to fulfill the OP's needs and that's all I wrote it for.
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public class DependencyFinder {
public static void main(String[] args) throws IOException {
if(args.length < 2) return;
DependencyFinder df = new DependencyFinder();
df.analyze(new File(args[0]), new File(args[1]), "org.apache.http/.*", "org.apache.commons.logging..*");
}
#SuppressWarnings("unchecked")
public void analyze(File inputFile, File outputFile, String sClassRegex, String dpClassRegex) throws IOException {
JarFile inJar = new JarFile(inputFile);
JarOutputStream outJar = new JarOutputStream(new FileOutputStream(outputFile));
for(Enumeration<JarEntry> entries = inJar.entries(); entries.hasMoreElements();) {
JarEntry inEntry = entries.nextElement();
InputStream inStream = inJar.getInputStream(inEntry);
JarEntry outEntry = new JarEntry(inEntry.getName());
outEntry.setTime(inEntry.getTime());
outJar.putNextEntry(outEntry);
OutputStream outStream = outJar;
// Only process class files, copy all other resources
if(inEntry.getName().endsWith(".class")) {
// Initialize class reader and writer
ClassReader classReader = new ClassReader(inStream);
ClassWriter classWriter = new ClassWriter(0);
String className = classReader.getClassName();
// Check whether to process this class
if(className.matches(sClassRegex)) {
System.out.println("Processing " + className);
// Parse entire class
ClassNode classNode = new ClassNode(Opcodes.ASM4);
classReader.accept(classNode, 0);
// Check super class and interfaces
String superClassName = classNode.superName;
if(superClassName.matches(dpClassRegex)) {
throw new RuntimeException(className + " extends " + superClassName);
}
for(String iface : (List<String>) classNode.interfaces) {
if(iface.matches(dpClassRegex)) {
throw new RuntimeException(className + " implements " + superClassName);
}
}
// Process methods
for(MethodNode method : (List<MethodNode>) classNode.methods) {
Type methodDesc = Type.getMethodType(method.desc);
boolean changed = false;
// Change return type if necessary
Type retType = methodDesc.getReturnType();
if(retType.getClassName().matches(dpClassRegex)) {
retType = Type.getObjectType("java/lang/Object");
changed = true;
}
// Change argument types if necessary
Type[] argTypes = methodDesc.getArgumentTypes();
for(int i = 0; i < argTypes.length; i++) {
if(argTypes[i].getClassName().matches(dpClassRegex)) {
argTypes[i] = Type.getObjectType("java/lang/Object");
changed = true;
}
}
if(changed) {
// Update method descriptor
System.out.print("Changing " + method.name + methodDesc);
methodDesc = Type.getMethodType(retType, argTypes);
method.desc = methodDesc.getDescriptor();
System.out.println(" to " + methodDesc);
}
// Remove method invocations
InsnList insns = method.instructions;
for(int i = 0; i < insns.size(); i++) {
AbstractInsnNode insn = insns.get(i);
// Ignore all other nodes
if(insn instanceof MethodInsnNode) {
MethodInsnNode mnode = (MethodInsnNode) insn;
Type[] cArgTypes = Type.getArgumentTypes(mnode.desc);
Type cRetType = Type.getReturnType(mnode.desc);
if(mnode.owner.matches(dpClassRegex)) {
// The method belongs to one of the classes we want to get rid of
System.out.println("Removing method call " + mnode.owner + "." +
mnode.name + " in " + method.name);
boolean isStatic = (mnode.getOpcode() == Opcodes.INVOKESTATIC);
if(!isStatic) {
// pop instance
insns.insertBefore(insn, new InsnNode(Opcodes.POP));
}
for(int j = 0; j < cArgTypes.length; j++) {
// pop argument on stack
insns.insertBefore(insn, new InsnNode(Opcodes.POP));
}
// Insert a constant value to repair the stack
if(cRetType.getSort() != Type.VOID) {
InsnNode valueInsn = getValueInstruction(cRetType);
insns.insertBefore(insn, valueInsn);
}
// Remove the actual method call
insns.remove(insn);
// Go back one instruction to not skip the next one
i--;
} else {
changed = false;
if(cRetType.getClassName().matches(dpClassRegex)) {
// Change return type
cRetType = Type.getObjectType("java/lang/Object");
changed = true;
}
for(int j = 0; j < cArgTypes.length; j++) {
if(cArgTypes[j].getClassName().matches(dpClassRegex)) {
// Change argument type
cArgTypes[j] = Type.getObjectType("java/lang/Object");
changed = true;
}
}
if(changed) {
// Update method invocation
System.out.println("Patching method call " + mnode.owner + "." +
mnode.name + " in " + method.name);
mnode.desc = Type.getMethodDescriptor(cRetType, cArgTypes);
}
}
} else if(insn instanceof FieldInsnNode) {
// Yeah I lied... we must not ignore all other instructions
FieldInsnNode fnode = (FieldInsnNode) insn;
Type fieldType = Type.getType(fnode.desc);
if(fnode.owner.matches(dpClassRegex)) {
System.out.println("Removing field access to " + fnode.owner + "." +
fnode.name + " in " + method.name);
// Patch code
switch(fnode.getOpcode()) {
case Opcodes.PUTFIELD:
case Opcodes.GETFIELD:
// Pop instance
insns.insertBefore(insn, new InsnNode(Opcodes.POP));
if(fnode.getOpcode() == Opcodes.PUTFIELD) break;
case Opcodes.GETSTATIC:
// Repair stack
insns.insertBefore(insn, getValueInstruction(fieldType));
break;
default:
throw new RuntimeException("Invalid opcode");
}
// Remove instruction
insns.remove(fnode);
i--;
} else {
if(fieldType.getClassName().matches(dpClassRegex)) {
// Change field type
System.out.println("Patching field access to " + fnode.owner +
"." + fnode.name + " in " + method.name);
fieldType = Type.getObjectType("java/lang/Object");
}
// Update field type
fnode.desc = fieldType.getDescriptor();
}
}
}
}
// Process fields
for(FieldNode field : (List<FieldNode>) classNode.fields) {
Type fieldType = Type.getType(field.desc);
if(fieldType.getClassName().matches(dpClassRegex)) {
System.out.print("Changing " + fieldType.getClassName() + " " + field.name);
fieldType = Type.getObjectType("java/lang/Object");
field.desc = fieldType.getDescriptor();
System.out.println(" to " + fieldType.getClassName());
}
}
// Class processed
classNode.accept(classWriter);
} else {
// Nothing changed
classReader.accept(classWriter, 0);
}
// Write class to JAR entry
byte[] bClass = classWriter.toByteArray();
outStream.write(bClass);
} else {
// Copy file
byte[] buffer = new byte[1024 * 64];
int read;
while((read = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, read);
}
}
outJar.closeEntry();
}
outJar.flush();
outJar.close();
inJar.close();
}
InsnNode getValueInstruction(Type type) {
switch(type.getSort()) {
case Type.INT:
case Type.BOOLEAN:
return new InsnNode(Opcodes.ICONST_0);
case Type.LONG:
return new InsnNode(Opcodes.LCONST_0);
case Type.OBJECT:
case Type.ARRAY:
return new InsnNode(Opcodes.ACONST_NULL);
default:
// I am lazy, I did not implement all types
throw new RuntimeException("Type not implemented: " + type);
}
}
}
I tried really hard, but always get a Class Not Found exception, from reading this answer
https://stackoverflow.com/a/3024261
I took my jar run the
dx --dex --output=C:\classes.dex C:\MyAndroidLib.jar
and got a dex file
then I run the
apt add C:\MyLib.jar C:\classes.dex
to create a jar with the dex file.
then I wrote the following code.
DexClassLoader classLoader = new DexClassLoader(
destPath, dirPath +"/" , null, getClass().getClassLoader());
Class<?> classToLoad = classLoader.loadClass("ClassImpl");
on debug I can see that the dex is inside the classLoader(under the mDexs member)
and the ClassImpl is the only class I got inside.
but I keep getting the class not found exception.
Anyone got a working sample of dynamic class loading from external jar ?
Someone knows whats my problem?
I did not use aapt... Only dex. And I got my class loaded, and my methods called.
Use this code for see inside the dex file:
DexFile dexfile = DexFile.loadDex(url_jar_path,
File.createTempFile("opt", "dex", context.getCacheDir()).getPath(), 0);
// Print all classes in the DexFile
Enumeration<String> classNames = dexfile.entries();
String classname = "";
while (true) {
if (! classNames.hasMoreElements()) {
break;
}
classname = classNames.nextElement();
}
Use this code for see inside the class:
Class class = dexclassloader.loadClass(ruta_clase_en_jar);
String name;
Method[] method_array = clase.getMethods();
for (Method i: method_array) {
name = i.getName ();
}
I am developing software that loads information from XML files using Android's implementation of java.xml.parsers.DocumentBuilder and DocumentBuilderFactory. I am writing unit tests of my objects and I need to be able to provide a variety of xml files that will exercise the code under test. I am using Eclipse and have a separate Android Test Project. I cannot find a way to put the test xml into the test project such that the code under test can open the files.
If I put the files in /assets of the test project, the code under test cannot see it.
If I put the files in the /assets of the code under test, it can of course see the files, but now I'm cluttering up my actual system with test only data files.
If I hand copy the files to the /sdcard/data directory, I can open them from the code under test, but that interferes with automating my tests.
Any suggestions of how to have different xml test files reside in the test package but be visible to the code under test would be greatly appreciated.
Here is how I tried to structure the unit test:
public class AppDescLoaderTest extends AndroidTestCase
{
private static final String SAMPLE_XML = "sample.xml";
private AppDescLoader m_appDescLoader;
private Application m_app;
protected void setUp() throws Exception
{
super.setUp();
m_app = new Application();
//call to system under test to load m_app using
//a sample xml file
m_appDescLoader = new AppDescLoader(m_app, SAMPLE_XML, getContext());
}
public void testLoad_ShouldPopulateDocument() throws Exception
{
m_appDescLoader.load();
}
}
This did not work as the SAMPLE_XML file is in the context of the test, but AndroidTestCase is providing a context for the system under test, which cannot see an asset from the test package.
This is the modified code that worked per answer given:
public class AppDescLoaderTest extends InstrumentationTestCase
{
...
protected void setUp() throws Exception
{
super.setUp();
m_app = new Application();
//call to system under test to load m_app using
//a sample xml file
m_appDescLoader = new AppDescLoader(m_app, SAMPLE_XML, getInstrumentation().getContext());
}
Option 1: Use InstrumentationTestCase
Suppose you got assets folder in both android project and test project, and you put the XML file in the assets folder. in your test code under test project, this will load xml from the android project assets folder:
getInstrumentation().getTargetContext().getResources().getAssets().open(testFile);
This will load xml from the test project assets folder:
getInstrumentation().getContext().getResources().getAssets().open(testFile);
Option 2: Use ClassLoader
In your test project, if the assets folder is added to project build path (which was automatically done by ADT plugin before version r14), you can load file from res or assets directory (i.e. directories under project build path) without Context:
String file = "assets/sample.xml";
InputStream in = this.getClass().getClassLoader().getResourceAsStream(file);
For Android and JVM unit tests I use following:
public final class DataStub {
private static final String BASE_PATH = resolveBasePath(); // e.g. "./mymodule/src/test/resources/";
private static String resolveBasePath() {
final String path = "./mymodule/src/test/resources/";
if (Arrays.asList(new File("./").list()).contains("mymodule")) {
return path; // version for call unit tests from Android Studio
}
return "../" + path; // version for call unit tests from terminal './gradlew test'
}
private DataStub() {
//no instances
}
/**
* Reads file content and returns string.
* #throws IOException
*/
public static String readFile(#Nonnull final String path) throws IOException {
final StringBuilder sb = new StringBuilder();
String strLine;
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"))) {
while ((strLine = reader.readLine()) != null) {
sb.append(strLine);
}
} catch (final IOException ignore) {
//ignore
}
return sb.toString();
}
}
All raw files I put into next path: ".../project_root/mymodule/src/test/resources/"
Try this for Kotlin:
val json = File("src\\main\\assets\\alphabets\\alphabets.json").bufferedReader().use { it.readText() }