ru.histone.Histone Maven / Gradle / Ivy
/**
* Copyright 2013 MegaFon
*
* Licensed 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.
*/
package ru.histone;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.histone.deparser.Deparser;
import ru.histone.deparser.IDeparser;
import ru.histone.evaluator.Evaluator;
import ru.histone.evaluator.nodes.NodeFactory;
import ru.histone.optimizer.AbstractASTWalker;
import ru.histone.optimizer.AdditionalDataForOptimizationDebug;
import ru.histone.optimizer.ConstantsSubstitutionOptimizer;
import ru.histone.optimizer.EliminateSingleNodeArrayOptimizer;
import ru.histone.optimizer.FragmentsConcatinationOptimizer;
import ru.histone.optimizer.InlineMacroOptimizer;
import ru.histone.optimizer.OptimizationProfile;
import ru.histone.optimizer.OptimizationTrace;
import ru.histone.optimizer.OptimizationTypes;
import ru.histone.optimizer.SafeASTEvaluationOptimizer;
import ru.histone.optimizer.SafeASTNodesMarker;
import ru.histone.parser.Parser;
import ru.histone.parser.ParserException;
import ru.histone.resourceloaders.AstResource;
import ru.histone.resourceloaders.ContentType;
import ru.histone.resourceloaders.Resource;
import ru.histone.resourceloaders.ResourceLoadException;
import ru.histone.resourceloaders.ResourceLoader;
import ru.histone.resourceloaders.StreamResource;
import ru.histone.resourceloaders.StringResource;
import ru.histone.utils.IOUtils;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
/**
* Main Histone engine class. Histone template parsing/evaluation is done here.
* Histone class is thread safe!
* You shouldn't create this class by yourself, use {@link HistoneBuilder} instead.
*
* @see HistoneBuilder
*/
public class Histone {
/**
* General Histone logger. All debug information and general logging goes here
*/
private static final Logger log = LoggerFactory.getLogger(Histone.class);
/**
* Special logger for histone template syntax errors
*/
private static final Logger RUNTIME_LOG = LoggerFactory.getLogger(Histone.class.getName() + ".RUNTIME_LOG");
/**
* @deprecated (should be moved to GlobalProperties)
*/
private static boolean devMode = false;
private Parser parser;
private Evaluator evaluator;
private NodeFactory nodeFactory;
private ResourceLoader resourceLoader;
private final IDeparser deparser = new Deparser();
public Histone(HistoneBootstrap bootstrap) {
this.parser = bootstrap.getParser();
this.evaluator = bootstrap.getEvaluator();
this.nodeFactory = bootstrap.getNodeFactory();
this.resourceLoader = bootstrap.getResourceLoader();
}
public ArrayNode parseTemplateToAST(String templateData) throws HistoneException {
return parseTemplateToAST(new StringReader(templateData));
}
public ArrayNode parseTemplateToAST(Reader templateReader) throws HistoneException {
String inputString = null;
try {
inputString = IOUtils.toString(templateReader);
} catch (IOException e) {
log.error("Error reading input Reader", e);
throw new HistoneException("Error reading input Reader", e);
}
return parser.parse(inputString);
}
/**
* Optimize specified AST.
*/
public ArrayNode optimizeAST(ArrayNode templateAST, OptimizationTypes... optimizationsToRun) throws HistoneException {
return optimizeAST(templateAST, nodeFactory.jsonObject(), optimizationsToRun);
}
public ArrayNode optimizeAST(ArrayNode templateAST, ObjectNode context, OptimizationTypes... optimizationsToRun) throws HistoneException {
// Deque optimizationsList = new LinkedList();
ArrayList optimizationsList = new ArrayList();
Set optimizationsToRunSet = new TreeSet();
Collections.addAll(optimizationsToRunSet, optimizationsToRun);
ConstantsSubstitutionOptimizer constantsSubstitutionOptimizer = new ConstantsSubstitutionOptimizer(nodeFactory, context);
if (optimizationsToRunSet.contains(OptimizationTypes.CONSTANTS_SUBSTITUTION)) {
optimizationsList.add(0, constantsSubstitutionOptimizer);
}
SafeASTNodesMarker safeASTNodesMarker = new SafeASTNodesMarker(nodeFactory, evaluator);
if (optimizationsToRunSet.contains(OptimizationTypes.SAFE_CODE_MARKER)) {
int idx = optimizationsList.indexOf(constantsSubstitutionOptimizer);
// if we don't have constantsSubstitutionOptimizer, then we will insert at idx=0,
// otherwise right after constantsSubstitutionOptimizer
optimizationsList.add(++idx, safeASTNodesMarker);
}
SafeASTEvaluationOptimizer safeASTEvaluationOptimizer = new SafeASTEvaluationOptimizer(nodeFactory, evaluator);
if (optimizationsToRunSet.contains(OptimizationTypes.SAFE_CODE_EVALUATION)) {
int idx = optimizationsList.indexOf(safeASTNodesMarker);
if (idx < 0) {
idx = optimizationsList.indexOf(constantsSubstitutionOptimizer);
// if we don't have constantsSubstitutionOptimizer, then we will insert at idx=0,
// otherwise right after constantsSubstitutionOptimizer
optimizationsList.add(++idx, safeASTNodesMarker);
optimizationsList.add(++idx, safeASTEvaluationOptimizer);
} else {
optimizationsList.add(++idx, safeASTEvaluationOptimizer);
}
}
// InlineMacroOptimizer inlineMacroOptimizer = new InlineMacroOptimizer(nodeFactory);
// if (optimizationsToRunSet.contains(OptimizationTypes.INLINE_MACRO)) {
// int idx = optimizationsList.indexOf(safeASTEvaluationOptimizer);
// optimizationsList.add(++idx, inlineMacroOptimizer);
// }
if (optimizationsList.size() > 0 || optimizationsToRunSet.contains(OptimizationTypes.FRAGMENT_CONCATENATION)) {
optimizationsList.add(new FragmentsConcatinationOptimizer(nodeFactory));
}
if (optimizationsList.size() > 0 || optimizationsToRunSet.contains(OptimizationTypes.ELIMINATE_SINGLE_NODE)) {
optimizationsList.add(new EliminateSingleNodeArrayOptimizer(nodeFactory));
}
ArrayNode ast = templateAST;
for (AbstractASTWalker optimization : optimizationsList) {
ast = optimization.process(ast);
}
return ast;
}
//
/**
* Optimize specified AST, saving debug data of optimization to {@link OptimizationTrace}.
*
* @see OptimizationProfile
* @see AdditionalDataForOptimizationDebug
*/
/*
public ArrayNode optimizeASTWithTrace(ArrayNode templateAST,
OptimizationTrace optimizationTrace,
OptimizationProfile optimizationProfile,
AdditionalDataForOptimizationDebug debugInfo) throws HistoneException {
optimizationTrace.setOriginalAstAndSource(templateAST, deparser.deparse(templateAST));
ArrayNode ast = templateAST;
if (optimizationProfile.isUseImportsResolving()) {
ast = astImportResolver.resolve(templateAST);
addFrame(optimizationTrace, "ImportsResolving", ast, debugInfo);
}
if (optimizationProfile.getUseOptimizationCycle()) {
long L1 = 0, L2 = 0; // counter for infinite loops
long g1 = AbstractASTWalker.hash(ast);
while (true) {
L1++;
if (L1 == 10) break;
if (optimizationProfile.isUseConstantsFolding()) {
long h1 = AbstractASTWalker.hash(templateAST);
while (true) {
L2++;
if (L2 == 10) {
L2 = 0;
break;
}
ast = constantFolding.foldConstants(ast);
long h2 = AbstractASTWalker.hash(ast);
if (h1 == h2) break;
else {
addFrame(optimizationTrace, "ConstantsFolding", ast, debugInfo);
h1 = h2;
}
}
}
if (optimizationProfile.isUseConstantPropagation()) {
long h1 = AbstractASTWalker.hash(templateAST);
while (true) {
L2++;
if (L2 == 10) {
L2 = 0;
break;
}
ast = constantPropagation.propagateConstants(ast);
long h2 = AbstractASTWalker.hash(ast);
if (h1 == h2) break;
else {
addFrame(optimizationTrace, "ConstantsPropagation", ast, debugInfo);
h1 = h2;
}
}
}
if (optimizationProfile.isRemoveConstantIfCases()) {
long h1 = AbstractASTWalker.hash(templateAST);
while (true) {
L2++;
if (L2 == 10) {
L2 = 0;
break;
}
ast = constantIfCases.replaceConstantIfs(ast);
long h2 = AbstractASTWalker.hash(ast);
if (h1 == h2) break;
else {
addFrame(optimizationTrace, "ConstantIfCases", ast, debugInfo);
h1 = h2;
}
}
}
if (optimizationProfile.isRemoveUselessVariables()) {
long h1 = AbstractASTWalker.hash(templateAST);
while (true) {
L2++;
if (L2 == 10) {
L2 = 0;
break;
}
ast = uselessVariables.removeUselessVariables(ast);
long h2 = AbstractASTWalker.hash(ast);
if (h1 == h2) break;
else {
addFrame(optimizationTrace, "RemoveUselessVariables", ast, debugInfo);
h1 = h2;
}
}
}
long g2 = AbstractASTWalker.hash(ast);
if (g1 == g2) break;
g1 = g2;
}
}
if (optimizationProfile.isUseAstOptimizer()) {
{
long j1 = AbstractASTWalker.hash(ast);
ast = astMarker.mark(ast);
long j2 = AbstractASTWalker.hash(ast);
if (j1 != j2) addFrame(optimizationTrace, "SafeASTNodesMarker", ast, debugInfo);
}
{
long j1 = AbstractASTWalker.hash(ast);
ast = astOptimizer.optimize(ast);
long j2 = AbstractASTWalker.hash(ast);
if (j1 != j2) addFrame(optimizationTrace, "SafeASTEvaluationOptimizer", ast, debugInfo);
}
}
if (optimizationProfile.isUseAstSimplifier()) {
long j1 = AbstractASTWalker.hash(ast);
ast = fragmentsConcatinationOptimization.simplify(ast);
long j2 = AbstractASTWalker.hash(ast);
if (j1 != j2) addFrame(optimizationTrace, "AstSimplifier", ast, debugInfo);
}
optimizationTrace.setProcessedAstAndSource(ast, deparser.deparse(ast));
return ast;
} */
/**
* Add frame information to {@link OptimizationTrace}.
*/
/* protected void addFrame(OptimizationTrace optimizationTrace, String name, ArrayNode optimizedAst, AdditionalDataForOptimizationDebug debugInfo) throws HistoneException {
long t1 = System.currentTimeMillis();
String outputAfterThisStep = evaluateAST(debugInfo.getTemplateLocation(), optimizedAst, debugInfo.getEvaluationContext());
long t2 = System.currentTimeMillis();
OptimizationTrace.Frame frame = new OptimizationTrace.Frame();
frame.setName(name);
frame.setProcessedAst(optimizedAst);
frame.setProcessedSource(deparser.deparse(optimizedAst));
frame.setDidBrokeCompability(!debugInfo.getOriginalOutput().equals(outputAfterThisStep));
frame.setEvaluationTimeAfterThisStep(t2 - t1);
frame.setAstLengthAfterThisStep(AbstractASTWalker.countNodes(optimizedAst));
optimizationTrace.getFrames().add(frame);
} */
//
public String evaluateAST(ArrayNode templateAST) throws HistoneException {
return evaluateAST(null, templateAST, NullNode.instance);
}
public String evaluateAST(String baseURI, ArrayNode templateAST, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
return evaluator.process(baseURI, templateAST, context);
}
public void evaluateAST(ArrayNode templateAST, Writer output) throws HistoneException {
evaluateAST(null, templateAST, NullNode.instance, output);
}
public void evaluateAST(String baseURI, ArrayNode templateAST, Writer output) throws HistoneException {
evaluateAST(baseURI, templateAST, NullNode.instance, output);
}
public void evaluateAST(ArrayNode templateAST, JsonNode context, Writer output) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
evaluateAST(null, templateAST, context, output);
}
public void evaluateAST(String baseURI, ArrayNode templateAST, JsonNode context, Writer output) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
String result = evaluateAST(baseURI, templateAST, context);
try {
output.write(result);
} catch (IOException e) {
throw new HistoneException("Error writing to output Writer", e);
}
}
/**
* Main function for Histone template evaluation.
*/
public String evaluate(String baseURI, String templateContent, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
ArrayNode ast = parser.parse(templateContent);
// ArrayNode optimizedAst = optimizeAST(ast);
return evaluator.process(baseURI, ast, context);
}
public ArrayNode evaluateAsAST(String baseURI, String templateContent, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
return parser.parse(templateContent);
}
public String evaluateURI(String uri, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
if (resourceLoader == null) throw new IllegalStateException("Resource loader is null for Histone instance");
Resource resource = resourceLoader.load(uri, null, new String[]{ContentType.TEXT, ContentType.AST});
String baseUri = resource.getBaseHref();
JsonNode ast = readAstFromResource(resource, uri, null);
return evaluateAST(baseUri, (ArrayNode) ast, context);
}
public String evaluate(String templateContent) throws HistoneException {
return evaluate(null, templateContent, nodeFactory.jsonNull());
}
public String evaluate(String templateContent, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
return evaluate(null, templateContent, context);
}
public String evaluate(Reader templateReader) throws HistoneException {
return evaluate(null, templateReader, nodeFactory.jsonNull());
}
public String evaluate(String baseURI, Reader templateReader, JsonNode context) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
String templateContent = null;
try {
templateContent = IOUtils.toString(templateReader);
} catch (IOException e) {
throw new HistoneException("Error reading input Reader");
}
return evaluate(baseURI, templateContent, context);
}
public void evaluate(String baseURI, Reader templateReader, JsonNode context, Writer outputWriter) throws HistoneException {
if (context == null) context = nodeFactory.jsonObject();
String result = evaluate(baseURI, templateReader, context);
try {
outputWriter.write(result);
} catch (IOException e) {
throw new HistoneException("Error writing to output Writer", e);
}
}
public void setGlobalProperty(GlobalProperty property, String value) {
evaluator.setGlobalProperty(property, value);
}
/**
* Logs histone syntax error to special logger
*
* @param msg message
* @param e exception
* @param args arguments values that should be replaced in message
*/
public static void runtime_log_error(String msg, Throwable e, Object... args) {
RUNTIME_LOG.error(msg, args);
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
// throw new HistoneException();
}
/**
* Logs histone syntax info to special logger
*
* @param msg message
* @param args arguments values that should be replaced in message
*/
public static void runtime_log_info(String msg, Object... args) {
if (devMode) {
runtime_log_error(msg, null, args);
} else {
RUNTIME_LOG.info(msg, args);
}
}
/**
* Logs histone syntax warning to special logger
*
* @param msg message
* @param args arguments values that should be replaced in message
*/
public static void runtime_log_warn(String msg, Object... args) {
if (devMode) {
runtime_log_error(msg, null, args);
} else {
RUNTIME_LOG.warn(msg, args);
}
}
/**
* Logs histone syntax error to special logger
*
* @param msg message
* @param e exception
* @param args arguments values that should be replaced in message
*/
public static void runtime_log_warn_e(String msg, Throwable e, Object... args) {
if (devMode) {
runtime_log_error(msg, e, args);
} else {
RUNTIME_LOG.warn(msg, e, args);
}
}
private JsonNode readAstFromResource(Resource resource, String path, String currentBaseURI) throws HistoneException {
JsonNode ast = null;
try {
if (resource == null) {
throw new ResourceLoadException(MessageFormat.format("Can't import resource by path = '{}'. Resource was not found.", path));
}
if (!(resource instanceof StringResource) && !(resource instanceof StreamResource) && !(resource instanceof AstResource)) {
throw new ResourceLoadException(MessageFormat.format("Can't import resource by path = '{}'. Resource type '{}' is unknown", path, resource.getClass()));
}
String templateContent = null;
if (resource instanceof StringResource) {
templateContent = ((StringResource) resource).getContent();
} else if (resource instanceof StreamResource) {
templateContent = IOUtils.toString(((StreamResource) resource).getContent());
} else if (resource instanceof AstResource) {
ast = ((AstResource) resource).getContent();
} else {
throw new ResourceLoadException(MessageFormat.format("Unsupported resource class: {0}", resource.getClass()));
}
if (resource instanceof StringResource || resource instanceof StreamResource) {
if (templateContent == null) {
throw new ResourceLoadException(MessageFormat.format("Can't import resource by path: {0}. Resource is unreadable", path));
}
if (resource.getContentType() == ContentType.TEXT) {
ast = parser.parse(templateContent);
} else if (resource.getContentType() == ContentType.AST) {
ast = nodeFactory.jsonNode(templateContent);
} else {
throw new ResourceLoadException(MessageFormat.format("Unsupported content-type:{0} of resource href:{1}, baseHref:{2}", resource.getContentType(), path, currentBaseURI));
}
} else {
if (ast == null) {
throw new ResourceLoadException(MessageFormat.format("Can't import resource by path = {0}. Resource is unreadable", path));
}
}
} catch (IOException e) {
throw new ResourceLoadException("Resource import failed! Resource reading error.", e);
} catch (ParserException e) {
throw new ResourceLoadException("Resource import failed! Resource parsing error.", e);
}
return ast;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy