| 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 | } |