001package com.randomnoun.common.jessop; 002 003/* (c) 2016 randomnoun. All Rights Reserved. This work is licensed under a 004 * BSD Simplified License. ( http://www.randomnoun.com/bsd-simplified.html ) 005 */ 006 007import java.io.PrintWriter; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010 011import javax.script.ScriptException; 012 013import org.apache.log4j.Logger; 014 015 016/** This is an abstract class that supports generic support for creating template scripts from jessop source. 017 * 018 * <p>This class is responsible for processing jessop declarations (e.g. <code><%@ jessop language="javascript" engine="rhino" %></code>), 019 * and switching to the correct language JessopScriptBuilder implementation. 020 * 021 * <p>Note that having multiple languages in the same script file is not yet supported by jessop. 022 * The declaration (if it exists) should therefore only appear once, and be the first thing that appears in a jessop source file. 023 * 024 * <p>If the declaration is missing, then the default JavascriptJessopScriptBuilder is used, using the 'rhino' engine. 025 * 026 * @author knoxg 027 */ 028// this should be subclassed by specific languages (javascript etc) 029public abstract class AbstractJessopScriptBuilder implements JessopScriptBuilder { 030 031 protected Logger logger = Logger.getLogger(AbstractJessopScriptBuilder.class); 032 protected JessopDeclarations declarations; 033 protected Tokeniser tokeniser; 034 protected PrintWriter pw; 035 036 @Override 037 public void setPrintWriter(PrintWriter pw) { 038 this.pw = pw; 039 } 040 041 @Override 042 public void setTokeniserAndDeclarations(Tokeniser t, JessopDeclarations declarations) { 043 this.tokeniser = t; 044 this.declarations = declarations; 045 } 046 @Override 047 public JessopDeclarations getDeclarations() { 048 return declarations; 049 } 050 051 @Override 052 public void emitDeclaration(int line, String s) throws ScriptException { 053 // declType attr1="val1" attr2="val2" 054 // don't really feel like tokenising this at the moment 055 s = s.trim(); 056 // can't do this in 1 regex for some reason 057 // Pattern declPattern = Pattern.compile("([^\\s\"]+)\\s*(?:(\\S+)=\"([^\"]*)\"\\s*)*$"); 058 // so breaking into subregexes 059 String declType; 060 Pattern declTypePattern = Pattern.compile("^([^\\s\"]+)"); 061 Matcher m = declTypePattern.matcher(s); 062 if (m.find()) { 063 declType = m.group(1); 064 } else { 065 throw new ScriptException("Could not parse declaration '" + s + "'", null, line); 066 } 067 if (!declType.equals("jessop")) { 068 logger.warn("Unknown declaration type '" + declType + "'"); 069 // just ignore unknown declarations 070 return; 071 } 072 s = s.substring(declType.length()).trim(); 073 logger.debug("s=" + s); 074 Pattern declAttrPattern = Pattern.compile("(\\S+)=\"([^\"]*)\""); 075 m = declAttrPattern.matcher(s); 076 while (m.find()) { 077 // do something 078 String attrName = m.group(1); 079 String attrValue = m.group(2); 080 if (attrName.equals("language")) { 081 // change the JessopScriptBuilder based on the language 082 // the registry of ScriptBuilders is kept in the EngineFactory 083 JessopScriptEngineFactory jsf = (JessopScriptEngineFactory) tokeniser.jse.getFactory(); 084 JessopScriptBuilder newBuilder = jsf.getJessopScriptBuilderForLanguage(attrValue); 085 newBuilder.setPrintWriter(pw); 086 newBuilder.setTokeniserAndDeclarations(tokeniser, declarations); // pass on tokeniser state and declarations to new jsb 087 tokeniser.setJessopScriptBuilder(newBuilder); // tokeniser should use this jsb from this point on 088 // should probably wait until all attributes are parsed, but hey 089 //if (declarations.engine==null) { 090 declarations.engine = newBuilder.getDefaultScriptEngineName(); 091 declarations.exceptionConverter = newBuilder.getDefaultExceptionConverterClassName(); 092 declarations.bindingsConverter = newBuilder.getDefaultBindingsConverterClassName(); 093 //} 094 095 /* 096 JessopScriptBuilder newBuilder; 097 if (attrValue.equals("javascript")) { 098 newBuilder = new JavascriptJessopScriptBuilder(); 099 newBuilder.setPrintWriter(pw); 100 newBuilder.setTokeniser(tokeniser, declarations); // pass on tokeniser state and declarations to new jsb 101 tokeniser.setJessopScriptBuilder(newBuilder); // tokeniser should use this jsb from this point on 102 if (declarations.engine==null) { declarations.engine = "rhino"; } // default engine for javascript 103 104 } else if (attrValue.equals("java")) { 105 newBuilder = new JavaJessopScriptBuilder(); 106 newBuilder.setPrintWriter(pw); 107 newBuilder.setTokeniser(tokeniser, declarations); 108 tokeniser.setJessopScriptBuilder(newBuilder); 109 if (declarations.engine==null) { declarations.engine = "beanshell"; } // default engine for java 110 111 } else if (attrValue.equals("lua")) { 112 newBuilder = new LuaJessopScriptBuilder(); 113 newBuilder.setPrintWriter(pw); 114 newBuilder.setTokeniser(tokeniser, declarations); 115 tokeniser.setJessopScriptBuilder(newBuilder); 116 if (declarations.engine==null) { declarations.engine = "luaj"; } // default engine for lua 117 118 } else if (attrValue.equals("python") || attrValue.equals("python2")) { 119 newBuilder = new Python2JessopScriptBuilder(); 120 newBuilder.setPrintWriter(pw); 121 newBuilder.setTokeniser(tokeniser, declarations); 122 tokeniser.setJessopScriptBuilder(newBuilder); 123 if (declarations.engine==null) { declarations.engine = "jython"; } // default engine for lua 124 125 } else { 126 throw new IllegalArgumentException("Unknown language '" + attrValue + "'"); 127 } 128 */ 129 130 } else if (attrName.equals("engine")) { 131 // if we're changing engines, this will reset the default exception converter. 132 // we may want to keep a registry of engine names -> ExceptionConverters 133 // at a later stage 134 if (!attrValue.equals(declarations.getEngine())) { 135 declarations.setExceptionConverter(null); 136 } 137 declarations.setEngine(attrValue); 138 139 } else if (attrName.equals("suppressEol")) { 140 declarations.setSuppressEol(Boolean.valueOf(attrValue)); 141 142 } else if (attrName.equals("compileTarget")) { 143 declarations.setCompileTarget(Boolean.valueOf(attrValue)); 144 145 } else if (attrName.equals("filename")) { 146 declarations.setFilename(attrValue); 147 148 } else if (attrName.equals("exceptionConverter")) { 149 declarations.setExceptionConverter(attrValue); 150 151 } else if (attrName.equals("bindingsConverter")) { 152 declarations.setBindingsConverter(attrValue); 153 154 } 155 logger.debug("Found attr " + m.group(1) + "," + m.group(2)); 156 } 157 } 158 159 @Override 160 public abstract void emitText(int line, String s); 161 162 @Override 163 public abstract void emitExpression(int line, String s); 164 165 @Override 166 public abstract void emitScriptlet(int line, String s); 167 168 @Override 169 public String getDefaultExceptionConverterClassName() { 170 return null; 171 } 172 173 @Override 174 public String getDefaultBindingsConverterClassName() { 175 return null; 176 } 177 178 /** Conditionally remove the first newline from the supplied string. 179 * 180 * <p>This method is used to perform <code>suppressEol</code> declaration processing. 181 * 182 * <p>When the <code>suppressEol</code> declaration is <code>true</code>, and the text to be emitted by the output script 183 * immediately follows a scriptlet and begins with a newline (or whitespace followed by a newline), 184 * then we want to remove that (whitespace and) newline. 185 * 186 * <p>If there are non-whitespace characters before the first newline, then it is not suppressed. 187 * 188 * @param s text which is to be emitted by the output script 189 * @param suppressEol if true, remove the beginning whitespace and newline, if it exists. 190 * 191 * @return the supplied string, with the first newline conditionally removed 192 */ 193 protected String suppressEol(String s, boolean suppressEol) { 194 // ok. if s starts with a newline, 195 // *and* suppressEol is true, 196 // *and* this text is being emitted on a line that has nothing but expressions (and whitespace), 197 // then suppress the newline. 198 if (s.indexOf("\n")!=-1 && suppressEol) { 199 boolean isFirstLineJustWhitespace = true; 200 int pos = 0; 201 while (pos<s.length() && isFirstLineJustWhitespace) { 202 char ch = s.charAt(pos); 203 if (ch=='\n') { pos++; break; } 204 if (!Character.isWhitespace(ch)) { isFirstLineJustWhitespace = false; break; } 205 pos++; 206 } 207 if (isFirstLineJustWhitespace) { 208 s = s.substring(pos); 209 } 210 } 211 return s; 212 } 213}