Custom Java classloader and inner classes - java

I have this method (in my own classloader) that loads classes from zip:
ZipInputStream in = new ZipInputStream(new FileInputStream(zip));
ZipEntry entry;
while ((entry = in.getNextEntry()) != null) {
if (!entry.isDirectory()) {
byte[] buffer = new byte[(int) entry.getSize()];
in.read(buffer);
if (!entry.getName().endsWith(".class"))
continue;
String name = entry.getName().replace(".class", "").replace("/", ".");
Class<?> cls = this.defineClass(name, buffer, 0, buffer.length);
this.resolveClass(cls);
}
}
The zip that im trying to load looks like this:
TestClass.class
TestClass$SomeOtherInnerClass.class
My problem is that defineClass() fails to load the TestClass$SomeOtherInnerClass. If this class is loaded before the actual TestClass i get this:
java.lang.NoClassDefFoundError: TestClass
I also tried to load the TestClass.class first but then im getting this error:
java.lang.ClassFormatError: Wrong InnerClasses attribute length in class file TestClass
Is there something i'm doing wrong?

I looks like you may not be overriding ClassLoader.findClass(). Without doing that, the ClassLoader you are extending does not know how to find these classes.
Override that function with something that simply looks up in a private static Map<String, Class<?>> for the class. As you load each class, put it into that map.
The difficulty will be in loading classes in the correct order, as your current implementation will not allow you to jump back to searching the Zip and calling defineClass() from your new findClass() method.

There's at least one bug in that you don't (necessarily) fully read the buffer (and ZipEntry.getSize may return -1).

Related

`Cannot find symbol` when trying to access a property of a child class but the variable is of type superclass

Java noob here, I have a variable of type OutputStream and later on in the function I have a condition where I either assign OutputStream to a new instance of FileOutputStream or ByteArrayOutputStream, however whenever I try to access any property that belongs to any of the subclasses. I get a Error cannot find symbol.
Is there a way to keep the variable of the same parent class and try to tell the runtime that whenever I need to access the property it would be of the child's class type?
Here is some pseudo code
public void work(Map params)
{
OutputStream output = null;
if(params.isAFile)
{
output = new FileOutputStream();
}
else
{
output = new ByteArrayOutputStream();
}
...do some work that makes use of the OutputStreams class
if(isFile)
{
return output
}
else
{
mybuffer = output.buf; //it fails here with Cannot find symbol since output is of type OutputStream when it should be treated as type ByteArrayOutputStream
return myBuffer;
}
}
You have to cast it like this:
(ByteArrayOutputStream output).buf
Java doesn't know that it can safely call the method in ByteArrayOutputStream since OutputStream doesn't have that method.

Determining the Efferent coupling between objects (CBO Metric) using the parsed byte-code generated by BCEL

I have built a program, which takes in a provided ".class" file and parses it using the BCEL, I've learnt how to calculate the LCOM4 value now. Now I would like to know how to calculate the CBO(Coupling between object) value of the class file. I've scoured the whole web, trying to find a proper tutorial about it, but I've been unable so far (I've read the whole javadoc regarding the BCEL as well and there was a similar question on stackoverflow but it has been removed). So I would like some help with this issue, as in some detailed tutorials or code snippets that would help me understand on how to do it.
OK, here you must compute the CBO of the classes within a whole set of classes. The set can be the content of a directory, of a jar file, or all the classes in a classpath.
I would fill a Map<String,Set<String>> with the class name as the key, and the classes it refers to:
private void addClassReferees(File file, Map<String, Set<String>> refMap)
throws IOException {
try (InputStream in = new FileInputStream(file)) {
ClassParser parser = new ClassParser(in, file.getName());
JavaClass clazz = parser.parse();
String className = clazz.getClassName();
Set<String> referees = new HashSet<>();
ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
for (Method method: clazz.getMethods()) {
Code code = method.getCode();
InstructionList instrs = new InstructionList(code.getCode());
for (InstructionHandle ih: instrs) {
Instruction instr = ih.getInstruction();
if (instr instanceof FieldOrMethod) {
FieldOrMethod ref = (FieldInstruction)instr;
String cn = ref.getClassName(cp);
if (!cn.equals(className)) {
referees.add(cn);
}
}
}
}
refMap.put(className, referees);
}
}
When you've added all the classes in the map, you need to filter the referees of each class to limit them to the set of classes considered, and add the backward links:
Set<String> classes = new TreeSet<>(refMap.keySet());
for (String className: classes) {
Set<String> others = refMap.get(className);
others.retainAll(classes);
for (String other: others) {
refMap.get(other).add(className);
}
}

How do I pass in the string from a text file into my JUnit code ?

I have the following file, which contains a binary representation of an .MSG file :
binaryMessage.txt
And I put it in my Eclipse workspace, in the following folder - src/main/resources/test :
I want to use the string which is within this text file , within the following JUnit code, so I tried the following way :
request.setContent("src/main/resources/test/binaryMessage");
mockMvc.perform(post(EmailController.PATH__METADATA_EXTRACTION_OPERATION)
.contentType(MediaType.APPLICATION_JSON)
.content(json(request)))
.andExpect(status().is2xxSuccessful());
}
But this doesn't work. Is there a way I can pass in the string the file directly without using IO code ?
You can't read a file without using IO code (or libraries that use IO code). That said, it's not that difficult to read the file into memory so you can send it.
To read a binary file into a byte[] you can use this method:
private byte[] readToByteArray(InputStream is) throws IOException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
} finally {
if (is != null) {
is.close();
}
}
}
Then you can do
request.setContent(readToByteArray(getClass().getResourceAsStream("test/binaryMessage")));
In addition to my comment on Samuel's answer, I just noticed that you depend on your concrete execution directory. I personally don't like that and normally use the class loader's functions to find resources.
Thus, to be independent of your working directory, you can use
getClass().getResource("/test/binaryMessage")
Convert this to URI and Path, then use Files.readAllBytes to fetch the contents:
Path resourcePath = Paths.get(getClass().getResource("/test/binaryMessage").toURI());
byte[] content = Files.readAllBytes(resourcePath);
... or even roll that into a single expression.
But to get back to your original question: no, this is I/O code, and you need it. But since the dawn of Java 7 (in 2011!) this does not need to be painful anymore.

Junit - mock a file

I'm trying to cover code that process a file. I'm trying to avoid using real file for tests, so I'm using Mockito.
This is the code I'm trying to test:
try {
byte[] data = Files.readAllBytes(((File) body).toPath());
immutableBody = data;
actualHeaderParams.put(HttpHeaders.CONTENT_LENGTH, (new Integer(data.length)).toString());
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
I'm using mock file:
File mockedFile = Mockito.mock(File.class);
but I get an Exception on 'toPath'. So I added some path or null, but then again I get Exceptions since the file doesn't exist in the path.
when(mockedFile.toPath()).thenReturn(Paths.get("test.txt"));
getting:
com.http.ApiException: There was a problem reading the file: test.txt
Is there any way doing it without creating a real file for the test?
Since you want to mock reading of files I assume you have some logic in this class which you would like to test in isolation (without using actual files), therefore I suggest to:
Move the responsibility of reading files into a separate class, so instead of having:
byte[] data = Files.readAllBytes(((File) body).toPath());
interleaved with your business logic, have:
byte[] data = fileReader.read(body);
and fileReader will be an instance of your class with a very simple implementation along these lines:
class FileToBytesReader {
byte[] read(File file) throws IOException {
return Files.readAllBytes(((File) body).toPath());
}
}
then in your test you can subsitute fileReader with a mock on which you can set expectations.
If you are using Java 8 you do not have to create the FileToBytesReader class, but you can use java.util.Function:
Function<File, byte[]> fileReader = (file) -> {
try {
return Files.readAllBytes(((File) file).toPath());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
BTW. If you are working on a legacy code and you cannot change the production code, then you have to use PowerMock to mock this static method.
I'm not sure there is an easy way but I might be wrong. You would probably need to mock the static Files.readAllBytes() method which you would need to use something like PowerMock to do. Or you could wrap this in a method which you can then mock the behaviour of:
public byte[] getAllBytesWrapper(File body) {
return Files.readAllBytes(body.toPath());
}
and then have a mock for this method:
when(classUnderTest.getAllBytesWrapper(any(File.class))).thenReturn("test".getBytes());
Mock Files.readAllBytes() with Matchers.any() as arguments. and return a byte array.

Loading a class with custom ClassLoader without using a String

I have created a custom ClassLoader and want to load a class. I am using this code at the moment to load the class from the Jar:
ByteArrayInputStream byteIS = new ByteArrayInputStream(data);
JarInputStream jarIS = new JarInputStream(byteIS);
JarEntry je;
je = jarIS.getNextJarEntry();
byte[] classbytes = new byte[(int) je.getSize()];
jarIS.read(classbytes, 0, classbytes.length);
jarIS.close();
CustomClassLoader classLoader = new CustomClassLoader();
classLoader.setClassContent(classbytes);
Class c = classLoader.findClass("Main");
Object object = c.newInstance();
Method[] methods = object.getClass().getMethods();
Object returnValue = methods[0].invoke(null, new Object[]{new String[]{}});
In this sample above you can clearly see I am trying to load class Main. Now imagine that my friend also creates a Jar, I cannot know on beforehand what the name of the class is. How can I avoid the usage of a String as argument?
You might want to have a look at the ServiceLoader API. You can define a common interface for service implementations (classes) that you don't know a priori.

Categories