diff --git a/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java b/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java index af3d55f621..2dc99a1693 100644 --- a/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java +++ b/core/src/main/java/tc/oc/pgm/filters/matcher/match/VariableFilter.java @@ -37,8 +37,7 @@ public boolean respondsTo(Class queryType) { @Override public boolean isDynamic() { - // Variables' setValue will always invalidate the filterable directly, no events required - return true; + return variable.isDynamic(); } @Override diff --git a/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java b/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java index a3946bd669..2f7dd98016 100644 --- a/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java +++ b/core/src/main/java/tc/oc/pgm/filters/parse/FeatureFilterParser.java @@ -21,7 +21,6 @@ import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; import tc.oc.pgm.variables.VariableDefinition; -import tc.oc.pgm.variables.VariableType; import tc.oc.pgm.variables.VariablesModule; public class FeatureFilterParser extends FilterParser { @@ -100,9 +99,6 @@ public Filter parseNot(Element el) throws InvalidXMLException { if (varMatch.matches()) { VariableDefinition variable = features.resolve(node, varMatch.group(1), VariableDefinition.class); - if (!variable.getVariableType().equals(VariableType.DUMMY)) { - throw new InvalidXMLException("Variable filters only support dummy variables!", node); - } Range range = XMLUtils.parseNumericRange(node, varMatch.group(2), Double.class); return new VariableFilter(variable, range); } diff --git a/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java b/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java index 1e66a2920b..a69d242807 100644 --- a/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java +++ b/core/src/main/java/tc/oc/pgm/filters/parse/FilterParser.java @@ -93,7 +93,6 @@ import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; import tc.oc.pgm.variables.VariableDefinition; -import tc.oc.pgm.variables.VariableType; public abstract class FilterParser implements XMLParser { @@ -660,9 +659,6 @@ public PlayerCountFilter parsePlayerCountFilter(Element el) throws InvalidXMLExc public Filter parseVariableFilter(Element el) throws InvalidXMLException { VariableDefinition varDef = features.resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); - if (!varDef.getVariableType().equals(VariableType.DUMMY)) { - throw new InvalidXMLException("Variable filters only support dummy variables!", el); - } Range range = XMLUtils.parseNumericRange(new Node(el), Double.class); if (varDef.getScope() == Party.class) diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java b/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java index d6b0c21c22..7175576efb 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariableDefinition.java @@ -1,5 +1,6 @@ package tc.oc.pgm.variables; +import java.util.function.Function; import tc.oc.pgm.api.match.Match; import tc.oc.pgm.features.SelfIdentifyingFeatureDefinition; import tc.oc.pgm.filters.Filterable; @@ -7,47 +8,34 @@ public class VariableDefinition> extends SelfIdentifyingFeatureDefinition { private final Class scope; - private final double def; - private final VariableType variableType; - - public VariableDefinition(String id, Class scope, double def, VariableType type) { + private final boolean isDynamic; + private final Function, Variable> builder; + + public VariableDefinition( + String id, + Class scope, + boolean isDynamic, + Function, Variable> builder) { super(id); this.scope = scope; - this.def = def; - this.variableType = type; + this.isDynamic = isDynamic; + this.builder = builder; } public Class getScope() { return scope; } - public double getDefault() { - return def; - } - - public Variable buildInstance() { - return getVariableType().buildInstance(this); + public boolean isDynamic() { + return isDynamic; } - public VariableType getVariableType() { - return variableType; + public Variable buildInstance() { + return builder.apply(this); } @SuppressWarnings("unchecked") public Variable getVariable(Match match) { return (Variable) match.getFeatureContext().get(this.getId()); } - - public static class Context, C> extends VariableDefinition { - private final C context; - - public Context(String id, Class scope, double def, VariableType type, C context) { - super(id, scope, def, type); - this.context = context; - } - - public C getContext() { - return context; - } - } } diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableParser.java b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java new file mode 100644 index 0000000000..6c9187e752 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/VariableParser.java @@ -0,0 +1,92 @@ +package tc.oc.pgm.variables; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.regex.Pattern; +import org.jdom2.Element; +import tc.oc.pgm.api.feature.FeatureReference; +import tc.oc.pgm.api.filter.Filterables; +import tc.oc.pgm.api.map.factory.MapFactory; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.api.player.MatchPlayer; +import tc.oc.pgm.filters.Filterable; +import tc.oc.pgm.teams.TeamFactory; +import tc.oc.pgm.util.MethodParser; +import tc.oc.pgm.util.MethodParsers; +import tc.oc.pgm.util.xml.InvalidXMLException; +import tc.oc.pgm.util.xml.Node; +import tc.oc.pgm.util.xml.XMLUtils; +import tc.oc.pgm.variables.types.BlitzVariable; +import tc.oc.pgm.variables.types.DummyVariable; +import tc.oc.pgm.variables.types.ScoreVariable; +import tc.oc.pgm.variables.types.TeamVariableAdapter; + +public class VariableParser { + // The limitation is due to them being used in exp4j formulas for. + public static final Pattern VARIABLE_ID = Pattern.compile("[A-Za-z_]\\w*"); + + private final MapFactory factory; + private final Map methodParsers; + + public VariableParser(MapFactory factory) { + this.factory = factory; + this.methodParsers = MethodParsers.getMethodParsersForClass(getClass()); + } + + public VariableDefinition parse(Element el) throws InvalidXMLException { + String id = Node.fromRequiredAttr(el, "id").getValue(); + if (!VARIABLE_ID.matcher(id).matches()) + throw new InvalidXMLException( + "Variable IDs must start with a letter or underscore and can only include letters, digits or underscores.", + el); + + Method parser = methodParsers.get(el.getName().toLowerCase()); + if (parser != null) { + try { + return (VariableDefinition) parser.invoke(this, el, id); + } catch (Exception e) { + throw InvalidXMLException.coerce(e, new Node(el)); + } + } else { + throw new InvalidXMLException("Unknown variable type: " + el.getName(), el); + } + } + + @MethodParser("variable") + public VariableDefinition parseDummy(Element el, String id) throws InvalidXMLException { + Class> scope = Filterables.parse(Node.fromRequiredAttr(el, "scope")); + double def = XMLUtils.parseNumber(Node.fromAttr(el, "default"), Double.class, 0d); + return new VariableDefinition<>(id, scope, true, vd -> new DummyVariable<>(vd, def)); + } + + @MethodParser("lives") + public VariableDefinition parseBlitzLives(Element el, String id) + throws InvalidXMLException { + return new VariableDefinition<>(id, MatchPlayer.class, false, BlitzVariable::new); + } + + @MethodParser("score") + public VariableDefinition parseScore(Element el, String id) throws InvalidXMLException { + return new VariableDefinition<>(id, Party.class, false, ScoreVariable::new); + } + + @MethodParser("with-team") + public VariableDefinition parseTeamAdapter(Element el, String id) + throws InvalidXMLException { + @SuppressWarnings("unchecked") + VariableDefinition var = + factory.getFeatures().resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); + if (var.getScope() != Party.class) { + throw new InvalidXMLException( + "Team scope is required for with-team variable, got " + var.getScope().getSimpleName(), + el); + } + + FeatureReference team = + factory.getFeatures().createReference(Node.fromRequiredAttr(el, "team"), TeamFactory.class); + + return new VariableDefinition<>( + id, Match.class, var.isDynamic(), vd -> new TeamVariableAdapter(vd, var, team)); + } +} diff --git a/core/src/main/java/tc/oc/pgm/variables/VariableType.java b/core/src/main/java/tc/oc/pgm/variables/VariableType.java deleted file mode 100644 index 571eec61d0..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/VariableType.java +++ /dev/null @@ -1,81 +0,0 @@ -package tc.oc.pgm.variables; - -import java.util.function.Function; -import org.jdom2.Element; -import tc.oc.pgm.api.map.factory.MapFactory; -import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.api.party.Party; -import tc.oc.pgm.api.player.MatchPlayer; -import tc.oc.pgm.filters.Filterable; -import tc.oc.pgm.util.xml.InvalidXMLException; -import tc.oc.pgm.variables.types.BlitzVariable; -import tc.oc.pgm.variables.types.DummyVariable; -import tc.oc.pgm.variables.types.ScoreVariable; -import tc.oc.pgm.variables.types.TeamVariable; - -public enum VariableType { - DUMMY( - DummyVariable::new, - (Class>) null, - MatchPlayer.class, - Party.class, - Match.class), - LIVES(BlitzVariable::new, MatchPlayer.class), - SCORE(ScoreVariable::new, Party.class), - TEAM(TeamVariable::new, TeamVariable.Context::new, Match.class); - - private final Function, Variable> builder; - private final ContextBuilder contextBuilder; - - private final Class> defaultScope; - private final Class[] supportedScopes; - - @SafeVarargs - VariableType( - Function, Variable> builder, - Class> defaultScope, - Class>... supportedScopes) { - this.builder = builder; - this.contextBuilder = null; - this.defaultScope = defaultScope; - this.supportedScopes = supportedScopes; - } - - @SafeVarargs - VariableType( - Function, Variable> builder, - ContextBuilder contextBuilder, - Class> defaultScope, - Class>... supportedScopes) { - this.builder = builder; - this.contextBuilder = contextBuilder; - this.defaultScope = defaultScope; - this.supportedScopes = supportedScopes; - } - - public Class> getDefaultScope() { - return defaultScope; - } - - public boolean supports(Class> cls) { - if (defaultScope == cls) return true; - for (Class supportedScope : supportedScopes) { - if (supportedScope.isAssignableFrom(cls)) { - return true; - } - } - return false; - } - - public Object build(MapFactory factory, Element el) throws InvalidXMLException { - return contextBuilder == null ? null : contextBuilder.build(factory, el); - } - - public Variable buildInstance(VariableDefinition definition) { - return builder.apply(definition); - } - - private interface ContextBuilder { - T build(MapFactory factory, Element el) throws InvalidXMLException; - } -} diff --git a/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java b/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java index d3ce06df72..89e8f525e5 100644 --- a/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java +++ b/core/src/main/java/tc/oc/pgm/variables/VariablesModule.java @@ -24,7 +24,6 @@ import tc.oc.pgm.score.ScoreMatchModule; import tc.oc.pgm.teams.TeamMatchModule; import tc.oc.pgm.util.xml.InvalidXMLException; -import tc.oc.pgm.util.xml.Node; import tc.oc.pgm.util.xml.XMLUtils; public class VariablesModule implements MapModule { @@ -101,41 +100,15 @@ public VariablesModule parse(MapFactory factory, Logger logger, Document doc) throws InvalidXMLException { ImmutableList.Builder> variables = ImmutableList.builder(); - for (Element variable : - XMLUtils.flattenElements(doc.getRootElement(), "variables", "variable")) { - - String id = Node.fromRequiredAttr(variable, "id").getValue(); - if (!VARIABLE_ID.matcher(id).matches()) - throw new InvalidXMLException( - "Variable IDs must start with a letter or underscore and can only include letters, digits or underscores.", - variable); - VariableType type = - XMLUtils.parseEnum( - Node.fromAttr(variable, "type"), VariableType.class, VariableType.DUMMY); - - Class> scope = parseScope(variable, type); - double def = XMLUtils.parseNumber(Node.fromAttr(variable, "default"), Double.class, 0d); - - if (!type.supports(scope)) { - throw new InvalidXMLException( - "VariableType " + type + " does not support scope: " + scope, variable); - } - - VariableDefinition varDef = - new VariableDefinition.Context<>(id, scope, def, type, type.build(factory, variable)); + VariableParser parser = new VariableParser(factory); + + for (Element variable : XMLUtils.flattenElements(doc.getRootElement(), "variables", null)) { + VariableDefinition varDef = parser.parse(variable); factory.getFeatures().addFeature(variable, varDef); variables.add(varDef); } return new VariablesModule(variables.build()); } - - private Class> parseScope(Element variable, VariableType type) - throws InvalidXMLException { - if (type.getDefaultScope() != null && variable.getAttribute("scope") == null) { - return type.getDefaultScope(); - } - return Filterables.parse(Node.fromRequiredAttr(variable, "scope")); - } } } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java index 3983244f69..a2dfbf2046 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/AbstractVariable.java @@ -4,16 +4,15 @@ import tc.oc.pgm.variables.Variable; import tc.oc.pgm.variables.VariableDefinition; -public abstract class AbstractVariable, D extends VariableDefinition> - implements Variable { - protected final D definition; +public abstract class AbstractVariable> implements Variable { + protected final VariableDefinition definition; - public AbstractVariable(D definition) { + public AbstractVariable(VariableDefinition definition) { this.definition = definition; } @Override - public D getDefinition() { + public VariableDefinition getDefinition() { return definition; } diff --git a/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java index 183f7745b6..bc9a200c2b 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/BlitzVariable.java @@ -5,12 +5,12 @@ import tc.oc.pgm.blitz.BlitzMatchModule; import tc.oc.pgm.variables.VariableDefinition; -public class BlitzVariable extends AbstractVariable> { +public class BlitzVariable extends AbstractVariable { private BlitzMatchModule bmm; - public BlitzVariable(VariableDefinition definition) { - super((VariableDefinition) definition); + public BlitzVariable(VariableDefinition definition) { + super(definition); } @Override diff --git a/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java index e39104aeb5..d254408a42 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/DummyVariable.java @@ -6,19 +6,20 @@ import tc.oc.pgm.filters.Filterable; import tc.oc.pgm.variables.VariableDefinition; -public class DummyVariable> - extends AbstractVariable> { +public class DummyVariable> extends AbstractVariable { + private final double def; private final Map values; - public DummyVariable(VariableDefinition definition) { + public DummyVariable(VariableDefinition definition, double def) { super(definition); + this.def = def; this.values = new HashMap<>(); } @Override protected double getValueImpl(T obj) { - return values.computeIfAbsent(obj, k -> definition.getDefault()); + return values.computeIfAbsent(obj, k -> def); } @Override diff --git a/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java index cfea4bc33c..9cbb44065c 100644 --- a/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java +++ b/core/src/main/java/tc/oc/pgm/variables/types/ScoreVariable.java @@ -6,12 +6,12 @@ import tc.oc.pgm.score.ScoreMatchModule; import tc.oc.pgm.variables.VariableDefinition; -public class ScoreVariable extends AbstractVariable> { +public class ScoreVariable extends AbstractVariable { private ScoreMatchModule smm; - public ScoreVariable(VariableDefinition definition) { - super((VariableDefinition) definition); + public ScoreVariable(VariableDefinition definition) { + super(definition); } @Override diff --git a/core/src/main/java/tc/oc/pgm/variables/types/TeamVariable.java b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariable.java deleted file mode 100644 index e21e835b34..0000000000 --- a/core/src/main/java/tc/oc/pgm/variables/types/TeamVariable.java +++ /dev/null @@ -1,59 +0,0 @@ -package tc.oc.pgm.variables.types; - -import org.jdom2.Element; -import tc.oc.pgm.api.feature.FeatureReference; -import tc.oc.pgm.api.map.factory.MapFactory; -import tc.oc.pgm.api.match.Match; -import tc.oc.pgm.api.party.Party; -import tc.oc.pgm.teams.Team; -import tc.oc.pgm.teams.TeamFactory; -import tc.oc.pgm.teams.TeamMatchModule; -import tc.oc.pgm.util.xml.InvalidXMLException; -import tc.oc.pgm.util.xml.Node; -import tc.oc.pgm.variables.Variable; -import tc.oc.pgm.variables.VariableDefinition; - -public class TeamVariable - extends AbstractVariable> { - - private Variable child; - private Team team; - - public TeamVariable(VariableDefinition definition) { - super((VariableDefinition.Context) definition); - } - - public void postLoad(Match match) { - team = match.needModule(TeamMatchModule.class).getTeam(definition.getContext().teamRef.get()); - child = definition.getContext().childRef.getVariable(match); - } - - @Override - protected double getValueImpl(Match match) { - return child.getValue(team); - } - - @Override - protected void setValueImpl(Match match, double value) { - child.setValue(team, value); - } - - public static class Context { - private final VariableDefinition childRef; - private final FeatureReference teamRef; - - public Context(MapFactory factory, Element el) throws InvalidXMLException { - VariableDefinition var = - factory.getFeatures().resolve(Node.fromRequiredAttr(el, "var"), VariableDefinition.class); - if (var.getScope() != Party.class) { - throw new InvalidXMLException( - "Wrong scope defined for var, scope must be " + var.getScope().getSimpleName(), el); - } - this.childRef = (VariableDefinition) var; - this.teamRef = - factory - .getFeatures() - .createReference(Node.fromRequiredAttr(el, "team"), TeamFactory.class); - } - } -} diff --git a/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java new file mode 100644 index 0000000000..0dc52362fb --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/variables/types/TeamVariableAdapter.java @@ -0,0 +1,43 @@ +package tc.oc.pgm.variables.types; + +import tc.oc.pgm.api.feature.FeatureReference; +import tc.oc.pgm.api.match.Match; +import tc.oc.pgm.api.party.Party; +import tc.oc.pgm.teams.Team; +import tc.oc.pgm.teams.TeamFactory; +import tc.oc.pgm.teams.TeamMatchModule; +import tc.oc.pgm.variables.Variable; +import tc.oc.pgm.variables.VariableDefinition; + +public class TeamVariableAdapter extends AbstractVariable { + + private final VariableDefinition childRef; + private final FeatureReference teamRef; + + private Variable child; + private Team team; + + public TeamVariableAdapter( + VariableDefinition definition, + VariableDefinition childRef, + FeatureReference teamRef) { + super(definition); + this.childRef = childRef; + this.teamRef = teamRef; + } + + public void postLoad(Match match) { + team = match.needModule(TeamMatchModule.class).getTeam(teamRef.get()); + child = childRef.getVariable(match); + } + + @Override + protected double getValueImpl(Match match) { + return child.getValue(team); + } + + @Override + protected void setValueImpl(Match match, double value) { + child.setValue(team, value); + } +}