3

I'm kind of stuck with this problem.

I have this FileDetails class which stores details/metadata of the file along with the complete file in a byte array. I want to send the FileDetails object inside ObjectOutputStream across network, where the receiver will simple read the file and cast it back to FileDetails.

Here is the code:

class FileDetails {

    private String fileName;
    private long fileSize;
    private byte[] fileData;

    public FileDetails(String fileName, long fileSize, byte[] fileData) {
        this.fileName = fileName;
        this.fileSize = fileSize;       
        this.fileData = fileData;
    }

    public String getFileName() {
        return fileName;
    }

    public long getFileSize() {
        return fileSize;
    }

    public byte[] getFileData() {
        return fileData;
    }

}


File file = new File("C://test.dat");
RandomAccessFile randFileAccess = new RandomAccessFile(file, "r");
byte[] buff = new byte[(int) file.length()];
randFileAccess.readFully(buff);

FileDetails fd = new FileDetails(file.getname(), file.length(); buff);

FileOutputStream fos = = new FileOutputStream(C://oos.dat);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(fd);
oos.write(buff);

The problem is that the file "test.dat" is quite large and it's not optimal to read it fully into the buffer(very large) in one go. I could have read the file into the buffer in chunks, but that would require me to create file and save data into the disk, which I cannot do as FileDetails object takes byte array.

How can I solve this problem? I want this approach only, i.e. Storing data as byte array in FileDetails object and then converting it to ObjectOutputStream, because I will be appending the appending an mp3 file infornt of the ObjectOutStream file and sending it over the internet.

Any suggetions? Or alternative approach?

Edit: Actually I am developing an android app. Where it stores the metadata of the file in a FileDetails object along with the file data in byte array. This FileDetails object is converted into an ObjectOutputStream file. Now an a specific mp3 file is appended in front of this ObjectOutputStream file, which is used to recognize that the file has been sent by my app. This combined mp3 file (which contains "hidden" ObjectOutputStream file) is send via a "popular" message app to the receiver. The receiver downloads the mp3 file through his "popular" message app. Now my app comes into action. It recognizes the mp3 file. And extracts the ObjectOutputStream file from the mp3 file. And casts it back to FileDetails and retrieves the Original file with it's metadata. Is my approach correct? Is there any other way to recognize my appended/hidden file?

Thanks a lot in advance.

Retr0spect
  • 187
  • 1
  • 13
  • 1
    You are free to define the protocol between sender and receiver? Send the metadata ("details") as an object but append the bytes "raw", i.e., by copying from the file (e.g. test.dat) to the output stream. This avoids storing the file data in a byte array. – laune Feb 08 '15 at 13:44
  • I cannot define any protocols. Actually I am developing an app for android, which makes this objectoutputstream file, appends it to mp3 file, and then send this mp3 file through another "popular" messaging app. The receiver downloads this mp3 file through this "popular" messaging app. Now my app comes into action and reads this mp3 file and if the mp3 matches a predefined pattern, it reads the objectoutputstream data and save the file to the SD Card. All in all it's an app that send data "hidden" inside mp3 file. – Retr0spect Feb 08 '15 at 13:53
  • But sender and receiver of that object output stream come from you? You define the class FileDetails, don't you - so it has to be at either end. – laune Feb 08 '15 at 13:57
  • Yes the files FileDetails class is at both the end. But I'm not sure how I can read and identify the bytes which are "metadata" and which are the actual file "test.dat". – Retr0spect Feb 08 '15 at 14:03
  • You can write custom methods readObject and writeObject for java.io.Serializable. I'll have a go at that and publish it here. – laune Feb 08 '15 at 14:05
  • I mean suppose first 1000 bytes are mp3 file and next xxx bytes are the metadata and yyy are the actual file data. I can seek to 1000+1 bytes, but how will I know exact xxx bytes are metadata and yyy bytes are file? Because both xxx and yyy will be different for different file. I am very confused. – Retr0spect Feb 08 '15 at 14:06
  • I will try to write customs methods as you said. But doubt I'll be able to do it. I'm very new to java. And thanks a lot for your kind help. – Retr0spect Feb 08 '15 at 14:09
  • How do you know how long the MP3 stuff is? – laune Feb 08 '15 at 14:44
  • Because I choose the mp3 file. This mp3 file is used only for identifying that the file(mp3+data) has been sent by me/myapp. I pack the mp3 inside android app's assets folder, pre-calculate it's md 5 hash and store it in a final String. Receiver's app reads the file upto mp3 file's .length() and calculates it's hash. If the hash matches the original stored hash, then it's confirmed that the file was sent using my app, and receiver can then extract the data using RandomAccessFile.seek(mpfile's .length) method. – Retr0spect Feb 08 '15 at 15:29

2 Answers2

2

Is it possible to add the class to the receiver? Then you could try something like this:

File file = new File("C://test.dat")
InputStream in = null;
   try {
     in = new BufferedInputStream(new FileInputStream(file));
     -> send over the network
    finally {
     if (in != null) {
       in.close();
     }
   }
 }

the receiver could just write the byte to a temporary file (and not hold them in memory)

InputStream is = socket.getInputStream();
FileOutputStream fos = new FileOutputStream("C://test.dat");
BufferedOutputStream bos = new BufferedOutputStream(fos);
//something like:
byte[] buffer = new byte[1024];
int len = is.read(buffer);
while (len != -1) {
    bos.write(buffer, 0, len);
    len = is.read(buffer);
}

and if the operation is finished, instantiate the object FileDetails fd = new FileDetails(the file you just created,....)

You can also send the class definition over network, if you must.

Leander
  • 1,322
  • 4
  • 16
  • 31
1

Here I've added read/writeObject methods:

class FileDetails implements Serializable {
  private static final int CHUNK_LEN = 0x10000; // 64k
  private String fileName;
  private long fileSize;
  private File file;

  // Note: everything can be deduced from a File object
  public FileDetails(File file) {
    this.fileName = file.getName();
    this.fileSize = file.length();       
    this.file = file;
  }

  public String getFileName() {
    return fileName;
  }

  public long getFileSize() {
    return fileSize;
  }

  // explicit coding for reading a FileDetails object
  private void readObject(ObjectInputStream stream)
    throws IOException, ClassNotFoundException {
    fileName = stream.readUTF();  // file name
    fileSize = stream.readLong(); // file size
    // file data as a series of byte[], length CHUNK_LEN
    long toRead = fileSize;
    // write file data to a File object, same path name
    file = new File( fileName );
    OutputStream os = new FileOutputStream( file );
    while( toRead > 0 ){
      // last byte arrays may be shorter than CHUNK_LEN
      int chunkLen = toRead > CHUNK_LEN ? CHUNK_LEN : (int)toRead;
      byte[] bytes = new byte[chunkLen];
      int nread = stream.read( bytes );
      // write data to file
      os.write( bytes, 0, nread );
      toRead -= nread;
    }
    os.close();
  }

  // explicit coding for writing a FileDetails object
  private void writeObject(ObjectOutputStream stream)
    throws IOException {
    stream.writeUTF( fileName );   // file name as an "UTF string"
    stream.writeLong( fileSize );  // file size
    // file data as a series of byte[], length CHUNK_LEN
    long toWrite = fileSize;
    // read file data from the File object passed to the constructor
    InputStream is = new FileInputStream( file );
    while( toWrite > 0 ){
      // last byte[] may be shorter than CHUNK_LEN
      int chunkLen = toWrite > CHUNK_LEN ? CHUNK_LEN : (int)toWrite;
      byte[] bytes = new byte[chunkLen];
      int nread = is.read( bytes );
      stream.write( bytes );
      toWrite -= nread;
    }
    is.close();
  }

  private void readObjectNoData()
    throws ObjectStreamException {
  }
}

I've tested this with a short file:

File file = new File( "test.dat" );
FileDetails fd = new FileDetails( file );
FileOutputStream fos = new FileOutputStream("oos.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject( fd );
oos.close();

// test on a local system: rename test.dat to avoid overwriting
file.renameTo( new File( "test.dat.sav" ) );

FileInputStream fis = new FileInputStream("oos.dat");
ObjectInputStream ois = new ObjectInputStream(fis);
FileDetails fd1 = (FileDetails)ois.readObject();
ois.close();
// now the file test.dat has been rewritten under the same path,
// i.e., test.dat exists again and test.dat.sav == test.dat

I'm not sure whether the receiver will be happy with some file being written according to a path name being sent in the message.

laune
  • 31,114
  • 3
  • 29
  • 42
  • The code is working perfectly. Laune, I cannot thank you enough. But one more thing. How can the receiver get back the original "test.dat" file? – Retr0spect Feb 08 '15 at 15:21
  • The receiver must (after skipping the mp3) do what I have coded in main after the rename: rebuild the FileDetails from the object input stream. - Some error handling code should be added; also, use try-with-resource to access the File, etc. – laune Feb 08 '15 at 16:21
  • I did that and I am getting FileDetails object. But I want to extract the actual "test.dat". Suppose I send a pdf file attached to the mp3 file as oos.dat(FileDetails object). How can receiver get the back the pdf file from oos.dat(FileDetails object)? Please pardon my ignorance, I have just started learning java. Thank you. – Retr0spect Feb 08 '15 at 16:43
  • But, during readObject, the file is written to "test.dat". Note that my main renames test.dat to test.dat.sav, so I'm actually rewriting the file from where it was read. In between it is stored in oos.dat, which represents the "data on the line". – laune Feb 08 '15 at 16:47
  • It's very confusing. I'll take a deep look into the code one more time. Cheers for your help. I really appreciate it. Have a great day laune. – Retr0spect Feb 08 '15 at 16:56
  • I can understand your vexation, to a degree. Methods readObject and writeObject aren't required if the class can be written and read "as is". Now (with the custom file data (de-)serialziation) they suddenly appear - without, apparently, being called. Both scenarios are triggered by the call to FileIn/OutputStream.read/writeObject(Object x), which introspects (= looks into the class code of) the object to see whether it is the "as is" or "explicit" case. – laune Feb 08 '15 at 17:39
  • I tried hard very hard to understand your code. But it's way too advanced for me. Also, I didn't understand anything what you just said. :( I guess I'll have to pick up some good book on java serialization. By the way, can you please give me the code that gets back test.dat from "fd1", i.e. FileDetails fd1 = (FileDetails)ois.readObject(); and writes it to the disk? – Retr0spect Feb 08 '15 at 22:20
  • Note that the file is already written to disk during deserialization, using the same file name. – laune Feb 09 '15 at 04:42