/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.scripting.solutionmodel;
import java.io.CharArrayReader;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.EcmaError;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.mozilla.javascript.IRFactory;
import org.mozilla.javascript.Parser;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.FunctionNode;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.documentation.ServoyDocumented;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.persistence.ScriptMethod;
import com.servoy.j2db.persistence.ScriptNameValidator;
import com.servoy.j2db.persistence.Solution;
import com.servoy.j2db.scripting.IJavaScriptType;
import com.servoy.j2db.solutionmodel.ISMDefaults;
import com.servoy.j2db.solutionmodel.ISMMethod;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.UUID;
/**
* @author jcompagner
*/
@ServoyDocumented(category = ServoyDocumented.RUNTIME, scriptingName = "JSMethod")
public class JSMethod implements IJavaScriptType, ISMMethod
{
protected final IApplication application;
protected final IJSScriptParent< ? > parent;
protected ScriptMethod sm;
protected boolean isCopy;
public static JSMethod createDummy()
{
return new JSMethod();
}
private JSMethod()
{
parent = null;
application = null;
sm = null;
isCopy = true;
}
public JSMethod(ScriptMethod sm, IApplication application, boolean isNew)
{
this.application = application;
this.sm = sm;
this.parent = null;
this.isCopy = isNew;
}
public JSMethod(IJSScriptParent< ? > parent, ScriptMethod sm, IApplication application, boolean isNew)
{
this.sm = sm;
this.application = application;
this.parent = parent;
this.isCopy = isNew;
}
void checkModification()
{
if (sm != null && !isCopy)
{
if (parent != null)
{
// make copy if needed
// then get the replace the item with the item of the copied relation.
try
{
sm = parent.getScriptCopy(sm);
}
catch (RepositoryException e)
{
Debug.error(e);
throw new RuntimeException("Can't alter script method " + sm.getName() + ", clone failed", e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
else
{
// then get the replace the item with the item of the copied relation.
sm = application.getFlattenedSolution().createPersistCopy(sm);
}
isCopy = true;
}
}
/**
* @clonedesc com.servoy.j2db.persistence.AbstractScriptProvider#getDeclaration()
*
* @sampleas com.servoy.j2db.solutionmodel.ISMMethod#getShowInMenu()
*/
@JSGetter
public String getCode()
{
if (sm == null) return null; // if a default constant
return sm.getDeclaration();
}
@JSSetter
public void setCode(String content)
{
if (sm == null) return; // if a default constant
checkModification();
String name = parseName(content);
if (!name.equals(sm.getName()))
{
try
{
sm.updateName(new ScriptNameValidator(application.getFlattenedSolution()), name);
}
catch (RepositoryException e)
{
throw new RuntimeException("Error updating the name from " + sm.getName() + " to " + name, e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
sm.setDeclaration(content);
if (parent instanceof JSDataSourceNode)
{
// foundset method
application.getFoundSetManager().reloadFoundsetMethod(((JSDataSourceNode)parent).getSupportChild().getDataSource(), sm);
}
else if (parent == null)
{
// global method
application.getScriptEngine().getScopesScope().getGlobalScope(sm.getScopeName()).put(sm, sm);
}
}
/**
* @clonedesc com.servoy.j2db.persistence.AbstractScriptProvider#getName()
*
* @sampleas com.servoy.j2db.scripting.solutionmodel.JSMethod#getCode()
*
* @return A String holding the name of this method.
*/
@JSFunction
public String getName()
{
if (sm == null) return null; // if a default constant
return sm.getName();
}
/**
* @clonedesc com.servoy.j2db.persistence.ISupportScope#getScopeName()
*
* @sample
* var methods = solutionModel.getGlobalMethods();
* for (var x in methods)
* application.output(methods[x].getName() + ' is defined in scope ' + methods[x].getScopeName());
*/
@JSFunction
public String getScopeName()
{
if (sm == null) return null; // if a default constant
return sm.getScopeName();
}
/**
* @clonedesc com.servoy.j2db.persistence.ScriptMethod#getShowInMenu()
*
* @sampleas com.servoy.j2db.scripting.solutionmodel.JSMethod#getCode()
*/
@JSGetter
public boolean getShowInMenu()
{
if (sm == null) return false; // if a default constant
return sm.getShowInMenu();
}
@JSSetter
public void setShowInMenu(boolean arg)
{
if (sm == null) return; // if a default constant
checkModification();
sm.setShowInMenu(arg);
}
/**
* Gets the argument array for this method if that is set for the specific action this method is taken from.
* Will return null by default. This is only for reading, you can't alter the arguments through this array,
* for that you need to create a new object through solutionModel.wrapMethodWithArguments(..) and assign it again.
*
* @sample
* var frm = solutionModel.getForm("myForm");
* var button = frm.getButton("button");
* // get the arguments from the button.
* // NOTE: string arguments will be returned with quotes (comp.onAction.getArguments()[0] == '\'foo\' evals to true)
* var arguments = button.onAction.getArguments();
* if (arguments && arguments.length > 1 && arguments[1] == 10) {
* // change the value and assign it back to the onAction.
* arguments[1] = 50;
* button.onAction = solutionModel.wrapMethodWithArguments(button.onAction,arguments);
* }
*
* @return Array of the arguments, null if not specified.
*/
@JSFunction
public Object[] getArguments()
{
return null;
}
/**
* Returns the UUID of the method object
*
* @sample
* var method = form.newMethod('function original() { application.output("Original function."); }');
* application.output(method.getUUID().toString());
*/
@JSFunction
public UUID getUUID()
{
return sm.getUUID();
}
public ScriptMethod getScriptMethod()
{
return sm;
}
@SuppressWarnings("nls")
@Override
public String toString()
{
if (sm == null)
{
if (this == ISMDefaults.COMMAND_DEFAULT) return "JSMethod[DEFAULT]";
if (this == ISMDefaults.COMMAND_NONE) return "JSMethod[NONE]";
return "JSMethod";
}
if (parent == null)
{
return "JSMethod[name:" + sm.getName() + ",global, solution:" + ((Solution)sm.getParent()).getName() + ']';
}
return "JSMethod[name:" + sm.getName() + ",parent:" + parent.toString() + ']';
}
static String parseName(String content)
{
CompilerEnvirons cenv = new CompilerEnvirons();
Parser parser = new Parser(cenv, new JSErrorReporter());
try
{
AstRoot parse = parser.parse(new CharArrayReader(content.toCharArray()), "", 0); //$NON-NLS-1$
new IRFactory(cenv, new JSErrorReporter()).transformTree(parse);
int functionCount = parse.getFunctionCount();
if (functionCount != 1) throw new RuntimeException("Only 1 function is allowed, found: " + functionCount + " when setting code of a method"); //$NON-NLS-1$ //$NON-NLS-2$
FunctionNode functionNode = parse.getFunctionNode(0);
String name = functionNode.getFunctionName().getIdentifier();
return name;
}
catch (Exception e)
{
throw new RuntimeException("Error parsing " + content, e); //$NON-NLS-1$
}
}
static class JSErrorReporter implements ErrorReporter
{
public void warning(String message, String sourceName, int line, String lineSource, int lineOffset)
{
//do Nothing
}
public void error(String message, String sourceName, int lineNumber, String lineSource, int lineOffset)
{
throw new EcmaError("compileerror", message, sourceName, lineNumber, lineSource, lineOffset); //$NON-NLS-1$
}
public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset)
{
return new EvaluatorException(message);
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((sm == null) ? 0 : sm.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
JSMethod other = (JSMethod)obj;
if (sm == null)
{
if (other.sm != null) return false;
}
else if (!sm.getUUID().equals(other.sm.getUUID())) return false;
return true;
}
}