| | 1 | package org.openstreetmap.josm.data; |
| | 2 | |
| | 3 | import java.util.logging.Level; |
| | 4 | import java.util.logging.Logger; |
| | 5 | import org.openstreetmap.josm.data.Preferences.Setting; |
| | 6 | import static org.openstreetmap.josm.tools.I18n.tr; |
| | 7 | |
| | 8 | import java.io.BufferedInputStream; |
| | 9 | import java.io.CharArrayReader; |
| | 10 | import java.io.CharArrayWriter; |
| | 11 | import java.io.File; |
| | 12 | import java.io.FileInputStream; |
| | 13 | import java.io.FileNotFoundException; |
| | 14 | import java.io.FileOutputStream; |
| | 15 | import java.io.IOException; |
| | 16 | import java.io.InputStream; |
| | 17 | import java.io.OutputStream; |
| | 18 | import java.net.HttpURLConnection; |
| | 19 | import java.net.URL; |
| | 20 | import java.util.ArrayList; |
| | 21 | import java.util.Collection; |
| | 22 | import java.util.Iterator; |
| | 23 | import java.util.List; |
| | 24 | import java.util.Map; |
| | 25 | import java.util.Map.Entry; |
| | 26 | import java.util.regex.Matcher; |
| | 27 | import java.util.regex.Pattern; |
| | 28 | import javax.swing.JOptionPane; |
| | 29 | import javax.xml.parsers.DocumentBuilder; |
| | 30 | import javax.xml.parsers.DocumentBuilderFactory; |
| | 31 | import javax.xml.parsers.ParserConfigurationException; |
| | 32 | import javax.xml.stream.XMLStreamException; |
| | 33 | import javax.xml.transform.Transformer; |
| | 34 | import javax.xml.transform.TransformerException; |
| | 35 | import javax.xml.transform.TransformerFactory; |
| | 36 | import javax.xml.transform.dom.DOMSource; |
| | 37 | import javax.xml.transform.stream.StreamResult; |
| | 38 | |
| | 39 | import org.w3c.dom.Document; |
| | 40 | import org.w3c.dom.Element; |
| | 41 | import org.w3c.dom.Node; |
| | 42 | import org.w3c.dom.NodeList; |
| | 43 | import org.xml.sax.SAXException; |
| | 44 | |
| | 45 | import org.openstreetmap.josm.Main; |
| | 46 | |
| | 47 | |
| | 48 | /** |
| | 49 | * Class to process configuration changes stored in XML |
| | 50 | * can be used to modify preferences, store/delete files in .josm folders etc |
| | 51 | */ |
| | 52 | public class CustomConfigurator { |
| | 53 | |
| | 54 | public CustomConfigurator() { |
| | 55 | } |
| | 56 | |
| | 57 | public void readXML(String dir, String fileName) { |
| | 58 | readXML(new File(dir, fileName)); |
| | 59 | } |
| | 60 | |
| | 61 | public void readXML(File file) { |
| | 62 | System.out.println("-- Reading custom preferences from " + file.getAbsolutePath() + " --"); |
| | 63 | |
| | 64 | InputStream is = null; |
| | 65 | try { |
| | 66 | is = new BufferedInputStream(new FileInputStream(file)); |
| | 67 | DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); |
| | 68 | builderFactory.setValidating(false); |
| | 69 | builderFactory.setNamespaceAware(true); |
| | 70 | DocumentBuilder builder = builderFactory.newDocumentBuilder(); |
| | 71 | Document document = builder.parse(is); |
| | 72 | processXML(document); |
| | 73 | Main.pref.save(); |
| | 74 | } catch (SAXException ex) { |
| | 75 | System.out.println("Can not parse XML parser:"); ex.printStackTrace(); |
| | 76 | } catch (ParserConfigurationException ex) { |
| | 77 | System.out.println("Can not configure XML parser:"); ex.printStackTrace(); |
| | 78 | } catch (FileNotFoundException ex) { |
| | 79 | System.out.println("File not found:"); ex.printStackTrace(); |
| | 80 | } catch (IOException ex) { |
| | 81 | System.out.println("Error reading file:"); ex.printStackTrace(); |
| | 82 | } finally { |
| | 83 | try { |
| | 84 | if (is != null) is.close(); |
| | 85 | } catch (IOException ex) { } |
| | 86 | } |
| | 87 | System.out.println("-- Reading complete --"); |
| | 88 | |
| | 89 | } |
| | 90 | |
| | 91 | private void processXML(Document document) { |
| | 92 | Element root = document.getDocumentElement(); |
| | 93 | NodeList childNodes = root.getChildNodes(); |
| | 94 | int nops = childNodes.getLength(); |
| | 95 | for (int i = 0; i < nops; i++) { |
| | 96 | Node item = childNodes.item(i); |
| | 97 | if (item.getNodeType() == Node.ELEMENT_NODE) { |
| | 98 | String elementName = item.getNodeName(); |
| | 99 | if ("preferences".equals(elementName)) { |
| | 100 | processPreferencesOperation((Element) item); |
| | 101 | } else if ("download".equals(elementName)) { |
| | 102 | processDownloadOperation((Element) item); |
| | 103 | } else if ("delete".equals(elementName)) { |
| | 104 | processFileDelete((Element) item); |
| | 105 | } else { |
| | 106 | System.out.println("Unknown element " + elementName); |
| | 107 | } |
| | 108 | |
| | 109 | } |
| | 110 | } |
| | 111 | } |
| | 112 | |
| | 113 | private void processPreferencesOperation(Element item) { |
| | 114 | String oper = item.getAttribute("operation"); |
| | 115 | System.out.println("PREFERENCES[" + oper + "]"); |
| | 116 | Preferences tmpPref; |
| | 117 | if ("replace".equals(oper)) { |
| | 118 | tmpPref = readPreferencesFromDOMElement(item); |
| | 119 | showPrefs(tmpPref); |
| | 120 | |
| | 121 | replacePreferences(tmpPref, Main.pref); |
| | 122 | } else if ("append".equals(oper)) { |
| | 123 | tmpPref = readPreferencesFromDOMElement(item); |
| | 124 | showPrefs(tmpPref); |
| | 125 | |
| | 126 | appendPreferences(tmpPref, Main.pref); |
| | 127 | } else if ("delete-keys".equals(oper)) { |
| | 128 | String pattern = item.getAttribute("pattern"); |
| | 129 | String key = item.getAttribute("key"); |
| | 130 | |
| | 131 | if (key!=null) deletePreferenceKey(key, Main.pref); |
| | 132 | if (pattern!=null) deletePreferenceKeyByPattern(pattern, Main.pref); |
| | 133 | } else if ("delete-values".equals(oper)) { |
| | 134 | tmpPref = readPreferencesFromDOMElement(item); |
| | 135 | showPrefs(tmpPref); |
| | 136 | |
| | 137 | deletePreferenceValues(tmpPref, Main.pref); |
| | 138 | } |
| | 139 | } |
| | 140 | |
| | 141 | private Preferences readPreferencesFromDOMElement(Element item) { |
| | 142 | Preferences tmpPref = new Preferences(); |
| | 143 | try { |
| | 144 | Transformer xformer = TransformerFactory.newInstance().newTransformer(); |
| | 145 | CharArrayWriter outputWriter = new CharArrayWriter(5000); |
| | 146 | StreamResult out = new StreamResult(outputWriter); |
| | 147 | |
| | 148 | xformer.transform(new DOMSource(item), out); |
| | 149 | |
| | 150 | CharArrayReader reader = new CharArrayReader(outputWriter.toCharArray()); |
| | 151 | tmpPref.fromXML(reader); |
| | 152 | } catch (XMLStreamException ex) { |
| | 153 | System.out.println("Error reading XML stream " + ex.getMessage()); |
| | 154 | } catch (TransformerException ex) { |
| | 155 | System.out.println("Error transforming DOM node to character array" + ex.getMessage()); |
| | 156 | } |
| | 157 | |
| | 158 | return tmpPref; |
| | 159 | } |
| | 160 | |
| | 161 | private void replacePreferences(Preferences fragment, Preferences mainpref) { |
| | 162 | // normal prefs |
| | 163 | for (Entry<String, String> entry : fragment.properties.entrySet()) { |
| | 164 | mainpref.put(entry.getKey(), entry.getValue()); |
| | 165 | } |
| | 166 | // "list" |
| | 167 | for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) { |
| | 168 | mainpref.putCollection(entry.getKey(), entry.getValue()); |
| | 169 | } |
| | 170 | // "lists" |
| | 171 | for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) { |
| | 172 | ArrayList<Collection<String>> array = new ArrayList<Collection<String>>(); |
| | 173 | array.addAll(entry.getValue()); |
| | 174 | mainpref.putArray(entry.getKey(), array); |
| | 175 | } |
| | 176 | /// "maps" |
| | 177 | for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) { |
| | 178 | mainpref.putListOfStructs(entry.getKey(), entry.getValue()); |
| | 179 | } |
| | 180 | |
| | 181 | } |
| | 182 | |
| | 183 | private void appendPreferences(Preferences fragment, Preferences mainpref) { |
| | 184 | // normal prefs |
| | 185 | for (Entry<String, String> entry : fragment.properties.entrySet()) { |
| | 186 | mainpref.put(entry.getKey(), entry.getValue()); |
| | 187 | } |
| | 188 | |
| | 189 | // "list" |
| | 190 | for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) { |
| | 191 | String key = entry.getKey(); |
| | 192 | |
| | 193 | Collection<String> existing = mainpref.collectionProperties.get(key); |
| | 194 | Collection<String> defaults = mainpref.collectionDefaults.get(key); |
| | 195 | |
| | 196 | if (existing==null && defaults==null) { |
| | 197 | defaultUnknownWarning(key); |
| | 198 | continue; |
| | 199 | } |
| | 200 | |
| | 201 | Collection<String> newItems = (existing!=null) ? |
| | 202 | new ArrayList<String>(existing) : new ArrayList<String>(defaults); |
| | 203 | |
| | 204 | for (String item : entry.getValue()) { |
| | 205 | // add nonexisting elements to then list |
| | 206 | if (!newItems.contains(item)) { |
| | 207 | newItems.add(item); |
| | 208 | } |
| | 209 | } |
| | 210 | mainpref.putCollection(entry.getKey(), newItems); |
| | 211 | } |
| | 212 | |
| | 213 | // "lists" |
| | 214 | for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) { |
| | 215 | String key = entry.getKey(); |
| | 216 | |
| | 217 | Collection<List<String>> existing = mainpref.arrayProperties.get(key); |
| | 218 | Collection<List<String>> defaults = mainpref.arrayDefaults.get(key); |
| | 219 | |
| | 220 | if (existing==null && defaults==null) { |
| | 221 | defaultUnknownWarning(key); |
| | 222 | continue; |
| | 223 | } |
| | 224 | |
| | 225 | ArrayList<Collection<String>> newLists = (existing!=null) ? |
| | 226 | new ArrayList<Collection<String>>(existing) : new ArrayList<Collection<String>>(defaults) ; |
| | 227 | |
| | 228 | for (Collection<String> list : entry.getValue()) { |
| | 229 | // add nonexisting list (equals comparison for lists is used implicitly) |
| | 230 | if (!newLists.contains(list)) { |
| | 231 | newLists.add(list); |
| | 232 | } |
| | 233 | } |
| | 234 | mainpref.putArray(entry.getKey(), newLists); |
| | 235 | } |
| | 236 | |
| | 237 | /// "maps" |
| | 238 | for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) { |
| | 239 | String key = entry.getKey(); |
| | 240 | |
| | 241 | Collection<Map<String, String>> existing = mainpref.listOfStructsProperties.get(key); |
| | 242 | Collection<Map<String, String>> defaults = mainpref.listOfStructsDefaults.get(key); |
| | 243 | |
| | 244 | if (existing==null && defaults==null) { |
| | 245 | defaultUnknownWarning(key); |
| | 246 | continue; |
| | 247 | } |
| | 248 | |
| | 249 | List<Map<String, String>> newMaps; |
| | 250 | newMaps = (existing != null) ? |
| | 251 | new ArrayList<Map<String, String>>(existing) : new ArrayList<Map<String, String>>(defaults); |
| | 252 | // get existing properties as list of maps |
| | 253 | |
| | 254 | for (Map<String, String> map : entry.getValue()) { |
| | 255 | // add nonexisting map (equals comparison for maps is used implicitly) |
| | 256 | if (!newMaps.contains(map)) { |
| | 257 | newMaps.add(map); |
| | 258 | } |
| | 259 | } |
| | 260 | mainpref.putListOfStructs(entry.getKey(), newMaps); |
| | 261 | } |
| | 262 | } |
| | 263 | |
| | 264 | |
| | 265 | private void deletePreferenceKeyByPattern(String pattern, Preferences pref) { |
| | 266 | Map<String, Setting> allSettings = pref.getAllSettings(); |
| | 267 | for (String key: allSettings.keySet()) { |
| | 268 | if (key.matches(pattern)) { |
| | 269 | System.out.println("!!! deleting key from preferences: "+key); |
| | 270 | pref.putSetting(key, allSettings.get(key).getNullInstance()); |
| | 271 | } |
| | 272 | } |
| | 273 | } |
| | 274 | |
| | 275 | private void deletePreferenceKey(String key, Preferences pref) { |
| | 276 | Map<String, Setting> allSettings = pref.getAllSettings(); |
| | 277 | if (allSettings.containsKey(key)) { |
| | 278 | System.out.println("!!! deleting key from preferences: "+key); |
| | 279 | pref.putSetting(key, allSettings.get(key).getNullInstance()); |
| | 280 | } |
| | 281 | } |
| | 282 | |
| | 283 | /** |
| | 284 | * Delete items from @param mainpref collections that match items from @param fragment collections |
| | 285 | */ |
| | 286 | private void deletePreferenceValues(Preferences fragment, Preferences mainpref) { |
| | 287 | |
| | 288 | Map<String, Setting> allSettings = mainpref.getAllSettings(); |
| | 289 | |
| | 290 | // normal prefs |
| | 291 | for (Entry<String, String> entry : fragment.properties.entrySet()) { |
| | 292 | // if mentioned value found, delete it |
| | 293 | if (entry.getValue().equals( mainpref.properties.get(entry.getKey()) )) |
| | 294 | mainpref.put(entry.getKey(), null); |
| | 295 | } |
| | 296 | |
| | 297 | // "list" |
| | 298 | for (Entry<String, List<String>> entry : fragment.collectionProperties.entrySet()) { |
| | 299 | String key = entry.getKey(); |
| | 300 | |
| | 301 | Collection<String> existing = mainpref.collectionProperties.get(key); |
| | 302 | Collection<String> defaults = mainpref.collectionDefaults.get(key); |
| | 303 | |
| | 304 | if (existing==null && defaults==null) { |
| | 305 | defaultUnknownWarning(key); |
| | 306 | continue; |
| | 307 | } |
| | 308 | |
| | 309 | Collection<String> newItems = (existing!=null) ? |
| | 310 | new ArrayList<String>(existing) : new ArrayList<String>(defaults); |
| | 311 | |
| | 312 | // remove mentioned items from collection |
| | 313 | for (String item : entry.getValue()) { |
| | 314 | System.out.printf("!!! deleting from list %s: %s\n", key, item); |
| | 315 | newItems.remove(item); |
| | 316 | } |
| | 317 | mainpref.putCollection(entry.getKey(), newItems); |
| | 318 | } |
| | 319 | |
| | 320 | // "lists" |
| | 321 | for (Entry<String, List<List<String>>> entry : fragment.arrayProperties.entrySet()) { |
| | 322 | String key = entry.getKey(); |
| | 323 | |
| | 324 | Collection<List<String>> existing = mainpref.arrayProperties.get(key); |
| | 325 | Collection<List<String>> defaults = mainpref.arrayDefaults.get(key); |
| | 326 | |
| | 327 | if (existing==null && defaults==null) { |
| | 328 | defaultUnknownWarning(key); |
| | 329 | continue; |
| | 330 | } |
| | 331 | |
| | 332 | ArrayList<Collection<String>> newLists = (existing!=null) ? |
| | 333 | new ArrayList<Collection<String>>(existing) : new ArrayList<Collection<String>>(defaults) ; |
| | 334 | |
| | 335 | // if items are found in one of lists, remove that list! |
| | 336 | Iterator<Collection<String>> listIterator = newLists.iterator(); |
| | 337 | while (listIterator.hasNext()) { |
| | 338 | Collection<String> list = listIterator.next(); |
| | 339 | for (Collection<String> removeList : entry.getValue()) { |
| | 340 | if (list.containsAll(removeList)) { |
| | 341 | // remove current list, because it matches search criteria |
| | 342 | System.out.printf("!!! deleting list from lists %s: %s\n", key, list); |
| | 343 | listIterator.remove(); |
| | 344 | } |
| | 345 | } |
| | 346 | } |
| | 347 | |
| | 348 | mainpref.putArray(entry.getKey(), newLists); |
| | 349 | } |
| | 350 | |
| | 351 | /// "maps" |
| | 352 | for (Entry<String, List<Map<String, String>>> entry : fragment.listOfStructsProperties.entrySet()) { |
| | 353 | String key = entry.getKey(); |
| | 354 | |
| | 355 | Collection<Map<String, String>> existing = mainpref.listOfStructsProperties.get(key); |
| | 356 | Collection<Map<String, String>> defaults = mainpref.listOfStructsDefaults.get(key); |
| | 357 | |
| | 358 | if (existing==null && defaults==null) { |
| | 359 | defaultUnknownWarning(key); |
| | 360 | continue; |
| | 361 | } |
| | 362 | |
| | 363 | List<Map<String, String>> newMaps; |
| | 364 | newMaps = (existing != null) ? |
| | 365 | new ArrayList<Map<String, String>>(existing) : new ArrayList<Map<String, String>>(defaults); |
| | 366 | Iterator<Map<String, String>> mapIterator = newMaps.iterator(); |
| | 367 | while (mapIterator.hasNext()) { |
| | 368 | Map<String, String> map = mapIterator.next(); |
| | 369 | for (Map<String, String> removeMap : entry.getValue()) { |
| | 370 | if (map.entrySet().containsAll( removeMap.entrySet() )) { |
| | 371 | // the map contain all mentioned key-value pair, so it should be deleted from "maps" |
| | 372 | System.out.printf("!!! deleting map from maps %s: %s\n", key, map); |
| | 373 | mapIterator.remove(); |
| | 374 | } |
| | 375 | } |
| | 376 | } |
| | 377 | mainpref.putListOfStructs(entry.getKey(), newMaps); |
| | 378 | } |
| | 379 | } |
| | 380 | |
| | 381 | private void defaultUnknownWarning(String key) { |
| | 382 | JOptionPane.showMessageDialog( |
| | 383 | Main.parent, |
| | 384 | tr("<html>Settings file asks to append preferences to <b>{0}</b>,<br/> but it's default value is unknown at this moment.<br/> Please activate corresponding function manually and retry importing.", key), |
| | 385 | tr("Warning"), |
| | 386 | JOptionPane.WARNING_MESSAGE); |
| | 387 | } |
| | 388 | |
| | 389 | private void showPrefs(Preferences tmpPref) { |
| | 390 | System.out.println("properties: " + tmpPref.properties); |
| | 391 | System.out.println("collections: " + tmpPref.collectionProperties); |
| | 392 | System.out.println("arrays: " + tmpPref.arrayProperties); |
| | 393 | System.out.println("maps: " + tmpPref.listOfStructsProperties); |
| | 394 | } |
| | 395 | |
| | 396 | private void processDownloadOperation(Element item) { |
| | 397 | String address= item.getAttribute("url"); |
| | 398 | String path= item.getAttribute("path"); |
| | 399 | String dir= Main.pref.getPreferencesDir(); |
| | 400 | if (path.contains("..") || path.startsWith("/") || path.contains(":")) return; // some basic protection |
| | 401 | File fOut = new File(dir,path); |
| | 402 | |
| | 403 | OutputStream out = null; |
| | 404 | InputStream in = null; |
| | 405 | HttpURLConnection downloadConnection; |
| | 406 | try { |
| | 407 | String mkdir=item.getAttribute("mkdir"); |
| | 408 | if (mkdir!=null) { |
| | 409 | File newDir = new File(dir,mkdir); |
| | 410 | newDir.mkdirs(); |
| | 411 | } |
| | 412 | |
| | 413 | URL url = new URL(address); |
| | 414 | System.out.printf("Downloading %s from %s\n", path, url.toString()); |
| | 415 | |
| | 416 | downloadConnection = (HttpURLConnection)url.openConnection(); |
| | 417 | downloadConnection.setRequestProperty("Cache-Control", "no-cache"); |
| | 418 | downloadConnection.setRequestProperty("User-Agent",Version.getInstance().getAgentString()); |
| | 419 | downloadConnection.setRequestProperty("Host", url.getHost()); |
| | 420 | downloadConnection.connect(); |
| | 421 | in = downloadConnection.getInputStream(); |
| | 422 | out = new FileOutputStream(fOut); |
| | 423 | byte[] buffer = new byte[8192]; |
| | 424 | for (int read = in.read(buffer); read != -1; read = in.read(buffer)) { |
| | 425 | out.write(buffer, 0, read); |
| | 426 | } |
| | 427 | } catch (IOException ex) { |
| | 428 | System.out.printf("Error downloading file %s from %s\n",path,address); |
| | 429 | } finally { |
| | 430 | try { |
| | 431 | if (out!=null) out.close(); |
| | 432 | if (in!=null) in.close(); |
| | 433 | } catch (IOException ex) { } |
| | 434 | } |
| | 435 | |
| | 436 | } |
| | 437 | |
| | 438 | private void processFileDelete(Element item) { |
| | 439 | String path= item.getAttribute("path"); |
| | 440 | String dir= Main.pref.getPreferencesDir(); |
| | 441 | if (path.contains("..") || path.startsWith("/") || path.contains(":")) return; // some basic protection |
| | 442 | |
| | 443 | System.out.printf("!!! deleting file %s in %s\n", path, dir); |
| | 444 | |
| | 445 | File fOut = new File(dir,path); |
| | 446 | if (fOut.exists()) { |
| | 447 | if ( !fOut.delete() ) { |
| | 448 | JOptionPane.showMessageDialog( |
| | 449 | Main.parent, |
| | 450 | tr("Can not delete file {0} in {1}", path, dir), |
| | 451 | tr("Warning"), |
| | 452 | JOptionPane.WARNING_MESSAGE); |
| | 453 | } |
| | 454 | } |
| | 455 | } |
| | 456 | } |