Ticket #21794: 21794.2.patch

File 21794.2.patch, 8.0 KB (added by taylor.smock, 2 years ago)

Add test

  • new file src/org/openstreetmap/josm/tools/PatternUtils.java

    diff --git a/src/org/openstreetmap/josm/tools/PatternUtils.java b/src/org/openstreetmap/josm/tools/PatternUtils.java
    new file mode 100644
    index 0000000000..b1e41a7873
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import java.util.concurrent.TimeUnit;
     5import java.util.regex.Pattern;
     6
     7import org.apache.commons.jcs3.access.CacheAccess;
     8import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
     9import org.openstreetmap.josm.data.cache.JCSCacheManager;
     10import org.openstreetmap.josm.spi.preferences.Config;
     11
     12/**
     13 * A class that caches compiled patterns.
     14 * @author Taylor Smock
     15 * @since xxx
     16 */
     17public final class PatternUtils {
     18    /** A string that is highly unlikely to appear in regexes to split a regex from its flags */
     19    private static final String MAGIC_STRING = "========";
     20    /** A cache for Java Patterns (no flags) */
     21    private static final CacheAccess<String, Pattern> cache = JCSCacheManager.getCache("java:pattern",
     22            Config.getPref().getInt("java.pattern.cache", 1024), 0, null);
     23
     24    static {
     25        // We don't want to keep these around forever, so set a reasonablish max idle life.
     26        final IElementAttributes defaultAttributes = cache.getDefaultElementAttributes();
     27        defaultAttributes.setIdleTime(TimeUnit.HOURS.toSeconds(1));
     28        cache.setDefaultElementAttributes(defaultAttributes);
     29    }
     30
     31    private PatternUtils() {
     32        // Hide the constructor
     33    }
     34
     35    /**
     36     * Compile a regex into a pattern. This may return a {@link Pattern} used elsewhere. This is safe.
     37     * @param regex The regex to compile
     38     * @return The immutable {@link Pattern}.
     39     * @see Pattern#compile(String)
     40     */
     41    public static Pattern compile(final String regex) {
     42        return compile(regex, 0);
     43    }
     44
     45    /**
     46     * Compile a regex into a pattern. This may return a {@link Pattern} used elsewhere. This is safe.
     47     * @param regex The regex to compile
     48     * @param flags The flags from {@link Pattern} to apply
     49     * @return The immutable {@link Pattern}.
     50     * @see Pattern#compile(String, int)
     51     */
     52    public static Pattern compile(String regex, int flags) {
     53        // Right now, the maximum value of flags is 511 (3 characters). This should avoid unnecessary array copying.
     54        final StringBuilder sb = new StringBuilder(3 + MAGIC_STRING.length() + regex.length());
     55        return cache.get(sb.append(flags).append(MAGIC_STRING).append(regex).toString(), () -> Pattern.compile(regex, flags));
     56    }
     57}
  • src/org/openstreetmap/josm/tools/Tag2Link.java

    diff --git a/src/org/openstreetmap/josm/tools/Tag2Link.java b/src/org/openstreetmap/josm/tools/Tag2Link.java
    index e4c91b1fbf..136b5d96b6 100644
    a b import java.net.MalformedURLException;  
    1010import java.net.URL;
    1111import java.util.Arrays;
    1212import java.util.Collections;
    13 import java.util.HashMap;
     13import java.util.EnumMap;
    1414import java.util.List;
    1515import java.util.Map;
    1616import java.util.Objects;
    1717import java.util.Optional;
     18import java.util.Set;
    1819import java.util.function.Supplier;
    1920import java.util.function.UnaryOperator;
    2021import java.util.regex.Matcher;
    public final class Tag2Link {  
    147148            return;
    148149        }
    149150
    150         final HashMap<OsmPrimitiveType, Optional<ImageResource>> memoize = new HashMap<>();
     151        final Map<OsmPrimitiveType, Optional<ImageResource>> memoize = new EnumMap<>(OsmPrimitiveType.class);
    151152        final Supplier<ImageResource> imageResource = () -> memoize
    152153                .computeIfAbsent(OsmPrimitiveType.NODE, type -> OsmPrimitiveImageProvider.getResource(key, value, type))
    153154                .orElse(null);
    public final class Tag2Link {  
    208209                    tr("View category on Wikimedia Commons"), getWikimediaCommonsUrl(i), imageResource.get()));
    209210        }
    210211
    211         wikidataRules.getValues(key).forEach(urlFormatter -> {
     212        final Set<String> formatterUrls = wikidataRules.getValues(key);
     213        if (!formatterUrls.isEmpty()) {
    212214            final String formattedValue = valueFormatter.getOrDefault(key, x -> x).apply(value);
    213             final String url = urlFormatter.replace("$1", formattedValue);
    214             linkConsumer.acceptLink(getLinkName(url, key), url, imageResource.get());
    215         });
     215            final Pattern dollarOne = PatternUtils.compile("$1", Pattern.LITERAL);
     216
     217            final String urlKey = formatterUrls.stream().map(urlFormatter -> dollarOne.matcher(urlFormatter)
     218                            .replaceAll(Matcher.quoteReplacement("(.*)"))).map(PatternUtils::compile)
     219                            .map(pattern -> pattern.matcher(value)).filter(Matcher::matches)
     220                            .map(matcher -> matcher.group(1)).findFirst().orElse(formattedValue);
     221
     222            formatterUrls.forEach(urlFormatter -> {
     223                // Check if the current value matches the formatter pattern -- some keys can take a full url or a key for
     224                // the formatter. Example: https://wiki.openstreetmap.org/wiki/Key:contact:facebook
     225                final String url = urlFormatter.replace("$1", urlKey);
     226                linkConsumer.acceptLink(getLinkName(url, key), url, imageResource.get());
     227            });
     228        }
    216229    }
    217230
    218231    private static String getWikimediaCommonsUrl(String i) {
  • test/unit/org/openstreetmap/josm/tools/Tag2LinkTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/Tag2LinkTest.java b/test/unit/org/openstreetmap/josm/tools/Tag2LinkTest.java
    index 6a30f09619..780b9e8bd3 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.tools;
    33
     4import static org.junit.jupiter.api.Assertions.assertEquals;
     5import static org.junit.jupiter.api.Assertions.assertTrue;
     6
    47import java.util.ArrayList;
    58import java.util.Arrays;
    69import java.util.List;
    710
    8 import org.junit.Assert;
    911import org.junit.jupiter.api.Test;
     12import org.junit.jupiter.params.ParameterizedTest;
     13import org.junit.jupiter.params.provider.ValueSource;
    1014import org.openstreetmap.josm.testutils.annotations.BasicPreferences;
     15import org.openstreetmap.josm.testutils.annotations.StaticClassCleanup;
    1116
    1217/**
    1318 * Test {@link Tag2Link}
    1419 */
    1520@BasicPreferences
     21@StaticClassCleanup(Tag2Link.class)
    1622class Tag2LinkTest {
    1723
    1824    List<String> links = new ArrayList<>();
    class Tag2LinkTest {  
    2228    }
    2329
    2430    void checkLinks(String... expected) {
    25         Assert.assertEquals(Arrays.asList(expected), links);
     31        assertEquals(Arrays.asList(expected), this.links);
    2632    }
    2733
    2834    /**
    class Tag2LinkTest {  
    3137    @Test
    3238    void testInitialize() {
    3339        Tag2Link.initialize();
    34         Assert.assertTrue("obtains at least 40 rules", Tag2Link.wikidataRules.size() > 40);
     40        assertTrue(Tag2Link.wikidataRules.size() > 40, "obtains at least 40 rules");
     41    }
     42
     43    /**
     44     * Unit test for links that may come in multiple forms.
     45     * Example: <a href="https://wiki.osm.org/wiki/Key:contact:facebook">https://wiki.openstreetmap.org/wiki/Key:contact:facebook</a>
     46     *
     47     * See also JOSM #21794
     48     * @param value The tag value for "contact:facebook"
     49     */
     50    @ParameterizedTest
     51    @ValueSource(strings = {"https://www.facebook.com/FacebookUserName", "FacebookUserName"})
     52    void testUrlKeyMultipleForms(final String value) {
     53        // We need the wikidata rules Since testInitialize tests initialization, reuse it.
     54        if (!Tag2Link.wikidataRules.containsKey("contact:facebook")) {
     55            this.testInitialize();
     56        }
     57        Tag2Link.getLinksForTag("contact:facebook", value, this::addLink);
     58        this.checkLinks("Open unavatar.now.sh // https://unavatar.now.sh/facebook/FacebookUserName",
     59                "Open facebook.com // https://www.facebook.com/FacebookUserName",
     60                "Open messenger.com // https://www.messenger.com/t/FacebookUserName");
    3561    }
    3662
    3763    /**