Solved

Truly invisible zip file access.

Posted on 2006-07-11
6
210 Views
Last Modified: 2011-09-20
All,

Has anyone seen a package that will allow me to do stuff like:

MyFile f = new MyFile ("zipFile.zip\\zippedFileName.ext");
MyFile g = new MyFile("normalFileName.ext");

I have a bunch of working, tested code that works with files and paths and I would like to add zip file handling with the minimum of fuss.

I can see and understand the zip file interface in java.util.zip and it will access zip files and create streams to the zipped contents but this would impact the clarity of the code significantly if I have to detect that I am working with a zip file in many places in the code.

I realise that if I work solely with streams and pass those around everywhere I'll get just about the same effect but each process needs to handle the file in a different way (check length, count records, parse data etc) so a stream is not so useful.

Besides, it would be quite a tidy solution if I could do most I/O using a file path in the above form.

Paul
0
Comment
Question by:PaulCaswell
  • 4
  • 2
6 Comments
 
LVL 13

Expert Comment

by:Webstorm
Comment Utility
Hi Paul,

I can't find such package.
You can create your own :

public class MyFile
{
    File f=null;
    ZipFile zip=null;
    ZipEntry entry=null;

    public MyFile(String path)
    // to quickly find separator between zipfile and zipentry, use | instead of \ or / for example
    {
          int i=path.indexOf('|'); //  generally | cannot be in file path
          if (i<0) f=new File(path);
          else
          {
               zip=new ZipFile(path.substring(0,i));
               entry=zip.getEntry(path.substring(i+1));
          }
    }

    public boolean exists()
    { return (f!=null && f.exists()) || entry!=null; }

    public String getName()
    { return (f!=null)?f.getName():(entry!=null)?entry.getName():null; }

    public long getSize()
    { return (f!=null)?f.length():(entry!=null)?entry.getSize():-1L; }

    public long getTime()
    { return (f!=null)?f.lastModified():(entry!=null)?entry.getTime():-1L; }

    public long isDirectory()
    { return (f!=null)?f.isDirectory():(entry!=null)?entry.isDirectory():false; }

    public InputStream getInputStream()
    { return (f!=null)? new FileInputStream(f) : (entry!=null)?entry.getInputStream() : null; }
}
0
 
LVL 16

Author Comment

by:PaulCaswell
Comment Utility
Thanks Webstorm,

I've done it this way. As a C expert and a C++-able programmer I suspect I have done things in a less-than-best way but if you could review/comment for my learning experience I would appreciate it.

First the ZFile clas which is equivalent to the File class so I can implement 'exists':

public class ZFile extends File {
    // The path to the zip file itself.
    String fileName = null;
    // The file inside the zip file.
    String zipEntry = null;
    // Pattern to look for .zip in a path.
    static private Pattern zipSplitter = Pattern.compile(".zip"+Pattern.quote(File.separator), Pattern.CASE_INSENSITIVE);


    // Just one creator to stop the ZFile() constructor from neing created.
    public ZFile(String pathname) {
      super(pathname);
      // Decode my path.
      decodePath (getAbsolutePath());
    }

    /**
     * exists
     *
     * @return boolean
     */
    public boolean exists() {
      boolean exists = true;
      // Normal paths pass straight through.
      if (!super.exists()) {
          // Currently not there.
          exists = false;
          // Decode the path.
          decodePath( getAbsolutePath() );
          // Did it decode correctly?
          if (fileName != null) {
            // Does the zip file exist?
            File zipFile = new File(fileName);
            if (zipFile.exists()) {
                // If we get here the zip file exists, does it contain the file specified?
                ZipFile zip = null;
                try {
                  // Open the zip file.
                  zip = new ZipFile(zipFile);
                  // Is this part in there?
                  ZipEntry entry = zip.getEntry(zipEntry);
                  if (entry != null) {
                      // Entry is in the file.
                      exists = true;

                  }
                  // We only want existence, not the file.
                  zip.close();
                  // Here I know that the zip file exists and the first entry specified in the full path also exists. My work is done.
                  // Check the rest of the parts. (later).
                  //for ( ;i < parts.length - 1; i++ ) {
                  //entry.isDirectory();
                  //}
                } catch (IOException ex) {
                  // Not there! No problem. exists is still false.
                }
            }
          }
      }
      return exists;
    }

    /**
     * decodePath
     */
    private void decodePath(String p) {
      // Could there be a zip file in the path?
      fileName = fileName(p);
      // And the entry.
      if ( p.length() >= fileName.length() + File.separator.length() ) {
          zipEntry = p.substring(fileName.length()+File.separator.length());
      } else {
          zipEntry = "";
      }
    }

    /**
     * toString
     *
     * @return String
     */
    public String toString() {
      return fileName;
    }

    /**
     * fileName
     *
     * @param fileName String
     * @return String
     */
    public static String fileName(String fileName) {
      Matcher match = zipSplitter.matcher(fileName);
      // By default its the whole file name.
      String name = fileName.substring(0);
      // Must be there and not at the end.
      if ( match.find() ) {
          // Its in there! Has it got an entry name too?
          name = fileName.substring(0,match.start());
          // Add the .zip but with the correct case so pull it out of the file name.
          int pos = match.end() - File.separator.length() - ".zip".length();
          name += fileName.substring(pos, pos + ".zip".length());
      }
      return name;
    }

    /**
     * zip
     *
     * @return boolean
     */
    public boolean zip() {
      return zipEntry.length() > 0;
    }

}

Then the ZReader which is like a FileReader but multiplexes between a normal FileReader and a Zip Stream:

public class ZReader extends FileReader {
    // My ZFile.
    ZFile zFile = null;
    // My ZipFile.
    ZipFile zipFile = null;
    // The entry in the zip file.
    ZipEntry zipEntry = null;
    // My ZipFileInputStream.
    // Remember that the file accessed by the 'this' methods will read the zip file itself, not the entry in the file.
    // All access of the entry must be redirected through this object.
    InputStreamReader z = null;

    public ZReader(String fileName) throws FileNotFoundException {
      // Use the static method in ZFile to grab the true file name on the way past.
      super(ZFile.fileName(fileName));
      /*
       * Hopefully, so long as my FileReader doppleganger found the file, the zip file is now open.
       * I can now present that stream to a ZipFile for opening the entity.
       */
      // Grab my file. It decides the file name and the entry name from the path name.
      zFile = new ZFile(fileName);
      // Is it a reference to an entry in a zip file?
      if ( zFile.zipEntry.length() > 0 ) {
          // Build my ZipFile object.
          try {
            // Use me as a stream.
            //z = new ZipInputStream(new InputStream(this));
            zipFile = new ZipFile(zFile.fileName);
            // Grab the entry.
            zipEntry = zipFile.getEntry(zFile.zipEntry);
            // Create the stream to that entry.
            z = new InputStreamReader(zipFile.getInputStream(zipEntry));
          } catch (IOException ex) {
            // Invalid file structure?
            throw new FileNotFoundException(ex.toString());
          }
      }
    }

    // Redirect the read to z if its a zip file.
    public int read(char b[], int off, int len) throws IOException {
      if ( z != null ) {
          return z.read(b, off, len);
      } else {
          return super.read(b, off, len);
      }
    }

    // Redirect the read to z if its a zip file.
    public int read() throws IOException {
      if ( z != null ) {
          return z.read();
      } else {
          return super.read();
      }
    }
    /**
     * Tell whether this stream is ready to be read.  An InputStreamReader is
     * ready if its input buffer is not empty, or if bytes are available to be
     * read from the underlying byte stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public boolean ready() throws IOException {
      if ( z != null ) {
          return z.ready();
      } else {
          return super.ready();
      }
    }

    /**
     * Close the stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void close() throws IOException {
      // Normal closure but should only close the entryStream.
      super.close();
      // Were we a zip file?
      if ( z != null ) {
          // Close the stream.
          z.close();
          z = null;
          // Close the ZipFile. Probably not a good idea.
          zipFile.close();
          zipFile = null;
      }
    }
}

The beauty of this setup is that all I need to do is change:

            // Open it.
            BufferedReader f = new BufferedReader(new FileReader(path + FileNames[STATUS_FILE]));

to:

            // Open it.
            BufferedReader f = new BufferedReader(new ZReader(path + FileNames[STATUS_FILE]));

and whether its a normal file or an entity in a zip file it still works.

Also:

          File f = new File(path + FileNames[i]);
          if (!f.exists()) {

becomes:

          ZFile f = new ZFile(path + FileNames[i]);
          if (!f.exists()) {

Any thoughts/enhancements/leaks?

Paul
0
 
LVL 13

Accepted Solution

by:
Webstorm earned 125 total points
Comment Utility

In ZFile  public static String fileName(String fileName)  :
>>    String name = fileName.substring(0);
should be :
      String name = fileName;
both will references the same String, String are immutable, so it's useless trying to clone them.

Otherwise your implementation don't need other performance hint.

Alternatively, you can implement it as I do in my previous comment, and use :

    MyFile mf=new MyFile("C:\\document.txt");
or
    MyFile mf=new MyFile("C:\\archive.zip|document.txt");

and then use both of them the same way :
    BufferedReader f = new BufferedReader(new InputStreamReader(mf.getInputStream()));

the ZipFile object in MyFile class isn't explicitly closed, but the finalize() method of ZipFile call the close() method when the ZipFile object is garbage-collected.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 16

Author Comment

by:PaulCaswell
Comment Utility
>>both will references the same String, String are immutable, so it's useless trying to clone them.
I think this came from my C/C++ experience. In C++ if you make a pointer to an object passed as a parameter you are likely to crash later. I guess this does not apply in Java, is that what 'immutable' means? That if I reference an object and it later changes, my copy does not?

>>... isnt explicitly closed ...
I thought about closure. Essentially I have two streams to the same file. As you can see from the comments:

    public void close() throws IOException {
      // Normal closure but should only close the entryStream.
      super.close();
      // Were we a zip file?
      if ( z != null ) {
          // Close the stream.
          z.close();
          z = null;
          // Close the ZipFile. Probably not a good idea.
          zipFile.close();
          zipFile = null;
      }
    }

First I invoke super.close which should close the entity stream. I then close the entity stream myself, then the file stream. My concern is that if the close is propagated throughout the whole object chain then the close of the file stream should close any entity streams open to it. Closing the entity stream first assumes that when the file is closed the entity stream object is resilient to multiple closure. Is this a safe assumption?

Sorry to wander a bit here. I'll try to get around to an 'Accept' in a little while. If you feel I am asking too many questions just say. Thanks for all your help. :-)

Paul
0
 
LVL 13

Expert Comment

by:Webstorm
Comment Utility
immutable means cannot be modified.
For example, if you want to append a string to another :
  String a = "first",b;
  b = a + " and second";    // variable a still contain "first", variable b reference a new String containing "first and second"
  a = a + " string";   //  variable a reference a new string containing "first string"
  // the original string "first" is still in memory but isn't referenced anymore, so will be garbage-collected.

Each operation on String objects (+, substring(), replace() ...) creates a new String. The original one is never modified.

>>I thought about closure.
I meant in my implementation.
0
 
LVL 13

Expert Comment

by:Webstorm
Comment Utility
:-)
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

Java contains several comparison operators (e.g., <, <=, >, >=, ==, !=) that allow you to compare primitive values. However, these operators cannot be used to compare the contents of objects. Interface Comparable is used to allow objects of a cl…
Introduction This article is the last of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article covers our test design approach and then goes through a simple test case example, how …
Viewers learn about the “while” loop and how to utilize it correctly in Java. Additionally, viewers begin exploring how to include conditional statements within a while loop and avoid an endless loop. Define While Loop: Basic Example: Explanatio…
Viewers will learn about the different types of variables in Java and how to declare them. Decide the type of variable desired: Put the keyword corresponding to the type of variable in front of the variable name: Use the equal sign to assign a v…

728 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now