How to code coverage a IOException catch JUnit5 - java

Have not been able to write a test that covers the catch in here:
public class ServiceToTest {
public void unzip(byte[] zipFile) {
try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(zipFile))) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
byte[] buffer = new byte[1024];
int len;
try (var file = new ByteArrayOutputStream(buffer.length)) {
while ((len = zipInputStream.read(buffer)) > 0) {
file.write(buffer, 0, len);
}
System.out.println(entry.getName());
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
// some logic or rethrow the exception
}
}
}
Have tried for a while with the help of Mockito but nothing seems to work with this one, I was thinking on mocking the ZipInputStream and throw a IOException when it tries to getNextEntry() or somehow throw the IOException at any moment so I can coverage that catch, but since everything is inside the method I do not know how could I mock something inside it? Another option was to change the IOException to Exception, and I can cause that catch by passing a null zipFile in my test, but that does not seem like the right way, any ideas?
Also the method does more stuff but I let it here as simple as I could so that we can focus on covering the catch

I would go with extracting the part which could cause IOException and override that in a test implementation to invoke the Exception block code
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ServiceToTest {
public void unzip(byte[] zipFile) {
try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(zipFile))) {
writeToFile(zipInputStream);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
protected void writeToFile(ZipInputStream zipInputStream) throws IOException {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
byte[] buffer = new byte[1024];
int len;
try (ByteArrayOutputStream file = new ByteArrayOutputStream(buffer.length)) {
while ((len = zipInputStream.read(buffer)) > 0) {
file.write(buffer, 0, len);
}
System.out.println(entry.getName());
}
}
}
}
The test should something be like
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipInputStream;
class ServiceTest {
#Test
public void shouldUnzip() {
ServiceToTest serviceToTest = new ServiceToTest();
serviceToTest.unzip(new File("yourFilePath").toString().getBytes());
//Assert happy path
}
#Test
public void shouldThrowIOException() {
ServiceToTest serviceToTest = new ServiceToTestChild();
serviceToTest.unzip(new File("yourFilePath").toString().getBytes());
//Assert exception path
}
private class ServiceToTestChild extends ServiceToTest {
#Override
protected void writeToFile(ZipInputStream zipInputStream) throws IOException {
throw new IOException();
}
}
}

Related

Java DecriptionInfo on Word DocX

I'm pulling my hair out as I cannot get the samples to work - hopefully someone can help..
I would like to DETECT if a docx and a doc file is password protected/encrypted. I have seen this posted in a few places but I cannot get it work - it doesnt throw an exception. Can someone see what I am doing wrong. Note I only need to detect the password..i dont want to open the document.
String fileLocation = "C:/myfile.docx";
File file = new File(fileLocation);
FileInputStream fis = new FileInputStream(file.getAbsolutePath());
POIFSFileSystem pfis = new POIFSFileSystem(fis);
try{
EncryptionInfo info = new EncryptionInfo(pfis);
EncryptionMode mode = info.getEncryptionMode();
Decryptor d = Decryptor.getInstance(info);
//Try and open it
if(!d.verifyPassword(Decryptor.DEFAULT_PASSWORD))
{
//file is encrypted
}
}
catch(GeneralSecurityException gse)
{
//file is encrypted
}
catch(EncryptedDocumentException edc)
{
//file is encrypted
}
I haven't elaborated much to get the code smaller, but I've simply taken one of the factory classes - like SlideShowFactory - and modified it for H/XWPF. As H/XWPF hasn't got a common interface on the document level (as of now), the quick&dirty approach is to return an Object.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentFactoryHelper;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
public class EncDetect {
public static void main(String[] args) {
String dir = "/home/kiwiwings/project/poi/poi/test-data";
String[] files = {
"document/bug53475-password-is-solrcell.docx",
"document/password_tika_binaryrc4.doc",
"document/58067.docx",
"document/58804.doc"
};
for (String f : files) {
try {
DocumentFactory.create(new File(dir, f));
System.out.println(f + " not encrypted");
} catch (EncryptedDocumentException e) {
System.out.println(f + " is encrypted");
} catch (Exception e) {
System.out.println(f + " " +e.getMessage());
}
}
}
static class DocumentFactory {
public static Object create(NPOIFSFileSystem fs) throws IOException {
return create(fs, null);
}
public static Object create(final NPOIFSFileSystem fs, String password) throws IOException {
DirectoryNode root = fs.getRoot();
// Encrypted OOXML files go inside OLE2 containers, is this one?
if (root.hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) {
InputStream stream = null;
try {
stream = DocumentFactoryHelper.getDecryptedStream(fs, password);
return createXWPFDocument(stream);
} finally {
IOUtils.closeQuietly(stream);
}
}
// If we get here, it isn't an encrypted XWPF file
// So, treat it as a regular HWPF one
boolean passwordSet = false;
if (password != null) {
Biff8EncryptionKey.setCurrentUserPassword(password);
passwordSet = true;
}
try {
return createHWPFDocument(fs);
} finally {
if (passwordSet) {
Biff8EncryptionKey.setCurrentUserPassword(null);
}
}
}
public static Object create(InputStream inp) throws IOException, EncryptedDocumentException {
return create(inp, null);
}
public static Object create(InputStream inp, String password) throws IOException, EncryptedDocumentException {
// If clearly doesn't do mark/reset, wrap up
if (! inp.markSupported()) {
inp = new PushbackInputStream(inp, 8);
}
// Ensure that there is at least some data there
byte[] header8 = IOUtils.peekFirst8Bytes(inp);
// Try to create
if (NPOIFSFileSystem.hasPOIFSHeader(header8)) {
NPOIFSFileSystem fs = new NPOIFSFileSystem(inp);
return create(fs, password);
}
if (DocumentFactoryHelper.hasOOXMLHeader(inp)) {
return createXWPFDocument(inp);
}
throw new IllegalArgumentException("Your InputStream was neither an OLE2 stream, nor an OOXML stream");
}
public static Object create(File file) throws IOException, EncryptedDocumentException {
return create(file, null);
}
public static Object create(File file, String password) throws IOException, EncryptedDocumentException {
return create(file, password, false);
}
public static Object create(File file, String password, boolean readOnly) throws IOException, EncryptedDocumentException {
if (!file.exists()) {
throw new FileNotFoundException(file.toString());
}
NPOIFSFileSystem fs = null;
try {
fs = new NPOIFSFileSystem(file, readOnly);
return create(fs, password);
} catch(OfficeXmlFileException e) {
IOUtils.closeQuietly(fs);
return createXWPFDocument(file, readOnly);
} catch(RuntimeException e) {
IOUtils.closeQuietly(fs);
throw e;
}
}
protected static Object createHWPFDocument(NPOIFSFileSystem fs) throws IOException, EncryptedDocumentException {
return new HWPFDocument(fs.getRoot());
}
protected static Object createXWPFDocument(InputStream stream) throws IOException, EncryptedDocumentException {
return new XWPFDocument(stream);
}
protected static Object createXWPFDocument(File file, boolean readOnly) throws IOException, EncryptedDocumentException {
try {
OPCPackage pkg = OPCPackage.open(file, readOnly ? PackageAccess.READ : PackageAccess.READ_WRITE);
return new XWPFDocument(pkg);
} catch (InvalidFormatException e) {
throw new IOException(e);
}
}
}
}

Why can't I use FileInputStream to feed MessageDigest object?

Why must I use DigestInputStream and not FileInputStream to get a digest of an file?
I have written a program that reads ints from FileInputStream, converts them to bytes and passes them to update method of MessageDigest object. But I have a suspicion that it doesn't work properly, because it calculates a digest of a very large file instanlty. Why doesn't it work?
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DigestDemo {
public static byte[] getSha1(String file) {
FileInputStream fis = null;
MessageDigest md = null;
try {
fis = new FileInputStream(file);
} catch(FileNotFoundException exc) {
System.out.println(exc);
}
try {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException exc) {
System.out.println(exc);
}
byte b = 0;
do {
try {
b = (byte) fis.read();
} catch (IOException e) {
System.out.println(e);
}
if (b != -1)
md.update(b);
} while(b != -1);
return md.digest();
}
public static void writeBytes(byte[] a) {
for (byte b : a) {
System.out.printf("%x", b);
}
}
public static void main(String[] args) {
String file = "C:\\Users\\Mike\\Desktop\\test.txt";
byte[] digest = getSha1(file);
writeBytes(digest);
}
}
You need to change the type of b to int,, and you need to call MessageDigest.doFinal() at the end of the file, but this is horrifically inefficient. Try reading and updating from a byte array.
There's too much try-catching in this code. Reduce it to one try and two catches, outside the loop.

see the content of a .bson file using java

I have a very large .bson file.
Now I have two question:
How can I see the content of that file? (I know it can do with "bsondump", but this command is slow, specialy for large database) (In fact I want to see the structure of that file)
How can I see the content of that file using java?
You can easily read/parse a bson file in Java using a BSONDecoder instance such as BasicBSONDecoder or DefaultBSONDecoder. These classes are included in mongo-java-driver.
Here's a simple example of a Java implementation of bsondump.
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.bson.BSONDecoder;
import org.bson.BSONObject;
import org.bson.BasicBSONDecoder;
public class BsonDump {
public void bsonDump(String filename) throws FileNotFoundException {
File file = new File(filename);
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
BSONDecoder decoder = new BasicBSONDecoder();
int count = 0;
try {
while (inputStream.available() > 0) {
BSONObject obj = decoder.readObject(inputStream);
if(obj == null){
break;
}
System.out.println(obj);
count++;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
}
}
System.err.println(String.format("%s objects read", count));
}
public static void main(String args[]) throws Exception {
if (args.length < 1) {
//TODO usage
throw new IllegalArgumentException("Expected <bson filename> argument");
}
String filename = args[0];
BsonDump bsonDump = new BsonDump();
bsonDump.bsonDump(filename);
}
}

How To Modify The Raw XML message of an Outbound CXF Request?

I would like to modify an outgoing SOAP Request.
I would like to remove 2 xml nodes from the Envelope's body.
I managed to set up an Interceptor and get the generated String value of the message set to the endpoint.
However, the following code does not seem to work as the outgoing message is not edited as expected. Does anyone have some code or ideas on how to do this?
public class MyOutInterceptor extends AbstractSoapInterceptor {
public MyOutInterceptor() {
super(Phase.SEND);
}
public void handleMessage(SoapMessage message) throws Fault {
// Get message content for dirty editing...
StringWriter writer = new StringWriter();
CachedOutputStream cos = (CachedOutputStream)message.getContent(OutputStream.class);
InputStream inputStream = cos.getInputStream();
IOUtils.copy(inputStream, writer, "UTF-8");
String content = writer.toString();
// remove the substrings from envelope...
content = content.replace("<idJustification>0</idJustification>", "");
content = content.replace("<indicRdv>false</indicRdv>", "");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(content.getBytes(Charset.forName("UTF-8")));
message.setContent(OutputStream.class, outputStream);
}
Based on the first comment, I created an abstract class which can easily be used to change the whole soap envelope.
Just in case someone wants a ready-to-use code part.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.log4j.Logger;
/**
* http://www.mastertheboss.com/jboss-web-services/apache-cxf-interceptors
* http://stackoverflow.com/questions/6915428/how-to-modify-the-raw-xml-message-of-an-outbound-cxf-request
*
*/
public abstract class MessageChangeInterceptor extends AbstractPhaseInterceptor<Message> {
public MessageChangeInterceptor() {
super(Phase.PRE_STREAM);
addBefore(SoapPreProtocolOutInterceptor.class.getName());
}
protected abstract Logger getLogger();
protected abstract String changeOutboundMessage(String currentEnvelope);
protected abstract String changeInboundMessage(String currentEnvelope);
public void handleMessage(Message message) {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage()
|| message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Outbound message: " + currentEnvelopeMessage);
}
String res = changeOutboundMessage(currentEnvelopeMessage);
if (res != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Outbound message has been changed: " + res);
}
}
res = res != null ? res : currentEnvelopeMessage;
InputStream replaceInStream = IOUtils.toInputStream(res, "UTF-8");
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
os.flush();
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
getLogger().warn("Unable to perform change.", ioe);
throw new RuntimeException(ioe);
}
} else {
try {
InputStream is = message.getContent(InputStream.class);
String currentEnvelopeMessage = IOUtils.toString(is, "UTF-8");
IOUtils.closeQuietly(is);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Inbound message: " + currentEnvelopeMessage);
}
String res = changeInboundMessage(currentEnvelopeMessage);
if (res != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Inbound message has been changed: " + res);
}
}
res = res != null ? res : currentEnvelopeMessage;
is = IOUtils.toInputStream(res, "UTF-8");
message.setContent(InputStream.class, is);
IOUtils.closeQuietly(is);
} catch (IOException ioe) {
getLogger().warn("Unable to perform change.", ioe);
throw new RuntimeException(ioe);
}
}
}
public void handleFault(Message message) {
}
private class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
protected void doFlush() throws IOException {
currentStream.flush();
}
protected void doClose() throws IOException {
}
protected void onWrite() throws IOException {
}
}
}
I had this problem as well today. After much weeping and gnashing of teeth, I was able to alter the StreamInterceptor class in the configuration_interceptor demo that comes with the CXF source:
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String soapMessage = IOUtils.toString(csnew.getInputStream());
...
The soapMessage variable will contain the complete SOAP message. You should be able to manipulate the soap message, flush it to an output stream and do a message.setContent(OutputStream.class... call to put your modifications on the message. This comes with no warranty, since I'm pretty new to CXF myself!
Note: CachedStream is a private class in the StreamInterceptor class. Don't forget to configure your interceptor to run in the PRE_STREAM phase so that the SOAP interceptors have a chance to write the SOAP message.
Following is able to bubble up server side exceptions. Use of os.close() instead of IOUtils.closeQuietly(os) in previous solution is also able to bubble up exceptions.
public class OutInterceptor extends AbstractPhaseInterceptor<Message> {
public OutInterceptor() {
super(Phase.PRE_STREAM);
addBefore(StaxOutInterceptor.class.getName());
}
public void handleMessage(Message message) {
OutputStream os = message.getContent(OutputStream.class);
CachedOutputStream cos = new CachedOutputStream();
message.setContent(OutputStream.class, cos);
message.getInterceptorChain.aad(new PDWSOutMessageChangingInterceptor(os));
}
}
public class OutMessageChangingInterceptor extends AbstractPhaseInterceptor<Message> {
private OutputStream os;
public OutMessageChangingInterceptor(OutputStream os){
super(Phase.PRE_STREAM_ENDING);
addAfter(StaxOutEndingInterceptor.class.getName());
this.os = os;
}
public void handleMessage(Message message) {
try {
CachedOutputStream csnew = (CachedOutputStream) message .getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString( csnew.getInputStream(), (String) message.get(Message.ENCODING));
csnew.flush();
IOUtils.closeQuietly(csnew);
String res = changeOutboundMessage(currentEnvelopeMessage);
res = res != null ? res : currentEnvelopeMessage;
InputStream replaceInStream = IOUtils.tolnputStream(res, (String) message.get(Message.ENCODING));
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
message.setContent(OutputStream.class, os);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
Good example for replacing outbound soap content based on this
package kz.bee.bip;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class SOAPOutboundInterceptor extends AbstractPhaseInterceptor<Message> {
public SOAPOutboundInterceptor() {
super(Phase.PRE_STREAM);
addBefore(SoapPreProtocolOutInterceptor.class.getName());
}
public void handleMessage(Message message) {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage()
|| message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
/* here we can set new data instead of currentEnvelopeMessage*/
InputStream replaceInStream = IOUtils.toInputStream(currentEnvelopeMessage, "UTF-8");
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
os.flush();
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
public void handleFault(Message message) {
}
private static class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
protected void doFlush() throws IOException {
currentStream.flush();
}
protected void doClose() throws IOException {
}
protected void onWrite() throws IOException {
}
}
}
a better way would be to modify the message using the DOM interface, you need to add the SAAJOutInterceptor first (this might have a performance hit for big requests) and then your custom interceptor that is executed in phase USER_PROTOCOL
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Node;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
abstract public class SoapNodeModifierInterceptor extends AbstractSoapInterceptor {
SoapNodeModifierInterceptor() { super(Phase.USER_PROTOCOL); }
#Override public void handleMessage(SoapMessage message) throws Fault {
try {
if (message == null) {
return;
}
SOAPMessage sm = message.getContent(SOAPMessage.class);
if (sm == null) {
throw new RuntimeException("You must add the SAAJOutInterceptor to the chain");
}
modifyNodes(sm.getSOAPBody());
} catch (SOAPException e) {
throw new RuntimeException(e);
}
}
abstract void modifyNodes(Node node);
}
this one's working for me. It's based on StreamInterceptor class from configuration_interceptor example in Apache CXF samples.
It's in Scala instead of Java but the conversion is straightforward.
I tried to add comments to explain what's happening (as far as I understand).
import java.io.OutputStream
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor
import org.apache.cxf.helpers.IOUtils
import org.apache.cxf.io.CachedOutputStream
import org.apache.cxf.message.Message
import org.apache.cxf.phase.AbstractPhaseInterceptor
import org.apache.cxf.phase.Phase
// java note: base constructor call is hidden at the end of class declaration
class StreamInterceptor() extends AbstractPhaseInterceptor[Message](Phase.PRE_STREAM) {
// java note: put this into the constructor after calling super(Phase.PRE_STREAM);
addBefore(classOf[SoapPreProtocolOutInterceptor].getName)
override def handleMessage(message: Message) = {
// get original output stream
val osOrig = message.getContent(classOf[OutputStream])
// our output stream
val osNew = new CachedOutputStream
// replace it with ours
message.setContent(classOf[OutputStream], osNew)
// fills the osNew instead of osOrig
message.getInterceptorChain.doIntercept(message)
// flush before getting content
osNew.flush()
// get filled content
val content = IOUtils.toString(osNew.getInputStream, "UTF-8")
// we got the content, we may close our output stream now
osNew.close()
// modified content
val modifiedContent = content.replace("a-string", "another-string")
// fill original output stream
osOrig.write(modifiedContent.getBytes("UTF-8"))
// flush before set
osOrig.flush()
// replace with original output stream filled with our modified content
message.setContent(classOf[OutputStream], osOrig)
}
}

Java Custom Class Loader

Any ideas why I am getting this error? (Yes, I looked up the error, and still haven't found a solution)
My error:
Exception in thread "main" java.lang.ClassFormatError: Truncated class file
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at org.fellixombc.mysql.util.MysqlClassLoader.findClass(MysqlClassLoader.java:22)
at org.fellixombc.mysql.util.MysqlClassLoader.loadClass(MysqlClassLoader.java:14)
at org.fellixombc.mysql.Main.main(Main.java:9)
Files:
Main.java
package org.fellixombc.mysql;
import org.fellixombc.mysql.util.MysqlClassLoader;
public class Main {
public static void main(String[] args) {
MysqlClassLoader mcl = new MysqlClassLoader();
try {
mcl.loadClass("org.fellixombc.mysql.net.Client");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Client.java:
package org.fellixombc.mysql.net;
public class Client {
public Client() {
System.out.println("Hello!");
}
}
MysqlClassLoder.java:
package org.fellixombc.mysql.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MysqlClassLoader extends ClassLoader {
public MysqlClassLoader() {
super(MysqlClassLoader.class.getClassLoader());
}
#Override
public Class<?> loadClass(String className) throws ClassNotFoundException {
return findClass(className);
}
#Override
public Class<?> findClass(String className) throws ClassNotFoundException {
byte[] b = null;
try {
b = loadClassData(className);
Class c = defineClass(className, b, 0, b.length);
if(c != null)
return c;
return super.findClass(className);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private byte[] loadClassData(String className) throws IOException {
int size = className.length();
byte buff[] = new byte[size];
// Open the file
FileInputStream fis = new FileInputStream("bin/" + className.replace('.', File.separatorChar) + ".class");
fis.available();
fis.read(buff);
fis.close();
return buff;
}
}
Yes, you're reading at most a byte count equal to the number of characters in the filename. Instead, you need to read the whole file. Here's one method, using readFully as you suggested.
File f = new File("bin/" + className.replace('.', File.separatorChar) + ".class");
DataInputStream is = new DataInputStream(new FileInputStream(f));
int len = (int)f.length();
byte[] buff = new byte[len];
is.readFully(buff);
is.close();
return buff;
Since you're not handling built-in classes like Object, I think you need to catch the FileNotFoundException from loadClassData in your findClass, then call super.findClass. E.g.:
try {
try {
b = loadClassData(className);
}
catch(FileNotFoundException fnf) {
return super.findClass(className);
}
Class c = defineClass(className, b, 0, b.length);
if(c != null)
return c;
return super.findClass(className);
} catch (IOException e) {
e.printStackTrace();
}
return null;
You are reading only N bytes (N=length of class name) from the .class file into the buffer (in loadClassData) before returning it.
You need to read the contents of the entire class before you return the buffer for the class to be properly defined.

Categories

Resources