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