MapWithAILayerInfo: Don't block JOSM startup

Signed-off-by: Taylor Smock <tsmock@fb.com>
pull/1/head
Taylor Smock 2021-06-30 16:03:17 -06:00
rodzic 64ff82db04
commit ed6b8bcd5d
7 zmienionych plików z 250 dodań i 89 usunięć

Wyświetl plik

@ -24,6 +24,7 @@ import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.download.DownloadDialog;
import org.openstreetmap.josm.gui.download.OSMDownloadSource;
import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;
@ -35,6 +36,7 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIObject;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIRemoteControl;
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIUploadHook;
import org.openstreetmap.josm.plugins.mapwithai.backend.MergeDuplicateWaysAction;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.ConnectingNodeInformationTest;
import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.RoutingIslandsTest;
@ -118,6 +120,9 @@ public final class MapWithAIPlugin extends Plugin implements Destroyable {
MapPaintUtils.addMapWithAIPaintStyles();
destroyables = new ArrayList<>();
// Run in EDT to avoid blocking (has to be run before MapWithAIDownloadOptions
// so its already initialized)
GuiHelper.runInEDT(MapWithAILayerInfo::getInstance);
MapWithAIDownloadOptions mapWithAIDownloadOptions = new MapWithAIDownloadOptions();
mapWithAIDownloadOptions.addGui(DownloadDialog.getInstance());
destroyables.add(mapWithAIDownloadOptions);

Wyświetl plik

@ -16,18 +16,18 @@ import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.swing.SwingUtilities;
import org.openstreetmap.josm.actions.ExpertToggleAction;
import org.openstreetmap.josm.data.StructUtils;
import org.openstreetmap.josm.data.imagery.ImageryInfo;
import org.openstreetmap.josm.data.preferences.BooleanProperty;
import org.openstreetmap.josm.gui.MainApplication;
import org.openstreetmap.josm.gui.PleaseWaitRunnable;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.CachedFile;
import org.openstreetmap.josm.io.NetworkManager;
import org.openstreetmap.josm.io.imagery.ImageryReader;
@ -48,6 +48,8 @@ public class MapWithAILayerInfo {
*/
public static final BooleanProperty SHOW_PREVIEW = new BooleanProperty("mapwithai.sources.preview", false);
/** Finish listeners */
private ListenerList<FinishListener> finishListenerListenerList = ListenerList.create();
/** List of all usable layers */
private final List<MapWithAIInfo> layers = Collections.synchronizedList(new ArrayList<>());
/** List of layer ids of all usable layers */
@ -70,23 +72,29 @@ public class MapWithAILayerInfo {
private static MapWithAILayerInfo instance;
public static MapWithAILayerInfo getInstance() {
AtomicBoolean finished;
final AtomicBoolean finished = new AtomicBoolean();
synchronized (DEFAULT_LAYER_SITES) {
if (instance == null) {
finished = new AtomicBoolean();
instance = new MapWithAILayerInfo(() -> finished.set(true));
instance = new MapWithAILayerInfo(() -> {
synchronized (finished) {
finished.set(true);
finished.notifyAll();
}
});
} else {
finished = null;
finished.set(true);
}
}
// Avoid a deadlock in the EDT.
if (finished != null && !SwingUtilities.isEventDispatchThread()) {
while (!finished.get()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Logging.error(e);
Thread.currentThread().interrupt();
if (!finished.get() && !SwingUtilities.isEventDispatchThread()) {
synchronized (finished) {
while (!finished.get()) {
try {
finished.wait();
} catch (InterruptedException e) {
Logging.error(e);
Thread.currentThread().interrupt();
}
}
}
}
@ -169,7 +177,12 @@ public class MapWithAILayerInfo {
layers.removeIf(i -> i.getUrl().contains("localhost:8111"));
Collections.sort(layers);
}
loadDefaults(false, MainApplication.worker, fastFail, listener);
// Ensure that the cache is initialized prior to running in the fork join pool
// on webstart
if (System.getSecurityManager() != null) {
ESRISourceReader.SOURCE_CACHE.getClass();
}
loadDefaults(false, ForkJoinPool.commonPool(), fastFail, listener);
}
/**
@ -188,18 +201,53 @@ public class MapWithAILayerInfo {
* @since 12634
*/
public void loadDefaults(boolean clearCache, ExecutorService worker, boolean fastFail, FinishListener listener) {
final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail, listener);
final DefaultEntryLoader loader = new DefaultEntryLoader(clearCache, fastFail);
if (this.finishListenerListenerList == null) {
this.finishListenerListenerList = ListenerList.create();
}
if (listener != null) {
this.finishListenerListenerList.addListener(listener);
}
if (worker == null) {
loader.realRun();
PleaseWaitRunnable pleaseWaitRunnable = new PleaseWaitRunnable(tr("Update default entries")) {
@Override
protected void cancel() {
loader.canceled = true;
}
@Override
protected void realRun() {
loader.run();
}
@Override
protected void finish() {
loader.finish();
}
};
pleaseWaitRunnable.run();
} else {
worker.execute(loader);
}
}
/**
* Add a listener for when the data finishes updating
*
* @param finishListener The listener
*/
public void addFinishListener(final FinishListener finishListener) {
if (this.finishListenerListenerList == null) {
finishListener.onFinish();
} else {
this.finishListenerListenerList.addListener(finishListener);
}
}
/**
* Loader/updater of the available imagery entries
*/
class DefaultEntryLoader extends PleaseWaitRunnable {
class DefaultEntryLoader implements Runnable {
private final boolean clearCache;
private final boolean fastFail;
@ -207,30 +255,28 @@ public class MapWithAILayerInfo {
private MapWithAISourceReader reader;
private boolean canceled;
private boolean loadError;
private final FinishListener listener;
DefaultEntryLoader(boolean clearCache, boolean fastFail, FinishListener listener) {
super(tr("Update default entries"));
DefaultEntryLoader(boolean clearCache, boolean fastFail) {
this.clearCache = clearCache;
this.fastFail = fastFail;
this.listener = listener;
}
@Override
protected void cancel() {
canceled = true;
Utils.close(reader);
}
@Override
protected void realRun() {
public void run() {
if (this.clearCache) {
ESRISourceReader.SOURCE_CACHE.clear();
}
for (String source : getImageryLayersSites()) {
if (canceled) {
return;
}
loadSource(source);
}
GuiHelper.runInEDT(this::finish);
this.finish();
}
protected void loadSource(String source) {
@ -242,6 +288,9 @@ public class MapWithAILayerInfo {
reader = new MapWithAISourceReader(source);
reader.setFastFail(fastFail);
Collection<MapWithAIInfo> result = reader.parse();
// This is called here to "pre-cache" the layer information, to avoid blocking
// the EDT
this.updateEsriLayers(result);
newLayers.addAll(result);
} catch (IOException ex) {
loadError = true;
@ -249,18 +298,26 @@ public class MapWithAILayerInfo {
}
}
@Override
protected void finish() {
defaultLayers.clear();
allDefaultLayers.clear();
defaultLayers.addAll(newLayers);
for (MapWithAIInfo layer : newLayers) {
/**
* Update the esri layer information
*
* @param layers The layers to update
*/
private void updateEsriLayers(@Nonnull final Collection<MapWithAIInfo> layers) {
for (MapWithAIInfo layer : layers) {
if (MapWithAIType.ESRI == layer.getSourceType()) {
allDefaultLayers.addAll(parseEsri(layer));
} else {
allDefaultLayers.add(layer);
}
}
}
protected void finish() {
defaultLayers.clear();
allDefaultLayers.clear();
defaultLayers.addAll(newLayers);
this.updateEsriLayers(newLayers);
defaultLayerIds.clear();
Collections.sort(defaultLayers, new MapWithAIInfo.MapWithAIInfoCategoryComparator());
@ -271,8 +328,10 @@ public class MapWithAILayerInfo {
if (!loadError && !defaultLayerIds.isEmpty()) {
dropOldEntries();
}
if (listener != null) {
listener.onFinish();
final ListenerList<FinishListener> listenerList = MapWithAILayerInfo.this.finishListenerListenerList;
MapWithAILayerInfo.this.finishListenerListenerList = null;
if (listenerList != null) {
listenerList.fireEvent(FinishListener::onFinish);
}
}
@ -283,8 +342,8 @@ public class MapWithAILayerInfo {
* @return The Feature Servers for the ESRI layer
*/
private Collection<MapWithAIInfo> parseEsri(MapWithAIInfo layer) {
try (ESRISourceReader esriReader = new ESRISourceReader(layer)) {
return esriReader.parse();
try {
return new ESRISourceReader(layer).parse();
} catch (IOException e) {
Logging.error(e);
}

Wyświetl plik

@ -14,6 +14,7 @@ import java.util.stream.Stream;
import javax.swing.table.DefaultTableModel;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
@ -49,6 +50,7 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
columnDataRetrieval.add(i -> i.getAttributionText(0, null, null));
columnDataRetrieval.add(info -> Optional.ofNullable(info.getTermsOfUseURL()).orElse(""));
columnDataRetrieval.add(i -> MapWithAILayerInfo.getInstance().getLayers().contains(i));
MapWithAILayerInfo.getInstance().addFinishListener(() -> GuiHelper.runInEDT(this::fireTableDataChanged));
}
/**
@ -58,12 +60,15 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
* @return The imagery info at the given row number
*/
public static MapWithAIInfo getRow(int row) {
if (row == 0 && MapWithAILayerInfo.getInstance().getAllDefaultLayers().isEmpty()) {
return new MapWithAIInfo(tr("Loading"), "");
}
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
}
@Override
public int getRowCount() {
return MapWithAILayerInfo.getInstance().getAllDefaultLayers().size();
return Math.max(MapWithAILayerInfo.getInstance().getAllDefaultLayers().size(), 1);
}
@Override
@ -76,7 +81,7 @@ class MapWithAIDefaultLayerTableModel extends DefaultTableModel {
@Override
public Object getValueAt(int row, int column) {
MapWithAIInfo info = MapWithAILayerInfo.getInstance().getAllDefaultLayers().get(row);
MapWithAIInfo info = getRow(row);
if (column < columnDataRetrieval.size()) {
return columnDataRetrieval.get(column).apply(info);
}

Wyświetl plik

@ -190,8 +190,8 @@ public class MapWithAIProvidersPanel extends JPanel {
if (MapWithAILayerTableModel.contains(info)) {
Color t = IMAGERY_BACKGROUND_COLOR.get();
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
} else if (this.area != null && (this.area.intersects(info.getBounds())
|| (info.getBounds() != null && info.getBounds().intersects(this.area)))) {
} else if (this.area != null && info.getBounds() != null
&& (this.area.intersects(info.getBounds()) || (info.getBounds().intersects(this.area)))) {
Color t = MAPWITHAI_AREA_BACKGROUND_COLOR.get();
GuiHelper.setBackgroundReadable(label, isSelected ? t.darker() : t);
} else {
@ -657,7 +657,8 @@ public class MapWithAIProvidersPanel extends JPanel {
info.setSourceType(MapWithAIType.THIRD_PARTY);
}
if (MapWithAIType.ESRI == info.getSourceType()) {
try (ESRISourceReader reader = new ESRISourceReader(info)) {
final ESRISourceReader reader = new ESRISourceReader(info);
try {
for (MapWithAIInfo i : reader.parse()) {
activeModel.addRow(i);
}
@ -845,11 +846,11 @@ public class MapWithAIProvidersPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent evt) {
MapWithAILayerInfo.getInstance().loadDefaults(true, MainApplication.worker, false, () -> {
defaultModel.fireTableDataChanged();
defaultTable.getSelectionModel().clearSelection();
defaultTableListener.clearMap();
GuiHelper.runInEDT(defaultModel::fireTableDataChanged);
GuiHelper.runInEDT(defaultTable.getSelectionModel()::clearSelection);
GuiHelper.runInEDT(defaultTableListener::clearMap);
/* loading new file may change active layers */
activeModel.fireTableDataChanged();
GuiHelper.runInEDT(activeModel::fireTableDataChanged);
});
}
}

Wyświetl plik

@ -1,18 +1,23 @@
// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.mapwithai.io.mapwithai;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonNumber;
@ -21,23 +26,30 @@ import javax.json.JsonReader;
import javax.json.JsonString;
import javax.json.JsonStructure;
import javax.json.JsonValue;
import javax.json.stream.JsonParsingException;
import org.openstreetmap.josm.data.cache.JCSCacheManager;
import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
import org.openstreetmap.josm.io.CachedFile;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
import org.openstreetmap.josm.spi.preferences.Config;
import org.openstreetmap.josm.tools.HttpClient;
import org.openstreetmap.josm.tools.Logging;
import org.openstreetmap.josm.tools.Utils;
import org.apache.commons.jcs3.access.CacheAccess;
import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
/**
* Take a {@link MapWithAIInfo.MapWithAIType#ESRI} layer and convert it to a
* list of "true" layers.
*/
public class ESRISourceReader implements Closeable {
public class ESRISourceReader {
/** The cache storing ESRI source information (json) */
public static final CacheAccess<String, String> SOURCE_CACHE = JCSCacheManager.getCache("mapwithai:esrisources", 5,
50_000, new File(Config.getDirs().getCacheDirectory(true), "mapwithai").getPath());
private final MapWithAIInfo source;
private final List<CachedFile> cachedFiles = new ArrayList<>();
private boolean fastFail;
private final List<MapWithAICategory> ignoreConflationCategories;
private static final String JSON_QUERY_PARAM = "?f=json";
@ -84,11 +96,13 @@ public class ESRISourceReader implements Closeable {
String next = "1";
String searchUrl = startReplace.matcher(search).replaceAll(next);
while (!next.equals("-1")) {
try (CachedFile layers = new CachedFile(url + "content/groups/" + group + searchUrl);
BufferedReader i = layers.getContentReader();
JsonReader reader = Json.createReader(i)) {
cachedFiles.add(layers);
layers.setFastFail(fastFail);
final String finalUrl = url + "content/groups/" + group + searchUrl;
final String jsonString = getJsonString(finalUrl, this.fastFail);
if (jsonString == null) {
continue;
}
try (JsonReader reader = Json
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
JsonStructure parser = reader.read();
if (parser.getValueType() == JsonValue.ValueType.OBJECT) {
JsonObject obj = parser.asJsonObject();
@ -102,7 +116,7 @@ public class ESRISourceReader implements Closeable {
information.add(parse(feature));
}
}
} catch (ClassCastException | IOException e) {
} catch (ClassCastException e) {
Logging.error(e);
next = "-1";
}
@ -115,6 +129,59 @@ public class ESRISourceReader implements Closeable {
return information;
}
/**
* Get the json string for a URL
*
* @param url The URL to get
* @param fastFail Fail fast (1 second)
* @return The json string, or {@code null}.
*/
@Nullable
private static String getJsonString(@Nonnull final String url, final boolean fastFail) {
String jsonString = SOURCE_CACHE.get(url);
// TODO FIXME remove sometime after January 2022 (give it a chance to cleanup
// directories)
CachedFile.cleanup(url);
if (jsonString == null) {
synchronized (SOURCE_CACHE) {
jsonString = SOURCE_CACHE.get(url);
if (jsonString == null) {
HttpClient client = null;
try {
client = HttpClient.create(new URL(url));
if (fastFail) {
client.setReadTimeout(1000);
}
final HttpClient.Response response = client.connect();
jsonString = response.fetchContent();
if (jsonString != null && response.getResponseCode() < 400
&& response.getResponseCode() >= 200) {
final IElementAttributes elementAttributes = SOURCE_CACHE.getDefaultElementAttributes();
// getExpiration returns milliseconds
final long expirationTime = response.getExpiration();
if (expirationTime > 0) {
elementAttributes.setMaxLife(response.getExpiration());
} else {
elementAttributes.setMaxLife(TimeUnit.SECONDS.toMillis(
Config.getPref().getLong("mirror.maxtime", TimeUnit.DAYS.toSeconds(7))));
}
SOURCE_CACHE.put(url, jsonString, elementAttributes);
} else {
jsonString = null;
}
} catch (final IOException e) {
Logging.error(e);
} finally {
if (client != null) {
client.disconnect();
}
}
}
}
}
return jsonString;
}
private MapWithAIInfo parse(JsonObject feature) {
// Use the initial esri server information to keep conflation info
MapWithAIInfo newInfo = new MapWithAIInfo(source);
@ -168,42 +235,60 @@ public class ESRISourceReader implements Closeable {
return (newInfo);
}
private static String featureService(MapWithAIInfo mapwithaiInfo, String url) {
String toGet = url.endsWith(JSON_QUERY_PARAM) ? url : url.concat(JSON_QUERY_PARAM);
try (CachedFile featureServer = new CachedFile(toGet);
BufferedReader br = featureServer.getContentReader();
JsonReader reader = Json.createReader(br)) {
/**
* Get feature service information
*
* @param mapwithaiInfo The info to update
* @param url The url to get
* @return The base url for the feature service
*/
@Nullable
private String featureService(@Nonnull MapWithAIInfo mapwithaiInfo, @Nonnull String url) {
final String toGet = url.endsWith(JSON_QUERY_PARAM) ? url : url.concat(JSON_QUERY_PARAM);
final String jsonString = getJsonString(toGet, this.fastFail);
if (jsonString == null) {
return null;
}
try (JsonReader reader = Json
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
JsonObject info = reader.readObject();
JsonArray layers = info.getJsonArray("layers");
// This fixes #20551
if (layers == null || layers.stream().noneMatch(jsonvalue -> jsonvalue != null)) {
if (layers == null || layers.stream().noneMatch(Objects::nonNull)) {
return null;
}
// TODO use all the layers?
JsonObject layer = layers.stream().filter(jsonValue -> jsonValue != null).findFirst()
.orElse(JsonObject.EMPTY_JSON_OBJECT).asJsonObject();
JsonObject layer = layers.stream().filter(Objects::nonNull).findFirst().orElse(JsonValue.EMPTY_JSON_OBJECT)
.asJsonObject();
if (layer.containsKey("id")) {
String partialUrl = (url.endsWith("/") ? url : url + "/") + layer.getInt("id");
mapwithaiInfo.setReplacementTags(getReplacementTags(partialUrl));
return partialUrl;
}
return null;
} catch (IOException e) {
} catch (JsonParsingException e) {
Logging.error(e);
return null;
}
return null;
}
private static Map<String, String> getReplacementTags(String layerUrl) {
/**
* Get the replacement tags for a feature service
*
* @param layerUrl The service url
* @return A map of replacement tags
*/
@Nonnull
private Map<String, String> getReplacementTags(@Nonnull String layerUrl) {
String toGet = layerUrl.endsWith(JSON_QUERY_PARAM) ? layerUrl : layerUrl.concat(JSON_QUERY_PARAM);
try (CachedFile featureServer = new CachedFile(toGet);
BufferedReader br = featureServer.getContentReader();
JsonReader reader = Json.createReader(br)) {
return reader.readObject().getJsonArray("fields").getValuesAs(JsonObject.class).stream()
.collect(Collectors.toMap(o -> o.getString("name"), ESRISourceReader::getReplacementTag));
} catch (IOException e) {
Logging.error(e);
final String jsonString = getJsonString(toGet, this.fastFail);
if (jsonString != null) {
try (JsonReader reader = Json
.createReader(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)))) {
return reader.readObject().getJsonArray("fields").getValuesAs(JsonObject.class).stream()
.collect(Collectors.toMap(o -> o.getString("name"), ESRISourceReader::getReplacementTag));
}
}
return Collections.emptyMap();
}
@ -225,10 +310,4 @@ public class ESRISourceReader implements Closeable {
public void setFastFail(boolean fastFail) {
this.fastFail = fastFail;
}
@Override
public void close() throws IOException {
cachedFiles.forEach(Utils::close);
}
}

Wyświetl plik

@ -9,6 +9,8 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
@ -19,6 +21,16 @@ class ESRISourceReaderTest {
@RegisterExtension
MapWithAITestRules rule = (MapWithAITestRules) new MapWithAITestRules().wiremock().projection();
@BeforeEach
void setUp() {
ESRISourceReader.SOURCE_CACHE.clear();
}
@AfterEach
void tearDown() {
this.setUp();
}
/**
* Test that ESRI servers are properly added
*
@ -33,15 +45,14 @@ class ESRISourceReaderTest {
String tUrl = rule.getWireMock().baseUrl() + "/sharing/rest";
for (String url : Arrays.asList(tUrl, tUrl + "/")) {
info.setUrl(url);
try (ESRISourceReader reader = new ESRISourceReader(info)) {
Collection<MapWithAIInfo> layers = reader.parse();
assertFalse(layers.isEmpty(), "There should be a MapWithAI layer");
assertTrue(layers.stream().noneMatch(i -> info.getUrl().equals(i.getUrl())),
"The ESRI server should be expanded to feature servers");
assertTrue(layers.stream().allMatch(i -> MapWithAIType.ESRI_FEATURE_SERVER.equals(i.getSourceType())),
"There should only be ESRI feature servers");
assertEquals(24, layers.size());
}
final ESRISourceReader reader = new ESRISourceReader(info);
Collection<MapWithAIInfo> layers = reader.parse();
assertFalse(layers.isEmpty(), "There should be a MapWithAI layer");
assertTrue(layers.stream().noneMatch(i -> info.getUrl().equals(i.getUrl())),
"The ESRI server should be expanded to feature servers");
assertTrue(layers.stream().allMatch(i -> MapWithAIType.ESRI_FEATURE_SERVER.equals(i.getSourceType())),
"There should only be ESRI feature servers");
assertEquals(24, layers.size());
}
}
}

Wyświetl plik

@ -20,6 +20,7 @@ import org.awaitility.Awaitility;
import org.awaitility.Durations;
import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation;
import org.junit.runners.model.InitializationError;
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.io.OsmApi;