package cn.bran.japid.template;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import cn.bran.japid.classmeta.AbstractTemplateClassMetaData;
import cn.bran.japid.classmeta.MimeTypeEnum;
import cn.bran.japid.compiler.JapidCompilationException;
import cn.bran.japid.compiler.JapidTemplateTransformer;
import cn.bran.japid.compiler.NamedArgRuntime;
import cn.bran.japid.compiler.OpMode;
import cn.bran.japid.compiler.TranslateTemplateTask;
import cn.bran.japid.exceptions.JapidTemplateException;
import cn.bran.japid.rendererloader.RendererClass;
import cn.bran.japid.rendererloader.RendererCompiler;
import cn.bran.japid.rendererloader.TemplateClassLoader;
import cn.bran.japid.util.DirUtil;
import cn.bran.japid.util.JapidFlags;
import cn.bran.japid.util.PlayDirUtil;
import cn.bran.japid.util.RenderInvokerUtils;
import cn.bran.japid.util.StackTraceUtils;
import cn.bran.japid.util.StringUtils;
import cn.bran.japid.util.WebUtils;
/**
* facade of Japid engine.
*
* Default is not to generate Play specific code from Japid scripts. Please set
* the static usePlay for using with Play.
*
* GloabalSettingsWithJapid initializes Japid engine in a Play2 environment.
*
* Japid Engine can be used as a generic advanced template engine. Here is how:
*
* <ol>
* <li>Initialize Japid, to be done once in your application.
*
* <pre>
* JapidRenderer.init(OpMode.prod, "japidroot", 1, null, JapidRenderer.class.getClassLoader()); // or
* // simply:
* // JapidRenderer.init(true|false)
* </pre>
*
* </li>
* <li>Use it anywhere in your app:
*
* <pre>
* RenderResult rr = JapidRenderer.renderWith("japidviews/hello.html", "John");
* // rr.toString() outputs the render result in text.
* </pre>
*
* </li>
* </ol>
*
* @author bran (bing.ran@gmail.com)
*
*/
public final class JapidRenderer {
public static final String VERSION = "0.9.17.1"; // need to match that in the build.scala
private static final String JAPIDROOT = "japidroot";
// private static final String RENDER_JAPID_WITH = "/renderJapidWith";
// private static AtomicLong lastTimeChecked = new AtomicLong(0);
// can be used to cache a plugin scoped valules
private static Map<String, Object> japidCache = new ConcurrentHashMap<String, Object>();
private static final String DEV_ERROR = "japidviews.devError";
public static boolean usePlay = false;
private static boolean presentErrorInHtml = true;
/**
*
*/
private static final String DEV_ERROR_FILE = "/japidviews/devError.html";
// where to persist the japid class cache
private static String classCacheRoot = null;
static HashSet<String> imports;
private static ClassLoader parentClassLoader;
private static boolean classesInited;
static {
imports = new HashSet<String>();
addImportStatic(WebUtils.class);
}
static AmmendableScheduledExecutor saveJapidClassesService = new AmmendableScheduledExecutor();
static boolean enableJITCachePersistence = true;
public static ConcurrentHashMap<String, RendererClass> dynamicClasses = new ConcurrentHashMap<String, RendererClass>();
// the japid scripts and thus classes contributed by jars on classpath
public static ConcurrentHashMap<String, RendererClass> jarContributedClasses = new ConcurrentHashMap<String, RendererClass>();
// last time that something in the Japid root was changed
private static long lastChanged = System.currentTimeMillis();
public static JapidTemplateBaseWithoutPlay getRenderer(String name) {
Class<? extends JapidTemplateBaseWithoutPlay> c = getClass(name);
try {
return c.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void addImport(String imp) {
imports.add(imp);
}
public static void addImportStatic(String imp) {
if (imp.startsWith("static"))
imports.add(imp);
else
imports.add("static " + imp);
}
public static void addImport(Class<?> cls) {
imports.add(cls.getName());
}
public static void addImportStatic(Class<?> cls) {
imports.add("static " + cls.getName() + ".*");
}
/**
* Get a newly loaded class for the template renderer
*
* @param name
* @return
*/
public static Class<? extends JapidTemplateBaseWithoutPlay> getClass(String name) {
refreshClasses();
return getClassWithoutRefresh(name);
}
public static RendererClass getRendererClass(String name) {
refreshClasses();
return getRendererClassWithoutRefresh(name);
}
private static Class<? extends JapidTemplateBaseWithoutPlay> getClassWithoutRefresh(String name) {
RendererClass rc = getRendererClassWithoutRefresh(name);
return rc.getClz();
}
private static RendererClass getRendererClassWithoutRefresh(String name) {
RendererClass rc = japidClasses.get(name);
if (rc == null)
throw new JapidTemplateNotFoundException(name, "classpath and " + flattern(templateRoots));
else {
if (rc.getClz() == null || (playClassloaderChanged() && !rc.getContributor().startsWith("jar"))) {
compileAndLoad(name, rc);
// try {
// new TemplateClassLoader(parentClassLoader).loadClass(name);
// } catch (java.lang.NoClassDefFoundError e) {
// // the class presented when the class was compiled but it
// could not be found at runtime.
// // we need to recompile the class
// compileAndLoad(name, rc);
// } catch (ClassNotFoundException e) {
// compileAndLoad(name, rc);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
}
}
return rc;
}
private static void compileAndLoad(String name, RendererClass rc) {
// long t = System.currentTimeMillis();
// if (rc.getBytecode() == null || t - rc.getLastCompiled() > 2000)
// compiler.compile(new String[] { rc.getClassName()});
// try {
// if (rc.getClz() == null || t - rc.getLastDefined() > 2000)
// new TemplateClassLoader(parentClassLoader).loadClass(name);
// } catch (ClassNotFoundException e1) {
// throw new RuntimeException(e1);
// }
// code recompiling is now in class loading so the above code is
// deprecated
try {
getClassLoader().loadClass(name);
} catch (ClassNotFoundException e1) {
throw new RuntimeException(e1);
}
}
// cache the classloader for a delay to buffer consecutive requests
// applicable to debug mode only
synchronized private static TemplateClassLoader getClassLoader() {
if (parentClassLoader == null)
throw new RuntimeException("parentClassLoader is null");
long now = System.currentTimeMillis();
if (now - newClassLoaderCreated > 2000 || lastClassLoader == null) {
newClassLoaderCreated = now;
lastClassLoader = new TemplateClassLoader(parentClassLoader);
}
return lastClassLoader;
}
private static long newClassLoaderCreated = 0;
private static TemplateClassLoader lastClassLoader;
private static boolean keepJavaFiles = true;
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param templateRoots2
* @return
*/
private static String flattern(Object[] templateRoots2) {
String re = "[" + StringUtils.join(templateRoots2, ",") + "]";
return re;
}
/**
* indicate if to save the intermediate Java artifacts. The default is true.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param keep
*/
public static void setKeepJavaFiles(boolean keep) {
keepJavaFiles = keep;
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @return
*/
private static boolean playClassloaderChanged() {
// for some reason even if the classloader remains the same, the classes
// are new
if (opMode == OpMode.prod)
return false;
//
// parentClassLoader = GlobalSettingsWithJapid._app.classloader();
return true;
}
static boolean timeToRefresh() {
if (opMode == OpMode.prod)
return false;
long now = System.currentTimeMillis();
if (now - lastRefreshed > refreshInterval) {
lastRefreshed = now;
return true;
} else
return false;
}
static synchronized void refreshClasses() {
if (!classesInited) {
classesInited = true;
} else {
if (!timeToRefresh())
return;
}
if (templateRoots == null)
return;
if (!JapidRenderer.keepJavaFiles) {
refreshClassesInMemory();
return;
}
try {
// there are two passes of directory scanning. XXX
String[] allTemps = DirUtil.getAllTemplateFileNames(templateRoots);
Set<String> currentClassesOnDir = createClassNameSet(allTemps);
Set<String> allScriptNames = new HashSet<String>(currentClassesOnDir);
Set<String> keySet = japidClasses.keySet();
if (!keySet.equals(currentClassesOnDir)) {
Set<String> classNamesRegistered = new HashSet<String>(keySet);
Set<String> classNamesDir = new HashSet<String>(currentClassesOnDir);
if (classNamesRegistered.containsAll(classNamesDir)) {
classNamesRegistered.removeAll(classNamesDir);
if (!classNamesRegistered.isEmpty()) {
for (String n : classNamesRegistered) {
if (!n.contains("$")) {
// if (!japidClasses.get(n).fromJar()) {
if (!specialClasses.contains(n)) {
touch();
break;
}
}
}
}
} else {
touch();
}
} else {
// no name changes
}
allScriptNames.removeAll(keySet); // got new templates
removeRemoved(currentClassesOnDir, keySet);
for (String c : allScriptNames) {
RendererClass rc = newRendererClass(c);
japidClasses.put(c, rc);
}
// now all the class set size is up to date
// now update any Java source code
// second disk scanning.
List<File> gen = gen(templateRoots);
// this would include both new and updated java
Set<String> updatedClasses = new HashSet<String>();
if (gen.size() > 0) {
for (File f : gen) {
String className = getClassName(f);
updatedClasses.add(className);
RendererClass rendererClass = japidClasses.get(className);
if (rendererClass == null) {
// this should not happen, since
throw new RuntimeException("any new class names should have been in the classes container: "
+ className);
// rendererClass = newRendererClass(className);
// japidClasses.put(className, rendererClass);
}
setSources(rendererClass, f);
removeInnerClasses(className);
cleanByteCode(rendererClass);
}
}
// find all render class without bytecode
for (Iterator<String> i = japidClasses.keySet().iterator(); i.hasNext();) {
String k = i.next();
RendererClass rc = japidClasses.get(k);
if (rc.getJavaSourceCode() == null) {
if (!rc.getClassName().contains("$")) {
try {
setSources(rc, k);
} catch (Exception e) {
JapidFlags.log("Cannot find the source Java file for " + rc.getClassName() + ". Dropped.");
i.remove();
continue;
}
cleanByteCode(rc);
updatedClasses.add(k);
} else {
rc.setLastUpdated(0);
}
} else {
if (rc.getBytecode() == null) {
cleanByteCode(rc);
updatedClasses.add(k);
}
}
}
// compile all
if (updatedClasses.size() > 0) {
dynamicClasses.clear();
String[] names = new String[updatedClasses.size()];
int i = 0;
for (String s : updatedClasses) {
names[i++] = s;
}
long t = System.currentTimeMillis();
// newly compiled class bytecode bodies are set in the global
// classes set ready for defining
compiler.compile(names);
howlong("compile time for " + names.length + " classes", t);
for (String k : japidClasses.keySet()) {
japidClasses.get(k).setClz(null);
}
TemplateClassLoader loader = getClassLoader();
for (String cname : updatedClasses) {
loader.loadClass(cname);
}
}
} catch (Exception e) {
if (e instanceof JapidTemplateException)
throw (JapidTemplateException) e;
throw new RuntimeException(e);
}
}
/**
* all artifacts in memory
*
* @author Bing Ran (bing.ran@gmail.com)
*/
static synchronized void refreshClassesInMemory() {
if (templateRoots == null)
return;
try {
Set<File> allTemplates = DirUtil.getAllTemplateFiles(templateRoots);
Set<File> toBeUpdated = new HashSet<File>();
// find out all the classes that need to be updated
for (File tf : allTemplates) {
String cname = getClassName(tf);
RendererClass rc = japidClasses.get(cname);
if (rc == null) {
toBeUpdated.add(tf);
} else if (rc.getScriptTimestamp() < tf.lastModified()) {
toBeUpdated.add(tf);
} else if (rc.getJavaSourceCode() == null || rc.getJavaSourceCode().length() == 0) {
toBeUpdated.add(tf);
} else if (rc.getBytecode() == null || rc.getBytecode().length == 0) {
toBeUpdated.add(tf);
}
}
Set<String> currentClassesOnDir = createClassNameSet(allTemplates);
Set<String> currentClassNames = japidClasses.keySet();
if (!currentClassNames.equals(currentClassesOnDir)) {
Set<String> classNamesRegistered = new HashSet<String>(currentClassNames);
Set<String> classNamesDir = new HashSet<String>(currentClassesOnDir);
if (classNamesRegistered.containsAll(classNamesDir)) {
classNamesRegistered.removeAll(classNamesDir);
if (!classNamesRegistered.isEmpty()) {
for (String n : classNamesRegistered) {
if (!n.contains("$")) {
if (!specialClasses.contains(n)) {
touch();
break;
}
}
}
}
} else {
touch();
}
} else {
// no name changes
}
// allClassNamesOnDir.removeAll(currentClassNames); // got new
// templates
removeRemoved(currentClassesOnDir, currentClassNames);
for (File tb : toBeUpdated) {
String scriptSrc = DirUtil.readFileAsString(tb);
String javaCode = JapidTemplateTransformer.generateInMemory(scriptSrc, cleanPath(tb), usePlay);
JapidFlags.log("converted: " + tb.getPath());
String className = getClassName(tb);
RendererClass rc = newRendererClass(className);
rc.setScriptFile(tb);
rc.setJapidSourceCode(scriptSrc);
rc.setJavaSourceCode(javaCode);
removeInnerClasses(className);
cleanByteCode(rc);
japidClasses.put(className, rc);
}
setupImports();
// compile all
if (toBeUpdated.size() > 0) {
dynamicClasses.clear(); // XXX why clear the dynamics?
Set<String> names = createClassNameSet(toBeUpdated);
long t = System.currentTimeMillis();
compiler.compile(names.toArray(new String[] {}));
howlong("compile time for " + names.size() + " classes", t);
TemplateClassLoader loader = getClassLoader();
for (String cname : names) {
loader.loadClass(cname);
}
}
} catch (Exception e) {
if (e instanceof JapidTemplateException)
throw (JapidTemplateException) e;
throw new RuntimeException(e);
}
}
/**
*
* @author Bing Ran (bing.ran@gmail.com)
*/
public static void persistJapidClassesLater() {
if (getOpMode() == OpMode.dev && enableJITCachePersistence) {
saveJapidClassesService.schedule(new Runnable() {
@Override
public void run() {
persistJapidClasses();
}
});
}
}
private static void setupImports() {
JapidTemplateTransformer.addImportLine("japidviews.*");
JapidTemplateTransformer.addImportLine("java.util.*");
if (usePlay) {
JapidTemplateTransformer.addImportLine("controllers.*");
JapidTemplateTransformer.addImportLine("models.*");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Context.Implicit");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Request");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Response");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Session");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Flash");
JapidTemplateTransformer.addImportLine("play.data.validation.Validation");
JapidTemplateTransformer.addImportLine("play.i18n.Lang");
JapidTemplateTransformer.addImportLine("play.data.Form");
JapidTemplateTransformer.addImportLine("play.data.Form.Field");
}
for (String imp : imports) {
JapidTemplateTransformer.addImportLine(imp);
}
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param allTemplates
* @return
*/
private static Set<String> createClassNameSet(Set<File> allTemplates) {
Set<String> names = new HashSet<String>();
for (File f : allTemplates) {
names.add(getClassName(f));
}
return names;
}
private static String cleanPath(File f) {
String path = f.getPath();
return path.substring(path.indexOf(JAPIDVIEWS));
}
/**
* @author Bing Ran (bing.ran@gmail.com)
*/
private static void touch() {
lastChanged = System.currentTimeMillis();
}
/**
* transform a Japid script file to Java code which is then compiled to
* bytecode stored in the global japidClasses object.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param srcFileName
* @param scriptSrc
*/
static synchronized void compileDevErroView(String srcFileName, String scriptSrc) {
try {
String c = DirUtil.deriveClassName(srcFileName);
RendererClass rc = newRendererClass(c);
japidClasses.put(c, rc);
String javaCode = JapidTemplateTransformer.generateInMemory(scriptSrc, srcFileName, usePlay);
rc.setJavaSourceCode(javaCode);
rc.setJapidSourceCode(scriptSrc);
removeInnerClasses(c);
cleanByteCode(rc);
compiler.compile(new String[] { DEV_ERROR });
} catch (Exception e) {
if (e instanceof JapidTemplateException)
throw (JapidTemplateException) e;
throw new RuntimeException(e);
}
}
private static void setSources(RendererClass rc, String className) {
boolean found = false;
String tried = "";
for (String r : templateRoots) {
String pathname = r + SEP + className.replace(".", SEP);
File f = new File(pathname + ".java");
tried += f.getAbsolutePath() + "; ";
try {
setSources(rc, f);
found = true;
} catch (IOException e) {
}
}
if (!found) {
throw new RuntimeException("could not find the source for: " + className + ". Tried: " + tried);
}
}
private static File setSources(RendererClass rc, File f) throws IOException {
rc.setJavaSourceCode(readSource(f));
File srcFile = DirUtil.mapJavatoSrc(f);
rc.setJapidSourceCode(readSource(srcFile));
rc.setScriptFile(srcFile);
return srcFile;
}
private static void removeInnerClasses(String className) {
for (Iterator<String> i = japidClasses.keySet().iterator(); i.hasNext();) {
String k = i.next();
if (k.startsWith(className + "$")) {
i.remove();
}
}
}
/**
* @param currentClassesOnDir
* what classes on the disc.
* @param classSetInMemory
* original set of class names
*/
private static void removeRemoved(Set<String> currentClassesOnDir, Set<String> classSetInMemory) {
// need to consider inner classes
// keySet.retainAll(currentClassesOnDir);
for (Iterator<String> i = classSetInMemory.iterator(); i.hasNext();) {
String k = i.next();
if (specialClasses.contains(k))
continue;
int q = k.indexOf('$');
if (q > 0) {
k = k.substring(0, q);
}
if (!currentClassesOnDir.contains(k)) {
i.remove();// changes to the keyset will result in change in the
// backing map.
}
}
}
static Set<String> specialClasses = new HashSet<String>();
// <classname RendererClass>
public static Map<String, RendererClass> japidClasses = new ConcurrentHashMap<String, RendererClass>();
// public static TemplateClassLoader crlr;
//
// public static TemplateClassLoader getCrlr() {
// return crlr;
// }
public static RendererCompiler compiler;
public static String[] templateRoots = { JAPIDROOT };
private static boolean rootsSet = false;
public static final String JAPIDVIEWS = "japidviews";
public static final String SEP = File.separator;
// public static String[] japidviews;
// static {
// initJapidViews();
// }
//
// private static void initJapidViews() {
// japidviews = new String[templateRoot.length];
// int i = 0;
// for (String r : templateRoot) {
// japidviews[i++] = r + SEP + JAPIDVIEWS + SEP;
// }
// }
// such as java.utils.*
// public static List<String> importlines = new ArrayList<String>();
public static int refreshInterval;
public static long lastRefreshed;
private static boolean inited;
public static boolean isInited() {
return inited;
}
private static OpMode opMode;
public static OpMode getOpMode() {
return opMode;
}
static void howlong(String string, long t) {
JapidFlags.log(string + ":" + (System.currentTimeMillis() - t) + "ms");
}
/**
* @param rendererClass
*/
static void cleanByteCode(RendererClass rendererClass) {
rendererClass.setBytecode(null);
rendererClass.setClz(null);
rendererClass.setLastUpdated(0);
}
static Set<String> createClassNameSet(String[] allHtml) {
// the names start with template root
Set<String> names = new HashSet<String>();
for (String f : allHtml) {
if (f.startsWith(JAPIDVIEWS)) {
names.add(getClassName(new File(f)));
}
}
return names;
}
//
// static String getSourceCode(String k) {
// String pathname = templateRoots + SEP + k;
// pathname = pathname.replace(".", SEP);
// File f = new File(pathname + ".java");
// return readSource(f);
// }
/**
* @param c
* @return
*/
static RendererClass newRendererClass(String c) {
RendererClass rc = new RendererClass();
rc.setClassName(c);
// the source code of the Java file might not be available yet
// rc.setSourceCode(getSouceCode(c));
rc.setLastUpdated(0);
return rc;
}
static String readSource(File f) throws IOException {
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedReader br = new BufferedReader(new InputStreamReader(bis, "UTF-8"));
StringBuilder b = new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
b.append(line + "\n");
}
br.close();
return b.toString();
}
public static String readFirstLine(File f) {
try {
FileInputStream fis = new FileInputStream(f);
BufferedInputStream bis = new BufferedInputStream(fis, 40);
BufferedReader br = new BufferedReader(new InputStreamReader(bis, "UTF-8"));
String line = br.readLine();
br.close();
return line;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static String getClassName(File f) {
String substring = cleanPath(f);
return DirUtil.deriveClassName(substring);
}
/**
* set the interval to check template changes.
*
* @param i
* the interval in seconds. Set it to {@link Integer.MAX_VALUE}
* to effectively disable refreshing
*/
public static void setRefreshInterval(int i) {
refreshInterval = i * 1000;
}
/**
* set the paths where to look for japid scripts.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param roots
*/
public static void setTemplateRoot(String... roots) {
templateRoots = roots;
rootsSet = true;
if (roots == null) {
JapidFlags.info("japid roots was set to null. Will search classpth only for Japid scripts.");
} else {
for (String r : roots) {
File file = new File(r);
String fullPath = file.getAbsolutePath();
if (file.exists()) {
if (!file.isDirectory()) {
throw new RuntimeException("Japid template root exists but is not a directory: " + fullPath);
} else {
File japidviews = new File(file, JAPIDVIEWS);
if (!japidviews.exists()) {
if (!japidviews.mkdir())
throw new RuntimeException(
"Japid template prefix folder does not exist and failed to be created: "
+ japidviews.getAbsolutePath() + ". " + "You should create manually.");
else
JapidFlags.log("created directory: " + japidviews.getAbsolutePath());
} else
JapidFlags.log("set Japid root to: " + fullPath);
}
} else {
JapidFlags.warn("root directory does not exist: " + fullPath);
}
}
}
}
/**
* The entry point for the command line tool japid.bat and japid.sh
*
* The "gen" and "regen" are probably the most useful ones.
*
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
if (args.length > 0) {
String arg0 = args[0];
setTemplateRoot(".");
if ("gen".equals(arg0)) {
gen(templateRoots);
} else if ("regen".equals(arg0)) {
regen(templateRoots);
} else if ("clean".equals(arg0)) {
for (String r : templateRoots)
delAllGeneratedJava(getJapidviewsDir(r));
} else if ("mkdir".equals(arg0)) {
mkdir(templateRoots);
// } else if ("changed".equals(arg0)) {
// changed(japidviews);
} else {
System.err.println("help: optionas are: gen, regen, mkdir and clean");
}
} else {
System.err.println("help: optionas are: gen, regen, mkdir and clean");
}
}
// private static void changed(String root) {
// List<File> changedFiles = DirUtil.findChangedSrcFiles(new File(root));
// for (File f : changedFiles) {
// System.out.println("changed: " + f.getPath());
// }
//
// }
/**
* note: create the basic layout: app/japidviews/_layouts
* app/japidviews/_tags
*
* then create a dir for each controller. //TODO
*
* @throws IOException
*
*/
static List<File> mkdir(String... root) throws IOException {
List<File> files = new ArrayList<File>();
if (getOpMode() == OpMode.prod)
return files;
if (!usePlay)
return files;
for (String r : root) {
files.addAll(PlayDirUtil.mkdir(r));
}
return files;
}
/**
* @param root
* @return
*/
private static String getJapidviewsDir(String root) {
return root + SEP + JAPIDVIEWS + SEP;
}
public static void regen() throws IOException {
regen(templateRoots);
}
public static void regen(String... roots) throws IOException {
removeDerivedJavaFiles(roots);
gen(roots);
}
/**
* remove all the java files derived from Japid scripts
*
* @author Bing Ran (bing.ran@gmail.com)
*/
public static void removeDerivedJavaFiles() {
removeDerivedJavaFiles(templateRoots);
}
private static void removeDerivedJavaFiles(String... roots) {
for (String root : roots) {
if (new File(root).exists())
delAllGeneratedJava(getJapidviewsDir(root));
}
}
static void delAllGeneratedJava(String pathname) {
String[] javas = DirUtil.getAllFileNames(new File(pathname), new String[] { "java" });
for (String j : javas) {
log("removed: " + pathname + j);
boolean delete = new File(pathname + File.separatorChar + j).delete();
if (!delete)
throw new RuntimeException("file was not deleted: " + j);
}
// log("removed: all none java tag java files in " +
// JapidPlugin.JAPIDVIEWS_ROOT);
}
/**
* update the java files from the html files, for the changed only
*
* @throws IOException
*/
static List<File> gen(String... packageRoots) throws IOException {
List<File> changedFiles = reloadChanged(packageRoots);
for (String p : packageRoots) {
rmOrphanJava(p);
}
return changedFiles;
}
/**
* @param roots
* the package root
* @return the updated Java files.
*/
static List<File> reloadChanged(String... roots) {
List<File> files = new ArrayList<File>();
if (roots == null)
return files;
// try {
// mkdir(roots);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
for (String r : roots) {
File root = new File(r);
if (!root.exists()) {
JapidFlags.warn("root directory does not exist: " + root.getAbsolutePath());
continue;
}
TranslateTemplateTask t = new TranslateTemplateTask();
t.addImport("japidviews.*");
t.addImport("java.util.*");
if (usePlay) {
t.addImport("controllers.*");
t.addImport("models.*");
t.addImport("play.data.validation.Validation");
t.addImport("play.i18n.Lang");
t.addImport("play.mvc.Http.Context.Implicit");
t.addImport("play.mvc.Http.Flash");
t.addImport("play.mvc.Http.Request");
t.addImport("play.mvc.Http.Response");
t.addImport("play.mvc.Http.Session");
t.addImport("play.data.Form");
t.addImport("play.data.Form.Field");
}
for (String imp : imports) {
t.addImport(imp);
}
t.setUsePlay(usePlay);
t.setPackageRoot(root);
t.setInclude(new File(r + SEP + JAPIDVIEWS + SEP));
// _layouts and _tags are deprecated
// if (DirUtil.hasLayouts(r))
// t.addImport("japidviews._layouts.*");
// if (DirUtil.hasTags(r))
// t.addImport("japidviews._tags.*");
t.execute();
files.addAll(t.getChangedTargetFiles());
}
return files;
}
/**
* get all the java files in a dir with the "java" removed
*
* @return
*/
static File[] getAllJavaFilesInDir(String root) {
// from source files only
String[] allFiles = DirUtil.getAllFileNames(new File(root), new String[] { ".java" });
File[] fs = new File[allFiles.length];
int i = 0;
for (String f : allFiles) {
String path = f.replace(".java", "");
fs[i++] = new File(path);
}
return fs;
}
/**
* delete orphaned java artifacts from the japidviews directory
*
* @param packageRoot
*
* @return
*/
static boolean rmOrphanJava(String packageRoot) {
boolean hasRealOrphan = false;
try {
String pathname = getJapidviewsDir(packageRoot);
File src = new File(pathname);
if (!src.exists()) {
return hasRealOrphan;
}
Set<File> oj = DirUtil.findOrphanJava(src, null);
for (File j : oj) {
String path = j.getPath();
// if (path.contains(DirUtil.JAVATAGS))
// continue;
// log("found: " + path);
hasRealOrphan = true;
String realfile = pathname + File.separator + path;
File file = new File(realfile);
boolean r = file.delete();
if (r)
JapidFlags.log("deleted orphan " + realfile);
else
JapidFlags.log("failed to delete: " + realfile);
}
} catch (Exception e) {
e.printStackTrace();
}
return hasRealOrphan;
}
static List<File> reloadChanged() {
return reloadChanged(templateRoots);
}
static void log(String m) {
if (JapidFlags.verbose)
System.out.println("[JapidRender]: " + m);
}
public static void gen() {
if (templateRoots == null) {
JapidFlags.info("japid roots was set to null. Will search for Japid scripts from classpath only");
compileJapidResources();
} else {
try {
gen(templateRoots);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// /**
// * set to development mode
// */
// public static void setDevMode() {
// devMode = true;
// }
// /**
// * set to production mode
// */
// public static void setProdMode() {
// devMode = false;
// }
//
//
// public static boolean isDevMode() {
// return opMode == OpMode.dev;
// }
/**
* A shorter version init() that takes default arguments. The mode matches
* that of the app; the japidviews folder is located in the "japidroot"
* directory in the application; the no-change-detection peroid is 3
* seconds.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param app
* @throws IOException
*/
public static void init(boolean isDevMode, Map<String, Object> config, ClassLoader clr) {
try {
init(isDevMode ? OpMode.dev : OpMode.prod, templateRoots, 3, config, clr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* To initialize Japid with default settings:
* <ol>
* <li>all japid scripts are located in {app_name}/japidroot/japidviews. Use
* {@link #setTemplateRoot(String...)} to chage it.</li>
* <li>The intermediary Java files derived form the Japid scripts are kept
* along with the source files in the file system. Use
* {@link #setKeepJavaFiles(boolean)} to change it.</li>
* </ol>
*
* @author Bing Ran (bing.ran@gmail.com)
* @param isDevMode
* to set the Japid runtime to run in dev mode or production
* mode. In dev mode Japid runtime monitors the changes of the
* Japid scripts and reload them on the fly.
*/
public static void init(boolean isDevMode) {
init(isDevMode, null, JapidRenderer.class.getClassLoader());
}
/**
* The <em>required</em> initialization step in using the JapidRender.
*
* @param opMode
* the operational mode of Japid. When set to OpMode.prod, it's
* assumed that all Java derivatives are already been generated
* and used directly. When set to OpMode.dev, and using
* none-static linking to using the renderer, file system changes
* are detected for every rendering request given the refresh
* interval is respected. New Java files are generated and
* compiled and new classes are loaded to serve the request.
* @param templateRoot
* the root directory to contain the "japidviews" directory tree.
* @param refreshInterval
* the minimal time, in second, that must elapse before trying to
* detect any changes in the file system.
* @param app
* the Play application instance
* @throws IOException
*/
public static void init(OpMode opMode, String[] templateRoot, int refreshInterval, Map<String, Object> app,
ClassLoader clr) throws IOException {
specialClasses.clear();
showCurrentDirectory();
File cf = new File(".");
String path = cf.getAbsolutePath();
setClassCacheRoot(path);
japidResourceCompiled = false;
inited = true;
JapidRenderer.opMode = opMode == null ? OpMode.dev : opMode;
if (!rootsSet)
setTemplateRoot(templateRoot);
setRefreshInterval(refreshInterval);
boolean yesno = false;
try {
String property = (String) app.get("japid.trace.file.html");
if (property == null)
yesno = false;
else if ("on".equalsIgnoreCase(property) || "yes".equalsIgnoreCase(property))
yesno = true;
} catch (Exception e) {
}
JapidTemplateBaseWithoutPlay.globalTraceFileHtml = yesno;
try {
String property = (String) app.get("japid.trace.file.json");
if (property == null)
yesno = false;
else if ("on".equalsIgnoreCase(property) || "yes".equalsIgnoreCase(property))
yesno = true;
} catch (Exception e) {
}
JapidTemplateBaseWithoutPlay.globalTraceFileJson = yesno;
// System.out.println("parent classloader: " + clr);
parentClassLoader = clr;
TemplateClassLoader classLoader = getClassLoader();
compiler = new RendererCompiler(japidClasses, classLoader);
// if (usePlay)
// initErrorRenderer();
touch();
if (opMode == OpMode.dev)
recoverClasses();
dynamicClasses.clear();
DirUtil.curVersion = VERSION;
AbstractTemplateClassMetaData.curVersion = VERSION;
JapidFlags.debug("Before compiling Japid script from classpath. version " + VERSION );
setupImports();
compileJapidResources();
try {
refreshClasses();
} catch (Exception e) {
JapidFlags
.log("There was an error in refreshing the japid classes. Will show the error in detail in processing a request: "
+ e);
}
System.out.println("[Japid] initialized version " + VERSION + " in " + getOpMode() + " mode");
}
/**
* @author Bing Ran (bing.ran@gmail.com)
*/
private static void initImports() {
if (usePlay) {
JapidTemplateTransformer.addImportLine("play.mvc.Http.Context.Implicit");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Request");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Response");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Session");
JapidTemplateTransformer.addImportLine("play.mvc.Http.Flash");
JapidTemplateTransformer.addImportLine("play.data.validation.Validation");
JapidTemplateTransformer.addImportLine("play.i18n.Lang");
}
}
private static void showCurrentDirectory() {
File cf = new File(".");
String path = cf.getAbsolutePath();
String[] fs = cf.list();
JapidFlags.debug("current directory: " + path + " Content in the folder: " + StringUtils.join(fs, ","));
}
/**
* @author Bing Ran (bing.ran@gmail.com)
*/
@SuppressWarnings("unchecked")
private static void recoverClasses() {
String templateRoot = getClassCacheRoot();
FileInputStream fos;
File file = new File(new File(templateRoot), JAPID_CLASSES_CACHE);
try {
if (file.exists()) {
// discard it if the file is too old
long t = System.currentTimeMillis();
if (t - file.lastModified() > 10000) {
// too old
JapidFlags.debug("the japid cache was too old. discarded.");
file.delete();
} else {
fos = new FileInputStream(file);
BufferedInputStream bos = new BufferedInputStream(fos);
ObjectInputStream ois = new ObjectInputStream(bos);
String version = (String) ois.readObject();
JapidFlags.debug("Japid version: " + VERSION + ". JapidCache version: " + version);
if (!version.equals(VERSION)) {
JapidFlags.debug("Japid classes mismatch. Discard cache.");
}
else {
japidClasses = (Map<String, RendererClass>) ois.readObject();
resourceJars = (HashSet<File>) ois.readObject();
HashSet<File> versionCheckedDirs = (HashSet<File>) ois.readObject();
JapidFlags.setVersionCheckedDirs(versionCheckedDirs);
JapidFlags.debug("recovered Japid classes from cache");
}
ois.close();
}
}
} catch (Exception e) {
JapidFlags.info("error in recovering class cache. Ignored: " + e);
// e.printStackTrace();
} finally {
if (file.exists()) {
file.delete();
}
}
}
public static String findTemplate() {
String japidRenderInvoker = StackTraceUtils.getJapidRenderInvoker();
return japidRenderInvoker;
}
/**
* If true, allow verbose logging to the console of the Japid activities.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param logVerbose
*/
public static void setLogVerbose(boolean logVerbose) {
JapidFlags.verbose = logVerbose;
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @return
*/
public static Class<? extends JapidTemplateBaseWithoutPlay> getErrorRendererClass() {
// return devErrorClass;
return japidviews.devError.class;
}
// @SuppressWarnings("unchecked")
// public static void initErrorRenderer() throws IOException {
// if (devErrorClass == null) {
// InputStream devErr =
// PlayDirUtil.class.getResourceAsStream(DEV_ERROR_FILE);
// ByteArrayOutputStream out = new ByteArrayOutputStream(8000);
// DirUtil.copyStreamClose(devErr, out);
// String devErrorSrc = out.toString("UTF-8");
//
// compileDevErroView(DEV_ERROR_FILE, devErrorSrc);
//
// try {
// devErrorClass = (Class<JapidTemplateBaseWithoutPlay>)
// getClassLoader().loadClass(DEV_ERROR);
// // japidClasses.get(DEV_ERROR).setClz(loadClass);
// japidClasses.remove(DEV_ERROR);
// specialClasses.add(DEV_ERROR);
// } catch (ClassNotFoundException e) {
// throw new RuntimeException(e);
// }
// }
// }
/**
* @return the lastChanged
*/
public static long getLastChanged() {
return lastChanged;
}
// static Class<JapidTemplateBaseWithoutPlay> devErrorClass;
private static String appPath;
private static boolean japidResourceCompiled;
private static HashSet<File> resourceJars = new HashSet<File>();
/**
* compile/recompile the class if disk change detected. Should later do the
* compiling based on dependency graph.
*
* @author Bing Ran (bing.ran@gmail.com)
* @param rc
*/
public static void recompile(RendererClass rc) {
if (rc.getBytecode() == null || rc.getLastCompiled() < getLastChanged()) {
compiler.compile(new String[] { rc.getClassName() });
}
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @return
*/
public static String[] getTemplateRoot() {
return templateRoots;
}
public static Map<String, Object> getCache() {
return japidCache;
}
/**
* register a dynamic japid template and get a key to it for later use
*
* @author Bing Ran (bing.ran@gmail.com)
* @param mimeType
* @param source
* @return key the key that the japid source is registered under
*/
public static String registerTemplate(MimeTypeEnum mimeType, String source) {
int hashCode = source.hashCode();
String key = registerTemplate(mimeType, source, (JAPIDVIEWS + "._dynamic" + hashCode).replace('-', '_'));
return key;
}
/**
* To register a template in the Japid engine. Once a template is
* registered, the template class can be retrieved with the name by invoking
* {@link #getClass(String)} or {@link #getRendererClass(String)}.
*
* This method can be used at runtime to compile a Japid script and later
* use it to render data by invoking
* JapidController.renderJapidWith(className, args...);
*
* @author Bing Ran (bing.ran@gmail.com)
* @param mimeType
* the MIME type of content generated by this template.
* @param source
* the script source of the Japid template
* @param key
* the key that the source script is registered under. must be in
* the form of a valid Java class name
* @return returns the key, which is prefixed with "japidviews." if it was
* not so
*/
public static String registerTemplate(MimeTypeEnum mimeType, String source, String key) {
refreshClasses();
if (!key.startsWith(JAPIDVIEWS)) {
key = JAPIDVIEWS + "." + key.replace('-', '_').replace(' ', '_');
}
RendererClass cl = dynamicClasses.get(key);
if (cl != null) {
if (source.equals(cl.getJapidSourceCode())) {
return key;
}
}
try {
String javaCode = JapidTemplateTransformer.generateInMemory(source, key, mimeType, usePlay);
JapidFlags.log("converted: " + key);
// System.out.println(javaCode);
RendererClass rc = newRendererClass(key);
rc.setJapidSourceCode(source);
rc.setJavaSourceCode(javaCode);
removeInnerClasses(key);
cleanByteCode(rc);
japidClasses.put(key, rc); // remember the current impl of class
// refresh will erase dynamic template
// class from this container.
compiler.compile(new String[] { key });
dynamicClasses.put(key, rc);
TemplateClassLoader loader = getClassLoader();
loader.loadClass(key);
} catch (Exception e) {
if (e instanceof JapidTemplateException)
throw (JapidTemplateException) e;
throw new RuntimeException(e);
}
return key;
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param key
* @return
*/
public static Class<? extends JapidTemplateBaseWithoutPlay> getDynamicRenderer(String key) {
RendererClass rendererClass = dynamicClasses.get(key);
return rendererClass.getClz();
}
public static void persistJapidClasses() {
try {
// save for future reloading
String cacheRoot = getClassCacheRoot();
FileOutputStream fos = new FileOutputStream(
new File(new File(cacheRoot), JapidRenderer.JAPID_CLASSES_CACHE));
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(bos);
oos.writeObject(VERSION);
oos.writeObject(japidClasses);
oos.writeObject(resourceJars);
oos.writeObject(JapidFlags.getVersionCheckedDirs());
JapidFlags.debug("japid template classes cached on disk.");
oos.close();
} catch (Exception e) {
System.out.println(e);
if (oos != null) {
try {
oos.close();
} catch (Exception ex) {
}
}
}
} catch (IOException e) {
System.out.println(e);
} finally {
}
}
public static final String JAPID_CLASSES_CACHE = ".japidClasses.cache";
/**
* render data to a Japid script in the full path of
* "{japidroot}/japidviews/{fully qualified class name with slash as separator}/{method name}.html"
*
* @author Bing Ran (bing.ran@gmail.com)
* @param args
* @return
*/
public static RenderResult render(Object... args) {
return renderWith(findTemplate(), args);
}
/**
* render data to a template with a path relative to the "japid root"
* directory. e.g.: "japidviews/myscript.html"
*
* @author Bing Ran (bing.ran@gmail.com)
* @param template
* @param args
* @return
*/
public static RenderResult renderWith(String template, Object... args) {
if (args == null)
args = new Object[0];
if (template == null || template.length() == 0) {
throw new RuntimeException("JapidRenderer: template name cannot be empty.");
}
// template = StringUtils.removeEnding(template, HTML);
template = DirUtil.deriveClassName(template);
String templateClassName = JapidRenderer.getTemplateClassName(template);
Class<? extends JapidTemplateBaseWithoutPlay> tClass = null;
try {
tClass = getClass(templateClassName);
} catch (Throwable t) {
RenderResult er = handleException(t);
return er;
}
if (tClass == null) {
throw new RuntimeException("Could not find a Japid template with the name: "
+ (templateClassName.replace('.', '/') + HTML));
} else {
try {
return RenderInvokerUtils.invokeRender(tClass, args);
} catch (Throwable t) {
RenderResult er = handleException(t);
return er;
}
}
}
public static RenderResult renderWith(String template) {
return renderWith(template, new Object[0]);
}
public static RenderResult getRenderResultWith(String template, NamedArgRuntime[] args) {
String templateClassName = JapidRenderer.getTemplateClassName(template);
Class<? extends JapidTemplateBaseWithoutPlay> tClass;
try {
tClass = getClass(templateClassName);
} catch (Throwable e) {
RenderResult er = handleException(e);
return er;
}
if (tClass == null) {
String templateFileName = templateClassName.replace('.', '/') + HTML;
throw new RuntimeException("Could not find a Japid template with the name of: " + templateFileName);
} else {
try {
return RenderInvokerUtils.invokeNamedArgsRender(tClass, args);
} catch (Throwable e) {
RenderResult er = handleException(e);
return er;
}
}
}
public static RenderResult renderDynamic(String template, Object... args) {
try {
String key = registerTemplate(MimeTypeEnum.html, template);
return renderDynamicByKey(key, args);
} catch (Throwable e) {
RenderResult er = handleException(e);
return er;
}
}
public static RenderResult renderDynamicByKey(String key, Object... args) {
Class<? extends JapidTemplateBaseWithoutPlay> clz = getDynamicRenderer(key);
return RenderInvokerUtils.invokeRender(clz, args);
}
/**
*
*/
public static final String JAPIDVIEWS_ROOT = "japidviews";
public static String getTemplateClassName(String template) {
String templateClassName = template.startsWith(JAPIDVIEWS_ROOT) ? template : JAPIDVIEWS_ROOT + File.separator
+ template;
templateClassName = templateClassName.replace('/', '.').replace('\\', '.');
return templateClassName;
}
public static final String HTML = ".html";
/**
* @author Bing Ran (bing.ran@gmail.com)
* @return
*/
public static String getAppPath() {
return appPath;
}
/**
* @param appPath
* the appPath to set
*/
public static void setAppPath(String appPath) {
JapidRenderer.appPath = appPath;
}
public static RenderResult handleException(Throwable e) throws RuntimeException {
if (!presentErrorInHtml)
if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new RuntimeException(e);
// if (Play.mode == Mode.PROD)
// throw new RuntimeException(e);
//
Class<? extends JapidTemplateBaseWithoutPlay> rendererClass = getErrorRendererClass();
if (e instanceof JapidTemplateException) {
RenderResult rr = RenderInvokerUtils.invokeRender(rendererClass, (JapidTemplateException) e);
return (rr);
}
if (e instanceof RuntimeException && e.getCause() != null)
e = e.getCause();
if (e instanceof JapidCompilationException) {
JapidCompilationException jce = (JapidCompilationException) e;
JapidTemplateException te = JapidTemplateException.from(jce);
RenderResult rr = RenderInvokerUtils.invokeRender(rendererClass, te);
return (rr);
}
e.printStackTrace();
// find the latest japidviews exception or the controller that caused
// the exception
StackTraceElement[] stackTrace = e.getStackTrace();
for (StackTraceElement ele : stackTrace) {
String className = ele.getClassName();
if (className.startsWith("japidviews")) {
int lineNumber = ele.getLineNumber();
RendererClass applicationClass = japidClasses.get(className);
if (applicationClass != null) {
// let's get the line of problem
int oriLineNumber = applicationClass.mapJavaLineToJapidScriptLine(lineNumber);
if (oriLineNumber > 0) {
if (rendererClass != null) {
String path = applicationClass.getScriptPath();
JapidTemplateException te = new JapidTemplateException("Japid Error", path + "("
+ oriLineNumber + "): " + e.getClass().getName() + ": " + e.getMessage(),
oriLineNumber, path, applicationClass.getJapidSourceCode());
RenderResult rr = RenderInvokerUtils.invokeRender(rendererClass, te);
return (rr);
}
}
}
} else if (className.startsWith("controllers.")) {
if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new RuntimeException(e);
}
}
JapidTemplateException te = new JapidTemplateException(e);
RenderResult rr = RenderInvokerUtils.invokeRender(rendererClass, te);
return rr;
// if (e instanceof RuntimeException)
// throw (RuntimeException) e;
// else
// throw new RuntimeException(e);
}
/**
* @return the presentErrorInHtml
*/
public static boolean isPresentErrorInHtml() {
return presentErrorInHtml;
}
/**
* @param presentErrorInHtml
* the presentErrorInHtml to set
*/
public static void setPresentErrorInHtml(boolean presentErrorInHtml) {
JapidRenderer.presentErrorInHtml = presentErrorInHtml;
}
public static void shutdown() {
saveJapidClassesService.shutdown();
saveJapidClassesService = new AmmendableScheduledExecutor();
}
/**
* @return the enableJITCachePersistence
*/
public static boolean isEnableJITCachePersistence() {
return enableJITCachePersistence;
}
/**
* controls if Just-In-Time persisting of the Japid classes is enabled.
*
* @param enableJITCachePersistence
* the enableJITCachePersistence to set
*/
public static void setEnableJITCachePersistence(boolean enableJITCachePersistence) {
JapidRenderer.enableJITCachePersistence = enableJITCachePersistence;
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param b
* @param classLoader
*/
public static void init(boolean b, ClassLoader classLoader) {
init(b, null, classLoader);
}
/**
* @return the classCacheRoot
*/
public static String getClassCacheRoot() {
if (classCacheRoot != null)
return classCacheRoot;
else {
String[] r = getTemplateRoot();
return r != null ? r[0] : "japidroot";
}
}
/**
* @param classCacheRoot
* the classCacheRoot to set
*/
public static void setClassCacheRoot(String classCacheRoot) {
JapidRenderer.classCacheRoot = classCacheRoot;
}
// scan classpath for japid scripts
private static void compileJapidResources() {
if (japidResourceCompiled == true)
return;
try {
Enumeration<URL> resources;
resources = JapidRenderer.class.getClassLoader().getResources("japidviews/");
// find out all the jars that contain japidviews
List<String> scriptNames = new ArrayList<String>();
while (resources.hasMoreElements()) {
URL u = resources.nextElement();
String protocol = u.getProtocol();
String path = u.getPath();
if (protocol.equals("jar")) {
if (path.startsWith("file:")) {
path = path.substring("file:".length());
}
path = path.substring(0, path.lastIndexOf('!'));
// test if already in cache
if (cachedAlready(path))
continue;
JarFile jarFile = new JarFile(path);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (shouldIgnore(name))
continue;
if (name.startsWith("japidviews/") && !name.endsWith("/")) {
RendererClass rc = process((name), jarFile.getInputStream(entry));
rc.setContributor(u.toString());
JapidFlags.debug("converted contributed script: " + u + ":" + name);
String cname = DirUtil.deriveClassName(name);
scriptNames.add(cname);
specialClasses.add(cname);
}
}
resourceJars.add(new File(path));
}
}
compiler.compile(scriptNames);
// TemplateClassLoader loader = getClassLoader();
// loader.loadClass(key);
japidResourceCompiled = true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param path
* @return
*/
private static boolean cachedAlready(String path) {
File f = new File(path);
for (Iterator<File> it = resourceJars.iterator(); it.hasNext();) {
File rf = it.next();
if (rf.getAbsolutePath().equals(f.getAbsolutePath())) {
// how about time stamp
if (rf.lastModified() == f.lastModified())
return true;
else {
it.remove();
return false;
}
}
}
return false;
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param name
* @return
*/
private static boolean shouldIgnore(String k) {
if (!k.endsWith(".html") && !k.endsWith(".txt") && !k.endsWith(".xml") && !k.endsWith(".json")
&& !k.endsWith(".css") && !k.endsWith(".js"))
return true;
else
return false;
}
// compile japid script to Java code from an inputstream
private static RendererClass process(String name, InputStream input) throws IOException {
InputStreamReader isr = new InputStreamReader(input, "UTF-8");
BufferedReader reader = new BufferedReader(isr);
String content = "";
String line;
while ((line = reader.readLine()) != null)
content += line + "\n";
reader.close();
String fqName = DirUtil.deriveClassName(name);
try {
String javaCode = JapidTemplateTransformer.generateInMemory(content, (fqName),
MimeTypeEnum.inferFromName(name), usePlay);
// System.out.println(javaCode);
RendererClass rc = newRendererClass(fqName);
rc.setJapidSourceCode(content);
rc.setJavaSourceCode(javaCode);
removeInnerClasses(fqName);
cleanByteCode(rc);
japidClasses.put(fqName, rc); // remember the current impl of class
// refresh will erase dynamic
// template
// class from this container.
return rc;
} catch (Exception e) {
if (e instanceof JapidTemplateException)
throw (JapidTemplateException) e;
throw new RuntimeException(e);
}
}
/**
* render data to the Japid derived class
*
* @author Bing Ran (bing.ran@gmail.com)
* @param rendererClass
* @param args
* @return
*/
public static RenderResult renderWith(Class<? extends JapidTemplateBaseWithoutPlay> rendererClass, Object... args) {
try {
return RenderInvokerUtils.invokeRender(rendererClass, args);
} catch (Throwable t) {
RenderResult er = handleException(t);
return er;
}
}
/**
* find out if a Japid class denoted by the template name is available
*
* @author Bing Ran (bing.ran@gmail.com)
* @param templateName
* @return
*/
public static boolean hasTemplate(String templateName) {
String template = DirUtil.deriveClassName(templateName);
String templateClassName = JapidRenderer.getTemplateClassName(template);
try {
Class<? extends JapidTemplateBaseWithoutPlay> c = getClass(templateClassName);
if (c == null)
return false;
else
return true;
} catch (JapidTemplateNotFoundException e) {
return false;
}
}
/**
* @author Bing Ran (bing.ran@gmail.com)
* @param templateName
* @return
*/
public static Class<? extends JapidTemplateBaseWithoutPlay> getTemplateClass(String templateName) {
String template = DirUtil.deriveClassName(templateName);
String templateClassName = JapidRenderer.getTemplateClassName(template);
return getRendererClass(templateClassName).getClz();
}
}