Skip to content

Commit

Permalink
SPI touch-ups (#71)
Browse files Browse the repository at this point in the history
 - Get encoders/decoders across AdapterCodec & Encoder/Decoder.installed() from one place.
 - Correct behavior for overriding default providers.
 - Refactorings.
  • Loading branch information
mizosoft committed May 7, 2024
1 parent 1c57604 commit 3c15027
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 Moataz Abdelnasser
* Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -26,7 +26,7 @@

import com.github.mizosoft.methanol.BodyAdapter.Decoder;
import com.github.mizosoft.methanol.BodyAdapter.Encoder;
import com.github.mizosoft.methanol.internal.spi.ServiceCache;
import com.github.mizosoft.methanol.internal.spi.BodyAdapterProviders;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpResponse.BodyHandler;
Expand All @@ -44,11 +44,6 @@
* and a {@link MediaType} specifying the desired format.
*/
public final class AdapterCodec {
private static final ServiceCache<Encoder> encodersServiceCache =
new ServiceCache<>(Encoder.class);
private static final ServiceCache<Decoder> decodersServiceCache =
new ServiceCache<>(Decoder.class);

/**
* Codec for the installed encoders & decoders. This is lazily created in a racy manner, which is
* OK since ServiceCache makes sure we see a constant snapshot of the services.
Expand Down Expand Up @@ -181,8 +176,7 @@ public static AdapterCodec installed() {
var installedCodec = lazyInstalledCodec;
if (installedCodec == null) {
installedCodec =
new AdapterCodec(
encodersServiceCache.getProviders(), decodersServiceCache.getProviders());
new AdapterCodec(BodyAdapterProviders.encoders(), BodyAdapterProviders.decoders());
lazyInstalledCodec = installedCodec;
}
return installedCodec;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Moataz Abdelnasser
* Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -22,7 +22,7 @@

package com.github.mizosoft.methanol;

import com.github.mizosoft.methanol.internal.spi.BodyAdapterFinder;
import com.github.mizosoft.methanol.internal.spi.BodyAdapterProviders;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpResponse.BodySubscriber;
import java.util.List;
Expand Down Expand Up @@ -74,7 +74,7 @@ interface Encoder extends BodyAdapter {

/** Returns an immutable list containing the installed encoders. */
static List<Encoder> installed() {
return BodyAdapterFinder.findInstalledEncoders();
return BodyAdapterProviders.encoders();
}

/**
Expand Down Expand Up @@ -119,7 +119,7 @@ default <T> BodySubscriber<Supplier<T>> toDeferredObject(

/** Returns an immutable list containing the installed decoders. */
static List<Decoder> installed() {
return BodyAdapterFinder.findInstalledDecoders();
return BodyAdapterProviders.decoders();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Moataz Abdelnasser
* Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,7 +24,7 @@

import static java.util.Objects.requireNonNull;

import com.github.mizosoft.methanol.internal.spi.DecoderFactoryFinder;
import com.github.mizosoft.methanol.internal.spi.BodyDecoderFactoryProviders;
import java.net.http.HttpResponse.BodySubscriber;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -114,7 +114,7 @@ interface Factory {
* factories
*/
static List<Factory> installedFactories() {
return DecoderFactoryFinder.findInstalledFactories();
return BodyDecoderFactoryProviders.factories();
}

/**
Expand All @@ -124,7 +124,7 @@ static List<Factory> installedFactories() {
* overridable.
*/
static Map<String, Factory> installedBindings() {
return DecoderFactoryFinder.getInstalledBindings();
return BodyDecoderFactoryProviders.bindings();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Moataz Abdelnasser
* Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -27,18 +27,17 @@
import java.util.List;

/** Utility class for loading/caching {@code Encoder/Decoder} providers. */
public class BodyAdapterFinder {
public class BodyAdapterProviders {
private static final ServiceProviders<Encoder> encoders = new ServiceProviders<>(Encoder.class);
private static final ServiceProviders<Decoder> decoders = new ServiceProviders<>(Decoder.class);

private static final ServiceCache<Encoder> encodersServiceCache = new ServiceCache<>(Encoder.class);
private static final ServiceCache<Decoder> decodersServiceCache = new ServiceCache<>(Decoder.class);
private BodyAdapterProviders() {}

private BodyAdapterFinder() {} // non-instantiable

public static List<Encoder> findInstalledEncoders() {
return encodersServiceCache.getProviders();
public static List<Encoder> encoders() {
return encoders.get();
}

public static List<Decoder> findInstalledDecoders() {
return decodersServiceCache.getProviders();
public static List<Decoder> decoders() {
return decoders.get();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Moataz Abdelnasser
* Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,44 +24,50 @@

import com.github.mizosoft.methanol.BodyDecoder;
import com.github.mizosoft.methanol.internal.annotations.DefaultProvider;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/** Utility class for finding decoder factories. */
public class DecoderFactoryFinder {
public class BodyDecoderFactoryProviders {
private static final ServiceProviders<BodyDecoder.Factory> factories =
new ServiceProviders<>(BodyDecoder.Factory.class);

private static final ServiceCache<BodyDecoder.Factory> CACHE =
new ServiceCache<>(BodyDecoder.Factory.class);
private static volatile @MonotonicNonNull Map<String, BodyDecoder.Factory> lazyBindings;

private static volatile @MonotonicNonNull Map<String, BodyDecoder.Factory> bindings;
private BodyDecoderFactoryProviders() {}

private DecoderFactoryFinder() {} // non-instantiable

public static List<BodyDecoder.Factory> findInstalledFactories() {
return CACHE.getProviders();
public static List<BodyDecoder.Factory> factories() {
return factories.get();
}

public static Map<String, BodyDecoder.Factory> getInstalledBindings() {
// Locking is not necessary as CACHE itself is locked so the result never changes
Map<String, BodyDecoder.Factory> cached = bindings;
if (cached == null) {
cached = createBindings();
bindings = cached;
public static Map<String, BodyDecoder.Factory> bindings() {
// Locking is not necessary as the cache itself is locked so the result never changes.
var bindings = lazyBindings;
if (bindings == null) {
bindings = createBindings();
lazyBindings = bindings;
}
return cached;
return bindings;
}

private static Map<String, BodyDecoder.Factory> createBindings() {
Map<String, BodyDecoder.Factory> bindings = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (BodyDecoder.Factory f : findInstalledFactories()) {
String enc = f.encoding();
// Only override if default
bindings.merge(
enc, f, (f1, f2) -> f1.getClass().isAnnotationPresent(DefaultProvider.class) ? f2 : f1);
}
return Collections.unmodifiableMap(bindings);
return factories().stream()
.collect(
Collectors.toMap(
BodyDecoder.Factory::encoding,
Function.identity(),
(f1, f2) -> {
// Allow overriding default providers.
if (f1.getClass().isAnnotationPresent(DefaultProvider.class)) {
return f2;
} else if (f2.getClass().isAnnotationPresent(DefaultProvider.class)) {
return f1;
} else {
return f2;
}
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,23 @@
import org.checkerframework.checker.nullness.qual.Nullable;

/** An object that loads {@literal &} caches service providers. */
public final class ServiceCache<S> {
private static final Logger logger = System.getLogger(ServiceCache.class.getName());
public final class ServiceProviders<S> {
private static final Logger logger = System.getLogger(ServiceProviders.class.getName());

private final Lazy<List<S>> providers;

public ServiceCache(Class<S> service) {
public ServiceProviders(Class<S> service) {
requireNonNull(service);
this.providers = Lazy.of(() -> loadProviders(service));
}

public List<S> getProviders() {
public List<S> get() {
return providers.get();
}

private static <U> List<U> loadProviders(Class<U> service) {
return ServiceLoader.load(service, service.getClassLoader()).stream()
.map(ServiceCache::tryGet)
.map(ServiceProviders::tryGet)
.filter(Objects::nonNull)
.collect(Collectors.toUnmodifiableList());
}
Expand All @@ -59,7 +59,7 @@ private static <U> List<U> loadProviders(Class<U> service) {
} catch (ServiceConfigurationError error) {
logger.log(
Level.WARNING,
"provider <" + provider.type() + "> will be ignored as it couldn't be instantiated",
"Provider <" + provider.type() + "> will be ignored as it couldn't be instantiated",
error);
return null;
}
Expand Down

0 comments on commit 3c15027

Please sign in to comment.