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]