Java tutorial
/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kie.dmn.feel.parser.feel11; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZonedDateTime; import java.time.chrono.ChronoPeriod; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.stream.Collectors; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.kie.dmn.api.feel.runtime.events.FEELEvent; import org.kie.dmn.feel.lang.CompositeType; import org.kie.dmn.feel.lang.Scope; import org.kie.dmn.feel.lang.SimpleType; import org.kie.dmn.feel.lang.Symbol; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.impl.FEELEventListenersManager; import org.kie.dmn.feel.lang.impl.JavaBackedType; import org.kie.dmn.feel.lang.types.AliasFEELType; import org.kie.dmn.feel.lang.types.BuiltInType; import org.kie.dmn.feel.lang.types.ScopeImpl; import org.kie.dmn.feel.lang.types.SymbolTable; import org.kie.dmn.feel.lang.types.VariableSymbol; import org.kie.dmn.feel.runtime.FEELFunction; import org.kie.dmn.feel.runtime.Range; import org.kie.dmn.feel.runtime.UnaryTest; import org.kie.dmn.feel.runtime.events.UnknownVariableErrorEvent; import org.kie.dmn.feel.util.EvalHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ParserHelper { public static final Logger LOG = LoggerFactory.getLogger(ParserHelper.class); private FEELEventListenersManager eventsManager; private SymbolTable symbols = new SymbolTable(); private Scope currentScope = symbols.getGlobalScope(); private Stack<String> currentName = new Stack<>(); private int dynamicResolution = 0; private boolean featDMN12EnhancedForLoopEnabled = true; // DROOLS-2307 DMN enhanced for loop private boolean featDMN12weekday = true; // DROOLS-2648 DMN v1.2 weekday on 'date', 'date and time' public ParserHelper() { this(null); } public ParserHelper(FEELEventListenersManager eventsManager) { this.eventsManager = eventsManager; // initial context is loaded currentName.push(Scope.LOCAL); } public SymbolTable getSymbolTable() { return symbols; } public void pushScope() { LOG.trace("pushScope()"); currentScope = new ScopeImpl(currentName.peek(), currentScope); } public void pushScope(Type type) { LOG.trace("pushScope()"); currentScope = new ScopeImpl(currentName.peek(), currentScope, type); } public void popScope() { LOG.trace("popScope()"); currentScope = currentScope.getParentScope(); } public void pushName(String name) { LOG.trace("pushName() {}", name); this.currentName.push(name); } public void pushName(ParserRuleContext ctx) { this.currentName.push(getName(ctx)); } private String getName(ParserRuleContext ctx) { String key = getOriginalText(ctx); if (ctx instanceof FEEL_1_1Parser.KeyStringContext) { key = EvalHelper.unescapeString(key); } return key; } public void popName() { LOG.trace("popName()"); this.currentName.pop(); } public void recoverScope() { recoverScope(currentName.peek()); } public void recoverScope(String name) { LOG.trace("[{}] recoverScope( name: {}) with currentScope: {}", this.currentScope.getName(), name, currentScope); Scope s = this.currentScope.getChildScopes().get(name); if (s != null) { currentScope = s; if (currentScope.getType() != null && currentScope.getType().equals(BuiltInType.UNKNOWN)) { enableDynamicResolution(); } } else { Symbol resolved = this.currentScope.resolve(name); if (resolved != null && resolved.getType() instanceof CompositeType) { pushName(name); pushScope(resolved.getType()); CompositeType type = (CompositeType) resolved.getType(); for (Map.Entry<String, Type> f : type.getFields().entrySet()) { this.currentScope.define(new VariableSymbol(f.getKey(), f.getValue())); } LOG.trace(".. PUSHED, scope name {} with symbols {}", this.currentName.peek(), this.currentScope.getSymbols()); } else if (resolved != null && resolved.getType() instanceof SimpleType) { BuiltInType resolvedBIType = null; if (resolved.getType() instanceof BuiltInType) { resolvedBIType = (BuiltInType) resolved.getType(); } else if (resolved.getType() instanceof AliasFEELType) { resolvedBIType = ((AliasFEELType) resolved.getType()).getBuiltInType(); } else { throw new UnsupportedOperationException("Unsupported BIType " + resolved.getType() + "!"); } pushName(name); pushScope(resolvedBIType); switch (resolvedBIType) { // FEEL spec table 53 case DATE: this.currentScope.define(new VariableSymbol("year", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("month", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("day", BuiltInType.NUMBER)); if (isFeatDMN12weekday()) { // Table 60 spec DMN v1.2 this.currentScope.define(new VariableSymbol("weekday", BuiltInType.NUMBER)); } break; case TIME: this.currentScope.define(new VariableSymbol("hour", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("minute", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("second", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("time offset", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("timezone", BuiltInType.NUMBER)); break; case DATE_TIME: this.currentScope.define(new VariableSymbol("year", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("month", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("day", BuiltInType.NUMBER)); if (isFeatDMN12weekday()) { // Table 60 spec DMN v1.2 this.currentScope.define(new VariableSymbol("weekday", BuiltInType.NUMBER)); } this.currentScope.define(new VariableSymbol("hour", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("minute", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("second", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("time offset", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("timezone", BuiltInType.NUMBER)); break; case DURATION: // TODO might need to distinguish between `years and months duration` and `days and time duration` this.currentScope.define(new VariableSymbol("years", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("months", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("days", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("hours", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("minutes", BuiltInType.NUMBER)); this.currentScope.define(new VariableSymbol("seconds", BuiltInType.NUMBER)); break; // table 53 applies only to type(e) is a date/time/duration case UNKNOWN: enableDynamicResolution(); break; default: break; } } else { pushScope(); } } } public void dismissScope() { LOG.trace("dismissScope()"); if (currentScope.getType() != null && currentScope.getType().equals(BuiltInType.UNKNOWN)) { disableDynamicResolution(); } popScope(); } public void validateVariable(ParserRuleContext ctx, List<String> qn, String name) { if (eventsManager != null && !isDynamicResolution()) { if (this.currentScope.getChildScopes().get(name) == null && this.currentScope.resolve(name) == null) { // report error FEELEventListenersManager.notifyListeners(eventsManager, () -> { String varName = qn.stream().collect(Collectors.joining(".")); return new UnknownVariableErrorEvent(FEELEvent.Severity.ERROR, "Unknown variable '" + varName + "'", ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine(), varName); }); } } } public boolean isDynamicResolution() { return dynamicResolution > 0; } public void disableDynamicResolution() { if (dynamicResolution > 0) { this.dynamicResolution--; } } public void enableDynamicResolution() { this.dynamicResolution++; } public void defineVariable(ParserRuleContext ctx) { defineVariable(getName(ctx)); } public void defineVariable(String variable) { VariableSymbol var = new VariableSymbol(variable); this.currentScope.define(var); } public void defineVariable(String variable, Type type) { LOG.trace("defining custom type symbol."); VariableSymbol var = new VariableSymbol(variable, type); this.currentScope.define(var); } public void startVariable(Token t) { this.currentScope.start(t.getText()); } public boolean followUp(Token t, boolean isPredict) { boolean dynamicResolutionResult = isDynamicResolution() && FEELParser.isVariableNamePartValid(t.getText(), currentScope); boolean follow = dynamicResolutionResult || this.currentScope.followUp(t.getText(), isPredict); // in case isPredict == false, will need to followUp in the currentScope, so that the TokenTree currentNode is updated as per expectations, // this is because the `follow` variable above, in the case of short-circuited on `dynamicResolutionResult`, // would skip performing any necessary update in the second part of the || predicate if (dynamicResolutionResult && !isPredict) { this.currentScope.followUp(t.getText(), isPredict); } return follow; } public static String getOriginalText(ParserRuleContext ctx) { int a = ctx.start.getStartIndex(); int b = ctx.stop.getStopIndex(); Interval interval = new Interval(a, b); return ctx.getStart().getInputStream().getText(interval); } public static List<Token> getAllTokens(ParseTree ctx, List<Token> tokens) { for (int i = 0; i < ctx.getChildCount(); i++) { ParseTree child = ctx.getChild(i); if (child instanceof TerminalNode) { tokens.add(((TerminalNode) child).getSymbol()); } else { getAllTokens(child, tokens); } } return tokens; } public static Type determineTypeFromClass(Class<?> clazz) { if (clazz == null) { return BuiltInType.UNKNOWN; } else if (Number.class.isAssignableFrom(clazz)) { return BuiltInType.NUMBER; } else if (String.class.isAssignableFrom(clazz)) { return BuiltInType.STRING; } else if (LocalDate.class.isAssignableFrom(clazz)) { return BuiltInType.DATE; } else if (LocalTime.class.isAssignableFrom(clazz) || OffsetTime.class.isAssignableFrom(clazz)) { return BuiltInType.TIME; } else if (ZonedDateTime.class.isAssignableFrom(clazz) || OffsetDateTime.class.isAssignableFrom(clazz) || LocalDateTime.class.isAssignableFrom(clazz)) { return BuiltInType.DATE_TIME; } else if (Duration.class.isAssignableFrom(clazz) || ChronoPeriod.class.isAssignableFrom(clazz)) { return BuiltInType.DURATION; } else if (Boolean.class.isAssignableFrom(clazz)) { return BuiltInType.BOOLEAN; } else if (UnaryTest.class.isAssignableFrom(clazz)) { return BuiltInType.UNARY_TEST; } else if (Range.class.isAssignableFrom(clazz)) { return BuiltInType.RANGE; } else if (FEELFunction.class.isAssignableFrom(clazz)) { return BuiltInType.FUNCTION; } else if (List.class.isAssignableFrom(clazz)) { return BuiltInType.LIST; } else if (Map.class.isAssignableFrom(clazz)) { // TODO not so sure about this one.. return BuiltInType.CONTEXT; } return JavaBackedType.of(clazz); } public boolean isFeatDMN12EnhancedForLoopEnabled() { return featDMN12EnhancedForLoopEnabled; } public void setFeatDMN12EnhancedForLoopEnabled(boolean featDMN12EnhancedForLoopEnabled) { this.featDMN12EnhancedForLoopEnabled = featDMN12EnhancedForLoopEnabled; } public boolean isFeatDMN12weekday() { return featDMN12weekday; } public void setFeatDMN12weekday(boolean featDMN12weekday) { this.featDMN12weekday = featDMN12weekday; } }