//{HEADER
/**
* This class is part of jnex 'Nexirius Application Framework for Java'
*
* Copyright (C) Nexirius GmbH, CH-4450 Sissach, Switzerland (www.nexirius.ch)
*
* <p>This library is free software; you can redistribute it and/or<br>
* modify it under the terms of the GNU Lesser General Public<br>
* License as published by the Free Software Foundation; either<br>
* version 2.1 of the License, or (at your option) any later version.</p>
*
* <p>This library is distributed in the hope that it will be useful,<br>
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
* Lesser General Public License for more details.</p>
*
* <p>You should have received a copy of the GNU Lesser General Public<br>
* License along with this library; if not, write to the Free Software<br>
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA</p>
* </blockquote>
*
* <p>
* Nexirius GmbH, hereby disclaims all copyright interest in<br>
* the library jnex' 'Nexirius Application Framework for Java' written<br>
* by Marcel Baumann.</p>
*/
//}HEADER
package com.nexirius.framework.htmlview;
import com.nexirius.framework.FWLog;
import com.nexirius.framework.datamodel.DataModel;
import com.nexirius.framework.htmlview.function.*;
import com.nexirius.util.Semaphore;
import com.nexirius.util.StringVector;
import com.nexirius.util.VariableResolver;
import com.nexirius.util.locale.LocaleManager;
import com.nexirius.util.locale.LocaleManagerImpl;
import com.nexirius.util.locale.LocaleEvent;
import java.io.*;
import java.util.Enumeration;
import java.util.Stack;
import java.util.Locale;
/**
* This class translates a HTML file which contains special functions $!function() and variables like $(VARIABLE) into a real HTML file
* by parsing it over and over again until all special tags and variables are replaced
*/
public class HTMLResolver implements Serializable {
VariableStore variableStore = new VariableStore();
HTMLStreamMap streamMapper = new HTMLStreamMap();
HTMLTemplateMap templateMap = new HTMLTemplateMap();
HTMLFunctionMap functionMap = new HTMLFunctionMap();
HTMLTranslator translator = new NullHTMLTranslator();
LocaleManager localeManager = new LocaleManagerImpl();
Stack templateStack = new Stack();
DataModel rootModel = null;
OutputStream observerStream = null; // just for debug!
public HTMLResolver() {
init();
}
private void init() {
registerHTMLFunction(new FieldHTMLFunction());
registerHTMLFunction(new IncludeHTMLFunction());
registerHTMLFunction(new IncludeBodyHTMLFunction());
registerHTMLFunction(new ArrayHTMLFunction());
registerHTMLFunction(new ButtonHTMLFunction());
registerHTMLFunction(new BackButtonHTMLFunction());
registerHTMLFunction(new PopupHTMLFunction());
registerHTMLFunction(new EventHTMLFunction());
registerHTMLFunction(new IconEventHTMLFunction());
registerHTMLFunction(new DefineHTMLFunction());
registerHTMLFunction(new FlagHTMLFunction());
registerHTMLFunction(new TranslateHTMLFunction());
registerHTMLFunction(new SelectHTMLFunction());
registerHTMLFunction(new ColorHTMLFunction());
registerHTMLFunction(new DebugHTMLFunction());
registerHTMLFunction(new PasswordFieldHTMLFunction());
registerHTMLFunction(new FormStartHTMLFunction());
registerHTMLFunction(new FormEndHTMLFunction());
registerHTMLFunction(new EncodeUrlHTMLFunction());
registerHTMLFunction(new TooltipHTMLFunction());
registerHTMLFunction(new GetChildTextHTMLFunction());
}
/**
* use this method to attach an observer before the recursive stream reader
*
* @param out use e.g. System.out
*/
public void setObserverStream(OutputStream out) {
observerStream = out;
}
/**
* access all nested templates into a string
*
* @return
*/
public String getTemplateStackAsString() {
StringBuffer ret = new StringBuffer(" in ");
Enumeration e = templateStack.elements();
Stack reverse = new Stack();
while (e.hasMoreElements()) {
reverse.push(e.nextElement());
}
while (!reverse.empty()) {
ret.append("'" + reverse.pop() + "'");
if (!reverse.empty()) {
ret.append(" accessed by ");
}
}
return ret.toString();
}
/**
* Return the model which belongs to the actual state
* @return
*/
public DataModel getRootModel() {
return rootModel;
}
/**
* Access all nested data models into a string. Walks up the parents of the
* given model until the actual root model (or null) is reached.
*
* @param model the bottom child from which it starts
* @return
*/
public String getModelStackAsString(DataModel model) {
if (rootModel == null) {
return "";
}
return rootModel.getChildName(model);
}
/**
* set the variable accessor (which translates variables into strings)
*/
public void setVariableStore(VariableStore va) {
variableStore = va;
}
public VariableStore getVariableStore() {
return variableStore;
}
/**
* get the HTMLTranslatorImpl
*/
public HTMLTranslator getHTMLTranslator() {
return translator;
}
/**
* By default a HTMLTranslatorImpl instance is already registered with this class.
*
* @param translator
*/
public void setTranslator(HTMLTranslator translator) {
this.translator = translator;
localeManager.addLocaleListener(translator);
}
public void setLocale(Locale locale) {
localeManager.setLocale(locale);
}
public Locale getLocale() {
return localeManager.getLocale();
}
/**
* translates all variables $(variable) from an inputstream into an outputstream and returns the number of
* translated variables
*
* @param in
* @param out
* @return
*/
public int resolveVariables(InputStream in, OutputStream out)
throws IOException {
return VariableResolver.translate(variableStore, in, out);
}
/**
* set the HTML stream mapper (which translates templates into input streams)
*/
public void setStreamMapper(HTMLStreamMap sm) {
streamMapper = sm;
}
/**
* set the mapper which translates data model types into templates
*/
public void setTemplateMap(HTMLTemplateMap tm) {
templateMap = tm;
}
/**
* access the registered HTMLTranslatorImpl to translate the given string
*
* @param s
* @return the translation or s if no translation found
*/
public String translate(String s) {
if (translator == null) {
return s;
}
return translator.translate(s);
}
/**
* access the template which is associated to a specific data model
*
* @param model the data model for which a template is searched
* @param isEditor there can be two templates registered for each model (one for edit and one foe view)
* @return
*/
public String getTemplateFor(DataModel model, boolean isEditor) {
return templateMap.getTemplateFor(model, isEditor);
}
/**
* The root model is used to translate any child model into a field name.
* This method is only used to complete child names into the variable $(FULLNAME)
*
* @param model
*/
public void setRootModel(DataModel model) {
rootModel = model;
}
/**
* translates a model (and eventually a template) into a byte array
*
* @param sessionVariable
* @param model the model which is the actual parent model
* @param template the template which is used to access an input stream. A null template is translated into a template by using HTMLTemplateMap
* @param isEditor the general type of the resulting HTML dialog (view or edit)
* @return the output stream packed into a byte array
* @throws IOException
* @throws Exception
*/
public byte[] resolve(HTMLSessionVariable sessionVariable, DataModel model, String template, boolean isEditor)
throws IOException, Exception {
if (model != null) {
FWLog.debug("resolve(" + model.getFieldName() + ", " + template + ", " + isEditor + ")");
}
if (template == null) {
if (model == null) {
return "No template found for null model".getBytes();
}
template = getTemplateFor(model, isEditor);
FWLog.debug("Using template -> " + template);
}
if (template != null) {
templateStack.push(template);
}
byte ret[] = resolve(sessionVariable, model, streamMapper.getInputStreamFor(model, template, isEditor), isEditor);
if (template != null) {
templateStack.pop();
}
return ret;
}
/**
* This method is called after the template has been translated into an input stream.
* It resolves the embedded variables $(variable) first and then the embedded functions !$function().
* It continues recursively until all variables and functions are resolved (this can lead into
* an infinite recursion which results in a stack overflow or out of memory exception)
*
* @param sessionVariable
* @param model the actual parent data model
* @param in the actual input stream
* @param isEditor the general type of the resulting HTML dialog (view or edit)
* @return the output stream packed into a byte array
* @throws Exception
*/
public byte[] resolve(HTMLSessionVariable sessionVariable, DataModel model, InputStream in, boolean isEditor)
throws Exception {
Semaphore semaphore = new Semaphore();
PipedInputStream in1 = new PipedInputStream();
PipedOutputStream out1 = new PipedOutputStream(in1);
PipedInputStream in2 = new PipedInputStream();
PipedOutputStream out2 = new PipedOutputStream(in2);
ByteArrayOutputStream out = new ByteArrayOutputStream();
VariableStore oldValues = null;
PushbackInputStream inParser = null;
if (observerStream == null) {
inParser = new PushbackInputStream(in2);
} else {
inParser = new PushbackInputStream(new InObserver(in2));
}
if (model != null) {
oldValues = new VariableStore();
variableStore.setVariable(VariableStore.FULLNAME, getModelStackAsString(model));
DataModelVariableHandler.setVariablesFor(model, variableStore, oldValues, translator);
}
// replace variables first
new PrecompilerThread(in, out1);
VariableResolverThread vr = new VariableResolverThread(new PushbackInputStream(in1), out2);
HTMLParserThread parser = new HTMLParserThread(sessionVariable, model, isEditor, inParser, out, semaphore);
semaphore.waitFor();
if (vr.getException() != null) {
throw vr.getException();
}
if (parser.getException() != null) {
throw parser.getException();
}
if ((vr.getNumberOfTranslations() + parser.getNumberOfTranslations()) != 0) {
// recurse as long as translations are done
return resolve(sessionVariable, model, new ByteArrayInputStream(out.toByteArray()), isEditor);
}
if (model != null) {
variableStore.set(oldValues);
}
return out.toByteArray();
}
/**
* this class is only used for debugging
*
* @author isc-mb
*/
class InObserver extends InputStream {
InputStream in;
public InObserver(InputStream in) {
this.in = in;
try {
observerStream.write("\nInObserver\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public int read() throws IOException {
int c = in.read();
if (c >= 0) {
observerStream.write((byte) c);
observerStream.flush();
}
return c;
}
}
/**
* translates a simple template into a byte array. calls: resolve(null, template, false)
*
* @param sessionVariable
* @param template
* @return
* @throws IOException
* @throws Exception
*/
public byte[] resolve(HTMLSessionVariable sessionVariable, String template)
throws IOException, Exception {
return resolve(sessionVariable, null, template, false);
}
/**
* this class is used to handle $/* and $* / comments
*/
class PrecompilerThread extends Thread {
int numberOfTranslations = 0;
InputStream in;
OutputStream out;
Exception exception = null;
PrecompilerThread(InputStream input, OutputStream output) {
in = input;
out = output;
start();
}
public Exception getException() {
return exception;
}
public void run() {
try {
int state = 0;
while (true) {
int c = in.read();
if (c < 0) {
break;
}
switch (state) {
case 0:
if (c == '$') {
state = 1;
} else {
out.write(c);
state = 0;
}
break;
case 1: // after $
if (c == '/') {
state = 2;
} else {
out.write('$');
out.write(c);
state = 0;
}
break;
case 2: // after /
if (c == '*') {
state = 3;
} else {
out.write('$');
out.write('/');
out.write(c);
state = 0;
}
break;
case 3: // inside comment
if (c == '$') {
state = 4;
}
break;
case 4: // after $
if (c == '*') {
state = 5;
} else {
state = 3;
}
break;
case 5: // after *
if (c == '/') {
state = 0;
} else {
state = 3;
}
break;
}
}
} catch (IOException e) {
exception = e;
}
try {
in.close();
out.close();
} catch (IOException e) {
exception = e;
}
}
public int getNumberOfTranslations() {
return numberOfTranslations;
}
}
/**
* this class is used to handle variable translations in a seperate thread
*/
class VariableResolverThread extends Thread {
int numberOfTranslations = 0;
InputStream in;
OutputStream out;
Exception exception = null;
VariableResolverThread(InputStream input, OutputStream output) {
in = input;
out = output;
start();
}
public Exception getException() {
return exception;
}
public void run() {
try {
numberOfTranslations = VariableResolver.translate(variableStore, in, out);
} catch (IOException e) {
exception = e;
}
try {
in.close();
out.close();
} catch (IOException e) {
exception = e;
}
}
public int getNumberOfTranslations() {
return numberOfTranslations;
}
}
/**
* this class is used to handle embedded function translation in a seperate thread
*/
class HTMLParserThread extends Thread {
HTMLSessionVariable sessionVariable;
int numberOfTranslations = 0;
DataModel model;
boolean isEditor;
PushbackInputStream in;
OutputStream out;
Semaphore semaphore;
Exception exception = null;
HTMLParserThread(HTMLSessionVariable sessionVariable, DataModel model, boolean isEditor, PushbackInputStream input, OutputStream output, Semaphore s) {
this.sessionVariable = sessionVariable;
semaphore = s;
try {
semaphore.reserve();
} catch (Exception ex) {
// should never happen
ex.printStackTrace();
}
this.model = model;
this.isEditor = isEditor;
in = input;
out = output;
start();
}
public Exception getException() {
return exception;
}
public void run() {
try {
HTMLParser parser = new HTMLParser(model, isEditor, HTMLResolver.this, out);
parser.parse(sessionVariable, in);
numberOfTranslations = parser.getNumberOfTranslations();
} catch (Exception e) {
exception = e;
}
try {
in.close();
out.close();
} catch (IOException e) {
exception = e;
}
semaphore.release();
}
public int getNumberOfTranslations() {
return numberOfTranslations;
}
}
/**
* Access the actual HTMLStreamMap
*/
public HTMLStreamMap getStreamMapper() {
return streamMapper;
}
/**
* Access the actual HTMLTemplateMap
*/
public HTMLTemplateMap getTemplateMap() {
return templateMap;
}
public void registerHTMLFunction(HTMLFunction function) {
functionMap.put(function);
}
public HTMLFunction getFunction(String functionName) {
return functionMap.get(functionName);
}
class NullHTMLTranslator implements HTMLTranslator {
public String translate(String s) {
return s;
}
public void localeChanged(LocaleEvent event) {
}
}
}