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

Last change on this file was 14441, checked in by Don-vip, 5 years ago

fix SonarQube issues

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