Subject: [PATCH] #23485: JOSM crashes when opening Imagery Preferences
---
Index: scripts/SyncEditorLayerIndex.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/scripts/SyncEditorLayerIndex.java b/scripts/SyncEditorLayerIndex.java
--- a/scripts/SyncEditorLayerIndex.java	(revision 18988)
+++ b/scripts/SyncEditorLayerIndex.java	(date 1708361798032)
@@ -6,10 +6,12 @@
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.lang.reflect.Field;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
@@ -37,14 +39,6 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import jakarta.json.Json;
-import jakarta.json.JsonArray;
-import jakarta.json.JsonNumber;
-import jakarta.json.JsonObject;
-import jakarta.json.JsonReader;
-import jakarta.json.JsonString;
-import jakarta.json.JsonValue;
-
 import org.openstreetmap.gui.jmapviewer.Coordinate;
 import org.openstreetmap.josm.data.Preferences;
 import org.openstreetmap.josm.data.imagery.ImageryInfo;
@@ -57,6 +51,8 @@
 import org.openstreetmap.josm.data.validation.routines.DomainValidator;
 import org.openstreetmap.josm.io.imagery.ImageryReader;
 import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.tools.HttpClient;
+import org.openstreetmap.josm.tools.Http1Client;
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
@@ -66,6 +62,14 @@
 import org.openstreetmap.josm.tools.Utils;
 import org.xml.sax.SAXException;
 
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonNumber;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonString;
+import jakarta.json.JsonValue;
+
 /**
  * Compare and analyse the differences of the editor layer index and the JOSM imagery list.
  * The goal is to keep both lists in sync.
@@ -236,6 +240,9 @@
     }
 
     void loadSkip() throws IOException {
+        if (Files.notExists(Paths.get(ignoreInputFile))) {
+            return;
+        }
         final Pattern pattern = Pattern.compile("^\\|\\| *(ELI|Ignore) *\\|\\| *\\{\\{\\{(.+)\\}\\}\\} *\\|\\|");
         try (BufferedReader fr = Files.newBufferedReader(Paths.get(ignoreInputFile), UTF_8)) {
             String line;
@@ -494,11 +501,24 @@
     }
 
     void loadJosmEntries() throws IOException, SAXException, ReflectiveOperationException {
+        if (Files.notExists(Paths.get(josmInputFile))) {
+            HttpClient.setFactory(Http1Client::new);
+            final HttpClient client = HttpClient.create(URI.create("https://josm.openstreetmap.de/maps").toURL());
+            try (InputStream inputStream = client.connect().getContent()) {
+                Files.copy(inputStream, Paths.get(josmInputFile));
+            } finally {
+                client.disconnect();
+            }
+        }
+
         try (ImageryReader reader = new ImageryReader(josmInputFile)) {
             josmEntries = reader.parse();
         }
 
         for (ImageryInfo e : josmEntries) {
+            if (!e.isValid()) {
+                myprintln("~~~ JOSM-Entry missing fields (" + String.join(", ", e.getMissingFields()) + "): " + getDescription(e));
+            }
             if (isBlank(getUrl(e))) {
                 myprintln("~~~ JOSM-Entry without URL: " + getDescription(e));
                 continue;
Index: src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java b/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
--- a/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(revision 18988)
+++ b/src/org/openstreetmap/josm/data/imagery/ImageryInfo.java	(date 1708360958673)
@@ -19,9 +19,6 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import jakarta.json.Json;
-import jakarta.json.JsonObject;
-import jakarta.json.JsonReader;
 import javax.swing.ImageIcon;
 
 import org.openstreetmap.josm.data.StructUtils.StructEntry;
@@ -39,6 +36,10 @@
 import org.openstreetmap.josm.tools.StreamUtils;
 import org.openstreetmap.josm.tools.Utils;
 
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+
 /**
  * Class that stores info about an image background layer.
  *
@@ -205,6 +206,8 @@
         }
     }
 
+    private static final String[] EMPTY_STRING = new String[0];
+
     private double pixelPerDegree;
     /** maximum zoom level for TMS imagery */
     private int defaultMaxZoom;
@@ -963,6 +966,46 @@
         }
     }
 
+    /**
+     * Check to see if this info is valid (the XSD is overly permissive due to limitations of the XSD syntax)
+     * @return {@code true} if this info is valid
+     * @since xxx
+     */
+    public boolean isValid() {
+        return this.getName() != null &&
+                this.getId() != null &&
+                this.getSourceType() != null &&
+                this.getUrl() != null &&
+                this.getImageryCategory() != null;
+    }
+
+    /**
+     * Get the missing fields for this info
+     * @return The missing fields, or an empty array
+     */
+    public String[] getMissingFields() {
+        if (this.isValid()) {
+            return EMPTY_STRING;
+        }
+        List<String> missingFields = new ArrayList<>();
+        if (this.getName() == null) {
+            missingFields.add("name");
+        }
+        if (this.getId() == null) {
+            missingFields.add("id");
+        }
+        if (this.getSourceType() == null) {
+            missingFields.add("type");
+        }
+        if (this.getUrl() == null) {
+            missingFields.add("url");
+        }
+        if (this.getImageryCategory() == null) {
+            missingFields.add("category");
+        }
+        return missingFields.toArray(new String[0]);
+    }
+
     /**
      * Return the sorted list of activated source IDs.
      * @return sorted list of activated source IDs
Index: src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java b/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
--- a/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(revision 18988)
+++ b/src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java	(date 1708360724185)
@@ -166,6 +166,8 @@
                 reader = new ImageryReader(source);
                 reader.setFastFail(fastFail);
                 Collection<ImageryInfo> result = reader.parse();
+                // See #23485 (remove invalid source entries)
+                result.removeIf(info -> !info.isValid());
                 newLayers.addAll(result);
             } catch (IOException ex) {
                 loadError = true;
