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

Last change on this file since 11296 was 10306, checked in by Don-vip, 8 years ago

sonar - pmd:ImmutableField + remove unused code

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