Jar file locator

Hey gyus,

some time ago I created the locator that searches the jar file looking for assets.

I know there is a ZipLocator, but it only looks inside the given zip file.

JarFileLocator searches deep into other jars if they are present within the current archive.

The only disadvantage is that jars stored inside the current archive are being unpacked to the disk and then searched.

They are of course deleted after they are used but if someone knows better solution - feel free to implement it :wink:

Here is the code. Feel free to use it :wink:



[java]



/**

  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.

    *
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:

    *
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.

    *
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.

    *
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.

    *
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

    */

    package com.jme3.asset.plugins;

    import java.io.BufferedInputStream;

    import java.io.BufferedOutputStream;

    import java.io.ByteArrayInputStream;

    import java.io.File;

    import java.io.FileOutputStream;

    import java.io.IOException;

    import java.io.InputStream;

    import java.util.ArrayList;

    import java.util.Enumeration;

    import java.util.List;

    import java.util.jar.JarEntry;

    import java.util.jar.JarFile;

    import java.util.logging.Level;

    import java.util.logging.Logger;

    import java.util.zip.ZipEntry;

    import com.jme3.asset.AssetInfo;

    import com.jme3.asset.AssetKey;

    import com.jme3.asset.AssetLocator;

    import com.jme3.asset.AssetManager;

    /**
  • This class tries to locate a resource inside the given jar file.
  • The jar file can contain other jars so they are being searched too recursively.
  • @author Marcin Roguski (Kealthas)

    /

    public class JarFileLocator implements AssetLocator {

    private static final Logger LOGGER = Logger.getLogger(JarFileLocator.class.getName());

    /
    * The root path is the jar file name. */

    protected String rootPath;

    @Override

    public void setRootPath(String rootPath) {

    this.rootPath = rootPath;

    }

    @Override

    @SuppressWarnings("rawtypes")

    public AssetInfo locate(AssetManager manager, AssetKey key) {

    File jarFile = new File(rootPath);

    InputStream inputStream = null;

    try {

    inputStream = this.searchJarFile(new JarFile(jarFile), key.getName());

    } catch(IOException e) {

    LOGGER.log(Level.WARNING, "Problem with opening/reading a jar file: {0}", jarFile.getName());

    }

    if(inputStream != null) {

    return new JarFileAssetInfo(manager, key, inputStream);

    }

    return null;

    }

    /**
  • This method searches the jar file looking for asset of a given name. If the asset is not found then other jars
  • within this one are being searched. If no asset is found then null is returned.
  • @param jarFile
  •    the jar file the locator is searching<br />
    
  • @param assetName
  •    the name of the asset<br />
    
  • @return asset’s input stream or null if none asset is found
  • @throws IOException
  •     an exception is thrown when input-output errors occur<br />
    

*/

private InputStream searchJarFile(JarFile jarFile, String assetName) throws IOException {

InputStream inputStream = null;

Enumeration<JarEntry> entries = jarFile.entries();

ZipEntry zipEntry = jarFile.getEntry(assetName);

if(zipEntry != null) {

inputStream = jarFile.getInputStream(zipEntry);

inputStream = this.toByteArrayInputStream(inputStream);

} else {

//look for jar files inside

while(entries.hasMoreElements() && inputStream == null) {

JarEntry entry = entries.nextElement();

if(entry.getName().endsWith(".jar")) {

zipEntry = jarFile.getEntry(entry.getName());

if(zipEntry != null) {//we have found a talisman game extension :slight_smile:

//getting the jar name (without folders)

String jarName = entry.getName().replace(’’, ‘/’);

jarName = jarName.substring(entry.getName().lastIndexOf(’/’) + 1);

inputStream = jarFile.getInputStream(zipEntry);

File file = this.saveTemporaryFile(jarName, inputStream);

if(file != null) {

JarFile newJarFile = new JarFile(file);

inputStream = this.searchJarFile(newJarFile, assetName);

newJarFile.close();

if(!file.delete()) {

LOGGER.warning("Unable to delete temporary file: " + file.getName());

}

}

}

}

}

}

return inputStream;

}

/**

  • This method unpacks internal jar file into temporarty file. The file should be removed after use.
  • @param name
  •    the name of the jar file to be unpacked<br />
    
  • @param inputStream
  •    the stream with jar's data<br />
    
  • @return an unpacked file or null if problems occured

    */

    private File saveTemporaryFile(String name, InputStream inputStream) {

    //choosing a unique name

    File file = new File(name);

    int fileCounter = 0;

    while(file.exists()) {

    name += fileCounter;

    ++fileCounter;

    file = new File(name);

    }

    //storing data from input stream inside the file

    BufferedOutputStream bos = null;

    try {

    bos = new BufferedOutputStream(new FileOutputStream(name));

    BufferedInputStream bis;

    if(inputStream instanceof BufferedInputStream) {

    bis = (BufferedInputStream)inputStream;

    } else {

    bis = new BufferedInputStream(inputStream);

    }

    int data = bis.read();

    while(data > -1) {

    bos.write(data);

    data = bis.read();

    }

    bos.flush();

    } catch(IOException e) {

    try {

    bos.close();

    } catch(IOException e1) {

    LOGGER.warning("Unable to close the stream for file: " + name);

    }

    bos = null;//so that finally doesn’t close it again

    if(!file.delete()) {

    LOGGER.warning("Unable to delete temporary file: " + name);

    }

    file = null;

    } finally {

    if(bos != null) {

    try {

    bos.close();

    } catch(IOException e) {

    LOGGER.warning("Unable to close the stream for file: " + name);

    }

    }

    }

    return file;

    }

    /**
  • This method copies th data from the given input stream to byte array input stream. This way the original stream
  • can be closed and the data is safely buffered into another stream.
  • @param inputStream
  •    the stream with source data<br />
    
  • @return stream with buffered data from the source stream
  • @throws IOException
  •     an exception is thrown when input-output errors occur<br />
    

*/

private ByteArrayInputStream toByteArrayInputStream(InputStream inputStream) throws IOException {

BufferedInputStream bis;

if(inputStream instanceof BufferedInputStream) {

bis = (BufferedInputStream)inputStream;

} else {

bis = new BufferedInputStream(inputStream);

}

List<Byte> dataList = new ArrayList<Byte>(bis.available());

int dataByte = inputStream.read();

while(dataByte > -1) {

dataList.add(Byte.valueOf((byte)dataByte));

dataByte = inputStream.read();

}

byte[] data = new byte[dataList.size()];

for(int i = 0; i < dataList.size(); ++i) {

data = dataList.get(i).byteValue();

}

return new ByteArrayInputStream(data);

}

/**

  • This class is an asset info containing the stream with asset data.
  • @author Marcin Roguski (Kaelthas)

    /

    private static class JarFileAssetInfo extends AssetInfo {

    /
    * The stream with asset’s data. */

    private InputStream inputStream;

    /**
  • Constructor requres the input stream.
  • @param assetManager
  •    the asset manager<br />
    
  • @param assetKey
  •    the asset key<br />
    
  • @param inputStream
  •    the input stream with asset's data<br />
    

*/

@SuppressWarnings("rawtypes")

public JarFileAssetInfo(AssetManager assetManager, AssetKey assetKey, InputStream inputStream) {

super(assetManager, assetKey);

this.inputStream = inputStream;

}

@Override

public InputStream openStream() {

return inputStream;

}

}

}

[/java]