From e3f000b8cabf19d9cd5ccca98c07071a7ad0f821 Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Tue, 30 Jul 2024 07:21:24 -0600 Subject: [PATCH 1/2] Use standardized GitHub workflow Signed-off-by: Taylor Smock --- .github/workflows/ant.yml | 103 +++++++++++++++----------------------- 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 153d9d2a..417a4782 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -2,71 +2,48 @@ name: Java CI on: push: - branches: [ main, fixup-tests ] + branches: + - main + - $default-branch + - $protected-branches pull_request: - branches: [ main ] - release: - types: [created] + branches: + - main + - $default-branch + workflow_dispatch: jobs: - build: + check-release-needed: runs-on: ubuntu-latest + outputs: + release_needed: ${{ steps.create_release_needed.outputs.release_needed }} steps: - - name: Set up JDK 8 - uses: actions/setup-java@v3 - with: - java-version: '8' - distribution: 'temurin' - - name: Cache - uses: actions/cache@v2.0.0 - with: - path: | - ~/.ivy2/cache/ - $GITHUB_WORKSPACE/josm/core/tools/ - key: ${{ runner.os }}-ivy2-${{ hashFiles('josm/plugins/${{ github.event.repository.name }}/build.xml', 'josm/plugins/00_core_tools/ivy.xml', 'josm/core/ivy.xml', 'josm/core/tools/ivy.xml') }} - - name: Clone JOSM - run: | - svn co --depth=immediates https://josm.openstreetmap.de/osmsvn/applications/editors/josm $GITHUB_WORKSPACE/josm - cd $GITHUB_WORKSPACE/josm - svn up --set-depth=immediates plugins - svn up --set-depth=infinity --accept=theirs-full core i18n plugins/{00_core_test_config,00_core_test_lib,00_core_tools,00_tools} - svn propget svn:externals | grep core | xargs -L1 svn co - cd core - ant dist - cd ../plugins - svn propget svn:externals | grep 00_core | xargs -L1 svn co - - uses: actions/checkout@v3 - with: - path: josm/plugins/${{ github.event.repository.name }} - - name: Build with Ant - run: | - cd $GITHUB_WORKSPACE/josm/plugins/${{ github.event.repository.name }} - if [ $GITHUB_REF_TYPE == "tag" ]; then - version=$GITHUB_REF_NAME - else - version="$GITHUB_REF_NAME-$GITHUB_SHA" - fi - ant -noinput -buildfile build.xml -Dplugin.version=$version - - name: Test with Ant - run: | - cd $GITHUB_WORKSPACE/josm/plugins/${{ github.event.repository.name }} - ant -noinput -buildfile build.xml -Dplugin.version=$version -Dtest.headless test - - name: Dump errors if failed - if: ${{ failure() }} - run: "grep -L ', Failures: 0, Skipped: ' test/report/*.txt | xargs cat" - - name: Upload Ant reports - if: ${{ always() }} - uses: actions/upload-artifact@v2 - with: - name: Ant reports for JOSM plugin ${{ github.event.repository.name }} - path: | - $GITHUB_WORKSPACE/josm/plugins/${{ github.event.repository.name }}/test/report/*.txt - $GITHUB_WORKSPACE/josm/plugins/${{ github.event.repository.name }}/test/report/TEST*.xml - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - josm/dist/${{ github.event.repository.name }}.jar - josm/dist/${{ github.event.repository.name }}-javadoc.jar - josm/dist/${{ github.event.repository.name }}-sources.jar + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: ${{ (github.repository == 'JOSM/wikipedia' && github.ref_type == 'branch' && github.ref_name == 'main' && github.event_name != 'schedule' && github.event_name != 'pull_request') && '0' || '1' }} + + - name: Set release needed + id: create_release_needed + run: | + last_tag=$(git describe --tags --abbrev=0 --always) + release_needed="false" + for file in $(git diff ${last_tag}..HEAD --name-only); do + if [[ $file = "src/main"* ]] || [[ $file = "" ]]; then + release_needed="true" + break + fi + done + echo "release_needed=$release_needed" >> $GITHUB_OUTPUT + + + call-workflow: + needs: check-release-needed + strategy: + matrix: + josm-revision: ["", "r18877"] + uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v2 + with: + josm-revision: ${{ matrix.josm-revision }} + perform-revision-tagging: ${{ matrix.josm-revision == 'r18877' && needs.check-release-needed.outputs.release_needed == 'true' }} + secrets: inherit From 013124a3630cacc9c35d1f56559101f48a42ed6e Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Tue, 30 Jul 2024 08:06:04 -0600 Subject: [PATCH 2/2] Significantly reduce performance impact We were previously regenerating the article list on just about ''every'' dataset change. This included node movement. We fix that by listening for events that are ''only'' related to objects gaining or losing tags. This additionally fixes a bunch of lint issues. Signed-off-by: Taylor Smock --- src/main/java/org/wikipedia/WikipediaApp.java | 118 +++++++++----- .../wikipedia/gui/WikipediaToggleDialog.java | 151 ++++++++++++------ 2 files changed, 173 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/wikipedia/WikipediaApp.java b/src/main/java/org/wikipedia/WikipediaApp.java index aa93ab29..c7512a83 100644 --- a/src/main/java/org/wikipedia/WikipediaApp.java +++ b/src/main/java/org/wikipedia/WikipediaApp.java @@ -4,6 +4,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.AbstractList; @@ -29,9 +30,11 @@ import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.Tagged; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.tools.HttpClient; import org.openstreetmap.josm.tools.I18n; +import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.LanguageInfo; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Pair; @@ -55,21 +58,28 @@ public final class WikipediaApp { private static final String STRING_URI_PIPE = Utils.encodeUrl("|"); + private static final String WIKIDATA = "wikidata"; + private static final String WIKIPEDIA = "wikipedia"; + + private final String[] wikipediaKeys; private final String wikipediaLang; private final SitematrixResult.Sitematrix.Site site; private WikipediaApp(final String wikipediaLang) throws IOException { this.wikipediaLang = wikipediaLang; + this.wikipediaKeys = new String[] {WIKIDATA, WIKIPEDIA, WIKIPEDIA + ':' + wikipediaLang}; final SitematrixResult.Sitematrix sitematrix = ApiQueryClient.query(WikidataActionApiQuery.sitematrix()); - final SitematrixResult.Sitematrix.Language language = sitematrix.getLanguages().stream().filter(it -> wikipediaLang.equalsIgnoreCase(it.getCode())).findFirst().orElse(null); - final SitematrixResult.Sitematrix.Site site; + final SitematrixResult.Sitematrix.Language language = sitematrix.getLanguages().stream() + .filter(it -> wikipediaLang.equalsIgnoreCase(it.getCode())).findFirst().orElse(null); if (language != null) { - site = language.getSites().stream().filter(it -> "wiki".equals(it.getCode())).findFirst().orElseThrow(() -> new IllegalArgumentException("No Wikipedia for language " + language.getName() + " (" + language.getCode() + ") found!")); + this.site = language.getSites().stream().filter(it -> "wiki".equals(it.getCode())).findFirst() + .orElseThrow(() -> new IllegalArgumentException("No Wikipedia for language " + language.getName() + + " (" + language.getCode() + ") found!")); } else { - site = sitematrix.getSpecialSites().stream().filter(it -> wikipediaLang.equals(it.getCode())).findFirst().orElseThrow(() -> new IllegalArgumentException("No wiki site for code '" + wikipediaLang + "' found!")); + this.site = sitematrix.getSpecialSites().stream().filter(it -> wikipediaLang.equals(it.getCode())).findFirst() + .orElseThrow(() -> new IllegalArgumentException("No wiki site for code '" + wikipediaLang + "' found!")); } - this.site = site; } public static WikipediaApp forLanguage(final String wikipediaLang) { @@ -108,20 +118,19 @@ private static HttpClient.Response connect(String url) throws IOException { public List getEntriesFromCoordinates(LatLon min, LatLon max) { try { // construct url - final String url = new StringBuilder(getSiteUrl()).append("/w/api.php") - .append("?action=query") - .append("&list=geosearch") - .append("&format=xml") - .append("&gslimit=500") - .append("&gsbbox=") - .append(max.lat()).append(STRING_URI_PIPE).append(min.lon()) - .append(STRING_URI_PIPE).append(min.lat()).append(STRING_URI_PIPE).append(max.lon()) - .toString(); + final String url = getSiteUrl() + "/w/api.php" + + "?action=query" + + "&list=geosearch" + + "&format=xml" + + "&gslimit=500" + + "&gsbbox=" + + max.lat() + STRING_URI_PIPE + min.lon() + + STRING_URI_PIPE + min.lat() + STRING_URI_PIPE + max.lon(); // parse XML document try (InputStream in = connect(url).getContent()) { final Document doc = newDocumentBuilder().parse(in); final String errorInfo = X_PATH.evaluateString("//error/@info", doc); - if (errorInfo != null && errorInfo.length() >= 1) { + if (errorInfo != null && !errorInfo.isEmpty()) { // I18n: {0} is the error message returned by the API new Notification(I18n.tr("Downloading entries with geo coordinates failed: {0}", errorInfo)) .setIcon(WikipediaPlugin.NOTIFICATION_ICON) @@ -133,24 +142,25 @@ public List getEntriesFromCoordinates(LatLon min, LatLon max) { final LatLon latLon = new LatLon( X_PATH.evaluateDouble("@lat", node), X_PATH.evaluateDouble("@lon", node)); - if ("wikidata".equals(wikipediaLang)) { + if (WIKIDATA.equals(wikipediaLang)) { return new WikidataEntry(name, null, latLon, null); } else { return new WikipediaEntry(wikipediaLang, name, latLon); } }).collect(Collectors.toList()); - if ("wikidata".equals(wikipediaLang)) { + if (WIKIDATA.equals(wikipediaLang)) { return new ArrayList<>(getLabelForWikidata(entries, Locale.getDefault())); } else { return entries; } } } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } - public static List getWikidataEntriesForQuery(final String languageForQuery, final String query, final Locale localeForLabels) { + public static List getWikidataEntriesForQuery(final String languageForQuery, final String query, + final Locale localeForLabels) { try { final String url = "https://www.wikidata.org/w/api.php" + "?action=wbsearchentities" + @@ -167,7 +177,7 @@ public static List getWikidataEntriesForQuery(final String langua return getLabelForWikidata(r, localeForLabels); } } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } @@ -184,12 +194,12 @@ public List getEntriesFromCategory(String category, int depth) { .collect(Collectors.toList()); } } catch (IOException ex) { - throw new RuntimeException(ex); + throw new UncheckedIOException(ex); } } public static List getEntriesFromClipboard(final String wikipediaLang, String clipboardStringContent) { - if ("wikidata".equals(wikipediaLang)) { + if (WIKIDATA.equals(wikipediaLang)) { List entries = new ArrayList<>(); Matcher matcher = RegexUtil.Q_ID_PATTERN.matcher(clipboardStringContent); while (matcher.find()) { @@ -231,7 +241,7 @@ public void updateWIWOSMStatus(List entries) { }); } } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } for (WikipediaEntry i : entries) { @@ -240,15 +250,33 @@ public void updateWIWOSMStatus(List entries) { } public boolean hasWikipediaTag(final OsmPrimitive p) { - return p.hasKey("wikidata", "wikipedia", "wikipedia:" + wikipediaLang); + return p.hasKey(wikipediaKeys); + } + + /** + * Check to see if a tagged object has had its wikipedia tag change + * @param primitive The tagged object to check + * @param originalKeys The original keys + * @return {@code true} if the tagged object has had a change in wikipedia keys + */ + public boolean tagChangeWikipedia(Tagged primitive, Map originalKeys) { + for (String key : wikipediaKeys) { + // If the key has been added or removed, it has been changed. + if (primitive.hasKey(key) != originalKeys.containsKey(key) || + // If the original key doesn't equal the new key, then it has been changed + (primitive.hasKey(key) && originalKeys.containsKey(key) && !originalKeys.get(key).equals(primitive.get(key)))) { + return true; + } + } + return false; } public Stream getWikipediaArticles(final OsmPrimitive p) { - if ("wikidata".equals(wikipediaLang)) { - return Stream.of(p.get("wikidata")).filter(Objects::nonNull); + if (WIKIDATA.equals(wikipediaLang)) { + return Stream.of(p.get(WIKIDATA)).filter(Objects::nonNull); } return Stream - .of("wikipedia", "wikipedia:" + wikipediaLang) + .of(WIKIPEDIA, WIKIPEDIA + ':' + wikipediaLang) .map(key -> WikipediaEntry.parseTag(key, p.get(key))) .filter(Objects::nonNull) .filter(wp -> wikipediaLang.equals(wp.lang)) @@ -263,9 +291,7 @@ public Stream getWikipediaArticles(final OsmPrimitive p) { public Map getWikidataForArticles(Collection articles) { final Map result = new HashMap<>(); // maximum of 50 titles - ListUtil.processInBatches(new ArrayList<>(articles), 50, batch -> { - result.putAll(resolveWikidataItems(batch)); - }); + ListUtil.processInBatches(new ArrayList<>(articles), 50, batch -> result.putAll(resolveWikidataItems(batch))); return result; } @@ -321,10 +347,10 @@ private Map getWikidataForArticles0(Collection articles) return ApiQueryClient.query(WikidataActionApiQuery.wbgetentities(site, articles)) .getEntities().values() .stream() - .filter(it -> RegexUtil.isValidQId(it.getId()) && it.getSitelinks().size() >= 1) + .filter(it -> RegexUtil.isValidQId(it.getId()) && !it.getSitelinks().isEmpty()) .collect(Collectors.toMap(it -> it.getSitelinks().iterator().next().getTitle(), WbgetentitiesResult.Entity::getId)); } catch (IOException ex) { - throw new RuntimeException(ex); + throw new UncheckedIOException(ex); } } @@ -336,9 +362,10 @@ private Map getWikidataForArticles0(Collection articles) */ Map resolveRedirectsForArticles(Collection articles) { try { - return articles.stream().collect(Collectors.toMap(it -> it, ApiQueryClient.query(WikipediaActionApiQuery.query(site, articles)).getQuery()::resolveRedirect)); + return articles.stream().collect(Collectors.toMap(it -> it, + ApiQueryClient.query(WikipediaActionApiQuery.query(site, articles)).getQuery()::resolveRedirect)); } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } @@ -359,7 +386,7 @@ public List getCategoriesForPrefix(final String prefix) { .collect(Collectors.toList()) ).orElse(new ArrayList<>()); } catch (IOException ex) { - throw new RuntimeException(ex); + throw new UncheckedIOException(ex); } } @@ -367,13 +394,16 @@ public static String getLabelForWikidata(String wikidataId, Locale locale, Strin try { final List entry = Collections.singletonList(new WikidataEntry(wikidataId)); return getLabelForWikidata(entry, locale, preferredLanguage).get(0).label; - } catch (IndexOutOfBoundsException ignore) { + } catch (IndexOutOfBoundsException indexOutOfBoundsException) { + Logging.trace(indexOutOfBoundsException); return null; } } - static List getLabelForWikidata(final List entries, final Locale locale, final String... preferredLanguage) { - final List wdEntries = entries.stream().map(it -> it instanceof WikidataEntry ? (WikidataEntry) it : null).filter(Objects::nonNull).collect(Collectors.toList()); + static List getLabelForWikidata(final List entries, final Locale locale, + final String... preferredLanguage) { + final List wdEntries = entries.stream() + .map(it -> it instanceof WikidataEntry ? (WikidataEntry) it : null).filter(Objects::nonNull).collect(Collectors.toList()); if (wdEntries.size() != entries.size()) { throw new IllegalArgumentException("The entries given to method `getLabelForWikidata` must all be of type WikidataEntry!"); } @@ -389,7 +419,9 @@ static List getLabelForWikidata(final List result = new ArrayList<>(wdEntries.size()); ListUtil.processInBatches(wdEntries, 50, batch -> { try { - final Map> entities = ApiQueryClient.query(WikidataActionApiQuery.wbgetentitiesLabels(batch.stream().map(it -> it.article).collect(Collectors.toList()))); + final Map> entities = + ApiQueryClient.query(WikidataActionApiQuery.wbgetentitiesLabels(batch.stream().map(it -> it.article) + .collect(Collectors.toList()))); if (entities != null) { for (final WikidataEntry batchEntry : batch) { Optional.ofNullable(entities.get(batchEntry.article)).flatMap(it -> it).ifPresent(entity -> { @@ -403,7 +435,7 @@ static List getLabelForWikidata(final List getInterwikiArticles(String article) { }).collect(Collectors.toList()); } } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } @@ -456,7 +488,7 @@ public LatLon getCoordinateForArticle(String article) { } } } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } @@ -482,7 +514,7 @@ private static DocumentBuilder newDocumentBuilder() { } catch (ParserConfigurationException e) { Logging.warn("Cannot create DocumentBuilder"); Logging.warn(e); - throw new RuntimeException(e); + throw new JosmRuntimeException(e); } } } diff --git a/src/main/java/org/wikipedia/gui/WikipediaToggleDialog.java b/src/main/java/org/wikipedia/gui/WikipediaToggleDialog.java index 8f78f2b2..03391a3c 100644 --- a/src/main/java/org/wikipedia/gui/WikipediaToggleDialog.java +++ b/src/main/java/org/wikipedia/gui/WikipediaToggleDialog.java @@ -11,11 +11,16 @@ import java.net.URL; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListCellRenderer; @@ -33,9 +38,11 @@ import org.openstreetmap.josm.data.osm.Tag; import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent.DatasetEventType; +import org.openstreetmap.josm.data.osm.event.DataChangedEvent; import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; import org.openstreetmap.josm.data.osm.event.DatasetEventManager; import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; +import org.openstreetmap.josm.data.osm.event.TagsChangedEvent; import org.openstreetmap.josm.data.osm.search.SearchMode; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.MainApplication; @@ -47,8 +54,10 @@ import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; import org.openstreetmap.josm.tools.I18n; import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.OpenBrowser; +import org.openstreetmap.josm.tools.Utils; import org.wikipedia.WikipediaApp; import org.wikipedia.actions.FetchWikidataAction; import org.wikipedia.actions.MultiAction; @@ -59,6 +68,12 @@ public class WikipediaToggleDialog extends ToggleDialog implements ActiveLayerChangeListener, DataSetListenerAdapter.Listener { + /** A string describing the context (use-case) for determining the dialog title */ + String titleContext; + final Set articles = new HashSet<>(); + final DefaultListModel model = new DefaultListModel<>(); + final JList list = new JList<>(model); + public WikipediaToggleDialog() { super( tr("Wikipedia"), @@ -75,6 +90,7 @@ public WikipediaToggleDialog() { new WikipediaLoadCoordinatesAction(true), new WikipediaLoadCategoryAction() }; + listSetup(list); createLayout(list, true, Arrays.asList( new SideButton(new ToggleWikiLayerAction(this)), MultiAction.createButton( @@ -89,57 +105,50 @@ public WikipediaToggleDialog() { updateTitle(); } - /** A string describing the context (use-case) for determining the dialog title */ - String titleContext = null; - final Set articles = new HashSet<>(); - final DefaultListModel model = new DefaultListModel<>(); - final JList list = new JList(model) { - - { - setToolTipText(tr("Double click on item to search for object with article name (and center coordinate)")); - addMouseListener(new MouseAdapter() { - - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2 && getSelectedValue() != null && MainApplication.getLayerManager().getEditDataSet() != null) { - final WikipediaEntry entry = getSelectedValue(); - if (entry.coordinate != null) { - BoundingXYVisitor bbox = new BoundingXYVisitor(); - bbox.visit(entry.coordinate); - MainApplication.getMap().mapView.zoomTo(bbox); - } - final String search = entry.getSearchText().replaceAll("\\(.*\\)", ""); - SearchAction.search(search, SearchMode.replace); + private void listSetup(JList list) { + list.setToolTipText(tr("Double click on item to search for object with article name (and center coordinate)")); + list.addMouseListener(new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && list.getSelectedValue() != null && MainApplication.getLayerManager().getEditDataSet() != null) { + final WikipediaEntry entry = list.getSelectedValue(); + if (entry.coordinate != null) { + BoundingXYVisitor bbox = new BoundingXYVisitor(); + bbox.visit(entry.coordinate); + MainApplication.getMap().mapView.zoomTo(bbox); } + final String search = entry.getSearchText().replaceAll("\\(.*\\)", ""); + SearchAction.search(search, SearchMode.replace); } - }); - - setCellRenderer(new DefaultListCellRenderer() { - - @Override - public JLabel getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - final WikipediaEntry entry = (WikipediaEntry) value; - final String labelText = "" + entry.getLabelText(); - final JLabel label = (JLabel) super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus); - if (entry.getWiwosmStatus() != null && entry.getWiwosmStatus()) { - label.setIcon(ImageProvider.getIfAvailable("misc", "grey_check")); - label.setToolTipText(/* I18n: WIWOSM server already links Wikipedia article to object/s */ tr("Available via WIWOSM server")); - } else if (articles.contains(entry.article)) { - label.setIcon(ImageProvider.getIfAvailable("misc", "green_check")); - label.setToolTipText(/* I18n: object/s from dataset contain link to Wikipedia article */ tr("Available in local dataset")); - } else { - label.setToolTipText(tr("Not linked yet")); - } - return label; + } + }); + + list.setCellRenderer(new DefaultListCellRenderer() { + + @Override + public JLabel getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + final WikipediaEntry entry = (WikipediaEntry) value; + final String labelText = "" + entry.getLabelText(); + final JLabel label = (JLabel) super.getListCellRendererComponent(list, labelText, index, isSelected, cellHasFocus); + if (entry.getWiwosmStatus() != null && entry.getWiwosmStatus()) { + label.setIcon(ImageProvider.getIfAvailable("misc", "grey_check")); + label.setToolTipText(/* I18n: WIWOSM server already links Wikipedia article to object/s */ tr("Available via WIWOSM server")); + } else if (articles.contains(entry.article)) { + label.setIcon(ImageProvider.getIfAvailable("misc", "green_check")); + label.setToolTipText(/* I18n: object/s from dataset contain link to Wikipedia article */ tr("Available in local dataset")); + } else { + label.setToolTipText(tr("Not linked yet")); } - }); + return label; + } + }); - final JPopupMenu popupMenu = new JPopupMenu(); - popupMenu.add(new OpenWikipediaArticleAction()); - popupMenu.add(new ZoomToWikipediaArticleAction()); - setComponentPopupMenu(popupMenu); - } - }; + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.add(new OpenWikipediaArticleAction()); + popupMenu.add(new ZoomToWikipediaArticleAction()); + list.setComponentPopupMenu(popupMenu); + } private void updateTitle() { final WikipediaApp app = newWikipediaApp(); @@ -159,7 +168,8 @@ private void updateTitle() { private WikipediaApp newWikipediaApp() { try { return WikipediaApp.forLanguage(list.getModel().getElementAt(0).lang); - } catch (ArrayIndexOutOfBoundsException ignore) { + } catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException) { + Logging.trace(arrayIndexOutOfBoundsException); return WikipediaApp.forLanguage(WikiProperties.WIKIPEDIA_LANGUAGE.get()); } } @@ -200,7 +210,7 @@ List getEntries() { } }.execute(); } catch (Exception ex) { - throw new RuntimeException(ex); + throw new JosmRuntimeException(ex); } } } @@ -213,7 +223,7 @@ abstract class UpdateWikipediaArticlesSwingWorker extends SwingWorker entries = getEntries(); entries.sort(null); - publish(entries.toArray(new WikipediaEntry[entries.size()])); + publish(entries.toArray(new WikipediaEntry[0])); ListUtil.processInBatches(entries, 20, batch -> { WikipediaApp.forLanguage(batch.get(0).lang).updateWIWOSMStatus(batch); list.repaint(); @@ -410,18 +420,53 @@ public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { @Override public void processDatasetEvent(AbstractDatasetChangedEvent event) { final Set typesToProcess = EnumSet.of( - DatasetEventType.DATA_CHANGED, DatasetEventType.PRIMITIVES_ADDED, DatasetEventType.PRIMITIVES_REMOVED, + DatasetEventType.PRIMITIVE_FLAGS_CHANGED, // Most "delete" commands actually hide the primitive in the dataset. DatasetEventType.TAGS_CHANGED); - if (!typesToProcess.contains(event.getType())) { + final Map> events; + if (event.getType() == DatasetEventType.DATA_CHANGED && event instanceof DataChangedEvent) { + final Map> temporaryEvents = + getRootEvents((DataChangedEvent) event).collect(Collectors.groupingBy(AbstractDatasetChangedEvent::getType)); + if (temporaryEvents.isEmpty()) { + events = Collections.singletonMap(event.getType(), Collections.singletonList(event)); + } else { + events = temporaryEvents; + } + } else if (typesToProcess.contains(event.getType())) { + events = Collections.singletonMap(event.getType(), Collections.singletonList(event)); + } else { + events = Collections.emptyMap(); + } + if (events.isEmpty()) { return; } final WikipediaApp app = newWikipediaApp(); - if (event.getPrimitives().stream().noneMatch(app::hasWikipediaTag)) { + final boolean tagChange = events.getOrDefault(DatasetEventType.TAGS_CHANGED, Collections.emptyList()).stream() + .filter(TagsChangedEvent.class::isInstance).map(TagsChangedEvent.class::cast) + .anyMatch(e -> app.tagChangeWikipedia(e.getPrimitive(), e.getOriginalKeys())); + final boolean primitiveAdded = events.getOrDefault(DatasetEventType.PRIMITIVES_ADDED, Collections.emptyList()).stream() + .map(AbstractDatasetChangedEvent::getPrimitives).flatMap(Collection::stream).anyMatch(app::hasWikipediaTag); + final boolean primitiveRemoved = events.getOrDefault(DatasetEventType.PRIMITIVES_REMOVED, Collections.emptyList()).stream() + .map(AbstractDatasetChangedEvent::getPrimitives).flatMap(Collection::stream).anyMatch(app::hasWikipediaTag); + final boolean primitiveMaybeRemoved = events.getOrDefault(DatasetEventType.PRIMITIVE_FLAGS_CHANGED, Collections.emptyList()).stream() + .map(AbstractDatasetChangedEvent::getPrimitives).flatMap(Collection::stream).anyMatch(app::hasWikipediaTag); + if (!tagChange && !primitiveAdded && !primitiveRemoved && !primitiveMaybeRemoved && !events.containsKey(DatasetEventType.DATA_CHANGED)) { return; } updateWikipediaArticles(); list.repaint(); } + + private static Stream getRootEvents(DataChangedEvent event) { + if (Utils.isEmpty(event.getEvents())) { + return Stream.empty(); + } + return event.getEvents().stream().flatMap(e -> { + if (e instanceof DataChangedEvent) { + return getRootEvents(event); + } + return Stream.of(e); + }); + } }