/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2011 Andreas Maschke
This 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 2.1 of the
License, or (at your option) any later version.
This software 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 this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.variation;
import java.lang.reflect.Field;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.codehaus.janino.SimpleCompiler;
import org.jwildfire.create.tina.base.Layer;
import org.jwildfire.create.tina.base.XForm;
import org.jwildfire.create.tina.base.XYZPoint;
public class CustomFullVariationWrapperFunc extends VariationFunc {
private static final long serialVersionUID = 1L;
public static final boolean DEBUG = false;
private static final String RESSOURCE_CODE = "code_full_variation";
private static HashMap<String, Class> builtin_variations;
private static String classDeclRegex = "\\s*public\\s+class\\s+(\\S+?Func)\\s+(.*)";
private static Pattern classDecl = Pattern.compile(classDeclRegex);
private static String default_code =
"package org.jwildfire.create.tina.variation;\n" +
"import org.jwildfire.create.tina.base.XForm;\n" +
"import org.jwildfire.create.tina.base.XYZPoint;\n" +
"\n" +
"public class DynamicCompiledLinear3DFunc extends SimpleVariationFunc {\n" +
" private static final long serialVersionUID = 1L;\n" +
" public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {\n" +
" pVarTP.x += pAmount * pAffineTP.x;\n" +
" pVarTP.y += pAmount * pAffineTP.y;\n" +
" if (pContext.isPreserveZCoordinate()) {\n" +
" pVarTP.z += pAmount * pAffineTP.z;\n" +
" }\n" +
" }\n" +
"\n" +
" public String getName() {\n" +
" return \"linear3D_dynamic\";\n" +
" }\n" +
"}\n";
private String code = default_code;
private String filtered_code = code;
private VariationFunc full_variation = null;
private String[] ressourceNames = { RESSOURCE_CODE };
static {
builtin_variations = new HashMap<String, Class>();
List<Class<? extends VariationFunc>> varClasses = VariationFuncList.getVariationClasses();
for (Class varClass : varClasses) {
builtin_variations.put(varClass.getSimpleName(), varClass);
}
}
public CustomFullVariationWrapperFunc() {
if (DEBUG) {
System.out.println("called CustomFullVariationWrapperFunc constructor");
}
}
@Override
public void transform(FlameTransformationContext pContext, XForm pXForm, XYZPoint pAffineTP, XYZPoint pVarTP, double pAmount) {
if (full_variation != null) {
full_variation.transform(pContext, pXForm, pAffineTP, pVarTP, pAmount);
}
}
@Override
public String[] getParameterNames() {
if (full_variation != null) {
return full_variation.getParameterNames();
}
else {
return new String[0];
}
}
@Override
public Object[] getParameterValues() {
if (full_variation != null) {
return full_variation.getParameterValues();
}
else {
return new Object[0];
}
}
public int getParameterIndex(String pName) {
if (full_variation != null) {
return full_variation.getParameterIndex(pName);
}
else {
return -1;
}
}
@Override
public void setParameter(String pName, double pValue) {
if (full_variation != null) {
full_variation.setParameter(pName, pValue);
}
else
throw new IllegalArgumentException(pName);
}
@Override
public String[] getRessourceNames() {
// ressourceNames gets recreated in compile() to include inner full_variation resources (after the RESSOURCE_CODE name)
return ressourceNames;
}
@Override
public byte[][] getRessourceValues() {
byte[] codeval = (code != null ? code.getBytes() : null);
if (ressourceNames.length == 1) { // only one resource, the RESSOURCE_CODE, so no resources for inner full_variation
return new byte[][] { codeval };
}
else {
byte[][] inner_resvals = full_variation.getRessourceValues();
byte[][] all_vals = new byte[inner_resvals.length + 1][]; // ressourceNames.length should be = inner_resvals.length + 1
all_vals[0] = codeval;
for (int i = 0; i < inner_resvals.length; i++) {
all_vals[i + 1] = inner_resvals[i];
}
return all_vals;
}
// return new byte[][] { (code != null ? code.getBytes() : null) };
}
// public int getResourceIndex(String pName) // method inherited from VariationFunc should work
// public byt[] getResource(String pName) // method inherited from VariationFunc should work
@Override
public RessourceType getRessourceType(String pName) {
if (pName.equals(RESSOURCE_CODE)) {
return RessourceType.JAVA_CODE;
}
else {
return full_variation.getRessourceType(pName);
}
}
/*
* setting resource
* if setting RESSOURCE_CODE, then filter out Java annotation lines (since Janino compiler throws error on these)
* otherwise pass through to wrapped VariationFunc
*/
@Override
public void setRessource(String pName, byte[] pValue) {
if (DEBUG) {
System.out.println("called setRessource: " + pName);
}
if (RESSOURCE_CODE.equalsIgnoreCase(pName)) {
if (pValue == null) {
if (DEBUG) {
System.out.println(" code resource null, setting full_variation to null");
}
filtered_code = "";
full_variation = null;
return;
}
else {
String new_code = new String(pValue);
if (new_code.equals(code)) {
if (DEBUG) {
System.out.println(" code resource unchanged, returning without filtering/validating/compiling");
}
return;
}
code = new_code;
StringBuffer bufcode = new StringBuffer(code.length());
Scanner codescanner = new Scanner(code);
while (codescanner.hasNextLine()) {
String line = codescanner.nextLine();
// filter out Java annotation lines (lines that start with "@"), since Janino compiler will throw error on annotations
if (line.matches("\\s*@.*")) {
if (DEBUG) {
System.out.println("filtering out: " + line);
}
}
// else if (line.matches("\\s*public\\s+class\\s+\\S+?Func\\s+.*")) {
else if (line.matches(classDeclRegex)) {
// extract name of class, make it Dynamic instead
Matcher mtch = classDecl.matcher(line);
String modline = line;
if (mtch.find()) {
String funcClass = mtch.group(1);
String remainder = mtch.group(2);
if (DEBUG) {
System.out.println("found class declaration: " + line);
System.out.println("variation class: " + funcClass);
System.out.println("remainder: " + remainder);
}
if (builtin_variations.get(funcClass) != null) {
String newClassName = "DynamicCompiled" + funcClass;
modline = "public class " + newClassName + " " + remainder;
if (DEBUG) {
System.out.println("found existing variation: " + ((Class) builtin_variations.get(funcClass)).getName());
System.out.println("REVISED LINE: " + modline);
}
}
}
bufcode.append(modline);
bufcode.append("\n");
}
else {
bufcode.append(line);
bufcode.append("\n");
}
}
filtered_code = bufcode.toString();
validate(); // compiles
}
}
else if (full_variation != null) {
full_variation.setRessource(pName, pValue);
}
else {
throw new IllegalArgumentException(pName);
}
}
@Override
public String getName() {
return "custom_wf_full";
}
@Override
public void init(FlameTransformationContext pContext, Layer pLayer, XForm pXForm, double pAmount) {
if (DEBUG) {
System.out.println("called init");
}
if (full_variation == null) {
validate();
}
if (full_variation != null) {
full_variation.init(pContext, pLayer, pXForm, pAmount);
}
}
@Override
public void validate() {
try {
if (DEBUG) {
System.out.println("called validate");
}
if (code != null) {
this.compile();
}
}
catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
/*
* compile "code" String,
* then take first class in compiled code that is a subclass of VariationFunc,
* create a new instance of this VariationFunc, and assign it to full_variation
*
* Still need to figure out a way to trigger TinaController.refreshParamCmb(TinaNonlinearControlsRow pRow, XForm pXForm, Variation pVar)
* in order to update TinaNonlinearControlsRow to reflect changed param names and values when code changes
*/
public void compile() {
if (DEBUG) {
System.out.println("called compile()");
}
// if there was a previous full_variation, keep it to try and copy shared params
VariationFunc prev_variation = full_variation;
try {
SimpleCompiler compiler = new SimpleCompiler();
compiler.cook(filtered_code);
ClassLoader cloader = compiler.getClassLoader();
Class varClass = null;
// a bunch of mucking about to find all classes compiled by compiler.cook(filtered_code)
// based on suggestion in
// https://stolenkid.wordpress.com/2009/03/11/browse-classloader/
// and
// http://stackoverflow.com/questions/2681459/how-can-i-list-all-classes-loaded-in-a-specific-class-loader
// but expanded to catch many possibilities for the "classes" field in ClassLoader,
// since above links only work if classesField.get(classloader) is a Vector,
// and it can be other Objects instead -- with current version of Janino it is a HashMap with class names as the keys
Field classesField = cloader.getClass().getDeclaredField("classes");
classesField.setAccessible(true);
Object classesLoaded = classesField.get(cloader);
Iterator classIter = null;
if (classesLoaded instanceof AbstractMap) {
classIter = ((AbstractMap) classesLoaded).keySet().iterator();
}
else if (classesLoaded instanceof AbstractCollection) {
classIter = ((AbstractCollection) classesLoaded).iterator();
}
else {
throw new IllegalArgumentException("unknown class " + String.valueOf(classesLoaded));
}
// construct full_variation as instance of first Class from classloader that is a subclass of VariationFunc
while (classIter.hasNext()) {
Object val = classIter.next();
String varClassName = null;
if (val instanceof String) {
varClassName = (String) val;
}
else if (val instanceof Class) {
varClassName = ((Class) val).getName();
}
else if (val instanceof Map.Entry) {
Map.Entry me = (Map.Entry) val;
if (me.getKey() instanceof String) {
varClassName = (String) me.getKey();
}
else if (me.getValue() instanceof String) {
varClassName = (String) me.getValue();
}
else if (me.getKey() instanceof Class) {
varClassName = ((Class) me.getKey()).getName();
}
else if (me.getValue() instanceof Class) {
varClassName = ((Class) me.getValue()).getName();
}
else {
varClassName = me.getValue().toString();
}
}
else {
varClassName = val.toString();
}
varClass = Class.forName(varClassName, true, cloader);
if (DEBUG) {
System.out.println("className: " + varClassName);
System.out.println("instance of VariationFunc: " + VariationFunc.class.isAssignableFrom(varClass));
}
if (VariationFunc.class.isAssignableFrom(varClass)) {
full_variation = null;
full_variation = (VariationFunc) varClass.newInstance();
String[] inner_resource_names = full_variation.getRessourceNames();
// redo ressourceNames to include any resources of the inner full_variation (added after the RESSOURCE_CODE name)
if (inner_resource_names == null) {
ressourceNames = new String[1];
ressourceNames[0] = RESSOURCE_CODE;
}
else {
ressourceNames = new String[inner_resource_names.length + 1];
ressourceNames[0] = RESSOURCE_CODE;
for (int k = 0; k < inner_resource_names.length; k++) {
ressourceNames[k + 1] = inner_resource_names[k];
}
if (DEBUG) {
System.out.println("new ressourceNames: " + Arrays.toString(ressourceNames));
}
}
if (DEBUG) {
System.out.println("full_variation: " + full_variation);
System.out.println("variation name: " + full_variation.getName());
}
break;
}
}
if (full_variation != null && prev_variation != null) {
// copy shared params from prev_variation
if (full_variation.getClass().getName().equals(prev_variation.getClass().getName())) {
if (DEBUG) {
System.out.println("variations compatible, copying params: " + full_variation.getClass().getName());
}
String[] prev_params = prev_variation.getParameterNames();
for (String prev_param : prev_params) {
Object prev_val = prev_variation.getParameter(prev_param);
Object cur_val = full_variation.getParameter(prev_param);
if (prev_val != null && cur_val != null) {
if (prev_val instanceof Number) {
full_variation.setParameter(prev_param, ((Number) prev_val).doubleValue());
if (DEBUG) {
System.out.println("param: " + prev_param + ", value: " + (Number) full_variation.getParameter(prev_param));
}
}
else {
if (DEBUG) {
System.out.println("prev_val not a number: " + prev_val + ", " + prev_val.getClass().getName());
}
}
}
}
}
else {
if (DEBUG) {
System.out.println("variations not compatible: " + full_variation.getClass().getName() + ", " + prev_variation.getClass().getName());
}
}
// should also copy shared resources??
}
}
catch (Throwable ex) {
System.out.println("##############################################################");
System.out.println(ex.getMessage());
ex.printStackTrace();
System.out.println("##############################################################");
// full_variation = null;
}
}
/*
Overriding VariationFunc.makeCopy() to make sure resources are copied first,
thus ensuring dynamic code is compiled before parameter names and values are copied
*/
public CustomFullVariationWrapperFunc makeCopy() {
CustomFullVariationWrapperFunc varCopy = (CustomFullVariationWrapperFunc) VariationFuncList.getVariationFuncInstance(this.getName());
// CustomFullVariationWrapperFunc varCopy = new CustomFullVariationWrapperFunc();
// ressources
String[] ressNames = this.getRessourceNames();
if (ressNames != null) {
for (int i = 0; i < ressNames.length; i++) {
byte[] val = this.getRessourceValues()[i];
varCopy.setRessource(ressNames[i], val);
}
}
// params
String[] paramNames = this.getParameterNames();
if (paramNames != null) {
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
Object val = this.getParameter(paramName);
Object copyVal = varCopy.getParameter(paramName);
if (val != null && copyVal != null) {
if (val instanceof Number) {
varCopy.setParameter(paramName, ((Number) val).doubleValue());
}
else {
throw new IllegalStateException();
}
}
else {
if (DEBUG) {
System.out.println("Copying, got a null for param " + paramName + ", prev = " + val + ", new = " + copyVal);
}
}
}
}
return varCopy;
}
public boolean ressourceCanModifyParams(String resourceName) {
if (resourceName.equalsIgnoreCase(RESSOURCE_CODE)) {
return true;
}
else {
return full_variation.ressourceCanModifyParams(resourceName);
}
}
@Override
public boolean ressourceCanModifyParams() {
return true;
}
@Override
public boolean dynamicParameterExpansion(String paramName) {
return full_variation.dynamicParameterExpansion(paramName);
}
public boolean dynamicParameterExpansion() {
return full_variation != null ? full_variation.dynamicParameterExpansion() : false;
}
@Override
public int getPriority() {
return full_variation != null ? full_variation.getPriority() : 0;
}
}