Search code examples
javaxmlcachingxpathguava

How doesn't add null value to Guava's LoadingCache?


I have to use Guava's LoadingCache for caching xpath expression to xpath values.

The trouble is that not all xpaths have values. Most of the time the value is null.

Code snippet:

private LoadingCache<String, List<String>> cachedXpaths = CacheBuilder.newBuilder()
        .expireAfterWrite(3, TimeUnit.MINUTES)
        .maximumSize(1000)
        .concurrencyLevel(5)
        .weakKeys()
        .build(new CacheLoader<String, List<String>>() {
            @Override
            public List<String> load(String key) throws Exception {
                return createListByKey(key);
            }
        });

private static List<String> createListByKey(String key) throws Exception {
    List<String> values = null;
    try {
        values = instance.getXpathValues(key);
    } catch (XPathExpressionException ignore) {
    }
    return values;
}

Result:

testEncounterSection(com.epam.cdatest.section.EncountersSectionTest)  Time elapsed: 0.002 sec  <<< FAILURE!
com.google.common.util.concurrent.UncheckedExecutionException: com.epam.cdatest.exceptions.XpathHasEmptyValueException
    at com.epam.cdatest.parsers.XpathEvaluator.getXpathValues(XpathEvaluator.java:123)
    at com.epam.cdatest.parsers.XpathEvaluator.createListByKey(XpathEvaluator.java:53)
    at com.epam.cdatest.parsers.XpathEvaluator.access$000(XpathEvaluator.java:32)
    at com.epam.cdatest.parsers.XpathEvaluator$1.load(XpathEvaluator.java:46)
    at com.epam.cdatest.parsers.XpathEvaluator$1.load(XpathEvaluator.java:43)

How to avoid calling load() when xpath value is empty?


Solution

  • If you want to differentiate between an empty List<String> and a null one, since LoadingCache doesn't support nulls, you can use a LoadingCache<String, Optional<List<String>>>:

    private LoadingCache<String, Optional<List<String>>> cachedXpaths = CacheBuilder.newBuilder()
            .expireAfterWrite(3, TimeUnit.MINUTES)
            .maximumSize(1000)
            .concurrencyLevel(5)
            .weakKeys()
            .build(new CacheLoader<String, Optional<List<String>>>() {
                @Override
                public Optional<List<String>> load(String key) {
                    try {
                        // If getXpathValues() can also return null, use fromNullable()
                        return Optional.of(instance.getXpathValues(key));
                    } catch (XPathExpressionException | XpathHasEmptyValueException ignore) {
                        // Maybe log something here as well
                        return Optional.absent();
                    }
                }
            });
    

    Update: if you're not familiar with Optional, there's the Javadoc, a page in the Wiki, as well as all the documentation for Java 8 which now includes an equivalent class.

    TL;DR it's a wrapper which can be tested for the presence of its content:

    Optional<List<String>> optionalXpaths = cachedXpaths.getUnchecked(str);
    if (optionalXpaths.isPresent()) {
        List<String> xpaths = optionalXpaths.get();
        // Do something with xpaths
    }
    

    or alternatively (but it's uglier):

    List<String> xpaths = cachedXpaths.getUnchecked(str).orNull();
    if (xpaths != null) {
        // Do something with xpaths
    }