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

Last change on this file since 12620 was 12620, checked in by Don-vip, 7 years ago

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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