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

Last change on this file since 12881 was 12881, checked in by bastiK, 7 years ago

see #15229 - move remaining classes to spi.preferences package, to make it self-contained

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