source: josm/trunk/src/org/openstreetmap/josm/data/preferences/PreferencesReader.java@ 9821

Last change on this file since 9821 was 9821, checked in by bastiK, 8 years ago

fixed #12522 - Advanced preferences: display default entries consistently

Saves default preference entries to a cache file (cache/default_preferences.xml), so the list of advanced preferences is filled with all known default values consistently from the start and not gradually as you use different features during a session.

  • Property svn:eol-style set to native
File size: 12.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.preferences;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.BufferedReader;
7import java.io.File;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.Reader;
11import java.nio.charset.StandardCharsets;
12import java.nio.file.Files;
13import java.util.ArrayList;
14import java.util.Collections;
15import java.util.LinkedHashMap;
16import java.util.List;
17import java.util.Map;
18import java.util.SortedMap;
19import java.util.TreeMap;
20
21import javax.xml.XMLConstants;
22import javax.xml.stream.XMLInputFactory;
23import javax.xml.stream.XMLStreamConstants;
24import javax.xml.stream.XMLStreamException;
25import javax.xml.stream.XMLStreamReader;
26import javax.xml.transform.stream.StreamSource;
27import javax.xml.validation.Schema;
28import javax.xml.validation.SchemaFactory;
29import javax.xml.validation.Validator;
30
31import org.openstreetmap.josm.Main;
32import org.openstreetmap.josm.io.CachedFile;
33import org.xml.sax.SAXException;
34
35/**
36 * Loads preferences from XML.
37 */
38public class PreferencesReader {
39
40 private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
41
42 private final SortedMap<String, Setting<?>> settings = new TreeMap<>();
43 private int version = 0;
44 private XMLStreamReader parser;
45
46 private final boolean defaults;
47
48 /**
49 * Constructs a new {@code PreferencesReader}.
50 * @param defaults true when reading from the cache file for default preferences,
51 * false for the regular preferences config file
52 */
53 public PreferencesReader(boolean defaults) {
54 this.defaults = defaults;
55 }
56
57 /**
58 * Validate the XML.
59 * @param f the file
60 * @throws IOException if any I/O error occurs
61 * @throws SAXException if any SAX error occurs
62 */
63 public static void validateXML(File f) throws IOException, SAXException {
64 try (BufferedReader in = Files.newBufferedReader(f.toPath(), StandardCharsets.UTF_8)) {
65 validateXML(in);
66 }
67 }
68
69 /**
70 * Validate the XML.
71 * @param in the {@link Reader}
72 * @throws IOException if any I/O error occurs
73 * @throws SAXException if any SAX error occurs
74 */
75 public static void validateXML(Reader in) throws IOException, SAXException {
76 try (CachedFile cf = new CachedFile("resource://data/preferences.xsd"); InputStream xsdStream = cf.getInputStream()) {
77 Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI).newSchema(new StreamSource(xsdStream));
78 Validator validator = schema.newValidator();
79 validator.validate(new StreamSource(in));
80 }
81 }
82
83 /**
84 * Parse preferences XML.
85 * @param f the file
86 * @throws IOException if any I/O error occurs
87 * @throws XMLStreamException if any XML stream error occurs
88 */
89 public void fromXML(File f) throws IOException, XMLStreamException {
90 try (BufferedReader in = Files.newBufferedReader(f.toPath(), StandardCharsets.UTF_8)) {
91 fromXML(in);
92 }
93 }
94
95 /**
96 * Parse preferences XML.
97 * @param in the {@link Reader}
98 * @throws XMLStreamException if any XML stream error occurs
99 */
100 public void fromXML(Reader in) throws XMLStreamException {
101 this.parser = XMLInputFactory.newInstance().createXMLStreamReader(in);
102 parse();
103 }
104
105 /**
106 * Return the parsed preferences as a settings map
107 * @return the parsed preferences as a settings map
108 */
109 public SortedMap<String, Setting<?>> getSettings() {
110 return settings;
111 }
112
113 /**
114 * Return the version from the XML root element.
115 * (Represents the JOSM version when the file was written.)
116 * @return the version
117 */
118 public int getVersion() {
119 return version;
120 }
121
122 private void parse() throws XMLStreamException {
123 int event = parser.getEventType();
124 while (true) {
125 if (event == XMLStreamConstants.START_ELEMENT) {
126 String topLevelElementName = defaults ? "preferences-defaults" : "preferences";
127 String localName = parser.getLocalName();
128 if (!topLevelElementName.equals(localName)) {
129 throw new XMLStreamException(tr("Expected element ''{0}'', but got ''{1}''", topLevelElementName, localName), parser.getLocation());
130 }
131 try {
132 version = Integer.parseInt(parser.getAttributeValue(null, "version"));
133 } catch (NumberFormatException e) {
134 if (Main.isDebugEnabled()) {
135 Main.debug(e.getMessage());
136 }
137 }
138 parseRoot();
139 } else if (event == XMLStreamConstants.END_ELEMENT) {
140 return;
141 }
142 if (parser.hasNext()) {
143 event = parser.next();
144 } else {
145 break;
146 }
147 }
148 parser.close();
149 }
150
151 private void parseRoot() throws XMLStreamException {
152 while (true) {
153 int event = parser.next();
154 if (event == XMLStreamConstants.START_ELEMENT) {
155 String localName = parser.getLocalName();
156 switch(localName) {
157 case "tag":
158 Setting setting;
159 if (defaults && isNil()) {
160 setting = new StringSetting(null);
161 } else {
162 String value = parser.getAttributeValue(null, "value");
163 if (value == null) {
164 throw new XMLStreamException(tr("value expected"), parser.getLocation());
165 }
166 setting = new StringSetting(value);
167 }
168 if (defaults) {
169 setting.setTime(Math.round(Double.parseDouble(parser.getAttributeValue(null, "time"))));
170 }
171 settings.put(parser.getAttributeValue(null, "key"), setting);
172 jumpToEnd();
173 break;
174 case "list":
175 case "lists":
176 case "maps":
177 parseToplevelList();
178 break;
179 default:
180 throwException("Unexpected element: "+localName);
181 }
182 } else if (event == XMLStreamConstants.END_ELEMENT) {
183 return;
184 }
185 }
186 }
187
188 private void jumpToEnd() throws XMLStreamException {
189 while (true) {
190 int event = parser.next();
191 if (event == XMLStreamConstants.START_ELEMENT) {
192 jumpToEnd();
193 } else if (event == XMLStreamConstants.END_ELEMENT) {
194 return;
195 }
196 }
197 }
198
199 private void parseToplevelList() throws XMLStreamException {
200 String key = parser.getAttributeValue(null, "key");
201 Long time = null;
202 if (defaults) {
203 time = Math.round(Double.parseDouble(parser.getAttributeValue(null, "time")));
204 }
205 String name = parser.getLocalName();
206
207 List<String> entries = null;
208 List<List<String>> lists = null;
209 List<Map<String, String>> maps = null;
210 if (defaults && isNil()) {
211 Setting setting;
212 switch (name) {
213 case "lists":
214 setting = new ListListSetting(null);
215 break;
216 case "maps":
217 setting = new MapListSetting(null);
218 break;
219 default:
220 setting = new ListSetting(null);
221 break;
222 }
223 setting.setTime(time);
224 settings.put(key, setting);
225 jumpToEnd();
226 } else {
227 while (true) {
228 int event = parser.next();
229 if (event == XMLStreamConstants.START_ELEMENT) {
230 String localName = parser.getLocalName();
231 switch(localName) {
232 case "entry":
233 if (entries == null) {
234 entries = new ArrayList<>();
235 }
236 entries.add(parser.getAttributeValue(null, "value"));
237 jumpToEnd();
238 break;
239 case "list":
240 if (lists == null) {
241 lists = new ArrayList<>();
242 }
243 lists.add(parseInnerList());
244 break;
245 case "map":
246 if (maps == null) {
247 maps = new ArrayList<>();
248 }
249 maps.add(parseMap());
250 break;
251 default:
252 throwException("Unexpected element: "+localName);
253 }
254 } else if (event == XMLStreamConstants.END_ELEMENT) {
255 break;
256 }
257 }
258 Setting setting;
259 if (entries != null) {
260 setting = new ListSetting(Collections.unmodifiableList(entries));
261 } else if (lists != null) {
262 setting = new ListListSetting(Collections.unmodifiableList(lists));
263 } else if (maps != null) {
264 setting = new MapListSetting(Collections.unmodifiableList(maps));
265 } else {
266 switch (name) {
267 case "lists":
268 setting = new ListListSetting(Collections.<List<String>>emptyList());
269 break;
270 case "maps":
271 setting = new MapListSetting(Collections.<Map<String, String>>emptyList());
272 break;
273 default:
274 setting = new ListSetting(Collections.<String>emptyList());
275 break;
276 }
277 }
278 if (defaults) {
279 setting.setTime(time);
280 }
281 settings.put(key, setting);
282 }
283 }
284
285 private List<String> parseInnerList() throws XMLStreamException {
286 List<String> entries = new ArrayList<>();
287 while (true) {
288 int event = parser.next();
289 if (event == XMLStreamConstants.START_ELEMENT) {
290 if ("entry".equals(parser.getLocalName())) {
291 entries.add(parser.getAttributeValue(null, "value"));
292 jumpToEnd();
293 } else {
294 throwException("Unexpected element: "+parser.getLocalName());
295 }
296 } else if (event == XMLStreamConstants.END_ELEMENT) {
297 break;
298 }
299 }
300 return Collections.unmodifiableList(entries);
301 }
302
303 private Map<String, String> parseMap() throws XMLStreamException {
304 Map<String, String> map = new LinkedHashMap<>();
305 while (true) {
306 int event = parser.next();
307 if (event == XMLStreamConstants.START_ELEMENT) {
308 if ("tag".equals(parser.getLocalName())) {
309 map.put(parser.getAttributeValue(null, "key"), parser.getAttributeValue(null, "value"));
310 jumpToEnd();
311 } else {
312 throwException("Unexpected element: "+parser.getLocalName());
313 }
314 } else if (event == XMLStreamConstants.END_ELEMENT) {
315 break;
316 }
317 }
318 return Collections.unmodifiableMap(map);
319 }
320
321 /**
322 * Check if the current element is nil (meaning the value of the setting is null).
323 * @return true, if the current element is nil
324 * @see https://msdn.microsoft.com/en-us/library/2b314yt2(v=vs.85).aspx
325 */
326 private boolean isNil() {
327 String nil = parser.getAttributeValue(XSI_NS, "nil");
328 return "true".equals(nil) || "1".equals(nil);
329 }
330
331 /**
332 * Throw RuntimeException with line and column number.
333 *
334 * Only use this for errors that should not be possible after schema validation.
335 * @param msg the error message
336 */
337 private void throwException(String msg) {
338 throw new RuntimeException(msg + tr(" (at line {0}, column {1})",
339 parser.getLocation().getLineNumber(), parser.getLocation().getColumnNumber()));
340 }
341}
Note: See TracBrowser for help on using the repository browser.