/* The contents of this file alone are PUBLIC DOMAIN, and are provided
 * by Approximatrix, LLC to improve the performance of the svgSalamander
 * library (https://svgsalamander.dev.java.net/) in support of the Draco
 * project (http://www.approximatrix.com/products/draco).
 * 
 * NO WARRANTY of any sort is provided with this code.  Use of this class
 * is one's OWN RISK.  Approximatrix, LLC makes no claims concerning the
 * safety and correctness of this code.
 */

package com.approximatrix.xml;

import java.util.Map;
import java.util.HashMap;

import java.net.URL;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;

import org.xml.sax.SAXException;
import org.xml.sax.InputSource;
import org.xml.sax.EntityResolver;

/** This class implements an EntityResolver for XML parsing,
 * incorporating both memory caching and a local document store
 * to drastically improve performance.
 * 
 * Original taken from:
 *   From: Nicolas LEHUEN [mailto:nicolas.lehuen@ubicco.com] 
 *   Sent: 05 September 2001 14:09 
 *   To: Sarno, Giuseppe [MAIFP:GM12:EXCH]; 'xml-dev@lists.xml.org' 
 *   Subject: RE: load DTD in memory. 
 *   (http://lists.xml.org/archives/xml-dev/200109/msg00151.html)
 * 
 * Modifications (22 July 2007):
 *   Jeff Armstrong [jeffrey.armstrong@approximatrix.com]
 *   Approximatrix, LLC
 */
public class CachingEntityResolver implements EntityResolver { 
	
	/** Holds all our cached external entitities */
	private Map cache=new HashMap<String,byte[]>();
	
	/** A static entity resolver that any part of the application can
	 * quickly access
	 */
	private static CachingEntityResolver staticEntityResolver = new CachingEntityResolver();
	
	/** Resource path to permanently stored documents */
	private static String permPath = "/com/approximatrix/xml/documents";
	
	/** Access the global, static entity resolver */
	public static CachingEntityResolver GetGlobalEntityResolver() {
		return staticEntityResolver;
	}
     
	/** This resolveEntity routine will first check the permamnent disk store for
	 * the entity based on the final filename.  If present, the URL is rewritten
	 * to point at the local copy.  Then, the memory store is checked for the file.
	 * If present, the file contents are loaded from the memory cache rather than
	 * netowrk/disk.  If not present in memory, the document is fetched and written
	 * into memory for future use.
	 */ 
    public synchronized InputSource resolveEntity(String publicIdP, String systemIdP) throws SAXException { 

    	// The PUBLIC id is the key to our cache 
    	byte[] resultP=(byte[])cache.get(publicIdP); 
    	if(resultP==null) { 
    		try { 
    			// The SYSTEM id is the URL used to fetch the entity
    			URL urlP=new URL(systemIdP);
    			urlP = CheckPermanent(urlP);
    			
    			// Broken:
    			//InputStream isP=urlP.openConnection();
    			// Correction:
    			InputStream isP=urlP.openStream();

    			ByteArrayOutputStream baosP=new ByteArrayOutputStream();

    			// We copy the input stream into the output stream 
    			// Fast buffer implementation 
    			// I could have used BufferInputStream and OutputStream 
    			// But it's much slower 
    			int readP; 
    			byte[] bufferP=new byte[1024]; 
    			while((readP=isP.read(bufferP))>-1) { 
    				baosP.write(bufferP,0,readP); 
    			} 
    			resultP=baosP.toByteArray(); 

    			// We store the result in the cache. 
    			cache.put(publicIdP,resultP); 
    		} 
    		catch(Exception eP) { 
    			throw new SAXException(eP); 
    		} 
    	} 
    	return new InputSource(new ByteArrayInputStream(resultP)); 
    }
    
    /** Returns a URL to the resource.  If the file is stored locally in
     * <code>permPath</code>, the URL is changed to the local copy.  If
     * not present, the URL is returned as is.
     * 
     * @param original the original URL
     * @return the local copy URL if present, otherwise, the original URL
     */
    private URL CheckPermanent(URL original) {
    	String filename = original.getPath();
    	if(filename == null)
    		return original;
    	
    	filename = filename.substring(filename.lastIndexOf('/'));
    	
    	URL ret = CachingEntityResolver.class.getResource(permPath+filename);
    	
    	return (ret == null ? original : ret);
    }
    
} 