001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.tools; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 import static org.openstreetmap.josm.tools.I18n.trn; 006 007 import java.io.IOException; 008 import java.net.HttpURLConnection; 009 import java.net.MalformedURLException; 010 import java.net.SocketException; 011 import java.net.URL; 012 import java.net.UnknownHostException; 013 import java.text.DateFormat; 014 import java.text.ParseException; 015 import java.text.SimpleDateFormat; 016 import java.util.Collection; 017 import java.util.Date; 018 import java.util.Locale; 019 import java.util.Set; 020 import java.util.TreeSet; 021 import java.util.regex.Matcher; 022 import java.util.regex.Pattern; 023 024 import org.openstreetmap.josm.Main; 025 import org.openstreetmap.josm.data.osm.Node; 026 import org.openstreetmap.josm.data.osm.OsmPrimitive; 027 import org.openstreetmap.josm.data.osm.Relation; 028 import org.openstreetmap.josm.data.osm.Way; 029 import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder; 030 import org.openstreetmap.josm.io.ChangesetClosedException; 031 import org.openstreetmap.josm.io.IllegalDataException; 032 import org.openstreetmap.josm.io.MissingOAuthAccessTokenException; 033 import org.openstreetmap.josm.io.OsmApi; 034 import org.openstreetmap.josm.io.OsmApiException; 035 import org.openstreetmap.josm.io.OsmApiInitializationException; 036 import org.openstreetmap.josm.io.OsmTransferException; 037 import org.openstreetmap.josm.io.auth.CredentialsManager; 038 039 @SuppressWarnings("CallToThreadDumpStack") 040 public class ExceptionUtil { 041 private ExceptionUtil() { 042 } 043 044 /** 045 * handles an exception caught during OSM API initialization 046 * 047 * @param e the exception 048 */ 049 public static String explainOsmApiInitializationException(OsmApiInitializationException e) { 050 e.printStackTrace(); 051 String msg = tr( 052 "<html>Failed to initialize communication with the OSM server {0}.<br>" 053 + "Check the server URL in your preferences and your internet connection.", Main.pref.get( 054 "osm-server.url", OsmApi.DEFAULT_API_URL)); 055 return msg; 056 } 057 058 059 /** 060 * Creates the error message 061 * 062 * @param e the exception 063 */ 064 public static String explainMissingOAuthAccessTokenException(MissingOAuthAccessTokenException e) { 065 e.printStackTrace(); 066 String msg = tr( 067 "<html>Failed to authenticate at the OSM server ''{0}''.<br>" 068 + "You are using OAuth to authenticate but currently there is no<br>" 069 + "OAuth Access Token configured.<br>" 070 + "Please open the Preferences Dialog and generate or enter an Access Token." 071 + "</html>", 072 Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL) 073 ); 074 return msg; 075 } 076 077 public static Pair<OsmPrimitive, Collection<OsmPrimitive>> parsePreconditionFailed(String msg) { 078 final String ids = "(\\d+(?:,\\d+)*)"; 079 final Collection<OsmPrimitive> refs = new TreeSet<OsmPrimitive>(); // error message can contain several times the same way 080 Matcher m; 081 m = Pattern.compile(".*Node (\\d+) is still used by relations " + ids + ".*").matcher(msg); 082 if (m.matches()) { 083 OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 084 for (String s : m.group(2).split(",")) { 085 refs.add(new Relation(Long.parseLong(s))); 086 } 087 return Pair.create(n, refs); 088 } 089 m = Pattern.compile(".*Node (\\d+) is still used by ways " + ids + ".*").matcher(msg); 090 if (m.matches()) { 091 OsmPrimitive n = new Node(Long.parseLong(m.group(1))); 092 for (String s : m.group(2).split(",")) { 093 refs.add(new Way(Long.parseLong(s))); 094 } 095 return Pair.create(n, refs); 096 } 097 m = Pattern.compile(".*The relation (\\d+) is used in relations? " + ids + ".*").matcher(msg); 098 if (m.matches()) { 099 OsmPrimitive n = new Relation(Long.parseLong(m.group(1))); 100 for (String s : m.group(2).split(",")) { 101 refs.add(new Relation(Long.parseLong(s))); 102 } 103 return Pair.create(n, refs); 104 } 105 m = Pattern.compile(".*Way (\\d+) is still used by relations " + ids + ".*").matcher(msg); 106 if (m.matches()) { 107 OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 108 for (String s : m.group(2).split(",")) { 109 refs.add(new Relation(Long.parseLong(s))); 110 } 111 return Pair.create(n, refs); 112 } 113 m = Pattern.compile(".*Way (\\d+) requires the nodes with id in " + ids + ".*").matcher(msg); // ... ", which either do not exist, or are not visible" 114 if (m.matches()) { 115 OsmPrimitive n = new Way(Long.parseLong(m.group(1))); 116 for (String s : m.group(2).split(",")) { 117 refs.add(new Node(Long.parseLong(s))); 118 } 119 return Pair.create(n, refs); 120 } 121 return null; 122 } 123 124 /** 125 * Explains an upload error due to a violated precondition, i.e. a HTTP return code 412 126 * 127 * @param e the exception 128 */ 129 public static String explainPreconditionFailed(OsmApiException e) { 130 e.printStackTrace(); 131 String msg = e.getErrorHeader(); 132 Pair<OsmPrimitive, Collection<OsmPrimitive>> conflict = parsePreconditionFailed(e.getErrorHeader()); 133 if (conflict != null) { 134 OsmPrimitive firstRefs = conflict.b.iterator().next(); 135 String objId = Long.toString(conflict.a.getId()); 136 Collection<Long> refIds= Utils.transform(conflict.b, new Utils.Function<OsmPrimitive, Long>() { 137 138 @Override 139 public Long apply(OsmPrimitive x) { 140 return x.getId(); 141 } 142 }); 143 String refIdsString = refIds.size() == 1 ? refIds.iterator().next().toString() : refIds.toString(); 144 if (conflict.a instanceof Node) { 145 if (firstRefs instanceof Node) { 146 return "<html>" + trn( 147 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 148 + " It is still referred to by node {1}.<br>" 149 + "Please load the node, remove the reference to the node, and upload again.", 150 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 151 + " It is still referred to by nodes {1}.<br>" 152 + "Please load the nodes, remove the reference to the node, and upload again.", 153 conflict.b.size(), objId, refIdsString) + "</html>"; 154 } else if (firstRefs instanceof Way) { 155 return "<html>" + trn( 156 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 157 + " It is still referred to by way {1}.<br>" 158 + "Please load the way, remove the reference to the node, and upload again.", 159 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 160 + " It is still referred to by ways {1}.<br>" 161 + "Please load the ways, remove the reference to the node, and upload again.", 162 conflict.b.size(), objId, refIdsString) + "</html>"; 163 } else if (firstRefs instanceof Relation) { 164 return "<html>" + trn( 165 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 166 + " It is still referred to by relation {1}.<br>" 167 + "Please load the relation, remove the reference to the node, and upload again.", 168 "<strong>Failed</strong> to delete <strong>node {0}</strong>." 169 + " It is still referred to by relations {1}.<br>" 170 + "Please load the relations, remove the reference to the node, and upload again.", 171 conflict.b.size(), objId, refIdsString) + "</html>"; 172 } else { 173 throw new IllegalStateException(); 174 } 175 } else if (conflict.a instanceof Way) { 176 if (firstRefs instanceof Node) { 177 return "<html>" + trn( 178 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 179 + " It is still referred to by node {1}.<br>" 180 + "Please load the node, remove the reference to the way, and upload again.", 181 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 182 + " It is still referred to by nodes {1}.<br>" 183 + "Please load the nodes, remove the reference to the way, and upload again.", 184 conflict.b.size(), objId, refIdsString) + "</html>"; 185 } else if (firstRefs instanceof Way) { 186 return "<html>" + trn( 187 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 188 + " It is still referred to by way {1}.<br>" 189 + "Please load the way, remove the reference to the way, and upload again.", 190 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 191 + " It is still referred to by ways {1}.<br>" 192 + "Please load the ways, remove the reference to the way, and upload again.", 193 conflict.b.size(), objId, refIdsString) + "</html>"; 194 } else if (firstRefs instanceof Relation) { 195 return "<html>" + trn( 196 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 197 + " It is still referred to by relation {1}.<br>" 198 + "Please load the relation, remove the reference to the way, and upload again.", 199 "<strong>Failed</strong> to delete <strong>way {0}</strong>." 200 + " It is still referred to by relations {1}.<br>" 201 + "Please load the relations, remove the reference to the way, and upload again.", 202 conflict.b.size(), objId, refIdsString) + "</html>"; 203 } else { 204 throw new IllegalStateException(); 205 } 206 } else if (conflict.a instanceof Relation) { 207 if (firstRefs instanceof Node) { 208 return "<html>" + trn( 209 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 210 + " It is still referred to by node {1}.<br>" 211 + "Please load the node, remove the reference to the relation, and upload again.", 212 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 213 + " It is still referred to by nodes {1}.<br>" 214 + "Please load the nodes, remove the reference to the relation, and upload again.", 215 conflict.b.size(), objId, refIdsString) + "</html>"; 216 } else if (firstRefs instanceof Way) { 217 return "<html>" + trn( 218 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 219 + " It is still referred to by way {1}.<br>" 220 + "Please load the way, remove the reference to the relation, and upload again.", 221 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 222 + " It is still referred to by ways {1}.<br>" 223 + "Please load the ways, remove the reference to the relation, and upload again.", 224 conflict.b.size(), objId, refIdsString) + "</html>"; 225 } else if (firstRefs instanceof Relation) { 226 return "<html>" + trn( 227 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 228 + " It is still referred to by relation {1}.<br>" 229 + "Please load the relation, remove the reference to the relation, and upload again.", 230 "<strong>Failed</strong> to delete <strong>relation {0}</strong>." 231 + " It is still referred to by relations {1}.<br>" 232 + "Please load the relations, remove the reference to the relation, and upload again.", 233 conflict.b.size(), objId, refIdsString) + "</html>"; 234 } else { 235 throw new IllegalStateException(); 236 } 237 } else { 238 throw new IllegalStateException(); 239 } 240 } else { 241 return tr( 242 "<html>Uploading to the server <strong>failed</strong> because your current<br>" 243 + "dataset violates a precondition.<br>" + "The error message is:<br>" + "{0}" + "</html>", 244 escapeReservedCharactersHTML(e.getMessage())); 245 } 246 } 247 248 public static String explainFailedBasicAuthentication(OsmApiException e) { 249 e.printStackTrace(); 250 return tr("<html>" 251 + "Authentication at the OSM server with the username ''{0}'' failed.<br>" 252 + "Please check the username and the password in the JOSM preferences." 253 + "</html>", 254 CredentialsManager.getInstance().getUsername() 255 ); 256 } 257 258 public static String explainFailedOAuthAuthentication(OsmApiException e) { 259 e.printStackTrace(); 260 return tr("<html>" 261 + "Authentication at the OSM server with the OAuth token ''{0}'' failed.<br>" 262 + "Please launch the preferences dialog and retrieve another OAuth token." 263 + "</html>", 264 OAuthAccessTokenHolder.getInstance().getAccessTokenKey() 265 ); 266 } 267 268 public static String explainFailedAuthorisation(OsmApiException e) { 269 e.printStackTrace(); 270 String header = e.getErrorHeader(); 271 String body = e.getErrorBody(); 272 if (body.equals("Your access to the API is temporarily suspended. Please log-in to the web interface to view the Contributor Terms. You do not need to agree, but you must view them.")) { 273 return tr("<html>" 274 +"Your access to the API is temporarily suspended.<br>" 275 + "Please log-in to the web interface to view the Contributor Terms.<br>" 276 + "You do not need to agree, but you must view them." 277 + "</html>"); 278 } 279 String msg = null; 280 if (header != null) { 281 if (body != null && !header.equals(body)) { 282 msg = header + " (" + body + ")"; 283 } else { 284 msg = header; 285 } 286 } else { 287 msg = body; 288 } 289 290 return tr("<html>" 291 + "Authorisation at the OSM server failed.<br>" 292 + "The server reported the following error:<br>" 293 + "''{0}''" 294 + "</html>", 295 msg 296 ); 297 } 298 299 public static String explainFailedOAuthAuthorisation(OsmApiException e) { 300 e.printStackTrace(); 301 return tr("<html>" 302 + "Authorisation at the OSM server with the OAuth token ''{0}'' failed.<br>" 303 + "The token is not authorised to access the protected resource<br>" 304 + "''{1}''.<br>" 305 + "Please launch the preferences dialog and retrieve another OAuth token." 306 + "</html>", 307 OAuthAccessTokenHolder.getInstance().getAccessTokenKey(), 308 e.getAccessedUrl() == null ? tr("unknown") : e.getAccessedUrl() 309 ); 310 } 311 312 /** 313 * Explains an OSM API exception because of a client timeout (HTTP 408). 314 * 315 * @param e the exception 316 * @return the message 317 */ 318 public static String explainClientTimeout(OsmApiException e) { 319 e.printStackTrace(); 320 return tr("<html>" 321 + "Communication with the OSM server ''{0}'' timed out. Please retry later." 322 + "</html>", 323 OsmApi.getOsmApi().getBaseUrl() 324 ); 325 } 326 327 /** 328 * Replies a generic error message for an OSM API exception 329 * 330 * @param e the exception 331 * @return the message 332 */ 333 public static String explainGenericOsmApiException(OsmApiException e) { 334 e.printStackTrace(); 335 String errMsg = e.getErrorHeader(); 336 if (errMsg == null) { 337 errMsg = e.getErrorBody(); 338 } 339 if (errMsg == null) { 340 errMsg = tr("no error message available"); 341 } 342 return tr("<html>" 343 + "Communication with the OSM server ''{0}''failed. The server replied<br>" 344 + "the following error code and the following error message:<br>" 345 + "<strong>Error code:<strong> {1}<br>" 346 + "<strong>Error message (untranslated)</strong>: {2}" 347 + "</html>", 348 OsmApi.getOsmApi().getBaseUrl(), 349 e.getResponseCode(), 350 errMsg 351 ); 352 } 353 354 /** 355 * Explains an error due to a 409 conflict 356 * 357 * @param e the exception 358 */ 359 public static String explainConflict(OsmApiException e) { 360 e.printStackTrace(); 361 String msg = e.getErrorHeader(); 362 if (msg != null) { 363 String pattern = "The changeset (\\d+) was closed at (.*)"; 364 Pattern p = Pattern.compile(pattern); 365 Matcher m = p.matcher(msg); 366 if (m.matches()) { 367 long changesetId = Long.parseLong(m.group(1)); 368 // Example: "2010-09-07 14:39:41 UTC". Always parsed with US locale, regardless 369 // of the current locale in JOSM 370 DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US); 371 Date closeDate = null; 372 try { 373 closeDate = formatter.parse(m.group(2)); 374 } catch(ParseException ex) { 375 System.err.println(tr("Failed to parse date ''{0}'' replied by server.", m.group(2))); 376 ex.printStackTrace(); 377 } 378 if (closeDate == null) { 379 msg = tr( 380 "<html>Closing of changeset <strong>{0}</strong> failed <br>because it has already been closed.", 381 changesetId 382 ); 383 } else { 384 SimpleDateFormat dateFormat = new SimpleDateFormat(); 385 msg = tr( 386 "<html>Closing of changeset <strong>{0}</strong> failed<br>" 387 +" because it has already been closed on {1}.", 388 changesetId, 389 dateFormat.format(closeDate) 390 ); 391 } 392 return msg; 393 } 394 msg = tr( 395 "<html>The server reported that it has detected a conflict.<br>" + 396 "Error message (untranslated):<br>{0}</html>", 397 msg 398 ); 399 } else { 400 msg = tr( 401 "<html>The server reported that it has detected a conflict."); 402 } 403 return msg; 404 } 405 406 /** 407 * Explains an exception thrown during upload because the changeset which data is 408 * uploaded to is already closed. 409 * 410 * @param e the exception 411 */ 412 public static String explainChangesetClosedException(ChangesetClosedException e) { 413 String msg; 414 SimpleDateFormat dateFormat = new SimpleDateFormat(); 415 msg = tr( 416 "<html>Failed to upload to changeset <strong>{0}</strong><br>" 417 +"because it has already been closed on {1}.", 418 e.getChangesetId(), 419 e.getClosedOn() == null ? "?" : dateFormat.format(e.getClosedOn()) 420 ); 421 e.printStackTrace(); 422 return msg; 423 } 424 425 /** 426 * Explains an exception with a generic message dialog 427 * 428 * @param e the exception 429 */ 430 public static String explainGeneric(Exception e) { 431 String msg = e.getMessage(); 432 if (msg == null || msg.trim().equals("")) { 433 msg = e.toString(); 434 } 435 e.printStackTrace(); 436 return escapeReservedCharactersHTML(msg); 437 } 438 439 /** 440 * Explains a {@link SecurityException} which has caused an {@link OsmTransferException}. 441 * This is most likely happening when user tries to access the OSM API from within an 442 * applet which wasn't loaded from the API server. 443 * 444 * @param e the exception 445 */ 446 447 public static String explainSecurityException(OsmTransferException e) { 448 String apiUrl = e.getUrl(); 449 String host = tr("unknown"); 450 try { 451 host = new URL(apiUrl).getHost(); 452 } catch (MalformedURLException ex) { 453 // shouldn't happen 454 } 455 456 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''<br>" 457 + "for security reasons. This is most likely because you are running<br>" 458 + "in an applet and because you did not load your applet from ''{1}''.", apiUrl, host); 459 return message; 460 } 461 462 /** 463 * Explains a {@link SocketException} which has caused an {@link OsmTransferException}. 464 * This is most likely because there's not connection to the Internet or because 465 * the remote server is not reachable. 466 * 467 * @param e the exception 468 */ 469 470 public static String explainNestedSocketException(OsmTransferException e) { 471 String apiUrl = e.getUrl(); 472 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 473 + "Please check your internet connection.", apiUrl); 474 e.printStackTrace(); 475 return message; 476 } 477 478 /** 479 * Explains a {@link IOException} which has caused an {@link OsmTransferException}. 480 * This is most likely happening when the communication with the remote server is 481 * interrupted for any reason. 482 * 483 * @param e the exception 484 */ 485 486 public static String explainNestedIOException(OsmTransferException e) { 487 IOException ioe = getNestedException(e, IOException.class); 488 String apiUrl = e.getUrl(); 489 String message = tr("<html>Failed to upload data to or download data from<br>" + "''{0}''<br>" 490 + "due to a problem with transferring data.<br>" + "Details(untranslated): {1}</html>", apiUrl, ioe 491 .getMessage()); 492 e.printStackTrace(); 493 return message; 494 } 495 496 /** 497 * Explains a {@link IllegalDataException} which has caused an {@link OsmTransferException}. 498 * This is most likely happening when JOSM tries to load data in in an unsupported format. 499 * 500 * @param e the exception 501 */ 502 public static String explainNestedIllegalDataException(OsmTransferException e) { 503 IllegalDataException ide = getNestedException(e, IllegalDataException.class); 504 String message = tr("<html>Failed to download data. " 505 + "Its format is either unsupported, ill-formed, and/or inconsistent.<br>" 506 + "<br>Details (untranslated): {0}</html>", ide.getMessage()); 507 e.printStackTrace(); 508 return message; 509 } 510 511 /** 512 * Explains a {@link OsmApiException} which was thrown because of an internal server 513 * error in the OSM API server.. 514 * 515 * @param e the exception 516 */ 517 518 public static String explainInternalServerError(OsmTransferException e) { 519 String apiUrl = e.getUrl(); 520 String message = tr("<html>The OSM server<br>" + "''{0}''<br>" + "reported an internal server error.<br>" 521 + "This is most likely a temporary problem. Please try again later.", apiUrl); 522 e.printStackTrace(); 523 return message; 524 } 525 526 /** 527 * Explains a {@link OsmApiException} which was thrown because of a bad 528 * request 529 * 530 * @param e the exception 531 */ 532 public static String explainBadRequest(OsmApiException e) { 533 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 534 String message = tr("The OSM server ''{0}'' reported a bad request.<br>", apiUrl); 535 if (e.getErrorHeader() != null && 536 (e.getErrorHeader().startsWith("The maximum bbox") || 537 e.getErrorHeader().startsWith("You requested too many nodes"))) { 538 message += "<br>" 539 + tr("The area you tried to download is too big or your request was too large." 540 + "<br>Either request a smaller area or use an export file provided by the OSM community."); 541 } else if (e.getErrorHeader() != null) { 542 message += tr("<br>Error message(untranslated): {0}", e.getErrorHeader()); 543 } 544 message = "<html>" + message + "</html>"; 545 e.printStackTrace(); 546 return message; 547 } 548 549 /** 550 * Explains a {@link OsmApiException} which was thrown because of 551 * bandwidth limit exceeded (HTTP error 509) 552 * 553 * @param e the exception 554 */ 555 public static String explainBandwidthLimitExceeded(OsmApiException e) { 556 // TODO: Write a proper error message 557 String message = explainGenericOsmApiException(e); 558 e.printStackTrace(); 559 return message; 560 } 561 562 563 /** 564 * Explains a {@link OsmApiException} which was thrown because a resource wasn't found. 565 * 566 * @param e the exception 567 */ 568 public static String explainNotFound(OsmApiException e) { 569 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 570 String message = tr("The OSM server ''{0}'' does not know about an object<br>" 571 + "you tried to read, update, or delete. Either the respective object<br>" 572 + "does not exist on the server or you are using an invalid URL to access<br>" 573 + "it. Please carefully check the server''s address ''{0}'' for typos." 574 , apiUrl); 575 message = "<html>" + message + "</html>"; 576 e.printStackTrace(); 577 return message; 578 } 579 580 /** 581 * Explains a {@link UnknownHostException} which has caused an {@link OsmTransferException}. 582 * This is most likely happening when there is an error in the API URL or when 583 * local DNS services are not working. 584 * 585 * @param e the exception 586 */ 587 588 public static String explainNestedUnknownHostException(OsmTransferException e) { 589 String apiUrl = e.getUrl(); 590 String host = tr("unknown"); 591 try { 592 host = new URL(apiUrl).getHost(); 593 } catch (MalformedURLException ex) { 594 // shouldn't happen 595 } 596 597 String message = tr("<html>Failed to open a connection to the remote server<br>" + "''{0}''.<br>" 598 + "Host name ''{1}'' could not be resolved. <br>" 599 + "Please check the API URL in your preferences and your internet connection.", apiUrl, host); 600 e.printStackTrace(); 601 return message; 602 } 603 604 /** 605 * Replies the first nested exception of type <code>nestedClass</code> (including 606 * the root exception <code>e</code>) or null, if no such exception is found. 607 * 608 * @param <T> 609 * @param e the root exception 610 * @param nestedClass the type of the nested exception 611 * @return the first nested exception of type <code>nestedClass</code> (including 612 * the root exception <code>e</code>) or null, if no such exception is found. 613 */ 614 protected static <T> T getNestedException(Exception e, Class<T> nestedClass) { 615 Throwable t = e; 616 while (t != null && !(nestedClass.isInstance(t))) { 617 t = t.getCause(); 618 } 619 if (t == null) 620 return null; 621 else if (nestedClass.isInstance(t)) 622 return nestedClass.cast(t); 623 return null; 624 } 625 626 /** 627 * Explains an {@link OsmTransferException} to the user. 628 * 629 * @param e the {@link OsmTransferException} 630 */ 631 public static String explainOsmTransferException(OsmTransferException e) { 632 if (getNestedException(e, SecurityException.class) != null) 633 return explainSecurityException(e); 634 if (getNestedException(e, SocketException.class) != null) 635 return explainNestedSocketException(e); 636 if (getNestedException(e, UnknownHostException.class) != null) 637 return explainNestedUnknownHostException(e); 638 if (getNestedException(e, IOException.class) != null) 639 return explainNestedIOException(e); 640 if (e instanceof OsmApiInitializationException) 641 return explainOsmApiInitializationException((OsmApiInitializationException) e); 642 643 if (e instanceof ChangesetClosedException) 644 return explainChangesetClosedException((ChangesetClosedException)e); 645 646 if (e instanceof OsmApiException) { 647 OsmApiException oae = (OsmApiException) e; 648 if (oae.getResponseCode() == HttpURLConnection.HTTP_PRECON_FAILED) 649 return explainPreconditionFailed(oae); 650 if (oae.getResponseCode() == HttpURLConnection.HTTP_GONE) 651 return explainGoneForUnknownPrimitive(oae); 652 if (oae.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) 653 return explainInternalServerError(oae); 654 if (oae.getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST) 655 return explainBadRequest(oae); 656 if (oae.getResponseCode() == 509) 657 return explainBandwidthLimitExceeded(oae); 658 } 659 return explainGeneric(e); 660 } 661 662 /** 663 * explains the case of an error due to a delete request on an already deleted 664 * {@link OsmPrimitive}, i.e. a HTTP response code 410, where we don't know which 665 * {@link OsmPrimitive} is causing the error. 666 * 667 * @param e the exception 668 */ 669 public static String explainGoneForUnknownPrimitive(OsmApiException e) { 670 String msg = tr( 671 "<html>The server reports that an object is deleted.<br>" 672 + "<strong>Uploading failed</strong> if you tried to update or delete this object.<br> " 673 + "<strong>Downloading failed</strong> if you tried to download this object.<br>" 674 + "<br>" 675 + "The error message is:<br>" + "{0}" 676 + "</html>", escapeReservedCharactersHTML(e.getMessage())); 677 return msg; 678 679 } 680 681 /** 682 * Explains an {@link Exception} to the user. 683 * 684 * @param e the {@link Exception} 685 */ 686 public static String explainException(Exception e) { 687 String msg = ""; 688 if (e instanceof OsmTransferException) { 689 msg = explainOsmTransferException((OsmTransferException) e); 690 } else { 691 msg = explainGeneric(e); 692 } 693 e.printStackTrace(); 694 return msg; 695 } 696 697 /** 698 * Replaces some HTML reserved characters (<, > and &) by their equivalent entity (<, > and &); 699 * @param s The unescaped string 700 * @return The escaped string 701 */ 702 public static String escapeReservedCharactersHTML(String s) { 703 return s == null ? "" : s.replace("&", "&").replace("<", "<").replace(">", ">"); 704 } 705 }