Merge pull request #359 from hapifhir/gg-5116b

minor fixes for release
This commit is contained in:
Grahame Grieve 2020-10-01 11:49:10 +10:00 committed by GitHub
commit 46b6c95bfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 222 additions and 139 deletions

View File

@ -1,7 +1,10 @@
Validator:
* Add date addition/subtraction to FHIRPath
* Add date addition/subtraction to FHIRPath + add parsing comments
* Fix questionnaire mode parameter support for validator
* add extra debugging when valdiator can't fetch content to validate
Other code changes:
* rename org.hl7.fhir.utilities.cache to org.hl7.fhir.utilities.npm
* report error locations for run time FHIRPath errors
* add search on IG registry to PackageClient
* add focus to FHIRPath function extensions

View File

@ -146,23 +146,8 @@ public class FHIRLexer {
}
public void next() throws FHIRLexerException {
skipWhitespaceAndComments();
current = null;
boolean last13 = false;
while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor))) {
if (source.charAt(cursor) == '\r') {
currentLocation.setLine(currentLocation.getLine() + 1);
currentLocation.setColumn(1);
last13 = true;
} else if (!last13 && (source.charAt(cursor) == '\n')) {
currentLocation.setLine(currentLocation.getLine() + 1);
currentLocation.setColumn(1);
last13 = false;
} else {
last13 = false;
currentLocation.setColumn(currentLocation.getColumn() + 1);
}
cursor++;
}
currentStart = cursor;
currentStartLocation = currentLocation;
if (cursor < source.length()) {
@ -208,9 +193,8 @@ public class FHIRLexer {
} else if (ch == '/') {
cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '/')) {
cursor++;
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n'))
cursor++;
// this is en error - should already have been skipped
error("This shoudn't happen?");
}
current = source.substring(currentStart, cursor);
} else if (ch == '$') {
@ -297,6 +281,33 @@ public class FHIRLexer {
}
private void skipWhitespaceAndComments() {
boolean last13 = false;
boolean done = false;
while (cursor < source.length() && !done) {
if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2))) {
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n'))
cursor++;
} else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) {
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
}
if (cursor >= source.length() -1) {
error("Unfinished comment");
} else {
cursor = cursor + 2;
}
} else if (Character.isWhitespace(source.charAt(cursor))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
} else {
done = true;
}
}
}
private boolean isDateChar(char ch,int start) {
int eot = source.charAt(start+1) == 'T' ? 10 : 20;

View File

@ -233,7 +233,7 @@ public class FHIRPathEngine {
* @param parameters
* @return
*/
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters);
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters);
/**
* Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
@ -2858,7 +2858,7 @@ public class FHIRPathEngine {
List<List<Base>> params = new ArrayList<List<Base>>();
for (ExpressionNode p : exp.getParameters())
params.add(execute(context, focus, p, true));
return hostServices.executeFunction(context.appInfo, exp.getName(), params);
return hostServices.executeFunction(context.appInfo, focus, exp.getName(), params);
}
default:
throw new Error("not Implemented yet");

View File

@ -405,11 +405,11 @@ public class LiquidEngine implements IEvaluationContext {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
if (externalHostServices == null)
return null;
LiquidEngineContext ctxt = (LiquidEngineContext) appContext;
return externalHostServices.executeFunction(ctxt.externalContext, functionName, parameters);
return externalHostServices.executeFunction(ctxt.externalContext, focus, functionName, parameters);
}
@Override

View File

@ -202,7 +202,7 @@ public class StructureMapUtilities {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
throw new Error("Not Implemented Yet");
}

View File

@ -64,7 +64,7 @@ public class FHIRPathTests {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.executeFunction), when item is element");
}

View File

@ -316,7 +316,7 @@ public class SnapShotGenerationTests {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
if ("fixture".equals(functionName)) {
String id = fp.convertToString(parameters.get(0));
Resource res = fetchFixture(id);

View File

@ -242,7 +242,7 @@ public class ComparisonRenderer implements IEvaluationContext {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
return null;
}

View File

@ -1,5 +1,10 @@
package org.hl7.fhir.r5.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import org.apache.poi.xssf.model.Comments;
import org.hl7.fhir.exceptions.FHIRException;
/*
@ -34,6 +39,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.SourceLocation;
import org.hl7.fhir.utilities.Utilities;
@ -65,6 +71,7 @@ public class FHIRLexer {
private int cursor;
private int currentStart;
private String current;
private List<String> comments = new ArrayList<>();
private SourceLocation currentLocation;
private SourceLocation currentStartLocation;
private int id;
@ -146,23 +153,8 @@ public class FHIRLexer {
}
public void next() throws FHIRLexerException {
skipWhitespaceAndComments();
current = null;
boolean last13 = false;
while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor))) {
if (source.charAt(cursor) == '\r') {
currentLocation.setLine(currentLocation.getLine() + 1);
currentLocation.setColumn(1);
last13 = true;
} else if (!last13 && (source.charAt(cursor) == '\n')) {
currentLocation.setLine(currentLocation.getLine() + 1);
currentLocation.setColumn(1);
last13 = false;
} else {
last13 = false;
currentLocation.setColumn(currentLocation.getColumn() + 1);
}
cursor++;
}
currentStart = cursor;
currentStartLocation = currentLocation;
if (cursor < source.length()) {
@ -208,9 +200,8 @@ public class FHIRLexer {
} else if (ch == '/') {
cursor++;
if (cursor < source.length() && (source.charAt(cursor) == '/')) {
cursor++;
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n'))
cursor++;
// this is en error - should already have been skipped
error("This shoudn't happen?");
}
current = source.substring(currentStart, cursor);
} else if (ch == '$') {
@ -296,7 +287,38 @@ public class FHIRLexer {
}
}
private void skipWhitespaceAndComments() {
comments.clear();
boolean last13 = false;
boolean done = false;
while (cursor < source.length() && !done) {
if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2))) {
int start = cursor+2;
while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) {
cursor++;
}
comments.add(source.substring(start, cursor).trim());
} else if (cursor < source.length() - 1 && "/*".equals(source.substring(cursor, cursor+2))) {
int start = cursor+2;
while (cursor < source.length() - 1 && !"*/".equals(source.substring(cursor, cursor+2))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
}
if (cursor >= source.length() -1) {
error("Unfinished comment");
} else {
comments.add(source.substring(start, cursor).trim());
cursor = cursor + 2;
}
} else if (Character.isWhitespace(source.charAt(cursor))) {
last13 = currentLocation.checkChar(source.charAt(cursor), last13);
cursor++;
} else {
done = true;
}
}
}
private boolean isDateChar(char ch,int start) {
int eot = source.charAt(start+1) == 'T' ? 10 : 20;
@ -321,9 +343,31 @@ public class FHIRLexer {
this.current = current;
}
public boolean hasComment() {
return !done() && current.startsWith("//");
public boolean hasComments() {
return comments.size() > 0;
}
public List<String> getComments() {
return comments;
}
public String getAllComments() {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("\r\n");
b.addAll(comments);
comments.clear();
return b.toString();
}
public String getFirstComment() {
if (hasComments()) {
String s = comments.get(0);
comments.remove(0);
return s;
} else {
return null;
}
}
public boolean hasToken(String kw) {
return !done() && kw.equals(current);
}
@ -472,10 +516,6 @@ public class FHIRLexer {
return b.toString();
}
void skipComments() throws FHIRLexerException {
while (!done() && hasComment())
next();
}
public int getCurrentStart() {
return currentStart;
}

View File

@ -285,7 +285,7 @@ public class FHIRPathEngine {
* @param parameters
* @return
*/
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters);
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters);
/**
* Implementation of resolve() function. Passed a string, return matching resource, if one is known - else null
@ -3463,7 +3463,7 @@ public class FHIRPathEngine {
for (ExpressionNode p : exp.getParameters()) {
params.add(execute(context, focus, p, true));
}
return hostServices.executeFunction(context.appInfo, exp.getName(), params);
return hostServices.executeFunction(context.appInfo, focus, exp.getName(), params);
}
default:
throw new Error("not Implemented yet");

View File

@ -632,11 +632,11 @@ public class LiquidEngine implements IEvaluationContext {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
if (externalHostServices == null)
return null;
LiquidEngineContext ctxt = (LiquidEngineContext) appContext;
return externalHostServices.executeFunction(ctxt.externalContext, functionName, parameters);
return externalHostServices.executeFunction(ctxt.externalContext, focus, functionName, parameters);
}
@Override

View File

@ -102,6 +102,7 @@ public class NPMPackageGenerator {
private JsonObject packageJ;
private JsonObject packageManifest;
private NpmPackageIndexBuilder indexer;
private String igVersion;
public NPMPackageGenerator(String destFile, String canonical, String url, PackageType kind, ImplementationGuide ig, Date date, boolean notForPublication) throws FHIRException, IOException {
@ -184,6 +185,7 @@ public class NPMPackageGenerator {
JsonObject npm = new JsonObject();
npm.addProperty("name", ig.getPackageId());
npm.addProperty("version", ig.getVersion());
igVersion = ig.getVersion();
npm.addProperty("tools-version", ToolsVersion.TOOLS_VERSION);
npm.addProperty("type", kind.getCode());
npm.addProperty("date", dt);
@ -395,5 +397,9 @@ public class NPMPackageGenerator {
}
}
public String version() {
return igVersion;
}
}

View File

@ -202,7 +202,7 @@ public class StructureMapUtilities {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
throw new Error("Not Implemented Yet");
}
@ -748,15 +748,12 @@ public class StructureMapUtilities {
FHIRLexer lexer = new FHIRLexer(text, srcName);
if (lexer.done())
throw lexer.error("Map Input cannot be empty");
lexer.skipComments();
lexer.token("map");
StructureMap result = new StructureMap();
result.setUrl(lexer.readConstant("url"));
lexer.token("=");
result.setName(lexer.readConstant("name"));
if (lexer.hasComment()) {
result.setDescription(getMultiLineComments(lexer));
}
result.setDescription(lexer.getAllComments());
while (lexer.hasToken("conceptmap"))
parseConceptMap(result, lexer);
@ -782,7 +779,6 @@ public class StructureMapUtilities {
map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format
result.getContained().add(map);
lexer.token("{");
lexer.skipComments();
// lexer.token("source");
// map.setSource(new UriType(lexer.readConstant("source")));
// lexer.token("target");
@ -824,25 +820,11 @@ public class StructureMapUtilities {
tgt.setCode(lexer.take());
if (tgt.getCode().startsWith("\""))
tgt.setCode(lexer.processConstant(tgt.getCode()));
if (lexer.hasComment())
tgt.setComment(lexer.take().substring(2).trim());
tgt.setComment(lexer.getFirstComment());
}
lexer.token("}");
}
private String getMultiLineComments(FHIRLexer lexer) {
String comment = null;
while (lexer.hasComment()) {
String newComment = lexer.take().substring(2).trim();
if (comment == null) {
comment = newComment;
} else {
comment += "\r\n"+newComment;
}
}
return comment;
}
private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
for (ConceptMapGroupComponent grp : map.getGroup()) {
if (grp.getSource().equals(srcs))
@ -895,9 +877,7 @@ public class StructureMapUtilities {
lexer.token("as");
st.setMode(StructureMapModelMode.fromCode(lexer.take()));
lexer.skipToken(";");
if (lexer.hasComment() && currentLine == lexer.getCurrentLocation().getLine()) {
st.setDocumentation(lexer.take().substring(2).trim());
}
st.setDocumentation(lexer.getFirstComment());
}
private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
@ -905,19 +885,11 @@ public class StructureMapUtilities {
int currentLine = lexer.getCurrentLocation().getLine();
result.addImport(lexer.readConstant("url"));
lexer.skipToken(";");
if (lexer.hasComment() && currentLine == lexer.getCurrentLocation().getLine()) {
lexer.next();
}
lexer.getFirstComment();
}
private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
String comment = null;
if (lexer.hasComment()) {
comment = getMultiLineComments(lexer);
if (lexer.done()) {
return ;
}
}
String comment = lexer.getAllComments();
lexer.token("group");
StructureMapGroupComponent group = result.addGroup();
if (comment != null) {
@ -975,7 +947,6 @@ public class StructureMapUtilities {
parseRule(result, group.getRule(), lexer, true);
}
} else {
lexer.skipComments();
while (lexer.hasToken("input"))
parseInput(group, lexer, false);
while (!lexer.hasToken("endgroup")) {
@ -1003,11 +974,8 @@ public class StructureMapUtilities {
if (!newFmt) {
lexer.token("as");
input.setMode(StructureMapInputMode.fromCode(lexer.take()));
if (lexer.hasComment()) {
input.setDocumentation(lexer.take().substring(2).trim());
}
input.setDocumentation(lexer.getFirstComment());
lexer.skipToken(";");
lexer.skipComments();
}
}
@ -1018,12 +986,7 @@ public class StructureMapUtilities {
lexer.token(":");
lexer.token("for");
} else {
if (lexer.hasComment()) {
rule.setDocumentation(this.getMultiLineComments(lexer));
if (lexer.hasToken("}")) {
return ; // catched a comment at the end
}
}
rule.setDocumentation(lexer.getFirstComment());
}
list.add(rule);
boolean done = false;
@ -1062,8 +1025,9 @@ public class StructureMapUtilities {
lexer.next();
}
}
} else if (lexer.hasComment()) {
rule.setDocumentation(getMultiLineComments(lexer));
}
if (!rule.hasDocumentation() && lexer.hasComments()) {
rule.setDocumentation(lexer.getFirstComment());
}
if (isSimpleSyntax(rule)) {
rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);

View File

@ -72,7 +72,7 @@ public class FHIRPathTests {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
throw new NotImplementedException("Not done yet (FHIRPathTestEvaluationServices.executeFunction), when item is element");
}

View File

@ -380,7 +380,7 @@ public class SnapShotGenerationTests {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
if ("fixture".equals(functionName)) {
String id = fp.convertToString(parameters.get(0));
Resource res = fetchFixture(id);

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.utilities;
import java.util.List;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
@ -76,4 +78,11 @@ public class CommaSeparatedStringBuilder {
append(s);
}
public void addAll(List<String> list) {
for (String s : list) {
append(s);
}
}
}

View File

@ -24,4 +24,23 @@ public class SourceLocation {
public String toString() {
return Integer.toString(line)+", "+Integer.toString(column);
}
public void newLine() {
setLine(getLine() + 1);
setColumn(1);
}
public boolean checkChar(char ch, boolean last13) {
if (ch == '\r') {
newLine();
return true;
} else if (ch == '\n') {
if (!last13) {
newLine();
}
return false;
} else {
setColumn(getColumn() + 1);
return false;
}
}
}

View File

@ -36,6 +36,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Stack;
@ -671,6 +673,13 @@ public class JsonTrackingParser {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(json);
}
public static JsonObject fetchJson(String source) throws IOException {
URL url = new URL(source+"?nocache=" + System.currentTimeMillis());
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setInstanceFollowRedirects(true);
return parseJson(c.getInputStream());
}
}

View File

@ -911,33 +911,4 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple
return false;
}
//public List<String> getUrls() throws IOException {
// if (allUrls == null)
// {
// IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini"));
// allUrls = new ArrayList<>();
// for (String s : ini.getPropertyNames("urls"))
// allUrls.add(ini.getStringProperty("urls", s));
// try {
// URL url = new URL("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json?nocache=" + System.currentTimeMillis());
// HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// connection.setRequestMethod("GET");
// InputStream json = connection.getInputStream();
// JsonObject packages = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(json));
// JsonArray guides = packages.getAsJsonArray("guides");
// for (JsonElement g : guides) {
// JsonObject gi = (JsonObject) g;
// if (gi.has("canonical"))
// if (!allUrls.contains(gi.get("canonical").getAsString()))
// allUrls.add(gi.get("canonical").getAsString());
// }
// } catch (Exception e) {
// System.out.println("Listing known Implementation Guides failed: "+e.getMessage());
// }
// }
// return allUrls;
//}
}

View File

@ -8,6 +8,7 @@ import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import java.io.File;
import java.io.FileInputStream;
@ -223,4 +224,46 @@ public class PackageClient {
}
}
public List<PackageInfo> listFromRegistry(String name, String canonical, String fhirVersion) throws IOException {
List<PackageInfo> result = new ArrayList<>();
JsonObject packages = JsonTrackingParser.fetchJson("https://raw.githubusercontent.com/FHIR/ig-registry/master/fhir-ig-list.json?nocache=" + System.currentTimeMillis());
for (JsonObject o : JSONUtil.objects(packages, "guides")) {
if (o.has("canonical")) {
String id = JSONUtil.str(o, "npm-name");
String pname = JSONUtil.str(o, "name");
String pcanonical = JSONUtil.str(o, "canonical");
String description = JSONUtil.str(o, "description");
boolean ok = true;
if (ok && !Utilities.noString(name)) {
ok = (pname != null && pname.contains(name)) || (description != null && description.contains(name)) || (id != null && id.contains(name));
}
if (ok && !Utilities.noString(canonical)) {
ok = pcanonical.contains(canonical);
}
String version = null;
String fVersion = null;
String url = null;
if (ok) {
// if we can find something...
for (JsonObject e : JSONUtil.objects(o, "editions")) {
if (fhirVersion == null || fhirVersion.equals(JSONUtil.str(e, "fhir-version"))) {
String v = JSONUtil.str(e, "ig-version");
if (version == null || VersionUtilities.isThisOrLater(version, v)) {
version = v;
fVersion = e.getAsJsonArray("fhir-version").get(0).getAsString();
url = JSONUtil.str(e, "url");
}
}
}
}
if (version != null) {
result.add(new PackageInfo(id, version, fVersion, description, url, pcanonical));
}
}
}
return result;
}
}

View File

@ -104,4 +104,12 @@ public class CachingPackageClientTests {
List<PackageInfo> matches = client.getVersions("Simplifier.Core.STU3X");
Assertions.assertTrue(matches.size() == 0);
}
@Test
public void testRegistry() throws IOException {
CachingPackageClient client = new CachingPackageClient("http://packages.fhir.org/packages");
List<PackageInfo> matches1 = client.listFromRegistry(null, null, null);
List<PackageInfo> matches2 = client.listFromRegistry(null, null, "4.0.1");
Assertions.assertTrue(matches1.size() > matches2.size());
}
}

View File

@ -226,7 +226,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION));
}

View File

@ -334,7 +334,7 @@ public class SnapShotGenerationXTests {
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
if ("fixture".equals(functionName)) {
String id = fp.convertToString(parameters.get(0));
Resource res = fetchFixture(id);

View File

@ -435,7 +435,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
@Override
public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) {
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
return null;
}