All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaitools.jiffle.Jiffle Maven / Gradle / Ivy

Go to download

Jiffle is a raster algebra language that can be used to create new images from logical and mathematical expressions involving source images and user defined variables. It is intended to let users concentrate on algorithms rather than tedious boiler-plate code.

The newest version!
/*
 * Copyright 2009-2011 Michael Bedward
 * 
 * This file is part of jai-tools.
 *
 * jai-tools is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * jai-tools is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with jai-tools.  If not, see .
 * 
 */

package jaitools.jiffle;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;

import org.codehaus.janino.SimpleCompiler;

import jaitools.CollectionFactory;
import jaitools.jiffle.parser.CheckAssignments;
import jaitools.jiffle.parser.CheckFunctionCalls;
import jaitools.jiffle.parser.JiffleLexer;
import jaitools.jiffle.parser.JiffleParser;
import jaitools.jiffle.parser.JiffleParserException;
import jaitools.jiffle.parser.Message;
import jaitools.jiffle.parser.MessageTable;
import jaitools.jiffle.parser.OptionsBlockReader;
import jaitools.jiffle.parser.ParsingErrorReporter;
import jaitools.jiffle.parser.RuntimeSourceGenerator;
import jaitools.jiffle.parser.SourceGenerator;
import jaitools.jiffle.parser.TagVars;
import jaitools.jiffle.parser.TransformExpressions;
import jaitools.jiffle.runtime.JiffleDirectRuntime;
import jaitools.jiffle.runtime.JiffleIndirectRuntime;
import jaitools.jiffle.runtime.JiffleRuntime;

/**
 * Compiles scripts and generates Java sources and executable bytecode for
 * runtime classes.
 * 

* Example of use: *


 * // A script to write sequential values to image pixels
 * String script = "init { n = 0; } dest = n++ ;" ;
 *
 * // We tell Jiffle about variable names that represent images
 * // (in this case, only "dest") via a Map of parameters
 * Map<String, Jiffle.ImageRole> imageParams = CollectionFactory.map();
 * imageParams.put("dest", Jiffle.ImageRole.DEST);
 *
 * // Using this constructor results in the script being compiled
 * // immediately (any errors will generate JiffleExceptions)
 * Jiffle jiffle = new Jiffle(script, imageParams);
 *
 * // Now get a runtime object
 * JiffleDirectRuntime runtime = jiffle.getRuntimeInstance();
 *
 * // Create an image to hold the results of the script and pass it
 * // to the runtime object
 * final int width = 10;
 * TiledImage destImg = ImageUtils.createConstantImage(width, width, 0.0d);
 * runtime.setDestinationImage("dest", destImg);
 *
 * // Evaluate the script for all destination image pixels
 * runtime.evaluateAll();
 * 
* For further examples of how to create and run Jiffle scripts see the * {@code jaitools.demo.jiffle} package in the JAI-tools demo module. * *

Implementation note

* The Jiffle compiler is really a Jiffle to Java translator. * When a client requests a runtime object, the script is translated and * the resulting Java source is passed to an embedded Janino compiler * which compiles the source to bytecode in memory. * * @see JiffleBuilder * @see jaitools.jiffle.runtime.JiffleExecutor * * @author Michael Bedward * @since 1.0 * @version $Id: Jiffle.java 1512 2011-03-07 02:21:45Z michael.bedward $ */ public class Jiffle { /** * Constants for runtime model. Jiffle supports two runtime models: *
    *
  1. * Direct - where the runtime class {@code evaluate} method directly * sets values in the destination image(s) *
  2. *
  3. * Indirect - where there is a single destination image and the runtime * class {@code evaluate} method returns the destination value, leaving * it up to the caller to set the value in the image. * object *
  4. *
* The indirect model is designed for use in an image operator or similar * context where further processing of destination values might be required * before writing to an image. */ public static enum RuntimeModel { /** The runtime class implements {@link JiffleDirectRuntime} */ DIRECT(JiffleDirectRuntime.class), /** The runtime class implements {@link JiffleIndirectRuntime} */ INDIRECT(JiffleIndirectRuntime.class); private Class runtimeClass; private RuntimeModel(Class clazz) { this.runtimeClass = clazz; } /** * Gets the runtime interface. * * @return the runtime interface */ public Class getRuntimeClass() { return runtimeClass; } /** * Gets the matching constant for the given runtime class. * * @param clazz a runtime class * * @return the contant or {@code null} if the class does not derive * from a supported base class */ public static RuntimeModel get(Class clazz) { for (RuntimeModel t : RuntimeModel.values()) { if (t.runtimeClass.isAssignableFrom(clazz)) { return t; } } return null; } } /** Number of Jiffle instances */ private static int refCount = 0; /** * Used to specify the roles of images referenced in * a Jiffle script. An image may be either read-only * ({@link Jiffle.ImageRole#SOURCE}) or write-only * ({@link Jiffle.ImageRole#DEST}) but not both. */ public static enum ImageRole { /** Indicates an image is used for input (read-only) */ SOURCE, /** Indicates an image is used for output (write-only) */ DEST; } /** A name: either a default or one set by the client */ private String name; private String theScript; private CommonTree primaryAST; private Map scriptOptions; private CommonTree finalAST; private CommonTokenStream tokens; private ParsingErrorReporter errorReporter; private Map imageParams; private MessageTable msgTable; /** * Creates a new instance. */ public Jiffle() { init(); } /** * Creates a new instance by compiling the provided script. Using this * constructor is equivalent to: *

     * Jiffle jiffle = new Jiffle();
     * jiffle.setScript(script);
     * jiffle.setImageParams(params);
     * jiffle.compile();
     * 
* * @param script Jiffle source code to compile * * @param params defines the names and roles of image variables * referred to in the script. * * @throws JiffleException if there are any errors compiling the script */ public Jiffle(String script, Map params) throws JiffleException { init(); setScript(script); setImageParams(params); compile(); } /** * Creates a new instance by compiling the script read from {@code scriptFile}. * Using this constructor is equivalent to: *

     * Jiffle jiffle = new Jiffle();
     * jiffle.setScript(scriptFile);
     * jiffle.setImageParams(params);
     * jiffle.compile();
     * 
* * @param scriptFile file containing the Jiffle script * * @param params defines the names and roles of image variables * referred to in the script. * * @throws JiffleException if the file cannot be read or if there are * any errors compiling the script */ public Jiffle(File scriptFile, Map params) throws JiffleException { init(); setScript(scriptFile); setImageParams(params); compile(); } /** * Sets the script. Calling this method will clear any previous script * and runtime objects. * * @param script a Jiffle script * @throws JiffleException if the script is null or empty */ public final void setScript(String script) throws JiffleException { if (script == null || script.trim().length() == 0) { throw new JiffleException("script is empty !"); } if (theScript != null) { clearCompiledObjects(); } // add extra new line just in case last statement hits EOF theScript = script + "\n"; } /** * Sets the script. Calling this method will clear any previous script * and runtime objects. * * @param scriptFile a file containing a Jiffle script * @throws JiffleException on errors reading the script file */ public final void setScript(File scriptFile) throws JiffleException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(scriptFile)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (line.length() > 0) { sb.append(line); sb.append('\n'); // put the newline back on for the parser } } setScript(sb.toString()); } catch (IOException ex) { throw new JiffleException("Could not read the script file", ex); } finally { if (reader != null) { try { reader.close(); } catch (IOException ignored) { } } } } /** * Gets the Jiffle script. * * @return the script or an empty {@code String} if none * has been set */ public String getScript() { return theScript == null ? "" : theScript; } /** * Sets the image parameters. These define which variables in * the script refer to images and their types (source or destination). *

* This may be called before or after setting the script. No check is * made between script and parameters until the script is compiled. * * @param params the image parameters */ public final void setImageParams(Map params) { imageParams.clear(); imageParams.putAll(params); } /** * Gets the current image parameters. The parameters are returned * as an unmodifiable map. * * @return image parameters or an empty {@code Map} if none * are set */ public Map getImageParams() { return Collections.unmodifiableMap(imageParams); } /** * Replaces the default name set for this object with a user-supplied name. * The name is solely for use by client code. No checks are made for * duplication between current instances. * * @param name the name to assign */ public void setName(String name) { this.name = name; } /** * Gets the name assigned to this object. This will either be the * default name or one assigned by the client via {@link #setName(String)} * * @return the name */ public String getName() { return name; } /** * Compiles the script into Java source for the runtime class. * * @throws JiffleException if no script has been set or if any errors * occur during compilation */ public final void compile() throws JiffleException { if (theScript == null) { throw new JiffleException("No script has been set"); } if (imageParams.isEmpty()) { throw new JiffleException("No image parameters set"); } clearCompiledObjects(); buildPrimaryAST(); checkOptions(); reportMessages(); if (!transformAndCheckVars()) { throw new JiffleException(messagesToString()); } } /** * Tests whether the script has been compiled successfully. * * @return {@code true} if the script has been compiled; * {@code false} otherwise */ public boolean isCompiled() { return (finalAST != null); } /** * Creates an instance of the default runtime class. *

* The default runtime class implements {@link JiffleDirectRuntime} and * extends an abstract base class provided by the Jiffle compiler. Objects * of this class evaluate the Jiffle script and write results directly to * the destination image(s). Client code can call either of the methods: *

    *
  • {@code evaluate(int x, int y)} *
  • {@code evaluateAll(JiffleProgressListener listener} *
* The {@code Jiffle} object must be compiled before calling this method. * * @return the runtime object * @throws JiffleException if the runtime object could not be created */ public JiffleDirectRuntime getRuntimeInstance() throws JiffleException { return (JiffleDirectRuntime) createRuntimeInstance( RuntimeModel.DIRECT, JiffleProperties.DEFAULT_DIRECT_BASE_CLASS); } /** * Creates a runtime object based using the class specified by {@code model}. *

* The {@code Jiffle} object must be compiled before calling this method. * * @param model the {@link Jiffle.RuntimeModel} * @return the runtime object * @throws JiffleException if the runtime object could not be created */ public JiffleRuntime getRuntimeInstance(RuntimeModel model) throws JiffleException { switch (model) { case DIRECT: return createRuntimeInstance(model, JiffleProperties.DEFAULT_DIRECT_BASE_CLASS); case INDIRECT: return createRuntimeInstance(model, JiffleProperties.DEFAULT_INDIRECT_BASE_CLASS); default: throw new IllegalArgumentException("Invalid runtime class type: " + model); } } /** * Gets the runtime object for this script. *

* The runtime object is an instance of {@link JiffleRuntime}. By default * it extends an abstract base class supplied JAI-tools: * {@link jaitools.jiffle.runtime.AbstractDirectRuntime} * when using the direct runtiem model or * {@link jaitools.jiffle.runtime.AbstractIndirectRuntime} * when using the indirect model. This method allows you to * specify a custom base class. The custom class must implement either * {@link JiffleDirectRuntime} or {@link JiffleIndirectRuntime}. * * @param the runtime base class type * @param baseClass the runtime base class * * @return the runtime object * @throws JiffleException if the runtime object could not be created */ public T getRuntimeInstance(Class baseClass) throws JiffleException { RuntimeModel model = RuntimeModel.get(baseClass); if (model == null) { throw new JiffleException(baseClass.getName() + " does not implement a required Jiffle runtime interface"); } return (T) createRuntimeInstance(model, baseClass); } /** * Gets a copy of the Java source for the runtime class. * * @param model the {@link Jiffle.RuntimeModel} * @param scriptInDocs whether to include the original Jiffle script * in the class javadocs * * @return source for the runtime class * @throws JiffleException on error creating the runtime source */ public String getRuntimeSource(RuntimeModel model, boolean scriptInDocs) throws JiffleException { Class baseClass = null; switch (model) { case DIRECT: baseClass = JiffleProperties.DEFAULT_DIRECT_BASE_CLASS; break; case INDIRECT: baseClass = JiffleProperties.DEFAULT_INDIRECT_BASE_CLASS; break; } return createRuntimeSource(model, baseClass.getName(), scriptInDocs); } /** * Initializes this object's name and runtime base class. */ private void init() { Jiffle.refCount++ ; name = JiffleProperties.get( JiffleProperties.NAME_KEY ) + refCount; imageParams = CollectionFactory.map(); } /** * Clears all compiler and runtime objects */ private void clearCompiledObjects() { primaryAST = null; finalAST = null; tokens = null; errorReporter = null; msgTable = new MessageTable(); } private void reportMessages() throws JiffleException { if (msgTable.hasErrors()) { throw new JiffleException(messagesToString()); } if (msgTable.hasWarnings()) { Map> messages = msgTable.getMessages(); System.err.println(messagesToString()); } } /** * Write error messages to a string */ private String messagesToString() { StringBuilder sb = new StringBuilder(); if (msgTable != null) { Map> messages = msgTable.getMessages(); for (String key : messages.keySet()) { for (Message msg : messages.get(key)) { sb.append(msg.toString()); sb.append(": "); sb.append(key); sb.append("\n"); } } } return sb.toString(); } /** * Build a preliminary AST from the jiffle script. Basic syntax and grammar * checks are done at this stage. * * @throws jaitools.jiffle.interpreter.JiffleException */ private void buildPrimaryAST() throws JiffleException { try { ANTLRStringStream input = new ANTLRStringStream(theScript); JiffleLexer lexer = new JiffleLexer(input); tokens = new CommonTokenStream(lexer); JiffleParser parser = new JiffleParser(tokens); primaryAST = (CommonTree) parser.prog().getTree(); } catch (RecognitionException ex) { throw new JiffleException( "error in script at or around line:" + ex.line + " col:" + ex.charPositionInLine); } } private void checkOptions() { CommonTreeNodeStream nodes = new CommonTreeNodeStream(primaryAST); nodes.setTokenStream(tokens); OptionsBlockReader reader = new OptionsBlockReader(nodes, msgTable); reader.downup(primaryAST); } /** * Transforms variable tokens to specific types and does some basic * error checking. * * @return {@code true} if no errors; {@code false} otherwise * @throws JiffleException on unintercepted parser errors */ private boolean transformAndCheckVars() throws JiffleException { try { CommonTree tree = primaryAST; CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(tokens); TagVars tag = new TagVars(nodes, imageParams, msgTable); tree = (CommonTree) tag.start().getTree(); if (msgTable.hasErrors()) return false; nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(tokens); CheckAssignments assignments = new CheckAssignments(nodes, msgTable); assignments.start(); if (msgTable.hasErrors()) return false; nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(tokens); TransformExpressions trexpr = new TransformExpressions(nodes); tree = (CommonTree) trexpr.start().getTree(); nodes = new CommonTreeNodeStream(tree); nodes.setTokenStream(tokens); CheckFunctionCalls calls = new CheckFunctionCalls(nodes, msgTable); calls.downup(tree); if (msgTable.hasErrors()) return false; finalAST = tree; return true; } catch (RecognitionException ex) { throw new JiffleException( "error in script at or around line:" + ex.line + " col:" + ex.charPositionInLine); } catch (JiffleParserException ex) { throw new JiffleException(ex); } } /** * Creates an instance of the runtime class. The Java source for the * class is created if not already cached and then compiled using * Janino's {@link SimpleCompiler}. * * @throws Exception */ private JiffleRuntime createRuntimeInstance(RuntimeModel model, Class baseClass) throws JiffleException { if (!isCompiled()) { throw new JiffleException("The script has not been compiled"); } String runtimeSource = createRuntimeSource(model, baseClass.getName(), false); try { SimpleCompiler compiler = new SimpleCompiler(); compiler.cook(runtimeSource); StringBuilder sb = new StringBuilder(); sb.append(JiffleProperties.get(JiffleProperties.RUNTIME_PACKAGE_KEY)).append("."); switch (model) { case DIRECT: sb.append(JiffleProperties.get(JiffleProperties.DIRECT_CLASS_KEY)); break; case INDIRECT: sb.append(JiffleProperties.get(JiffleProperties.INDIRECT_CLASS_KEY)); break; default: throw new IllegalArgumentException("Internal compiler error"); } Class clazz = compiler.getClassLoader().loadClass(sb.toString()); return (JiffleRuntime) clazz.newInstance(); } catch (Exception ex) { throw new JiffleException("Runtime source error", ex); } } /** * Creates the Java source code for the runtime class. * * @param scriptInDocs whether to include the Jiffle script in the class * javadocs * * @throws JiffleException if an error occurs generating the source */ private String createRuntimeSource(RuntimeModel model, String baseClassName, boolean scriptInDocs) throws JiffleException { CommonTreeNodeStream nodes = new CommonTreeNodeStream(finalAST); nodes.setTokenStream(tokens); SourceGenerator generator = new RuntimeSourceGenerator(nodes); generator.setBaseClassName(baseClassName); generator.setRuntimeModel(model); return generator.getSource(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy