/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.autoplot.jythonsupport; import java.util.HashMap; import java.util.Map; import org.python.core.Py; import org.python.core.PyDictionary; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PyTuple; /** * Code to support writing Python Functions. * @author eew */ public final class FunctionSupport { private static final String EXACT_PARAMETERS = "%s() takes exactly %d arguments (%d given)"; private static final String AT_MOST_PARAMETERS = "%s() takes at most %d arguments (%d given)"; private static final String AT_LEAST_PARAMETERS = "%s() takes at least %d arguments (%d given)"; private static final String EXACT_KW_PARAMETERS = "%s() takes exactly %d non-keyword arguments (%d given)"; private static final String AT_MOST_KW_PARAMETERS = "%s() takes at most %d non-keyword arguments (%d given)"; private static final String AT_LEAST_KW_PARAMETERS = "%s() takes at least %d non-keyword arguments (%d given)"; private static final String UNEXPECTED_KEYWORD = "%s() got an unexpected keyword argument '%s'"; private static final String MULTIPLE_VALUES = "%s() got multiple values for keyword argument '%s'"; private static final String DUPLICATE_ARGUMENT = "duplicate argument '%s' in function definition"; private String name; private String[] formalParameters; private PyObject[] defaults; private String extraPositionalParameters; private String extraKeywordParameters; public FunctionSupport(String name, String[] parameters) { this(name, parameters, null, null, null); } public FunctionSupport(String name, String[] parameters, PyObject[] defaults) { this(name, parameters, defaults, null, null); } public FunctionSupport( String name, String[] parameters, PyObject[] defaults, String extraPositionalParameters, String extraKeywordParameters) { int nParameters = parameters == null ? 0 : parameters.length; int nDefaults = defaults == null ? 0 : defaults.length; if (nDefaults > nParameters) { throw new IllegalArgumentException("more defaults were specified than parameters"); } this.name = name; if (parameters == null) { this.formalParameters = new String[0]; } else { this.formalParameters = new String[parameters.length]; System.arraycopy(parameters, 0, this.formalParameters, 0, nParameters); } if (defaults == null) { this.defaults = new PyObject[0]; } else { this.defaults = new PyObject[defaults.length]; System.arraycopy(defaults, 0, this.defaults, 0, nDefaults); } this.extraPositionalParameters = extraPositionalParameters; this.extraKeywordParameters = extraKeywordParameters; //extraPositionalParameters and extraKeywordParameters cannot occur //in the formalParameters array if (findParameter(extraPositionalParameters) >= 0) { String message = String.format(DUPLICATE_ARGUMENT, extraPositionalParameters); throw new IllegalArgumentException(message); } if (findParameter(extraKeywordParameters) >= 0) { String message = String.format(DUPLICATE_ARGUMENT, extraKeywordParameters); throw new IllegalArgumentException(message); } } // private static void checkNull(PyObject[] defaults) { // for (int i = 0; i < defaults.length; i++) { // if (defaults[i] == null) { // throw new NullPointerException("Null default not allowed. Use Py.None instead."); // } // } // } /** Processes the arguments for a jython method invocation in a way that * mimics the way arguments are process for native python defined method. * This algorithm is based on the description of the process given in * section 5.3.4 of the version 2.4.4 Python Reference Manual. * * http://www.python.org/doc/2.4.4/ref/calls.html * * @param args * @param keywords * @return */ public Map args(PyObject[] args, String[] keywords) { Map result; PyObject[] parameters; PyTuple extraParameters = null; PyDictionary extraKeywords = null; int nParams = formalParameters.length; int keywordOffset = args.length - keywords.length; int defaultsOffset = formalParameters.length - defaults.length; //Create n unfilled slots //(where n is the number of formal parameters specified) parameters = new PyObject[nParams]; //Copy up to n positional parameters into the unfilled slots //(where n is the number of formal parameters specified) System.arraycopy(args, 0, parameters, 0, Math.min(keywordOffset,nParams)); //Either copy extra parameters into the extraParameters array //or raise a TypeError if (keywordOffset > nParams && extraPositionalParameters != null) { int nExtraParameters = keywordOffset - nParams; PyObject[] tmp = new PyObject[nExtraParameters]; System.arraycopy(args, nParams, tmp, 0, nExtraParameters); extraParameters = new PyTuple(tmp); } else if (keywordOffset > nParams) { String message; if (keywords.length == 0) { if (defaults.length == 0) { message = EXACT_PARAMETERS; } else { message = AT_MOST_PARAMETERS; } } else if (defaults.length == 0) { message = EXACT_KW_PARAMETERS; } else { message = AT_MOST_KW_PARAMETERS; } message = String.format(message, name, nParams, keywordOffset); throw Py.TypeError(message); } else if (extraPositionalParameters != null) { extraParameters = new PyTuple(); } //Copy keyword arguments into the parameter slots or the extra //keyword parameters map if (extraKeywordParameters != null) { extraKeywords = new PyDictionary(); } for (int i = 0; i < keywords.length; i++) { int iSlot = findParameter(keywords[i]); if (iSlot < 0 && extraKeywordParameters != null) { extraKeywords.__setitem__(new PyString(keywords[i]), args[i+keywordOffset]); } else if (iSlot < 0) { String message = String.format(UNEXPECTED_KEYWORD, name, keywords[i]); throw Py.TypeError(message); } else if (parameters[iSlot] == null) { parameters[iSlot] = args[i+keywordOffset]; } else { String message = String.format(MULTIPLE_VALUES, name, keywords[i]); throw Py.TypeError(message); } } //Fill in default values for (int i = 0; i < defaults.length; i++) { if (parameters[i+defaultsOffset] == null) { parameters[i+defaultsOffset] = defaults[i]; } } //Check for unfilled slots for (int i = 0; i < nParams; i++) { if (parameters[i] == null) { String message; if (keywords.length == 0) { if (defaults.length == 0) { message = EXACT_PARAMETERS; } else { message = AT_LEAST_PARAMETERS; } } else if (defaults.length == 0) { message = EXACT_KW_PARAMETERS; } else { message = AT_LEAST_KW_PARAMETERS; } message = String.format(message, name, defaultsOffset, keywordOffset); throw Py.TypeError(message); } } result = new HashMap(); for (int i = 0; i < nParams; i++) { result.put(formalParameters[i], parameters[i]); } if (extraPositionalParameters != null) { result.put(extraPositionalParameters, extraParameters); } if (extraKeywordParameters != null) { result.put(extraKeywordParameters, extraKeywords); } return result; } private int findParameter(String keyword) { for (int i = 0; i < formalParameters.length; i++) { if (keyword != null && keyword.equals(formalParameters[i])) return i; } return -1; } }