source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java@ 6386

Last change on this file since 6386 was 6379, checked in by simon04, 10 years ago

OpeningHourTest: better wording for test description

File size: 8.2 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.InputStreamReader;
7import java.lang.reflect.InvocationTargetException;
8import java.lang.reflect.Method;
9import java.util.ArrayList;
10import java.util.Arrays;
11import java.util.Collections;
12import java.util.List;
13
14import javax.script.Invocable;
15import javax.script.ScriptEngine;
16import javax.script.ScriptEngineManager;
17import javax.script.ScriptException;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.command.ChangePropertyCommand;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.validation.FixableTestError;
26import org.openstreetmap.josm.data.validation.Severity;
27import org.openstreetmap.josm.data.validation.Test;
28import org.openstreetmap.josm.data.validation.TestError;
29import org.openstreetmap.josm.io.MirroredInputStream;
30
31/**
32 * Tests the correct usage of the opening hour syntax of the tags
33 * {@code opening_hours}, {@code collection_times}, {@code service_times} according to
34 * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a>.
35 *
36 * @since 6370
37 */
38public class OpeningHourTest extends Test {
39
40 /**
41 * Javascript engine
42 */
43 public static final ScriptEngine ENGINE = new ScriptEngineManager().getEngineByName("JavaScript");
44
45 /**
46 * Constructs a new {@code OpeningHourTest}.
47 */
48 public OpeningHourTest() {
49 super(tr("Opening hours syntax"),
50 tr("This test checks the correct usage of the opening hours syntax."));
51 }
52
53 @Override
54 public void initialize() throws Exception {
55 super.initialize();
56 if (ENGINE != null) {
57 ENGINE.eval(new InputStreamReader(new MirroredInputStream("resource://data/opening_hours.js"), "UTF-8"));
58 // fake country/state to not get errors on holidays
59 ENGINE.eval("var nominatiomJSON = {address: {state: 'Bayern', country_code: 'de'}};");
60 ENGINE.eval("var oh = function (value, mode) {return new opening_hours(value, nominatiomJSON, mode);};");
61 } else {
62 Main.warn("Unable to initialize OpeningHourTest because no JavaScript engine has been found");
63 }
64 }
65
66 static enum CheckMode {
67 TIME_RANGE(0), POINTS_IN_TIME(1), BOTH(2);
68 final int code;
69
70 CheckMode(int code) {
71 this.code = code;
72 }
73 }
74
75 protected Object parse(String value, CheckMode mode) throws ScriptException, NoSuchMethodException {
76 return ((Invocable) ENGINE).invokeFunction("oh", value, mode.code);
77 }
78
79 @SuppressWarnings("unchecked")
80 protected List<Object> getList(Object obj) {
81 if (obj == null || "".equals(obj)) {
82 return Arrays.asList();
83 } else if (obj instanceof String) {
84 final Object[] strings = ((String) obj).split("\\n");
85 return Arrays.asList(strings);
86 } else if (obj instanceof List) {
87 return (List<Object>) obj;
88 } else if ("sun.org.mozilla.javascript.internal.NativeArray".equals(obj.getClass().getName())) {
89 List<Object> list = new ArrayList<Object>();
90 try {
91 Method getIds = obj.getClass().getMethod("getIds");
92 Method get = obj.getClass().getMethod("get", long.class);
93 Object[] ids = (Object[]) getIds.invoke(obj);
94 for (Object id : ids) {
95 list.add(get.invoke(obj, id));
96 }
97 } catch (NoSuchMethodException e) {
98 Main.error("Unable to run OpeningHourTest because of NoSuchMethodException by reflection: "+e.getMessage());
99 } catch (IllegalArgumentException e) {
100 Main.error("Unable to run OpeningHourTest because of IllegalArgumentException by reflection: "+e.getMessage());
101 } catch (IllegalAccessException e) {
102 Main.error("Unable to run OpeningHourTest because of IllegalAccessException by reflection: "+e.getMessage());
103 } catch (InvocationTargetException e) {
104 Main.error("Unable to run OpeningHourTest because of InvocationTargetException by reflection: "+e.getMessage());
105 }
106 return list;
107 } else {
108 throw new IllegalArgumentException("Not expecting class " + obj.getClass());
109 }
110 }
111
112 public class OpeningHoursTestError {
113 final Severity severity;
114 final String message, prettifiedValue;
115
116 public OpeningHoursTestError(String message, Severity severity, String prettifiedValue) {
117 this.message = message;
118 this.severity = severity;
119 this.prettifiedValue = prettifiedValue;
120 }
121
122 public OpeningHoursTestError(String message, Severity severity) {
123 this(message, severity, null);
124 }
125
126 public TestError getTestError(final OsmPrimitive p, final String key) {
127 if (prettifiedValue == null) {
128 return new TestError(OpeningHourTest.this, severity, message, 2901, p);
129 } else {
130 return new FixableTestError(OpeningHourTest.this, severity, message, 2901, p,
131 new ChangePropertyCommand(p, key, prettifiedValue));
132 }
133 }
134
135 public String getMessage() {
136 return message;
137 }
138
139 public String getPrettifiedValue() {
140 return prettifiedValue;
141 }
142
143 public Severity getSeverity() {
144 return severity;
145 }
146 }
147
148 /**
149 * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
150 * <a href="https://github.com/ypid/opening_hours.js">opening_hours.js</a> and returns a list containing
151 * validation errors or an empty list. Null values result in an empty list.
152 * @param value the opening hour value to be checked.
153 * @param mode whether to validate {@code value} as a time range, or points in time, or both.
154 * @return a list of {@link TestError} or an empty list
155 */
156 public List<OpeningHoursTestError> checkOpeningHourSyntax(final String value, CheckMode mode) {
157 if (ENGINE == null || value == null || value.trim().isEmpty()) {
158 return Collections.emptyList();
159 }
160 try {
161 final Object r = parse(value, mode);
162 final List<OpeningHoursTestError> errors = new ArrayList<OpeningHoursTestError>();
163 String prettifiedValue = null;
164 try {
165 prettifiedValue = (String) ((Invocable) ENGINE).invokeMethod(r, "prettifyValue");
166 } catch (Exception e) {
167 Main.debug(e.getMessage());
168 }
169 for (final Object i : getList(((Invocable) ENGINE).invokeMethod(r, "getWarnings"))) {
170 errors.add(new OpeningHoursTestError(i.toString(), Severity.WARNING, prettifiedValue));
171 }
172 return errors;
173 } catch (ScriptException ex) {
174 final String message = ex.getMessage()
175 .replaceAll("[^:]*Exception: ", "opening_hours - ")
176 .replaceAll("\\(<Unknown source.*", "")
177 .trim();
178 return Arrays.asList(new OpeningHoursTestError(message, Severity.ERROR));
179 } catch (final Exception ex) {
180 throw new RuntimeException(ex);
181 }
182 }
183
184 public List<OpeningHoursTestError> checkOpeningHourSyntax(final String value) {
185 return checkOpeningHourSyntax(value, CheckMode.TIME_RANGE);
186 }
187
188 protected void check(final OsmPrimitive p, final String key, CheckMode mode) {
189 for (OpeningHoursTestError e : checkOpeningHourSyntax(p.get(key), mode)) {
190 errors.add(e.getTestError(p, key));
191 }
192 }
193
194 protected void check(final OsmPrimitive p) {
195 check(p, "opening_hours", CheckMode.TIME_RANGE);
196 check(p, "collection_times", CheckMode.BOTH);
197 check(p, "service_times", CheckMode.BOTH);
198 }
199
200 @Override
201 public void visit(final Node n) {
202 check(n);
203 }
204
205 @Override
206 public void visit(final Relation r) {
207 check(r);
208 }
209
210 @Override
211 public void visit(final Way w) {
212 check(w);
213 }
214}
Note: See TracBrowser for help on using the repository browser.