From e281175ccc8bd244196d2d27e09726de82479b2d Mon Sep 17 00:00:00 2001 From: Alexander Nittka Date: Tue, 26 Nov 2024 13:20:22 +0100 Subject: [PATCH] improved expression handling and more * handle global title in label provider * improved expression validation * no error for optional pilot episode * follow up news variable validation * validation support for global variables * support betty love modifier * validation function dot missing and is first language --- .../db/ui/labeling/DatabaseLabelProvider.java | 3 + .../src/org/tvtower/db/Database.xtext | 9 + .../org/tvtower/db/DatabaseRuntimeModule.java | 6 + .../org/tvtower/db/constants/EffectType.java | 4 +- ...abaseLinkingDiagnosticMessageProvider.java | 19 ++ .../db/validation/CommonTagsValidator.java | 225 +++++++++++++++++- .../tvtower/db/validation/NewsValidator.java | 40 +++- .../db/validation/ScriptValidator.java | 12 +- .../expressionhelper/SelfExpression.java | 12 +- .../expressionhelper/SimpleExpression.java | 22 +- ...xpression.java => TrivialExpressions.java} | 12 +- pom.xml | 2 +- 12 files changed, 334 insertions(+), 32 deletions(-) create mode 100644 org.tvtower.db/src/org/tvtower/db/scoping/DatabaseLinkingDiagnosticMessageProvider.java rename org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/{LocaleExpression.java => TrivialExpressions.java} (56%) diff --git a/org.tvtower.db.ui/src/org/tvtower/db/ui/labeling/DatabaseLabelProvider.java b/org.tvtower.db.ui/src/org/tvtower/db/ui/labeling/DatabaseLabelProvider.java index e6bf336..0ec903c 100644 --- a/org.tvtower.db.ui/src/org/tvtower/db/ui/labeling/DatabaseLabelProvider.java +++ b/org.tvtower.db.ui/src/org/tvtower/db/ui/labeling/DatabaseLabelProvider.java @@ -157,6 +157,9 @@ private String withFictional(Person p, String name) { private String fromTitle(Title t) { if (t != null) { + if(t.getGlobal()!=null) { + return t.getGlobal(); + } EList lStrings = t.getLstrings(); if (!lStrings.isEmpty()) { return lStrings.get(0).getText(); diff --git a/org.tvtower.db/src/org/tvtower/db/Database.xtext b/org.tvtower.db/src/org/tvtower/db/Database.xtext index 6fcbcbd..501b762 100644 --- a/org.tvtower.db/src/org/tvtower/db/Database.xtext +++ b/org.tvtower.db/src/org/tvtower/db/Database.xtext @@ -44,6 +44,15 @@ PersonLocalization: '<''programmeroles''>' roles+=RoleLocale* '' + ('<''globalvariables''>' + variables+=GlobalVariable* + '')? +; + +GlobalVariable: + '<'var=IDorKeyword'>' + => content=TextContent + '' ; MayContainVariables:NewsItem|ScriptTemplate; diff --git a/org.tvtower.db/src/org/tvtower/db/DatabaseRuntimeModule.java b/org.tvtower.db/src/org/tvtower/db/DatabaseRuntimeModule.java index 14ac5a9..643eae0 100644 --- a/org.tvtower.db/src/org/tvtower/db/DatabaseRuntimeModule.java +++ b/org.tvtower.db/src/org/tvtower/db/DatabaseRuntimeModule.java @@ -3,6 +3,7 @@ */ package org.tvtower.db; +import org.eclipse.xtext.linking.ILinkingDiagnosticMessageProvider; import org.eclipse.xtext.naming.IQualifiedNameProvider; import org.eclipse.xtext.naming.SimpleNameProvider; import org.eclipse.xtext.parser.antlr.Lexer; @@ -14,6 +15,7 @@ import org.tvtower.db.parser.antlr.internal.InternalDatabaseLexer; import org.tvtower.db.resource.DatabaseResourceDescriptionStrategy; import org.tvtower.db.resource.DatabaseResourceServiceProvider; +import org.tvtower.db.scoping.DatabaseLinkingDiagnosticMessageProvider; import org.tvtower.db.validation.DatabaseConfigurableIssueCodesProvider; import com.google.inject.Binder; @@ -59,4 +61,8 @@ public Class bindConfigurableIssueCode return DatabaseConfigurableIssueCodesProvider.class; } + public Class bindLinkingErrorMessageProvider() { + return DatabaseLinkingDiagnosticMessageProvider.class; + } + } diff --git a/org.tvtower.db/src/org/tvtower/db/constants/EffectType.java b/org.tvtower.db/src/org/tvtower/db/constants/EffectType.java index ddf4a85..6ef9ee4 100644 --- a/org.tvtower.db/src/org/tvtower/db/constants/EffectType.java +++ b/org.tvtower.db/src/org/tvtower/db/constants/EffectType.java @@ -8,6 +8,7 @@ public class EffectType extends TVTEnum { public static final String NEWS_AVAILABILITY="modifyNewsAvailability"; public static final String PROGRAMME_AVAILABILITY="modifyProgrammeAvailability"; public static final String SCRIPT_AVAILABILITY="modifyScriptAvailability"; + public static final String BETTY_LOVE="modifyBettyLove"; EffectType() { add(NEWS, "news"); @@ -17,10 +18,11 @@ public class EffectType extends TVTEnum { add(SCRIPT_AVAILABILITY, "script availability"); add(PERSON, "change person popularity"); add(GENRE, "change genre popularity"); + add(BETTY_LOVE, "change betty love"); } public boolean isNewsTrigger(String type) { - return type!=null && type.startsWith(NEWS); + return NEWS.equals(type) || NEWS_CHOICE.equals(type); } } diff --git a/org.tvtower.db/src/org/tvtower/db/scoping/DatabaseLinkingDiagnosticMessageProvider.java b/org.tvtower.db/src/org/tvtower/db/scoping/DatabaseLinkingDiagnosticMessageProvider.java new file mode 100644 index 0000000..db66b6d --- /dev/null +++ b/org.tvtower.db/src/org/tvtower/db/scoping/DatabaseLinkingDiagnosticMessageProvider.java @@ -0,0 +1,19 @@ +package org.tvtower.db.scoping; + +import org.eclipse.xtext.diagnostics.DiagnosticMessage; +import org.eclipse.xtext.linking.impl.LinkingDiagnosticMessageProvider; +import org.tvtower.db.database.Effect; + +public class DatabaseLinkingDiagnosticMessageProvider extends LinkingDiagnosticMessageProvider { + + @Override + public DiagnosticMessage getUnresolvedProxyMessage(ILinkingDiagnosticContext context) { + // do not try to resolve variables within effects + if (context.getContext() instanceof Effect) { + if (context.getLinkText() != null && context.getLinkText().matches("\\$\\{\\w+\\}")) { + return null; + } + } + return super.getUnresolvedProxyMessage(context); + } +} diff --git a/org.tvtower.db/src/org/tvtower/db/validation/CommonTagsValidator.java b/org.tvtower.db/src/org/tvtower/db/validation/CommonTagsValidator.java index 66866a6..f33366d 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/CommonTagsValidator.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/CommonTagsValidator.java @@ -2,22 +2,28 @@ import java.math.BigDecimal; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.print.attribute.standard.Severity; - +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.preferences.IPreferenceValues; import org.eclipse.xtext.preferences.IPreferenceValuesProvider; +import org.eclipse.xtext.util.OnChangeEvictingCache; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ConfigurableIssueCodesProvider; @@ -32,6 +38,8 @@ import org.tvtower.db.database.ContainsLanguageStrings; import org.tvtower.db.database.Database; import org.tvtower.db.database.DatabasePackage; +import org.tvtower.db.database.Effect; +import org.tvtower.db.database.GlobalVariable; import org.tvtower.db.database.GroupAttractivity; import org.tvtower.db.database.LanguageString; import org.tvtower.db.database.MayContainVariables; @@ -45,6 +53,7 @@ import org.tvtower.db.validation.expressionhelper.SimpleExpression; import com.google.common.base.Strings; +import com.google.common.collect.Sets; import com.google.inject.Inject; //TODO validate created_by defined and not empty @@ -57,6 +66,8 @@ public class CommonTagsValidator extends AbstractDatabaseValidator { private IPreferenceValuesProvider valuesProvider; @Inject private ConfigurableIssueCodesProvider issuCodeProvider; + @Inject + private OnChangeEvictingCache cache; private boolean checkLocalizationDuplicates=false; @@ -158,8 +169,12 @@ public void checkLanguageStringsContainer(ContainsLanguageStrings c) { } } + int index=0; for (LanguageString l : c.getLstrings()) { String language = l.getLangage(); + if("all".equals(language) && index!=0) { + error("'all' should be first defined language", l, $.getLanguageString_Langage()); + } if (languages.contains(language)) { error("duplicate language", l, $.getLanguageString_Langage()); } else { @@ -175,6 +190,7 @@ public void checkLanguageStringsContainer(ContainsLanguageStrings c) { // if("pl".equals(language)) { // plMissing=false; // } + index++; } // if(plMissing && c instanceof Title) { @@ -223,7 +239,6 @@ private void validateOptionsCount(LanguageString s, AtomicInteger optionsCount) } } - //TODO implement variable validation for followup news private boolean isFollowUpNews(EObject o) { NewsItem newsItem = EcoreUtil2.getContainerOfType(o, NewsItem.class); if(newsItem!=null && NewsType.FOLLOW_UP_NEWS.equals(newsItem.getType())) { @@ -250,16 +265,15 @@ private void validateExpressions(String text, EObject context, EStructuralFeatur //first simple variables Matcher simpleVariableMatcher = SIMPLE_VARIABLE_PATTERN.matcher(text); + List globalVariables = null; while (simpleVariableMatcher.find()) { + if(globalVariables == null) { + globalVariables=getGlobalVaribales(context); + } String variable = simpleVariableMatcher.group(1); if (definedVariables == null) { definedVariables = getVariablesFromContainers(context); - } - if(definedVariables.isEmpty()) { - if(!isFollowUpNews(context)) { - error("no variable definitions found", f); - } - continue; + definedVariables.addAll(globalVariables); } boolean found = false; for (String def : definedVariables) { @@ -268,7 +282,7 @@ private void validateExpressions(String text, EObject context, EStructuralFeatur break; } } - if (!found) { + if (!found){ error("simple variable " + variable + " not defined", f); } } @@ -292,18 +306,68 @@ private void validateExpressions(String text, EObject context, EStructuralFeatur private List getVariablesFromContainers(EObject o) { + if(isFollowUpNews(o)) { + NewsChains chains=cache.get("newschains", o.eResource(), ()->{ + return new NewsChains(o.eResource()); + }); + return new ArrayList(chains.getDefinedVariablesFor(EcoreUtil2.getContainerOfType(o, NewsItem.class))); + } MayContainVariables vContainer = EcoreUtil2.getContainerOfType(o, MayContainVariables.class); + if (vContainer == null) { return new ArrayList<>(); } else { List result = getVariablesFromContainers(vContainer.eContainer()); - if(vContainer.getVariables()!=null) { - result.addAll(vContainer.getVariables().getVariable().stream().map(v->v.getVar()).collect(Collectors.toList())); + if (vContainer.getVariables() != null) { + result.addAll(vContainer.getVariables().getVariable().stream().map(v -> v.getVar()) + .collect(Collectors.toList())); } return result; } } + //hack to get the global variable list + //either it is in the resource set or we load the resource again + private List getGlobalVaribales(EObject o) { + return cache.get("gv", o.eResource(), ()->{ + ResourceSet s=o.eResource().getResourceSet(); + Resource gvr = null; + for (Resource r : s.getResources()) { + if (r.getURI().toString().endsWith("/lang/en.xml")) { + gvr = r; + break; + } + } + if (gvr == null) { + URI uri = s.getResources().get(0).getURI(); + URI gvrUri = null; + List segmentList = uri.segmentsList(); + if (segmentList.contains("lang")) { + gvrUri = uri.trimSegments(1); + } else if (segmentList.contains("user")) { + gvrUri = uri.trimSegments(2); + } else { + gvrUri = uri.trimSegments(1); + } + gvrUri = gvrUri.appendSegment("lang").appendSegment("en.xml"); + try { + gvr = s.getResource(gvrUri, true); + } catch (Exception e) { + gvr = null; + // ignore - resource could not be loaded + } + } + if (gvr != null) { + EList contents = gvr.getContents(); + if (!contents.isEmpty() && contents.get(0) instanceof Database) { + List gv = EcoreUtil2.getAllContentsOfType(contents.get(0), GlobalVariable.class); + return gv.stream().map(v -> v.getVar()).collect(Collectors.toList()); + } + } + return new ArrayList<>(); + }); + } + @Check public void checkGroupAttractivity(GroupAttractivity a) { Set defined = new HashSet<>(); @@ -357,4 +421,139 @@ public void checkModifiers(Modifier modifier) { } } -} + // helper class for "linear" news chaines leading to a particular news item + private class NewsChains { + + // guid->variables directly defined by that news item + Map> idToVariables = new HashMap<>(); + // guid->guidis of triggering news items for navigating back to the chain's + // origin + Map> idToTriggering = new HashMap<>(); + + // we do not (explicityl) consider cross-file news chains + public NewsChains(Resource r) { + // temporary map of triggered news ids + Map> idToTriggered = new HashMap<>(); + for (EObject o : r.getContents()) { + List news = EcoreUtil2.getAllContentsOfType(o, NewsItem.class); + for (NewsItem item : news) { + String id = item.getName(); + // store directly defined variables + Set variables = new HashSet<>(); + if (item.getVariables() != null) { + item.getVariables().getVariable().forEach(v -> { + variables.add(v.getVar()); + }); + } + if (!variables.isEmpty()) { + idToVariables.put(id, variables); + } + + // store triggered news + Set triggered = new HashSet<>(); + if (item.getEffects() != null) { + for (Effect e : item.getEffects().getEffects()) { + if (Constants.effectType.isNewsTrigger(e.getType()) && e.getNews() != null) { + e.getNews().getNews().forEach(p -> { + NewsItem triggeredItem = p.getNews(); + if (triggeredItem != null && !triggeredItem.eIsProxy()) { + triggered.add(triggeredItem.getName()); + } + }); + } + } + } + if (!triggered.isEmpty()) { + idToTriggered.put(id, triggered); + } + } + } + // transform triggered news to a map storing predecessor ids + for (Entry> triggered : idToTriggered.entrySet()) { + String triggering = triggered.getKey(); + for (String tr : triggered.getValue()) { + idToTriggering.computeIfAbsent(tr, k -> new HashSet<>()).add(triggering); + } + } + } + + // variables defined for an item is the set of variables present in all chains + // leading to that item + public Set getDefinedVariablesFor(NewsItem n) { + List chains = getChains(n.getName()); + Set all = new HashSet<>(); + if (!chains.isEmpty()) { + all = chains.get(0).getVariables(idToVariables); + if (chains.size() > 1) { + for (NewsChain c : chains) { + all = Sets.intersection(all, c.getVariables(idToVariables)); + } + } + } + return all; + } + + private List getChains(String finalElement) { + List result = new ArrayList<>(); + NewsChain initial = new NewsChain(finalElement); + result.add(initial); + //update list of chains until all have found their start element + while (!result.stream().allMatch(c -> c.startFound)) { + List newResult = new ArrayList<>(); + for (NewsChain c : result) { + if (c.startFound) { + newResult.add(c); + } else { + newResult.addAll(c.predecessors(idToTriggering)); + } + + } + result = newResult; + } + return result; + } + } + + private class NewsChain { + Set elements = new HashSet<>(); + String startElement; + boolean startFound = false; + + NewsChain(String startElement) { + this.startElement = startElement; + elements.add(startElement); + } + + List predecessors(Map> triggering) { + List result = new ArrayList<>(); + //no trigger -> done + if (!triggering.containsKey(startElement)) { + startFound = true; + result.add(this); + } else { + for (String newStart : triggering.get(startElement)) { + //circular chain (self-triggering) -> done + if (elements.contains(newStart)) { + startFound = true; + result.add(this); + } else { + NewsChain c = new NewsChain(newStart); + c.elements.addAll(elements); + result.add(c); + } + } + } + return result; + } + + Set getVariables(Map> variableMap) { + Set result = new HashSet<>(); + for (String element : elements) { + if (variableMap.containsKey(element)) { + result.addAll(variableMap.get(element)); + } + } + return result; + } + } +} \ No newline at end of file diff --git a/org.tvtower.db/src/org/tvtower/db/validation/NewsValidator.java b/org.tvtower.db/src/org/tvtower/db/validation/NewsValidator.java index bf8f0f9..ff0c8e5 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/NewsValidator.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/NewsValidator.java @@ -5,6 +5,7 @@ import java.math.BigDecimal; import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.EValidatorRegistrar; @@ -184,10 +185,6 @@ public void checkEffect(Effect e) { Constants.effectType.isValidValue(e.getType(), "type", true).ifPresent(err -> error(err, $.getEffect_Type())); Constants.programmGenre.isValidValue(e.getGenre(), "genre", false) .ifPresent(err -> error(err, $.getEffect_Genre())); - CommonValidation.getDecimalRangeError(e.getValueMin(), "valueMin", VALUE_MIN, VALUE_MAX, false) - .ifPresent(err -> error(err, $.getEffect_ValueMin())); - CommonValidation.getDecimalRangeError(e.getValueMax(), "valueMax", VALUE_MIN, VALUE_MAX, false) - .ifPresent(err -> error(err, $.getEffect_ValueMax())); CommonValidation.getIntRangeError(e.getProbability(), "probability", 0, 100, false) .ifPresent(err -> error(err, $.getEffect_Probability())); Constants._boolean.isValidValue(e.getEnable(), "enable", false) @@ -200,6 +197,7 @@ public void checkEffect(Effect e) { Boolean choiceExpected = Boolean.FALSE; Boolean enableExpected = Boolean.FALSE; Boolean newsExpected = Boolean.FALSE; + boolean checkDecimalMinMax=true; Class expectedClass= null; switch (e.getType()) { @@ -220,6 +218,15 @@ public void checkEffect(Effect e) { checkMinMax = Boolean.TRUE; genreExpected = Boolean.TRUE; break; + case EffectType.BETTY_LOVE: + checkMinMax = Boolean.TRUE; + checkDecimalMinMax=false; + //different value range for betty love + CommonValidation.getIntRangeError(e.getValueMin(), "valueMin", -1000, 1000, true) + .ifPresent(err -> error(err, $.getEffect_ValueMin())); + CommonValidation.getIntRangeError(e.getValueMax(), "valueMax", -1000, 1000, true) + .ifPresent(err -> error(err, $.getEffect_ValueMax())); + break; case EffectType.NEWS_AVAILABILITY: newsExpected = Boolean.TRUE; enableExpected = Boolean.TRUE; @@ -240,6 +247,13 @@ public void checkEffect(Effect e) { } effectTypeField("valueMin", e.getValueMin(), checkMinMax); effectTypeField("valueMax", e.getValueMin(), checkMinMax); + if(checkDecimalMinMax) { + CommonValidation.getDecimalRangeError(e.getValueMin(), "valueMin", VALUE_MIN, VALUE_MAX, false) + .ifPresent(err -> error(err, $.getEffect_ValueMin())); + CommonValidation.getDecimalRangeError(e.getValueMax(), "valueMax", VALUE_MIN, VALUE_MAX, false) + .ifPresent(err -> error(err, $.getEffect_ValueMax())); + } + CommonValidation.getMinMaxError(e.getValueMin(), e.getValueMax()) .ifPresent(err -> error(err, $.getEffect_ValueMin())); effectTypeField("genre", e.getGenre(), genreExpected); @@ -293,13 +307,21 @@ private void referenceField(String field, Object value, boolean expected, Class } private void referenceField(String field, Object value, boolean expected, Class expectedRefType, EStructuralFeature f) { - if(expected && value ==null) { - error(field+ " expected for this effect type", f); - }else if(!expected&& value!=null) { + if (expected && value == null) { + error(field + " expected for this effect type", f); + } else if (!expected && value != null) { error(field + " not allowed for this effect type", f); } - if(expected &&value!=null &&! (expectedRefType.isAssignableFrom(value.getClass()))) { - error(expectedRefType.getSimpleName() + " expected", f); + if (expected && value != null) { + if (value instanceof EObject) { + if (((EObject) value).eIsProxy()) { + // do not validate unresolved entries + return; + } + } + if (!(expectedRefType.isAssignableFrom(value.getClass()))) { + error(expectedRefType.getSimpleName() + " expected", f); + } } } diff --git a/org.tvtower.db/src/org/tvtower/db/validation/ScriptValidator.java b/org.tvtower.db/src/org/tvtower/db/validation/ScriptValidator.java index b6ddabf..67abbca 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/ScriptValidator.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/ScriptValidator.java @@ -147,7 +147,17 @@ private void validateEpisodes(ScriptTemplate t) { } else { int count = t.getChildren().getChild().size(); checkMinMaxSlope(t.getEpisodes(), 1, count); - if (t.getChildren().getChild().stream().anyMatch(c -> c.getEpisodes() != null)) { + + //making a pilot episode optional is OK + int skipPilot = 0; + if (count > 0 && t.getChildren().getChild().get(0).getEpisodes() != null) { + MinMaxSlope pilotEpisodes = t.getChildren().getChild().get(0).getEpisodes().getData(); + if ("0".equals(pilotEpisodes.getMin()) + && (pilotEpisodes.getMax() == null || "1".equals(pilotEpisodes.getMax()))) { + skipPilot = 1; + } + } + if (t.getChildren().getChild().stream().skip(skipPilot).anyMatch(c -> c.getEpisodes() != null)) { error("episodes in parent and children are not supported", $.getScriptTemplate_Episodes()); } } diff --git a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SelfExpression.java b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SelfExpression.java index a8dedd5..57d3211 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SelfExpression.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SelfExpression.java @@ -15,9 +15,9 @@ class SelfExpression extends AbstractExpression { private static boolean STRICT = true; private static final List scriptParams = ImmutableList.of(// - "role"); + "role", "parent"); private static final List programmeParams = ImmutableList.of(// - "cast", "role"); + "cast", "role", "parent"); private static final List supportedNameParam = ImmutableList.of(// "name", "firstname", "lastname", "fullname", "nickname", "title"); @@ -39,6 +39,7 @@ static String validate(List params, EObject context) { return "illegal context for .self"; } + //TODO validate parent!! private static String getProgrammError(List params, Programme context) { String param1 = params.get(0); if(!isStringParam(param1)) { @@ -72,11 +73,18 @@ private static String getProgrammError(List params, Programme context) { return null; } + //TODO validate parent!! private static String getScriptError(List params, ScriptTemplate context) { String param1 = params.get(0); if(!isStringParam(param1)) { return "first parameter must be string parameter"; } + if("parent".equals(param1)) { + ScriptTemplate parent = EcoreUtil2.getContainerOfType(context, ScriptTemplate.class); + if(parent !=null) { + return getScriptError(params.subList(1, params.size()), parent); + } + } if (STRICT && !isStringFromCollection(param1, scriptParams)) { return "unsupported self paramter " + param1; } diff --git a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SimpleExpression.java b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SimpleExpression.java index 8d70dc9..69e427c 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SimpleExpression.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/SimpleExpression.java @@ -17,9 +17,10 @@ public class SimpleExpression { private static final Pattern SIMPLE_EXPRESSION_PARAM_PATTERN = Pattern.compile("(:[^$:]+)"); private static final Pattern FUNCTION_PATTERN = Pattern.compile("\\$\\{(\\.\\w+):"); private static final Pattern WT_REPLACE_PATTERN = Pattern.compile("&wt_(\\\"\\w+\\\")"); + private static final Pattern MISSING_DOT_PATTERN = Pattern.compile("\\$\\{(\\w+):"); private static final List KNOWN_COMPLEX_FUNCTIONS = ImmutableList.of(// - ".and", ".eq", ".gt", ".lt", ".gte", ".or", ".if"); + ".and", ".eq", ".gt", ".gte", ".lt", ".lte", ".or", ".if", ".person", ".csv"); public static List get(String content) { Set startIndexes = new HashSet<>(); @@ -51,6 +52,16 @@ public static List get(String content) { result.add(exp); } } + + // check for function syntax errors + Matcher functionNameErrorMatcher = MISSING_DOT_PATTERN.matcher(content); + while (functionNameErrorMatcher.find()) { + String fct = functionNameErrorMatcher.group(1).toLowerCase(); + SimpleExpression exp = new SimpleExpression(); + exp.function = fct; + result.add(exp); + } + return result; } @@ -76,7 +87,9 @@ public List getParams() { public String getValidationError(EObject context) { String error = null; String errorInfix = " in simple expression "; - if (params != null) { + if (getFunction() != null && getFunction().charAt(0) != '.') { + error = "missing function dot - ${" + getFunction() + ":..."; + } else if (params != null) { switch (getFunction()) { case ".worldtime": error = WorldTimeExpression.validate(params); @@ -88,7 +101,10 @@ public String getValidationError(EObject context) { error = PersonGeneratorExpression.validate(params); break; case ".locale": - error = LocaleExpression.validate(params); + error = TrivialExpressions.validateLocale(params); + break; + case ".ucfirst": + error = TrivialExpressions.validateUcfirst(params); break; case ".self": error = SelfExpression.validate(params, context); diff --git a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/LocaleExpression.java b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/TrivialExpressions.java similarity index 56% rename from org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/LocaleExpression.java rename to org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/TrivialExpressions.java index 48aabb0..803cba0 100644 --- a/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/LocaleExpression.java +++ b/org.tvtower.db/src/org/tvtower/db/validation/expressionhelper/TrivialExpressions.java @@ -2,9 +2,9 @@ import java.util.List; -class LocaleExpression extends AbstractExpression { +class TrivialExpressions extends AbstractExpression { - static String validate(List params) { + static String validateLocale(List params) { int paramCount = params.size(); if (paramCount < 1 || paramCount > 2) { return "one or two parameters expected"; @@ -15,4 +15,12 @@ static String validate(List params) { } return null; } + + static String validateUcfirst(List params) { + int paramCount = params.size(); + if (paramCount != 1) { + return "one parameter expected"; + } + return null; + } } diff --git a/pom.xml b/pom.xml index a1e7c59..87b85dc 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.tvtower.db - 1.3.0-SNAPSHOT + 2.0.0-SNAPSHOT org.tvtower.db.parent pom