Ignore:
Timestamp:
2020-03-01T21:33:56+01:00 (4 years ago)
Author:
simon04
Message:

fix #18140 - Switch to OpeningHoursParser

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java

    r15815 r15978  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.io.Reader;
    7 import java.util.ArrayList;
    8 import java.util.Arrays;
     6import java.io.StringReader;
    97import java.util.Collections;
    108import java.util.List;
     9import java.util.Locale;
     10import java.util.Objects;
    1111
    12 import javax.script.Invocable;
    13 import javax.script.ScriptEngine;
    14 import javax.script.ScriptException;
    15 import javax.swing.JOptionPane;
    16 
     12import ch.poole.openinghoursparser.OpeningHoursParser;
     13import ch.poole.openinghoursparser.ParseException;
     14import ch.poole.openinghoursparser.Rule;
     15import ch.poole.openinghoursparser.Util;
    1716import org.openstreetmap.josm.command.ChangePropertyCommand;
    1817import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    2019import org.openstreetmap.josm.data.validation.Test.TagTest;
    2120import org.openstreetmap.josm.data.validation.TestError;
    22 import org.openstreetmap.josm.gui.Notification;
    23 import org.openstreetmap.josm.gui.util.GuiHelper;
    24 import org.openstreetmap.josm.io.CachedFile;
    25 import org.openstreetmap.josm.tools.LanguageInfo;
    26 import org.openstreetmap.josm.tools.Logging;
    27 import org.openstreetmap.josm.tools.Utils;
    2821
    2922/**
    3023 * Tests the correct usage of the opening hour syntax of the tags
    3124 * {@code opening_hours}, {@code collection_times}, {@code service_times} according to
    32  * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a>.
     25 * <a href="https://github.com/simonpoole/OpeningHoursParser">OpeningHoursParser</a>.
    3326 *
    34  * @since 6370
     27 * @since 6370 (using opening_hours.js), 15978 (using OpeningHoursParser)
    3528 */
    3629public class OpeningHourTest extends TagTest {
    37 
    38     /**
    39      * Javascript engine
    40      */
    41     public static final ScriptEngine ENGINE = Utils.getJavaScriptEngine();
    4230
    4331    /**
     
    4735        super(tr("Opening hours syntax"),
    4836                tr("This test checks the correct usage of the opening hours syntax."));
    49     }
    50 
    51     @Override
    52     public void initialize() throws Exception {
    53         super.initialize();
    54         if (ENGINE != null) {
    55             try (CachedFile cf = new CachedFile("resource://data/validator/opening_hours.js");
    56                  Reader reader = cf.getContentReader()) {
    57                 ENGINE.eval("var console={};console.debug=print;console.log=print;console.warn=print;console.error=print;");
    58                 ENGINE.eval(reader);
    59                 // fake country/state to not get errors on holidays
    60                 ENGINE.eval("var nominatimJSON = {address: {country_code: 'xa'}};");
    61                 ENGINE.eval(
    62                         "var oh = function (value, tag_key, mode, locale) {" +
    63                         " try {" +
    64                         "    var conf = {tag_key: tag_key, locale: locale, additional_rule_separator: false};" +
    65                         "    if (mode > -1) {" +
    66                         "      conf.mode = mode;" +
    67                         "    }" +
    68                         "    var r = new opening_hours(value, nominatimJSON, conf);" +
    69                         "    r.getErrors = function() {return [];};" +
    70                         "    return r;" +
    71                         "  } catch (err) {" +
    72                         "    return {" +
    73                         "      prettifyValue: function() {return null;}," +
    74                         "      getWarnings: function() {return [];}," +
    75                         "      getErrors: function() {return [err.toString()]}" +
    76                         "    };" +
    77                         "  }" +
    78                         "};");
    79             }
    80         } else {
    81             Logging.warn("Unable to initialize OpeningHourTest because no JavaScript engine has been found");
    82         }
    8337    }
    8438
     
    10458
    10559    /**
    106      * Parses the opening hour syntax of the {@code value} given according to
    107      * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns an object on which
    108      * methods can be called to extract information.
    109      * @param value the opening hour value to be checked
    110      * @param tagKey the OSM key (should be "opening_hours", "collection_times" or "service_times")
    111      * @param mode whether to validate {@code value} as a time range, or points in time, or both. Can be null
    112      * @param locale the locale code used for localizing messages
    113      * @return The value returned by the underlying method. Usually a {@code jdk.nashorn.api.scripting.ScriptObjectMirror}
    114      * @throws ScriptException if an error occurs during invocation of the underlying method
    115      * @throws NoSuchMethodException if underlying method with given name or matching argument types cannot be found
    116      * @since 13147
    117      */
    118     public Object parse(String value, String tagKey, CheckMode mode, String locale) throws ScriptException, NoSuchMethodException {
    119         return ((Invocable) ENGINE).invokeFunction("oh", value, tagKey, mode != null ? mode.code : -1, locale);
    120     }
    121 
    122     @SuppressWarnings("unchecked")
    123     protected List<Object> getList(Object obj) throws ScriptException, NoSuchMethodException {
    124         if (obj == null || "".equals(obj)) {
    125             return Arrays.asList();
    126         } else if (obj instanceof String) {
    127             final Object[] strings = ((String) obj).split("\\\\n");
    128             return Arrays.asList(strings);
    129         } else if (obj instanceof List) {
    130             return (List<Object>) obj;
    131         } else {
    132             // recursively call getList() with argument converted to newline-separated string
    133             return getList(((Invocable) ENGINE).invokeMethod(obj, "join", "\\n"));
    134         }
    135     }
    136 
    137     /**
    13860     * An error concerning invalid syntax for an "opening_hours"-like tag.
    13961     */
     
    14466
    14567        /**
    146          * Constructs a new {@code OpeningHoursTestError} with a known pretiffied value.
     68         * Constructs a new {@code OpeningHoursTestError} with a known prettified value.
    14769         * @param message The error message
    14870         * @param severity The error severity
     
    203125
    204126    /**
    205      * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
    206      * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns a list containing
    207      * validation errors or an empty list. Null values result in an empty list.
     127     * Checks for a correct usage of the opening hour syntax of the {@code value} given,
     128     * and returns a list containing validation errors or an empty list. Null values result in an empty list.
    208129     * @param key the OSM key (should be "opening_hours", "collection_times" or "service_times"). Used in error message
    209130     * @param value the opening hour value to be checked.
     
    211132     */
    212133    public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value) {
    213         return checkOpeningHourSyntax(key, value, null, false, LanguageInfo.getJOSMLocaleCode());
     134        return checkOpeningHourSyntax(key, value, false, Locale.getDefault());
    214135    }
    215136
    216137    /**
    217      * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
    218      * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns a list containing
    219      * validation errors or an empty list. Null values result in an empty list.
     138     * Checks for a correct usage of the opening hour syntax of the {@code value} given,
     139     * and returns a list containing validation errors or an empty list. Null values result in an empty list.
    220140     * @param key the OSM key (should be "opening_hours", "collection_times" or "service_times").
    221141     * @param value the opening hour value to be checked.
    222      * @param mode whether to validate {@code value} as a time range, or points in time, or both. Can be null
    223142     * @param ignoreOtherSeverity whether to ignore errors with {@link Severity#OTHER}.
    224143     * @param locale the locale code used for localizing messages
    225144     * @return a list of {@link TestError} or an empty list
    226145     */
    227     public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value, CheckMode mode,
    228             boolean ignoreOtherSeverity, String locale) {
    229         if (ENGINE == null || value == null || value.isEmpty()) {
     146    public List<OpeningHoursTestError> checkOpeningHourSyntax(final String key, final String value, boolean ignoreOtherSeverity, Locale locale) {
     147        if (value == null || value.isEmpty()) {
    230148            return Collections.emptyList();
    231149        }
    232         final List<OpeningHoursTestError> errors = new ArrayList<>();
     150
     151        ch.poole.openinghoursparser.I18n.setLocale(locale);
     152        String prettifiedValue = null;
    233153        try {
    234             final Object r = parse(value, key, mode, locale);
    235             String prettifiedValue = null;
    236             try {
    237                 prettifiedValue = getOpeningHoursPrettifiedValues(r);
    238             } catch (ScriptException | NoSuchMethodException e) {
    239                 Logging.warn(e);
     154            final List<Rule> rules = new OpeningHoursParser(new StringReader(value)).rules(false);
     155            prettifiedValue = Util.rulesToOpeningHoursString(rules);
     156            if (!Objects.equals(value, prettifiedValue)) {
     157                // parse again in strict mode for detailed message
     158                new OpeningHoursParser(new StringReader(value)).rules(true);
    240159            }
    241             for (final Object i : getOpeningHoursErrors(r)) {
    242                 errors.add(new OpeningHoursTestError(getErrorMessage(key, i), Severity.ERROR, prettifiedValue));
    243             }
    244             for (final Object i : getOpeningHoursWarnings(r)) {
    245                 errors.add(new OpeningHoursTestError(getErrorMessage(key, i), Severity.WARNING, prettifiedValue));
    246             }
    247             if (!ignoreOtherSeverity && errors.isEmpty() && prettifiedValue != null && !value.equals(prettifiedValue)) {
    248                 errors.add(new OpeningHoursTestError(tr("opening_hours value can be prettified"), Severity.OTHER, prettifiedValue));
    249             }
    250         } catch (ScriptException | NoSuchMethodException ex) {
    251             Logging.error(ex);
    252             GuiHelper.runInEDT(() -> new Notification(Utils.getRootCause(ex).getMessage()).setIcon(JOptionPane.ERROR_MESSAGE).show());
     160        } catch (ParseException e) {
     161            return Collections.singletonList(new OpeningHoursTestError(e.getMessage(), Severity.WARNING, prettifiedValue));
    253162        }
    254         return errors;
    255     }
    256163
    257     /**
    258      * Returns the prettified value returned by the opening hours parser.
    259      * @param r result of {@link #parse}
    260      * @return the prettified value returned by the opening hours parser
    261      * @throws NoSuchMethodException if method "prettifyValue" or matching argument types cannot be found
    262      * @throws ScriptException if an error occurs during invocation of the JavaScript method
    263      * @since 13296
    264      */
    265     public final String getOpeningHoursPrettifiedValues(Object r) throws NoSuchMethodException, ScriptException {
    266         return (String) ((Invocable) ENGINE).invokeMethod(r, "prettifyValue");
    267     }
    268 
    269     /**
    270      * Returns the list of errors returned by the opening hours parser.
    271      * @param r result of {@link #parse}
    272      * @return the list of errors returned by the opening hours parser
    273      * @throws NoSuchMethodException if method "getErrors" or matching argument types cannot be found
    274      * @throws ScriptException if an error occurs during invocation of the JavaScript method
    275      * @since 13296
    276      */
    277     public final List<Object> getOpeningHoursErrors(Object r) throws NoSuchMethodException, ScriptException {
    278         return getList(((Invocable) ENGINE).invokeMethod(r, "getErrors"));
    279     }
    280 
    281     /**
    282      * Returns the list of warnings returned by the opening hours parser.
    283      * @param r result of {@link #parse}
    284      * @return the list of warnings returned by the opening hours parser
    285      * @throws NoSuchMethodException if method "getWarnings" or matching argument types cannot be found
    286      * @throws ScriptException if an error occurs during invocation of the JavaScript method
    287      * @since 13296
    288      */
    289     public final List<Object> getOpeningHoursWarnings(Object r) throws NoSuchMethodException, ScriptException {
    290         return getList(((Invocable) ENGINE).invokeMethod(r, "getWarnings"));
    291     }
    292 
    293     /**
    294      * Translates and shortens the error/warning message.
    295      * @param o error/warning message returned by {@link #getOpeningHoursErrors} or {@link #getOpeningHoursWarnings}
    296      * @return translated/shortened error/warning message
    297      * @since 13298
    298      */
    299     public static String getErrorMessage(Object o) {
    300         return o.toString().trim()
    301         .replace("Unexpected token:", tr("Unexpected token:"))
    302         .replace("Unexpected token (school holiday parser):", tr("Unexpected token (school holiday parser):"))
    303         .replace("Unexpected token in number range:", tr("Unexpected token in number range:"))
    304         .replace("Unexpected token in week range:", tr("Unexpected token in week range:"))
    305         .replace("Unexpected token in weekday range:", tr("Unexpected token in weekday range:"))
    306         .replace("Unexpected token in month range:", tr("Unexpected token in month range:"))
    307         .replace("Unexpected token in year range:", tr("Unexpected token in year range:"))
    308         .replace("This means that the syntax is not valid at that point or it is currently not supported.", tr("Invalid/unsupported syntax."));
    309     }
    310 
    311     /**
    312      * Translates and shortens the error/warning message.
    313      * @param key OSM key
    314      * @param o error/warning message returned by {@link #getOpeningHoursErrors} or {@link #getOpeningHoursWarnings}
    315      * @return translated/shortened error/warning message
    316      */
    317     static String getErrorMessage(String key, Object o) {
    318         return key + " - " + getErrorMessage(o);
     164        if (ignoreOtherSeverity || Objects.equals(value, prettifiedValue)) {
     165            return Collections.emptyList();
     166        } else {
     167            return Collections.singletonList(
     168                    new OpeningHoursTestError(tr("{0} value can be prettified", key), Severity.OTHER, prettifiedValue));
     169        }
    319170    }
    320171
Note: See TracChangeset for help on using the changeset viewer.