/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * 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 the 'Xith3D Project Group' 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) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.resources; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.jagatoo.loaders.textures.locators.TextureStreamLocator; import org.jagatoo.loaders.textures.locators.TextureStreamLocatorURL; import org.xith3d.loaders.texture.TextureLoader; /** * The ResourceLocator serves as an abstraction layer to locate resources from * a base folder or base URL.<br> * Use the {@link #create(String)} method to create an instance and the {@link #getResource(String)} * or {@link #getResourceAsStream(String)} method to get child resources.<br> * You can also create sub-ResourceLocators to handle sub-locations more easily.<br> * If you want to use a specific instance as a singleton, you the {@link #setSingletonInstance(ResourceLocator)} * or {@link #useAsSingletonInstance()} method. Then you can also use the {@link ResLoc} * class to have shorter commands in your coding. * * @see ResLoc * * @author Marvin Froehlich (aka Qudus) */ public class ResourceLocator { private static ResourceLocator singletonInstance = null; private URL baseURL; /** * If you want to use one ResourceLocator as a singleton, first invoke this * and then use the getInstance() method to access the instance everywhere. * * @param resLoc the ResourceLocator instance to use as the singleton instance * * @see #useAsSingletonInstance() * @see #getInstance() * @see ResLoc */ public static void setSingletonInstance( ResourceLocator resLoc ) { singletonInstance = resLoc; } /** * If you want to use one ResourceLocator as a singleton, first invoke this * and then use the getInstance() method to access the instance everywhere. * * @see #setSingletonInstance(ResourceLocator) * @see #getInstance() * @see ResLoc */ public void useAsSingletonInstance() { singletonInstance = this; } /** * If you want to use one ResourceLocator as a singleton, use this. * But remember to first (once) invoke the setSingletonInstance() method. * * @see #setSingletonInstance(ResourceLocator) * @see ResLoc * * @return the singleton instance (if already set) */ public static ResourceLocator getInstance() { return ( singletonInstance ); } /** * Sets the base-URL of this ResourceLocator. */ protected void setBaseURL( URL baseURL ) { this.baseURL = baseURL; } /** * Sets the baseURL. The given baseFolder is converted to * a URL and is taken as the base-URL of the new instance. * * @param baseFolder * * @throws FileNotFoundException if the folder does not exist * @throws IllegalArgumentException if the baseFolder is not a directory */ protected void setBaseURL( File baseFolder ) throws FileNotFoundException, IllegalArgumentException { if ( !baseFolder.exists() ) throw new FileNotFoundException( "The base-folder does not exist (\"" + baseFolder + "\")." ); if ( !baseFolder.isDirectory() ) throw new IllegalArgumentException( "The given base-folder is not a directory (\"" + baseFolder + "\")." ); try { setBaseURL( baseFolder.toURI().toURL() ); } catch ( MalformedURLException e ) { e.printStackTrace(); } } /** * Sets the baseURL. The given baseFolder is converted to * a URL and is taken as the base-URL of the new instance. * * @param foldername * * @throws FileNotFoundException if the folder does not exist * @throws IllegalArgumentException if the baseFolder is not a directory */ protected void setBaseURL( String foldername ) throws FileNotFoundException, IllegalArgumentException { setBaseURL( new File( foldername ) ); } /** * @return the base-URL of this ResourceLocator */ public URL getBaseURL() { return ( baseURL ); } /** * Creates a new ResourceLocator instance with the sub-URL as its baseURL. * * @param subResource the relative resource to complete with this * instance's baseURL to the new instance's baseURL * @return the new sub-ResourceLocator * * @throws MalformedURLException if something was wrong with the resource */ public ResourceLocator getSubLocator( String subResource ) throws MalformedURLException { subResource = subResource.replace( '\\', '/' ); if ( !subResource.endsWith( "/" ) ) subResource += "/"; URL subURL = new URL( getBaseURL(), subResource ); return ( new ResourceLocator( subURL ) ); } /** * Creates a resource as a URL from this ResourceLocator. The given * resource-name must be relative to this instance's baseURL. * * @param name the relative resource to complete with this * instance's baseURL to an absolute one * @return the resource * * @throws MalformedURLException if something was wrong with the resource */ public URL getResource( String name ) throws MalformedURLException { name = name.replace( '\\', '/' ); return ( new URL( getBaseURL(), name ) ); } /** * Creates a resource as a URL from this ResourceLocator. The given * resource-name must be relative to this instance's baseURL. * * @param name the relative resource to complete with this * instance's baseURL to an absolute one * @return an InputStream from the resource * * @throws MalformedURLException if something was wrong with the resource * @throws IOException when the InputStream could not be created */ public InputStream getResourceAsStream( String name ) throws MalformedURLException, IOException { URL resource = getResource( name ); return ( resource.openStream() ); } private final int findAllResources( JarEntry[] entries, int i0, String basePath, String extension, boolean recursively, boolean foldersToo, List< URL > resources ) { for ( int i = i0; i < entries.length; i++ ) { final JarEntry entry = entries[ i ]; final boolean isCandidate; if ( recursively ) { isCandidate = entry.getName().startsWith( basePath ); } else if ( entry.isDirectory() ) { isCandidate = entry.getName().equals( basePath ); } else { final int lastSlashPos = entry.getName().lastIndexOf( '/' ); isCandidate = entry.getName().substring( lastSlashPos ).equals( basePath ); } if ( isCandidate ) { if ( ( extension == null ) || ( entry.getName().endsWith( extension ) ) ) { if ( !entry.isDirectory() || foldersToo ) { resources.add( ResourceLocator.class.getClassLoader().getResource( entry.getName() ) ); } } if ( entry.isDirectory() && recursively ) { i = findAllResources( entries, i + 1, entry.getName(), extension, recursively, foldersToo, resources ); } } else if ( resources.size() > 0 ) { return ( i + 1 ); } } return ( Integer.MAX_VALUE - 100 ); } private final void findAllResources( JarFile jarFile, String basePath, String extension, boolean recursively, boolean foldersToo, List< URL > resources ) { final JarEntry[] entries = new JarEntry[ jarFile.size() ]; Enumeration< JarEntry > jarEntries = jarFile.entries(); int i = 0; while ( jarEntries.hasMoreElements() ) { entries[ i++ ] = jarEntries.nextElement(); } findAllResources( entries, 0, basePath, extension, recursively, foldersToo, resources ); } private final void findAllResources( File baseFolder, String extension, boolean recursively, boolean foldersToo, List< URL > resources ) { for ( File file: baseFolder.listFiles() ) { if ( ( extension == null ) || ( file.getName().endsWith( extension ) ) ) { if ( !file.isDirectory() || foldersToo ) { try { resources.add( file.toURI().toURL() ); } catch ( MalformedURLException e ) { e.printStackTrace(); } } } if ( file.isDirectory() && recursively ) { findAllResources( file, extension, recursively, foldersToo, resources ); } } } /** * Searches for all child resources in this ResourceLoator. * * @param extension * @param recursively * @param foldersToo * * @return a List of all found resources */ public List< URL > findAllResources( String extension, boolean recursively, boolean foldersToo ) { ArrayList< URL > resources = new ArrayList< URL >(); if ( this.getBaseURL().getProtocol().equals( "jar" ) ) { if ( this.getBaseURL().getPath().startsWith( "file:" ) ) { String jarFileName = this.getBaseURL().getPath().substring( 5, this.getBaseURL().getPath().indexOf( ".jar!" ) + 4 ); String basePath = this.getBaseURL().getPath().substring( this.getBaseURL().getPath().indexOf( ".jar!" ) + 6 ); try { findAllResources( new JarFile( jarFileName ), basePath, extension, recursively, foldersToo, resources ); } catch ( IOException e ) { e.printStackTrace(); } } } if ( this.getBaseURL().getProtocol().equals( "file" ) ) { try { findAllResources( new File( this.getBaseURL().toURI() ), extension, recursively, foldersToo, resources ); } catch ( URISyntaxException e ) { e.printStackTrace(); } } return ( resources ); } /** * Creates a TextureStreamLocator to be added to the TextureLoader. * * @param resourceName the relative resource to complete with this * instance's baseURL to an absolute one * It is taken as the constructor's argument for the TextureStreamLocatorURL. * @return the created TextureStreamLocatorURL to be added to the TextureLoader * * @throws MalformedURLException if something was wrong with the resource */ public TextureStreamLocatorURL getTSL( String resourceName ) throws MalformedURLException { resourceName = resourceName.replace( '\\', '/' ); if ( !resourceName.endsWith( "/" ) ) resourceName += "/"; URL resource = getResource( resourceName ); return ( new TextureStreamLocatorURL( resource ) ); } /** * Creates a TextureStreamLocator to be added to the TextureLoader. * The result of getBaseURL() is taken as the constructor's argument for * the TextureStreamLocatorURL. * * @return the created TextureStreamLocatorURL to be added to the TextureLoader */ public TextureStreamLocatorURL getTSL() { return ( new TextureStreamLocatorURL( getBaseURL() ) ); } /** * Creates a TextureStreamLocator and adds it to the TextureLoader. * * @param resourceName the relative resource to complete with this * instance's baseURL to an absolute one * It is taken as the constructor's argument for the TextureStreamLocatorURL. * * @throws MalformedURLException if something was wrong with the resource */ public TextureStreamLocator createAndAddTSL( String resourceName ) throws MalformedURLException { resourceName = resourceName.replace( '\\', '/' ); if ( !resourceName.endsWith( "/" ) ) resourceName += "/"; URL resource = getResource( resourceName ); TextureStreamLocator tsl = new TextureStreamLocatorURL( resource ); TextureLoader.getInstance().addTextureStreamLocator( tsl ); return ( tsl ); } /** * Creates a TextureStreamLocator and adds it to the TextureLoader. * The result of getBaseURL() is taken as the constructor's argument for * the TextureStreamLocatorURL. */ public TextureStreamLocator createAndAddTSL() { TextureStreamLocator tsl = new TextureStreamLocatorURL( getBaseURL() ); TextureLoader.getInstance().addTextureStreamLocator( tsl ); return ( tsl ); } /** * Creates a new ResourceLocator instance with the given base-URL.<br> * No check for availability is done on the baseURL. * * @param baseURL */ public ResourceLocator( URL baseURL ) { setBaseURL( baseURL ); } /** * Creates a new ResourceLocator instance. The given baseFolder is converted to * a URL and is taken as the base-URL of the new instance. * * @param baseFolder * * @throws FileNotFoundException if the folder does not exist * @throws IllegalArgumentException if the baseFolder is not a directory */ protected ResourceLocator( File baseFolder ) throws FileNotFoundException, IllegalArgumentException { setBaseURL( baseFolder ); } /** * Creates a new ResourceLocator instance. The given baseFolder is converted to * a URL and is taken as the base-URL of the new instance. * * @param foldername * * @throws FileNotFoundException if the folder does not exist * @throws IllegalArgumentException if the baseFolder is not a directory */ protected ResourceLocator( String foldername ) throws FileNotFoundException, IllegalArgumentException { setBaseURL( foldername ); } /** * Creates a new ResourceLocator instance. * The given baseResource is first converted to a slash-only form. * If it doesn't end with '/', one slash is appended. * If a resource with this name can be retrieved from the ClassLoader, it * is taken as the baseURL for the new instance. * If the resource couldn't be retrieved, a file with that name is checked * for existance and for being a directory. If this is a hit, it is * converted to a URL and taken as the baseURL for the new instance. * * Otherwise an IllegalArgumentException is thrown with more info. * * @param dummy * @param baseResource * * @throws IllegalArgumentException if something is wrong with the given baseResource */ protected ResourceLocator( Object dummy, String baseResource ) { baseResource = baseResource.replace( '\\', '/' ); if ( !baseResource.endsWith( "/" ) ) baseResource += "/"; try { URL baseURL = ResourceLocator.class.getResource( baseResource ); if ( baseURL != null ) { setBaseURL( baseURL ); return; } baseURL = ResourceLocator.class.getClassLoader().getResource( baseResource ); if ( baseURL != null ) { setBaseURL( baseURL ); return; } File baseFile = new File( baseResource ); if ( baseFile.exists() ) { if ( !baseFile.isDirectory() ) { setBaseURL( new File( baseResource ).getParentFile() ); return; } setBaseURL( baseFile ); return; } baseFile = new File( System.getProperty( "user.dir" ), baseResource ); if ( baseFile.exists() ) { if ( !baseFile.isDirectory() ) { setBaseURL( new File( baseResource ).getParentFile() ); return; } setBaseURL( new File( baseResource ) ); return; } throw new IllegalArgumentException( "Resource not found \"" + baseResource + "\"" ); } catch ( IllegalArgumentException e ) { throw e; } catch ( Exception e ) { throw new IllegalArgumentException( "Resource not found \"" + baseResource + "\"", e ); } } /** * Creates a new ResourceLocator instance. * The given baseResource is first converted to a slash-only form. * If it doesn't end with '/', one slash is appended. * If a resource with this name can be retrieved from the ClassLoader, it * is taken as the baseURL for the new instance. * If the resource couldn't be retrieved, a file with that name is checked * for existance and for being a directory. If this is a hit, it is * converted to a URL and taken as the baseURL for the new instance. * * Otherwise an IllegalArgumentException is thrown with more info. * * @param baseResource * * @return the new created ResourceLocator instance * * @throws IllegalArgumentException if something is wrong with the given baseResource */ public static ResourceLocator create( String baseResource ) { return ( new ResourceLocator( null, baseResource ) ); } }