code: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements. See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership. The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the  "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /*
   * $Id$
   */
  
  package org.apache.xalan.xsltc.dom;
  
  import java.io.File;
  import java.io.PrintWriter;
  import java.net.URL;
  import java.net.URLConnection;
  import java.net.URLDecoder;
  import java.util.Date;
  import java.util.Hashtable;
  
  import javax.xml.parsers.ParserConfigurationException;
  import javax.xml.parsers.SAXParser;
  import javax.xml.parsers.SAXParserFactory;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.sax.SAXSource;
  
  import org.apache.xalan.xsltc.DOM;
  import org.apache.xalan.xsltc.DOMCache;
  import org.apache.xalan.xsltc.DOMEnhancedForDTM;
  import org.apache.xalan.xsltc.Translet;
  import org.apache.xalan.xsltc.runtime.AbstractTranslet;
  import org.apache.xalan.xsltc.runtime.BasisLibrary;
  import org.apache.xalan.xsltc.runtime.Constants;
  import org.apache.xml.utils.SystemIDResolver;
  
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.XMLReader;
  
  /**
   * @author Morten Jorgensen
   */
  public final class DocumentCache implements DOMCache {
  
      private int       _size;
      private Hashtable _references;
      private String[]  _URIs;
      private int       _count;
      private int       _current;
      private SAXParser _parser;
      private XMLReader _reader;
      private XSLTCDTMManager _dtmManager;
  
      private static final int REFRESH_INTERVAL = 1000;
  
      /*
       * Inner class containing a DOMImpl object and DTD handler
       */
      public final class CachedDocument {
  	
  	// Statistics data
  	private long _firstReferenced;
  	private long _lastReferenced;
  	private long _accessCount;
  	private long _lastModified;
  	private long _lastChecked;
  	private long _buildTime;
  
  	// DOM and DTD handler references
  	private DOMEnhancedForDTM _dom = null;
  	
  	/**
  	 * Constructor - load document and initialise statistics
  	 */
  	public CachedDocument(String uri) {
  	    // Initialise statistics variables
  	    final long stamp = System.currentTimeMillis();
  	    _firstReferenced = stamp;
  	    _lastReferenced  = stamp;
  	    _accessCount     = 0;
  	    loadDocument(uri);
  
  	    _buildTime = System.currentTimeMillis() - stamp;
  	}
  
  	/**
  	 * Loads the document and updates build-time (latency) statistics
  	 */
  	public void loadDocument(String uri) {
  
  	    try {
  		final long stamp = System.currentTimeMillis();
                  _dom = (DOMEnhancedForDTM)_dtmManager.getDTM(
                                   new SAXSource(_reader, new InputSource(uri)),
                                   false, null, true, false);
  		_dom.setDocumentURI(uri);
  
  		// The build time can be used for statistics for a better
  		// priority algorithm (currently round robin).
  		final long thisTime = System.currentTimeMillis() - stamp;
  		if (_buildTime > 0)
  		    _buildTime = (_buildTime + thisTime) >>> 1;
  		else
  		    _buildTime = thisTime;
  	    }
  	    catch (Exception e) {
  		_dom = null;
  	    }
  	}
  
  	public DOM getDocument()       { return(_dom); }
  
  	public long getFirstReferenced()   { return(_firstReferenced); }
  
  	public long getLastReferenced()    { return(_lastReferenced); }
  
  	public long getAccessCount()       { return(_accessCount); }
  
  	public void incAccessCount()       { _accessCount++; }
  
  	public long getLastModified()      { return(_lastModified); }
  
  	public void setLastModified(long t){ _lastModified = t; }
  
  	public long getLatency()           { return(_buildTime); }
  
  	public long getLastChecked()       { return(_lastChecked); }
  	
  	public void setLastChecked(long t) { _lastChecked = t; }
  
  	public long getEstimatedSize() {
  	    if (_dom != null)
  		return(_dom.getSize() << 5); // ???
  	    else
  		return(0);
  	}
  
      }
  
      /**
       * DocumentCache constructor
       */
      public DocumentCache(int size) throws SAXException {
          this(size, null);
          try {
              _dtmManager = (XSLTCDTMManager)XSLTCDTMManager.getDTMManagerClass()
                                                            .newInstance();
          } catch (Exception e) {
              throw new SAXException(e);
          }
      }
  
      /**
       * DocumentCache constructor
       */
      public DocumentCache(int size, XSLTCDTMManager dtmManager) throws SAXException {
  	_dtmManager = dtmManager;
  	_count = 0;
  	_current = 0;
  	_size  = size;
  	_references = new Hashtable(_size+2);
  	_URIs = new String[_size];
  
  	try {
  	    // Create a SAX parser and get the XMLReader object it uses
  	    final SAXParserFactory factory = SAXParserFactory.newInstance();
  	    try {
  		factory.setFeature(Constants.NAMESPACE_FEATURE,true);
  	    }
  	    catch (Exception e) {
  		factory.setNamespaceAware(true);
  	    }
  	    _parser = factory.newSAXParser();
  	    _reader = _parser.getXMLReader();
  	}
  	catch (ParserConfigurationException e) {
  	    BasisLibrary.runTimeError(BasisLibrary.NAMESPACES_SUPPORT_ERR);
  	}
      }
  
      /**
       * Returns the time-stamp for a document's last update
       */
      private final long getLastModified(String uri) {
  	try {
  	    URL url = new URL(uri);
  	    URLConnection connection = url.openConnection();
  	    long timestamp = connection.getLastModified();
  	    // Check for a "file:" URI (courtesy of Brian Ewins)
  	    if (timestamp == 0){ // get 0 for local URI
  	        if ("file".equals(url.getProtocol())){
  	            File localfile = new File(URLDecoder.decode(url.getFile()));
  	            timestamp = localfile.lastModified();
  	        }
  	    }
  	    return(timestamp);
  	}
  	// Brutal handling of all exceptions
  	catch (Exception e) {
  	    return(System.currentTimeMillis());
  	}
      }
  
      /**
       *
       */
      private CachedDocument lookupDocument(String uri) {
  	return((CachedDocument)_references.get(uri));
      }
  
      /**
       *
       */
      private synchronized void insertDocument(String uri, CachedDocument doc) {
  	if (_count < _size) {
  	    // Insert out URI in circular buffer
  	    _URIs[_count++] = uri;
  	    _current = 0;
  	}
  	else {
  	    // Remove oldest URI from reference Hashtable
  	    _references.remove(_URIs[_current]);
  	    // Insert our URI in circular buffer
  	    _URIs[_current] = uri;
  	    if (++_current >= _size) _current = 0;
  	}
  	_references.put(uri, doc);
      }
  
      /**
       *
       */
      private synchronized void replaceDocument(String uri, CachedDocument doc) {
  	CachedDocument old = (CachedDocument)_references.get(uri);
  	if (doc == null)
  	    insertDocument(uri, doc);
  	else
  	    _references.put(uri, doc);
      }
  
      /**
       * Returns a document either by finding it in the cache or
       * downloading it and putting it in the cache.
       */
      public DOM retrieveDocument(String baseURI, String href, Translet trs) {
  	CachedDocument doc;
  
      String uri = href;
      if (baseURI != null && baseURI.length() != 0) {
          try {
              uri = SystemIDResolver.getAbsoluteURI(uri, baseURI);
          } catch (TransformerException te) {
              // ignore    
          }
      }
      
  	// Try to get the document from the cache first
  	if ((doc = lookupDocument(uri)) == null) {
  	    doc = new CachedDocument(uri);
  	    if (doc == null) return null; // better error handling needed!!!
  	    doc.setLastModified(getLastModified(uri));
  	    insertDocument(uri, doc);
  	}
  	// If the document is in the cache we must check if it is still valid
  	else {
  	    long now = System.currentTimeMillis();
  	    long chk = doc.getLastChecked();
  	    doc.setLastChecked(now);
  	    // Has the modification time for this file been checked lately?
  	    if (now > (chk + REFRESH_INTERVAL)) {
  		doc.setLastChecked(now);
  		long last = getLastModified(uri);
  		// Reload document if it has been modified since last download
  		if (last > doc.getLastModified()) {
  		    doc = new CachedDocument(uri);
  		    if (doc == null) return null;
  		    doc.setLastModified(getLastModified(uri));
  		    replaceDocument(uri, doc);
  		}
  	    }
  	    
  	}
  
  	// Get the references to the actual DOM and DTD handler
  	final DOM dom = doc.getDocument();
  
  	// The dom reference may be null if the URL pointed to a
  	// non-existing document
  	if (dom == null) return null;
  
  	doc.incAccessCount(); // For statistics
  
  	final AbstractTranslet translet = (AbstractTranslet)trs;
  
  	// Give the translet an early opportunity to extract any
          // information from the DOM object that it would like.
  	translet.prepassDocument(dom);
  
  	return(doc.getDocument());
      }
  
      /**
       * Outputs the cache statistics
       */
      public void getStatistics(PrintWriter out) {
  	out.println("<h2>DOM cache statistics</h2><center><table border=\"2\">"+
  		    "<tr><td><b>Document URI</b></td>"+
  		    "<td><center><b>Build time</b></center></td>"+
  		    "<td><center><b>Access count</b></center></td>"+
  		    "<td><center><b>Last accessed</b></center></td>"+
  		    "<td><center><b>Last modified</b></center></td></tr>");
  
  	for (int i=0; i<_count; i++) {
  	    CachedDocument doc = (CachedDocument)_references.get(_URIs[i]);
  	    out.print("<tr><td><a href=\""+_URIs[i]+"\">"+
  		      "<font size=-1>"+_URIs[i]+"</font></a></td>");
  	    out.print("<td><center>"+doc.getLatency()+"ms</center></td>");
  	    out.print("<td><center>"+doc.getAccessCount()+"</center></td>");
  	    out.print("<td><center>"+(new Date(doc.getLastReferenced()))+
  		      "</center></td>");
  	    out.print("<td><center>"+(new Date(doc.getLastModified()))+
  		      "</center></td>");
  	    out.println("</tr>");
  	}
  
  	out.println("</table></center>");
      }
  }
  