Dear Developers,

The first version is my class is available.
It is a little performance "tweak", because this read from disk only, when original template is changed, and make a cached compressed copy of it. With compressed input, the memory use is lower, velocity variables insertion is faster and bandwidth usage is also lower.
Any comments are welcome :-)

Best Regards:
   Ferenc Lutischán

package com.ys.velocity;

/*
* 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.
*/

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.io.UnicodeInputStream;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.apache.velocity.util.StringUtils;

/**
* A loader for templates stored on the file system.  Treats the template
* as relative to the configured root path.  If the root path is empty
* treats the template name as an absolute path.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Lutischan Ferenc</a>
*/
public class HTMLCompressFileResourceLoader extends ResourceLoader {
   /**
    * The paths to search for templates.
    */
   private List paths = new ArrayList();
private static final String ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$\\";
   private static final String SPECIAL = "{};=*/+-%()[]<>"+(char) 10;
   private static final String END_LINE = "{};=*/+-%([<>"+(char) 10;

   /**
    * Used to map the path that a template was found on
    * so that we can properly check the modification
    * times of the files. This is synchronizedMap
    * instance.
    */
   private Map templatePaths = Collections.synchronizedMap(new HashMap());

/** Shall we inspect unicode files to see what encoding they contain?. */
   private boolean unicode = false;

   /**
* @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
    */
   public void init( ExtendedProperties configuration) {
       if (log.isTraceEnabled()) {
log.trace("HTMLCompressFileResourceLoader : initialization starting.");
       }

       paths.addAll( configuration.getVector("path") );

       // unicode files may have a BOM marker at the start, but Java
       // has problems recognizing the UTF-8 bom. Enabling unicode will
       // recognize all unicode boms.
       unicode = configuration.getBoolean("unicode", false);

       if (log.isDebugEnabled()) {
           log.debug("Do unicode file recognition:  " + unicode);
       }

       // trim spaces from all paths
       StringUtils.trimStrings(paths);
       if (log.isInfoEnabled()) {
           // this section lets tell people what paths we will be using
           int sz = paths.size();
           for( int i=0; i < sz; i++) {
log.info("HTMLCompressFileResourceLoader : adding path '" + (String) paths.get(i) + "'");
           }
log.trace("HTMLCompressFileResourceLoader : initialization complete.");
       }
   }

   /**
    * Get an InputStream so that the Runtime can build a
    * template with it.
    *
    * @param templateName name of template to get
    * @return InputStream containing the template
    * @throws ResourceNotFoundException if template not found
    *         in the file template path.
    */
   public InputStream getResourceStream(String templateName)
   throws ResourceNotFoundException {
       /*
        * Make sure we have a valid templateName.
        */
       if (org.apache.commons.lang.StringUtils.isEmpty(templateName)) {
           /*
            * If we don't get a properly formed templateName then
            * there's not much we can do. So we'll forget about
            * trying to search any more paths for the template.
            */
           throw new ResourceNotFoundException(
                   "Need to specify a file name or file path!");
       }

       String template = StringUtils.normalizePath(templateName);
       if ( template == null || template.length() == 0 ) {
           String msg = "File resource error : argument " + template +
                   " contains .. and may be trying to access " +
                   "content outside of template root.  Rejected.";

           log.error("HTMLCompressFileResourceLoader : " + msg);

           throw new ResourceNotFoundException( msg );
       }

       int size = paths.size();
       for (int i = 0; i < size; i++) {
           String path = (String) paths.get(i);
           InputStream inputStream = null;

           try {
               inputStream = findTemplate(path, template);
           } catch (IOException ioe) {
               log.error("While loading Template " + template + ": ", ioe);
           }

           if (inputStream != null) {
               /*
                * Store the path that this template came
                * from so that we can check its modification
                * time.
                */
               templatePaths.put(templateName, path);
               return inputStream;
           }
       }

       /*
        * We have now searched all the paths for
        * templates and we didn't find anything so
        * throw an exception.
        */
throw new ResourceNotFoundException("HTMLCompressFileResourceLoader : cannot find " + template);
   }

   /**
    * Try to find a template given a normalized path.
    *
    * @param path a normalized path
    * @param template name of template to find
    * @return InputStream input stream that will be parsed
    *
    */
private InputStream findTemplate(final String path, final String template)
   throws IOException {
       try {
           File file = optimizeToNewFile(path, template);

           if (file != null) {

               FileInputStream fis = null;
               try {
                   fis = new FileInputStream(file.getAbsolutePath());

                   if (unicode) {
                       UnicodeInputStream uis = null;

                       try {
                           uis = new UnicodeInputStream(fis, true);

                           if (log.isDebugEnabled()) {
log.debug("File Encoding for " + file + " is: " + uis.getEncodingFromStream());
                           }

                           return new BufferedInputStream(uis);
                       } catch(IOException e) {
                           closeQuiet(uis);
                           throw e;
                       }
                   } else {
                       return new BufferedInputStream(fis);
                   }
               } catch (IOException e) {
                   closeQuiet(fis);
                   throw e;
               }
           } else {
               return null;
           }
       } catch(FileNotFoundException fnfe) {
           /*
* log and convert to a general Velocity ResourceNotFoundException
            */
           return null;
       }
   }

   private void closeQuiet(final InputStream is) {
       if (is != null) {
           try {
               is.close();
           } catch(IOException ioe) {
               // Ignore
           }
       }
   }

   /**
    * How to keep track of all the modified times
    * across the paths.  Note that a file might have
    * appeared in a directory which is earlier in the
    * path; so we should search the path and see if
    * the file we find that way is the same as the one
    * that we have cached.
    * @param resource
    * @return True if the source has been modified.
    */
   public boolean isSourceModified(Resource resource) {
       /*
        * we assume that the file needs to be reloaded;
        * if we find the original file and it's unchanged,
        * then we'll flip this.
        */
       boolean modified = true;

       String fileName = resource.getName();
       String path = (String) templatePaths.get(fileName);
       File currentFile = null;

       for (int i = 0; currentFile == null && i < paths.size(); i++) {
           String testPath = (String) paths.get(i);
           File testFile = getFile(testPath, fileName);
           if (testFile.canRead()) {
               currentFile = testFile;
           }
       }
       File file = getFile(path, fileName);
       if (currentFile == null || !file.exists()) {
           /*
            * noop: if the file is missing now (either the cached
            * file is gone, or the file can no longer be found)
            * then we leave modified alone (it's set to true); a
            * reload attempt will be done, which will either use
            * a new template or fail with an appropriate message
            * about how the file couldn't be found.
            */
       } else if (currentFile.equals(file) && file.canRead()) {
           /*
            * if only if currentFile is the same as file and
            * file.lastModified() is the same as
            * resource.getLastModified(), then we should use the
            * cached version.
            */
           modified = (file.lastModified() != resource.getLastModified());
       }

       /*
        * rsvc.debug("isSourceModified for " + fileName + ": " + modified);
        */
       return modified;
   }

   /**
* @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
    */
   public long getLastModified(Resource resource) {
       String path = (String) templatePaths.get(resource.getName());
       File file = getFile(path, resource.getName());

       if (file.canRead()) {
           return file.lastModified();
       } else {
           return 0;
       }
   }


   /**
* Create a File based on either a relative path if given, or absolute path otherwise
    */
   private File getFile(String path, String template) {

       File file = null;

       if("".equals(path)) {
           file = new File( template );
       } else {
           /*
            *  if a / leads off, then just nip that :)
            */
           if (template.startsWith("/")) {
               template = template.substring(1);
           }

           file = new File( path, template );
       }

       return file;
   }

private File optimizeToNewFile(String path, String template) throws IOException {
       File origFile = getFile(path,template);
       File newFile = getFile(path,template+"_optimized");

// if optimized version is exists, and origFile is newer, we recreate it
       if (origFile.canRead()) {
           if (newFile.exists()) {
               if (origFile.lastModified() > newFile.lastModified() ) {
                   optimize(origFile, newFile);
               }
           } else {
               optimize(origFile, newFile);
           }
           return newFile;
       } else {
           return null;
       }
   }

   private void optimize(File origFile, File newFile) throws IOException {
       BufferedInputStream bi = null;
       BufferedOutputStream bo = null;

       try {
           int size = (int) origFile.length();
           byte[] origBytes = new byte[ size ];

bi = new BufferedInputStream(new FileInputStream(origFile.getAbsolutePath()));
           int bytesRead = bi.read( origBytes );

           if ( bytesRead != size ) {
               throw new IOException( "error reading file: " + origFile );
           }
bo = new BufferedOutputStream(new FileOutputStream(newFile.getAbsolutePath()));
           byte newBytes[] = compressHTML(origBytes, bo);
       } catch (IOException e) {
           throw e;
       } finally {
           bi.close();
           bo.close();
       }
   }

private byte[] compressHTML(byte[] origBytes, BufferedOutputStream bo) throws IOException {
       byte newBytes[] = null;
       newBytes = new byte[origBytes.length];
       int i = 0;
       int j = 0;
       int end = 0;

       for (i = 0; i < origBytes.length; i++) {
           byte curByte = origBytes[i];
           switch (curByte) {
               case 32: case 9:// space
                   if (j > 0 && newBytes[j-1] != 32) { // only one space
                       if (origBytes[i+1] == '<') {
                           if (!checkTag(i, origBytes, "/div>")
                                   && !checkTag(i, origBytes, "/body>")
                                   && !checkTag(i, origBytes, "/tr>")
                                   && !checkTag(i, origBytes, "option>")
                                   && !checkTag(i, origBytes, "/html>")) {
                               newBytes[j] = 32;
                               j++;

                           }
                       } else {
                           newBytes[j] = 32;
                           j++;
                       }
                   }
                   break;
               case 10: case 13: // skip lf, cr
                   break;
               case '<': // tag begining
                   if ( checkTag(i, origBytes, "pre") ) {
                       end = getTagEnd(i, origBytes, "/pre>");
System.arraycopy(origBytes, i, newBytes, j, end-i+1);
                       j+=end-i+1;
                       i = end;
                   } else if ( checkTag(i, origBytes, "textarea") ) {
                       end = getTagEnd(i, origBytes, "/textarea>");
System.arraycopy(origBytes, i, newBytes, j, end-i+1);
                       j+=end-i+1;
                       i = end;
                   } else if (checkTag(i, origBytes, "script")){
                       end = getTagEnd(i, origBytes, "/script>");
                       byte newJs[] = getCompressedJs(origBytes, i, end);
System.arraycopy(newJs, 0, newBytes, j, newJs.length);
                       j+=newJs.length;
                       i = end;
                   } else if (checkTag(i, origBytes, "style")) {
                       end = getTagEnd(i, origBytes, "/style>");
                       byte newCSS[] = getCompressedCSS(origBytes, i, end);
System.arraycopy(newCSS, 0, newBytes, j, newCSS.length);
                       j+=newCSS.length;
                       i = end;
                   } else if (checkTag(i, origBytes, "<!--")) { // comment
                       i = skipComment(i, origBytes);
                   } else {
                       newBytes[j] = '<';
                       j++;
                   }
                   break;
               default:
                   newBytes[j] = origBytes[i];
                   j++;
                   break;
           }
       }
       bo.write(newBytes, 0, j);

       return newBytes;
   }

   private boolean checkTag(int i, byte[] origBytes, String tag) {
       String upperTag = tag.toUpperCase();

       if (i + upperTag.length() >= origBytes.length) {
           return false;
       }
       String curTag = new String(origBytes, i+1, upperTag.length());
       return upperTag.equals(curTag.toUpperCase());
   }

   private int skipComment(int i, byte[] origBytes) {
       int j = i;
       while (j < origBytes.length) {
           if (origBytes[j] == '-' && checkTag(j, origBytes, "->")) {
               j = j+3;
               break;
           }
           j++;
       }
       return j;
   }

   private int getTagEnd(int i, byte[] origBytes, String tag) {
       int j = i;
       while (j < origBytes.length) {
           if (origBytes[j] == '<' && checkTag(j, origBytes, tag)) {
               j = j+tag.length();
               break;
           }
           j++;
       }
       return j;
   }

   private byte[] getCompressedJs(byte[] origBytes, int start, int end) {
       boolean isHTMLComment = false;
       byte temp[] = new byte[end-start+1];
       int j= 0;

       for (int k = start; k<=end; k++) {
           byte curByte = origBytes[k];
           switch (curByte) {
               case 32: case 9:// space, tab
if (k<end && isAlpha(origBytes[k+1]) && !isSpecial(temp[j-1])) { // only one space
                       temp[j] = 32;
                       j++;
                   }
                   break;
               case 10: // lf
                   if (isHTMLComment || (j>0 && !isEndLine(temp[j-1])) ) {
                       temp[j] = 10;
                       j++;
                   }
                   break;
               case 13: // cr
                   break;
               case '/': // tag begining

// if script is commented with '<!--', '//' comment is not removed,
                   // because e.g.: '//--> </script>''
                   if ( origBytes[k+1] == '/' && !isHTMLComment ) {
                       while (k<=end && origBytes[k] != '\n') {k++;}
                   } else
                       if ( origBytes[k+1] == '*' ) {
                           k++;
while (k<=end && origBytes[k-1] != '*' && origBytes[k] != '/') {k++;}
                       } else {
                           temp[j] = origBytes[k];
                           j++;
                       }
                   break;
               case '"': // tag begining
                   while (k <= end) {
                       temp[j] = origBytes[k];
                       j++;
                       k++;
                       if (origBytes[k] == '"' ) {
                           temp[j] = origBytes[k];
                           j++;
                           break;
                       }
                   }
                   break;
               case '\'': // tag begining
                   while (k <= end) {
                       temp[j] = origBytes[k];
                       j++;
                       k++;
                       if (origBytes[k] == '\'' ) {
                           temp[j] = origBytes[k];
                           j++;
                           break;
                       }
                   }
                   break;
               case '<':
if (origBytes[k+1] == '!' && origBytes[k+2] == '-' && origBytes[k+3] == '-') {
                       isHTMLComment = true;
                   }
                   temp[j] = '<';
                   j++;
                   break;
               default:
                   temp[j] = origBytes[k];
                   j++;
                   break;
           }
       }
       byte result[] = new byte[j];
       System.arraycopy(temp, 0, result, 0, j);
       return result;
   }

   private byte[] getCompressedCSS(byte[] origBytes, int start, int end) {
       byte temp[] = new byte[end-start+1];
       int j= 0;

       for (int k = start; k<=end; k++) {
           byte curByte = origBytes[k];
           switch (curByte) {
               case 32: case 9: case 10: case 13:// space, tab
                   if (j <8) {
                       temp[j] = 32;
                       j++;
                   }
                   break;
               default:
                   temp[j] = origBytes[k];
                   j++;
                   break;
           }
       }
       byte result[] = new byte[j];
       System.arraycopy(temp, 0, result, 0, j);
       return result;
   }
private boolean isAlpha(byte b) {
       return ALPHA.indexOf((char) b)!=-1 || b > 126;
   }

   private boolean isSpecial(byte b) {
       return SPECIAL.indexOf((char) b)!=-1;
   }

   private boolean isEndLine(byte b) {
       return END_LINE.indexOf((char) b)!=-1;
   }
}


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to