diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d31c9149..805b9a37d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,11 +27,11 @@ jobs: steps: - name: Checkout code if: github.event.inputs.perform_version == '' - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Checkout full repository # Required when performing an existing release. if: github.event.inputs.perform_version != '' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: '0' - name: Setup git user @@ -43,7 +43,7 @@ jobs: git config --global user.name "Kill Bill core team" git config --global url."https://${BUILD_USER}:${BUILD_TOKEN}@github.com/".insteadOf "git@github.com:" - name: Configure Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: temurin @@ -65,7 +65,7 @@ jobs: # Will be pushed as part of the release process, only if the release is successful git commit -m "pom.xml: update killbill-oss-parent to ${{ github.event.inputs.parent_version }}" - name: Configure settings.xml for release - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 21 distribution: temurin @@ -102,4 +102,3 @@ jobs: run: | PROJECT_VERSION=$(git describe --abbrev=0 | cut -d '-' -f 3) gh workflow -R killbill/killbill-oss-parent run release.yml -f commons_version=${PROJECT_VERSION} - diff --git a/automaton/pom.xml b/automaton/pom.xml index 988ac7c60..02874c53f 100644 --- a/automaton/pom.xml +++ b/automaton/pom.xml @@ -31,15 +31,15 @@ spotbugs-exclude.xml - - com.google.code.findbugs - jsr305 - jakarta.activation jakarta.activation-api runtime + + jakarta.annotation + jakarta.annotation-api + jakarta.xml.bind jakarta.xml.bind-api diff --git a/automaton/src/main/java/org/killbill/automaton/dot/DOTBuilder.java b/automaton/src/main/java/org/killbill/automaton/dot/DOTBuilder.java index 32b138695..1a3646b5b 100644 --- a/automaton/src/main/java/org/killbill/automaton/dot/DOTBuilder.java +++ b/automaton/src/main/java/org/killbill/automaton/dot/DOTBuilder.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.MapJoiner; diff --git a/clock/pom.xml b/clock/pom.xml index f6c3d0919..21e07470d 100644 --- a/clock/pom.xml +++ b/clock/pom.xml @@ -33,6 +33,13 @@ guice runtime + + + io.netty + netty-common + ${netty.version} + test + it.ozimov embedded-redis @@ -51,6 +58,12 @@ org.redisson redisson test + + + io.netty + netty-common + + org.slf4j diff --git a/concurrent/pom.xml b/concurrent/pom.xml index 8b5eeb4dc..5eb8ab489 100644 --- a/concurrent/pom.xml +++ b/concurrent/pom.xml @@ -33,15 +33,15 @@ reload4j test - - com.google.code.findbugs - jsr305 - com.google.inject guice runtime + + jakarta.annotation + jakarta.annotation-api + org.slf4j slf4j-api diff --git a/concurrent/src/main/java/org/killbill/commons/profiling/ProfilingData.java b/concurrent/src/main/java/org/killbill/commons/profiling/ProfilingData.java index a4a2ddde2..675b91bd8 100644 --- a/concurrent/src/main/java/org/killbill/commons/profiling/ProfilingData.java +++ b/concurrent/src/main/java/org/killbill/commons/profiling/ProfilingData.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; public class ProfilingData { diff --git a/embeddeddb/postgresql/pom.xml b/embeddeddb/postgresql/pom.xml index 046a06ee3..f8456a2b1 100644 --- a/embeddeddb/postgresql/pom.xml +++ b/embeddeddb/postgresql/pom.xml @@ -28,10 +28,6 @@ killbill-embeddeddb-postgresql Kill Bill library of embedded dbs: PostgreSQL - - com.google.code.findbugs - jsr305 - io.airlift command @@ -47,6 +43,10 @@ embedded-postgres test + + jakarta.annotation + jakarta.annotation-api + org.kill-bill.commons killbill-embeddeddb-common diff --git a/embeddeddb/postgresql/src/test/java/org/killbill/commons/embeddeddb/postgresql/KillBillTestingPostgreSqlServer.java b/embeddeddb/postgresql/src/test/java/org/killbill/commons/embeddeddb/postgresql/KillBillTestingPostgreSqlServer.java index 83d71baad..6963b8c66 100644 --- a/embeddeddb/postgresql/src/test/java/org/killbill/commons/embeddeddb/postgresql/KillBillTestingPostgreSqlServer.java +++ b/embeddeddb/postgresql/src/test/java/org/killbill/commons/embeddeddb/postgresql/KillBillTestingPostgreSqlServer.java @@ -25,7 +25,7 @@ import java.sql.SQLException; import java.sql.Statement; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; import org.slf4j.Logger; diff --git a/jdbi/pom.xml b/jdbi/pom.xml index 30d36c908..7f6f6b032 100644 --- a/jdbi/pom.xml +++ b/jdbi/pom.xml @@ -37,10 +37,6 @@ com.fasterxml classmate - - com.google.code.findbugs - jsr305 - com.h2database h2 @@ -58,6 +54,10 @@ + + jakarta.annotation + jakarta.annotation-api + jakarta.inject jakarta.inject-api diff --git a/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DBIProvider.java b/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DBIProvider.java index bfa9bc80b..c082ad757 100644 --- a/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DBIProvider.java +++ b/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DBIProvider.java @@ -22,7 +22,7 @@ import java.util.LinkedHashSet; import java.util.Set; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Provider; import javax.sql.DataSource; diff --git a/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DataSourceProvider.java b/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DataSourceProvider.java index a79114596..b7679b6b4 100644 --- a/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DataSourceProvider.java +++ b/jdbi/src/main/java/org/killbill/commons/jdbi/guice/DataSourceProvider.java @@ -24,7 +24,7 @@ import java.sql.SQLException; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Provider; import javax.sql.DataSource; diff --git a/jooby/pom.xml b/jooby/pom.xml index 8e837dcd0..ed71aab20 100644 --- a/jooby/pom.xml +++ b/jooby/pom.xml @@ -35,10 +35,6 @@ com.google.inject guice - - com.google.guava - guava - com.typesafe config @@ -177,6 +173,10 @@ 4.5.14 test + + org.kill-bill.commons + killbill-utils + diff --git a/jooby/src/main/java/org/jooby/Asset.java b/jooby/src/main/java/org/jooby/Asset.java index 8f8465b69..d255c11b5 100644 --- a/jooby/src/main/java/org/jooby/Asset.java +++ b/jooby/src/main/java/org/jooby/Asset.java @@ -20,9 +20,9 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Base64; -import com.google.common.io.BaseEncoding; -import com.google.common.primitives.Longs; import org.jooby.funzy.Throwing; import jakarta.annotation.Nonnull; @@ -138,11 +138,11 @@ default String etag() { StringBuilder b = new StringBuilder(32); b.append("W/\""); - BaseEncoding b64 = BaseEncoding.base64(); + Base64.Encoder b64 = Base64.getEncoder(); int lhash = resource().toURI().hashCode(); - b.append(b64.encode(Longs.toByteArray(lastModified() ^ lhash))); - b.append(b64.encode(Longs.toByteArray(length() ^ lhash))); + b.append(b64.encodeToString(ByteBuffer.allocate(Long.BYTES).putLong(lastModified() ^ lhash).array())); + b.append(b64.encodeToString(ByteBuffer.allocate(Long.BYTES).putLong(length() ^ lhash).array())); b.append('"'); return b.toString(); } catch (URISyntaxException x) { diff --git a/jooby/src/main/java/org/jooby/Cookie.java b/jooby/src/main/java/org/jooby/Cookie.java index 36cfa0c75..79d656be4 100644 --- a/jooby/src/main/java/org/jooby/Cookie.java +++ b/jooby/src/main/java/org/jooby/Cookie.java @@ -15,9 +15,8 @@ */ package org.jooby; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.io.BaseEncoding; +import org.killbill.commons.utils.Splitter; +import org.killbill.commons.utils.Strings; import static java.util.Objects.requireNonNull; import org.jooby.funzy.Throwing; import org.jooby.internal.CookieImpl; @@ -35,7 +34,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -417,9 +415,6 @@ public Optional maxAge() { */ public class Signature { - /** Remove trailing '='. */ - private static final Pattern EQ = Pattern.compile("=+$"); - /** Algorithm name. */ public static final String HMAC_SHA256 = "HmacSHA256"; @@ -448,7 +443,7 @@ public static String sign(final String value, final String secret) { Mac mac = Mac.getInstance(HMAC_SHA256); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256)); byte[] bytes = mac.doFinal(value.getBytes(StandardCharsets.UTF_8)); - return EQ.matcher(BaseEncoding.base64().encode(bytes)).replaceAll("") + SEP + value; + return java.util.Base64.getEncoder().withoutPadding().encodeToString(bytes) + SEP + value; } catch (Exception ex) { throw new IllegalArgumentException("Can't sign value", ex); } diff --git a/jooby/src/main/java/org/jooby/Env.java b/jooby/src/main/java/org/jooby/Env.java index 432f3d70e..ba8e9beb3 100644 --- a/jooby/src/main/java/org/jooby/Env.java +++ b/jooby/src/main/java/org/jooby/Env.java @@ -15,8 +15,7 @@ */ package org.jooby; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; +import org.killbill.commons.utils.Splitter; import com.google.inject.Key; import com.google.inject.name.Names; import com.typesafe.config.Config; @@ -25,6 +24,8 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -332,11 +333,11 @@ default Env build(final Config config) { String name = config.hasPath("application.env") ? config.getString("application.env") : "dev"; return new Env() { - private ImmutableList.Builder> start = ImmutableList.builder(); + private List> start = new ArrayList<>(); - private ImmutableList.Builder> started = ImmutableList.builder(); + private List> started = new ArrayList<>(); - private ImmutableList.Builder> shutdown = ImmutableList.builder(); + private List> shutdown = new ArrayList<>(); private Map> xss = new HashMap<>(); @@ -393,7 +394,7 @@ public String toString() { @Override public List> stopTasks() { - return shutdown.build(); + return Collections.unmodifiableList(shutdown); } @Override @@ -416,12 +417,12 @@ public LifeCycle onStarted(final Throwing.Consumer task) { @Override public List> startTasks() { - return this.start.build(); + return Collections.unmodifiableList(this.start); } @Override public List> startedTasks() { - return this.started.build(); + return Collections.unmodifiableList(this.started); } @Override diff --git a/jooby/src/main/java/org/jooby/Err.java b/jooby/src/main/java/org/jooby/Err.java index 9640bb0e9..0d4fdd7c2 100644 --- a/jooby/src/main/java/org/jooby/Err.java +++ b/jooby/src/main/java/org/jooby/Err.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Throwables; +import org.killbill.commons.utils.Throwables; import jakarta.annotation.Nullable; diff --git a/jooby/src/main/java/org/jooby/Jooby.java b/jooby/src/main/java/org/jooby/Jooby.java index aeb0ad1e9..14f70ded3 100644 --- a/jooby/src/main/java/org/jooby/Jooby.java +++ b/jooby/src/main/java/org/jooby/Jooby.java @@ -15,16 +15,13 @@ */ package org.jooby; -import com.google.common.base.Joiner; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.escape.Escaper; -import com.google.common.html.HtmlEscapers; -import com.google.common.net.UrlEscapers; -import com.google.common.util.concurrent.MoreExecutors; +import org.killbill.commons.utils.escape.Escaper; +import org.killbill.commons.utils.html.HtmlEscapers; +import org.killbill.commons.utils.net.UrlEscapers; +import org.killbill.commons.utils.Joiner; +import static org.killbill.commons.utils.Preconditions.checkArgument; +import static org.killbill.commons.utils.Preconditions.checkState; +import org.killbill.commons.utils.concurrent.DirectExecutor; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; @@ -118,6 +115,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -513,7 +511,7 @@ static class MvcClass implements Route.Props { String path; - ImmutableMap.Builder attrs = ImmutableMap.builder(); + Map attrs = new LinkedHashMap<>(); private List consumes; @@ -583,7 +581,7 @@ public MvcClass renderer(final String name) { } public Route.Definition apply(final Route.Definition route) { - attrs.build().forEach(route::attr); + attrs.forEach(route::attr); if (name != null) { route.name(name); } @@ -2640,9 +2638,9 @@ private Injector bootstrap(final Config args, /** executors: */ if (!defaultExecSet) { // default executor - executor(MoreExecutors.directExecutor()); + executor(DirectExecutor.INSTANCE); } - executor("direct", MoreExecutors.directExecutor()); + executor("direct", DirectExecutor.INSTANCE); executor("server", ServerExecutorProvider.class); /** Some basic xss functions. */ @@ -2822,9 +2820,9 @@ private Injector bootstrap(final Config args, // clear bag and freeze it this.bag.clear(); - this.bag = ImmutableSet.of(); + this.bag = Collections.emptySet(); this.executors.clear(); - this.executors = ImmutableList.of(); + this.executors = Collections.emptyList(); return injector; } @@ -3024,7 +3022,11 @@ private Config buildConfig(final Config source, final Config args, // set module config Config moduleStack = ConfigFactory.empty(); - for (Config module : ImmutableList.copyOf(modules).reverse()) { + // FIXME: Java 21 has better .reverse() support. + // Not using it yet since I don't think we really need hard 'Java 21' minimum, yet + List reversedModules = new ArrayList<>(modules); + Collections.reverse(reversedModules); + for (Config module : reversedModules) { moduleStack = moduleStack.withFallback(module); } @@ -3162,7 +3164,7 @@ private Config defaultConfig(final Config conf, final String cpath) { if (!conf.hasPath("application.lang")) { locales = Optional.ofNullable(this.languages) .map(langs -> LocaleUtils.parse(Joiner.on(",").join(langs))) - .orElse(ImmutableList.of(Locale.getDefault())); + .orElse(List.of(Locale.getDefault())); } else { locales = LocaleUtils.parse(conf.getString("application.lang")); } @@ -3303,7 +3305,7 @@ static String logback(final Config conf) { logback = conf.getString("logback.configurationFile"); } else { String env = conf.hasPath("application.env") ? conf.getString("application.env") : null; - ImmutableList.Builder files = ImmutableList.builder(); + List files = new ArrayList<>(); // TODO: sanitization of arguments File userdir = new File(System.getProperty("user.dir")); File confdir = new File(userdir, "conf"); @@ -3313,8 +3315,7 @@ static String logback(final Config conf) { } files.add(new File(userdir, "logback.xml")); files.add(new File(confdir, "logback.xml")); - logback = files.build() - .stream() + logback = files.stream() .filter(File::exists) .map(File::getAbsolutePath) .findFirst() diff --git a/jooby/src/main/java/org/jooby/MediaType.java b/jooby/src/main/java/org/jooby/MediaType.java index c23881b73..cebf81130 100644 --- a/jooby/src/main/java/org/jooby/MediaType.java +++ b/jooby/src/main/java/org/jooby/MediaType.java @@ -15,7 +15,7 @@ */ package org.jooby; -import static com.google.common.base.Preconditions.checkArgument; +import static org.killbill.commons.utils.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import java.io.File; @@ -28,8 +28,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -79,7 +77,7 @@ public static class Matcher { * @return True if the matcher matches the given media type. */ public boolean matches(final MediaType candidate) { - return doFirst(ImmutableList.of(candidate)).isPresent(); + return doFirst(List.of(candidate)).isPresent(); } /** @@ -117,7 +115,7 @@ public boolean matches(final List candidates) { * @return A first most relevant media type or an empty optional. */ public Optional first(final MediaType candidate) { - return first(ImmutableList.of(candidate)); + return first(List.of(candidate)); } /** @@ -160,10 +158,10 @@ public Optional first(final List candidates) { */ public List filter(final List types) { checkArgument(types != null && types.size() > 0, "Media types are required"); - ImmutableList.Builder result = ImmutableList.builder(); + List result = new ArrayList<>(); final List sortedTypes; if (types.size() == 1) { - sortedTypes = ImmutableList.of(types.get(0)); + sortedTypes = List.of(types.get(0)); } else { sortedTypes = new ArrayList<>(types); Collections.sort(sortedTypes); @@ -175,7 +173,7 @@ public List filter(final List types) { } } } - return result.build(); + return Collections.unmodifiableList(result); } /** @@ -202,7 +200,7 @@ private Optional doFirst(final List candidates) { /** * Default parameters. */ - private static final Map DEFAULT_PARAMS = ImmutableMap.of("q", "1"); + private static final Map DEFAULT_PARAMS = Map.of("q", "1"); /** * A JSON media type. @@ -247,7 +245,7 @@ private Optional doFirst(final List candidates) { public static final MediaType all = new MediaType("*", "*"); /** Any media type. */ - public static final List ALL = ImmutableList.of(MediaType.all); + public static final List ALL = List.of(MediaType.all); /** Form multipart-data media type. */ public static final MediaType multipart = new MediaType("multipart", "form-data"); @@ -300,15 +298,15 @@ private Optional doFirst(final List candidates) { private static final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); static { - cache.put("html", ImmutableList.of(html)); - cache.put("json", ImmutableList.of(json)); - cache.put("css", ImmutableList.of(css)); - cache.put("js", ImmutableList.of(js)); - cache.put("octetstream", ImmutableList.of(octetstream)); - cache.put("form", ImmutableList.of(form)); - cache.put("multipart", ImmutableList.of(multipart)); - cache.put("xml", ImmutableList.of(xml)); - cache.put("plain", ImmutableList.of(plain)); + cache.put("html", List.of(html)); + cache.put("json", List.of(json)); + cache.put("css", List.of(css)); + cache.put("js", List.of(js)); + cache.put("octetstream", List.of(octetstream)); + cache.put("form", List.of(form)); + cache.put("multipart", List.of(multipart)); + cache.put("xml", List.of(xml)); + cache.put("plain", List.of(plain)); cache.put("*", ALL); } @@ -326,7 +324,7 @@ private Optional doFirst(final List candidates) { private MediaType(final String type, final String subtype, final Map parameters) { this.type = requireNonNull(type, "A mime type is required."); this.subtype = requireNonNull(subtype, "A mime subtype is required."); - this.params = ImmutableMap.copyOf(requireNonNull(parameters, "Parameters are required.")); + this.params = Collections.unmodifiableMap(new LinkedHashMap<>(requireNonNull(parameters, "Parameters are required."))); this.wildcardType = "*".equals(type); this.wildcardSubtype = "*".equals(subtype); this.name = type + "/" + subtype; @@ -576,7 +574,7 @@ public static List parse(final String value) throws Err.BadMediaType * @return A media type matcher. */ public static Matcher matcher(final MediaType acceptable) { - return matcher(ImmutableList.of(acceptable)); + return matcher(List.of(acceptable)); } /** diff --git a/jooby/src/main/java/org/jooby/Mutant.java b/jooby/src/main/java/org/jooby/Mutant.java index b6925c876..6e52dbace 100644 --- a/jooby/src/main/java/org/jooby/Mutant.java +++ b/jooby/src/main/java/org/jooby/Mutant.java @@ -21,7 +21,7 @@ import java.util.Set; import java.util.SortedSet; -import com.google.common.primitives.Primitives; +import org.killbill.commons.utils.Primitives; import com.google.inject.TypeLiteral; import com.google.inject.util.Types; diff --git a/jooby/src/main/java/org/jooby/Renderer.java b/jooby/src/main/java/org/jooby/Renderer.java index bd03c534a..f0e4fe1be 100644 --- a/jooby/src/main/java/org/jooby/Renderer.java +++ b/jooby/src/main/java/org/jooby/Renderer.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Set; -import com.google.common.base.CaseFormat; +import org.killbill.commons.utils.CaseFormat; import com.google.inject.Key; import com.google.inject.TypeLiteral; diff --git a/jooby/src/main/java/org/jooby/Request.java b/jooby/src/main/java/org/jooby/Request.java index 470a60001..fce58a662 100644 --- a/jooby/src/main/java/org/jooby/Request.java +++ b/jooby/src/main/java/org/jooby/Request.java @@ -15,9 +15,7 @@ */ package org.jooby; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.net.UrlEscapers; +import org.killbill.commons.utils.net.UrlEscapers; import com.google.inject.Key; import com.google.inject.TypeLiteral; import static java.util.Objects.requireNonNull; @@ -27,6 +25,7 @@ import jakarta.annotation.Nullable; import java.io.IOException; import java.nio.charset.Charset; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Locale.LanguageRange; @@ -680,7 +679,7 @@ default boolean is(final List types) { */ @Nonnull default Optional accepts(final MediaType... types) { - return accepts(ImmutableList.copyOf(types)); + return accepts(List.of(types)); } /** @@ -1310,7 +1309,7 @@ default Request set(final TypeLiteral type, final Object value) { */ @Nonnull default Request push(final String path) { - return push(path, ImmutableMap.of()); + return push(path, Collections.emptyMap()); } /** diff --git a/jooby/src/main/java/org/jooby/Response.java b/jooby/src/main/java/org/jooby/Response.java index fd5203502..f043e77b7 100644 --- a/jooby/src/main/java/org/jooby/Response.java +++ b/jooby/src/main/java/org/jooby/Response.java @@ -21,12 +21,11 @@ import java.io.FileInputStream; import java.io.InputStream; import java.nio.charset.Charset; +import java.util.List; import java.util.Optional; import org.jooby.Cookie.Definition; -import com.google.common.collect.ImmutableList; - import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -384,7 +383,7 @@ default Response cookie(final String name, final String value) { */ @Nonnull default Response header(final String name, final Object... values) { - return header(name, ImmutableList.builder().add(values).build()); + return header(name, List.of(values)); } /** diff --git a/jooby/src/main/java/org/jooby/Result.java b/jooby/src/main/java/org/jooby/Result.java index 78d229eb3..c632cd711 100644 --- a/jooby/src/main/java/org/jooby/Result.java +++ b/jooby/src/main/java/org/jooby/Result.java @@ -17,15 +17,14 @@ import static java.util.Objects.requireNonNull; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; +import org.killbill.commons.utils.Joiner; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -107,7 +106,7 @@ public Optional ifGet(final List types) { public T get(final List types) { Supplier provider = MediaType .matcher(types) - .first(ImmutableList.copyOf(data.keySet())) + .first(List.copyOf(data.keySet())) .map(it -> data.remove(it)) .orElseThrow( () -> new Err(Status.NOT_ACCEPTABLE, Joiner.on(", ").join(types))); @@ -131,7 +130,7 @@ protected Result clone() { } - private static Map NO_HEADERS = ImmutableMap.of(); + private static Map NO_HEADERS = Collections.emptyMap(); /** Response headers. */ protected Map headers = NO_HEADERS; @@ -326,7 +325,7 @@ public Result header(final String name, final Object... values) { requireNonNull(name, "Header's name is required."); requireNonNull(values, "Header's values are required."); - return header(name, ImmutableList.copyOf(values)); + return header(name, List.of(values)); } /** @@ -356,10 +355,9 @@ protected Result clone() { } private void setHeader(final String name, final Object val) { - headers = ImmutableMap. builder() - .putAll(headers) - .put(name, val) - .build(); + var newMap = new LinkedHashMap<>(headers); + newMap.put(name, val); + headers = Collections.unmodifiableMap(newMap); } } diff --git a/jooby/src/main/java/org/jooby/Route.java b/jooby/src/main/java/org/jooby/Route.java index 68ddaa78f..89d0f3c62 100644 --- a/jooby/src/main/java/org/jooby/Route.java +++ b/jooby/src/main/java/org/jooby/Route.java @@ -15,13 +15,10 @@ */ package org.jooby; -import com.google.common.base.CaseFormat; -import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import com.google.common.primitives.Primitives; +import org.killbill.commons.utils.CaseFormat; +import org.killbill.commons.utils.Primitives; +import org.killbill.commons.utils.Strings; +import static org.killbill.commons.utils.Preconditions.checkArgument; import com.google.inject.Key; import com.google.inject.TypeLiteral; import static java.util.Objects.requireNonNull; @@ -723,7 +720,7 @@ class Definition implements Props { private List excludes = Collections.emptyList(); - private Map attributes = ImmutableMap.of(); + private Map attributes = Collections.emptyMap(); private Mapper mapper; @@ -928,10 +925,9 @@ public Definition attr(final String name, final Object value) { requireNonNull(value, "Attribute value is required."); if (valid(value)) { - attributes = ImmutableMap.builder() - .putAll(attributes) - .put(name, value) - .build(); + var newMap = new java.util.LinkedHashMap<>(attributes); + newMap.put(name, value); + attributes = Collections.unmodifiableMap(newMap); } return this; } @@ -1102,10 +1098,10 @@ public boolean canProduce(final String... types) { public Definition consumes(final List types) { checkArgument(types != null && types.size() > 0, "Consumes types are required"); if (types.size() > 1) { - this.consumes = Lists.newLinkedList(types); + this.consumes = new java.util.LinkedList<>(types); Collections.sort(this.consumes); } else { - this.consumes = ImmutableList.of(types.get(0)); + this.consumes = List.of(types.get(0)); } return this; } @@ -1114,10 +1110,10 @@ public Definition consumes(final List types) { public Definition produces(final List types) { checkArgument(types != null && types.size() > 0, "Produces types are required"); if (types.size() > 1) { - this.produces = Lists.newLinkedList(types); + this.produces = new java.util.LinkedList<>(types); Collections.sort(this.produces); } else { - this.produces = ImmutableList.of(types.get(0)); + this.produces = List.of(types.get(0)); } return this; } @@ -2034,17 +2030,15 @@ default void next(final Request req, final Response rsp) throws Throwable { /** * Well known HTTP methods. */ - List METHODS = ImmutableList.builder() - .add(GET, - POST, - PUT, - DELETE, - PATCH, - HEAD, - CONNECT, - OPTIONS, - TRACE) - .build(); + List METHODS = List.of(GET, + POST, + PUT, + DELETE, + PATCH, + HEAD, + CONNECT, + OPTIONS, + TRACE); /** * @return Current request path. diff --git a/jooby/src/main/java/org/jooby/Session.java b/jooby/src/main/java/org/jooby/Session.java index 6248e4d51..50225e6b3 100644 --- a/jooby/src/main/java/org/jooby/Session.java +++ b/jooby/src/main/java/org/jooby/Session.java @@ -15,7 +15,7 @@ */ package org.jooby; -import com.google.common.io.BaseEncoding; +import java.util.Base64; import static java.util.Objects.requireNonNull; import jakarta.annotation.Nonnull; @@ -240,7 +240,7 @@ interface Store { default String generateID() { byte[] bytes = new byte[30]; rnd.nextBytes(bytes); - return BaseEncoding.base64Url().encode(bytes); + return Base64.getUrlEncoder().encodeToString(bytes); } } diff --git a/jooby/src/main/java/org/jooby/Sse.java b/jooby/src/main/java/org/jooby/Sse.java index fb34fcf5f..b5e3d8e23 100644 --- a/jooby/src/main/java/org/jooby/Sse.java +++ b/jooby/src/main/java/org/jooby/Sse.java @@ -15,7 +15,6 @@ */ package org.jooby; -import com.google.common.collect.ImmutableList; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; @@ -535,7 +534,7 @@ public Sse() { protected void handshake(final Request req, final Runnable handler) throws Exception { this.injector = req.require(Injector.class); - this.renderers = ImmutableList.copyOf(injector.getInstance(Renderer.KEY)); + this.renderers = List.copyOf(injector.getInstance(Renderer.KEY)); this.produces = req.route().produces(); this.locals = req.attributes(); this.lastEventId = req.header("Last-Event-ID"); @@ -869,7 +868,7 @@ protected boolean shouldClose(final Throwable ex) { } private CompletableFuture> send(final Event event) { - List produces = event.type().>map(ImmutableList::of) + List produces = event.type().>map(List::of) .orElse(this.produces); SseRenderer ctx = new SseRenderer(renderers, produces, StandardCharsets.UTF_8, locale, locals); return Try.apply(() -> { diff --git a/jooby/src/main/java/org/jooby/WebSocket.java b/jooby/src/main/java/org/jooby/WebSocket.java index e808cf2d5..79e557807 100644 --- a/jooby/src/main/java/org/jooby/WebSocket.java +++ b/jooby/src/main/java/org/jooby/WebSocket.java @@ -15,7 +15,7 @@ */ package org.jooby; -import com.google.common.base.Preconditions; +import org.killbill.commons.utils.Preconditions; import com.google.inject.Key; import com.google.inject.TypeLiteral; import static java.util.Objects.requireNonNull; diff --git a/jooby/src/main/java/org/jooby/handlers/AssetHandler.java b/jooby/src/main/java/org/jooby/handlers/AssetHandler.java index d1d8d3093..49fd6cfda 100644 --- a/jooby/src/main/java/org/jooby/handlers/AssetHandler.java +++ b/jooby/src/main/java/org/jooby/handlers/AssetHandler.java @@ -15,7 +15,7 @@ */ package org.jooby.handlers; -import com.google.common.base.Strings; +import org.killbill.commons.utils.Strings; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; import org.jooby.Asset; diff --git a/jooby/src/main/java/org/jooby/handlers/Cors.java b/jooby/src/main/java/org/jooby/handlers/Cors.java index ad6e1fa0c..d056d7447 100644 --- a/jooby/src/main/java/org/jooby/handlers/Cors.java +++ b/jooby/src/main/java/org/jooby/handlers/Cors.java @@ -28,7 +28,6 @@ import jakarta.inject.Inject; import jakarta.inject.Named; -import com.google.common.collect.ImmutableList; import com.typesafe.config.Config; /** @@ -69,7 +68,7 @@ private static class Matcher implements Predicate { private boolean wild; public Matcher(final List values, final Predicate predicate) { - this.values = ImmutableList.copyOf(values); + this.values = List.copyOf(values); this.predicate = predicate; this.wild = values.contains("*"); } @@ -281,7 +280,7 @@ public boolean anyHeader() { * @return True if a header is allowed. */ public boolean allowHeader(final String header) { - return allowHeaders(ImmutableList.of(header)); + return allowHeaders(List.of(header)); } /** @@ -384,7 +383,7 @@ public Cors withMaxAge(final int preflightMaxAge) { @SuppressWarnings({"unchecked", "rawtypes" }) private List list(final Object value) { - return value instanceof List ? (List) value : ImmutableList.of(value.toString()); + return value instanceof List ? (List) value : List.of(value.toString()); } private static Matcher> allMatch(final List values) { diff --git a/jooby/src/main/java/org/jooby/handlers/CorsHandler.java b/jooby/src/main/java/org/jooby/handlers/CorsHandler.java index d11cf8406..128b248c3 100644 --- a/jooby/src/main/java/org/jooby/handlers/CorsHandler.java +++ b/jooby/src/main/java/org/jooby/handlers/CorsHandler.java @@ -29,8 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; +import org.killbill.commons.utils.Joiner; +import org.killbill.commons.utils.Splitter; /** * Handle preflight and simple CORS requests. CORS options are set via: {@link Cors}. diff --git a/jooby/src/main/java/org/jooby/handlers/CsrfHandler.java b/jooby/src/main/java/org/jooby/handlers/CsrfHandler.java index 3635a4705..848f4bf9a 100644 --- a/jooby/src/main/java/org/jooby/handlers/CsrfHandler.java +++ b/jooby/src/main/java/org/jooby/handlers/CsrfHandler.java @@ -29,8 +29,6 @@ import org.jooby.Session; import org.jooby.Status; -import com.google.common.collect.ImmutableSet; - /** *

Cross Site Request Forgery handler

* @@ -82,7 +80,7 @@ */ public class CsrfHandler implements Route.Filter { - private final Set REQUIRE_ON = ImmutableSet.of("POST", "PUT", "DELETE", "PATCH"); + private final Set REQUIRE_ON = Set.of("POST", "PUT", "DELETE", "PATCH"); private String name; diff --git a/jooby/src/main/java/org/jooby/handlers/SSIHandler.java b/jooby/src/main/java/org/jooby/handlers/SSIHandler.java index d5d81ca2e..475227928 100644 --- a/jooby/src/main/java/org/jooby/handlers/SSIHandler.java +++ b/jooby/src/main/java/org/jooby/handlers/SSIHandler.java @@ -27,7 +27,7 @@ import org.jooby.Response; import org.jooby.Route; -import com.google.common.io.CharStreams; +import org.killbill.commons.utils.io.CharStreams; /** *

server side include

diff --git a/jooby/src/main/java/org/jooby/internal/AbstractRendererContext.java b/jooby/src/main/java/org/jooby/internal/AbstractRendererContext.java index 435035145..1b31e901f 100644 --- a/jooby/src/main/java/org/jooby/internal/AbstractRendererContext.java +++ b/jooby/src/main/java/org/jooby/internal/AbstractRendererContext.java @@ -35,7 +35,7 @@ import org.jooby.Status; import org.jooby.View; -import com.google.common.base.Joiner; +import org.killbill.commons.utils.Joiner; public abstract class AbstractRendererContext implements Renderer.Context { diff --git a/jooby/src/main/java/org/jooby/internal/AppPrinter.java b/jooby/src/main/java/org/jooby/internal/AppPrinter.java index 373a1b50b..f4cc40e88 100644 --- a/jooby/src/main/java/org/jooby/internal/AppPrinter.java +++ b/jooby/src/main/java/org/jooby/internal/AppPrinter.java @@ -15,10 +15,10 @@ */ package org.jooby.internal; -import com.google.common.base.Strings; import com.typesafe.config.Config; import org.jooby.Route; import org.jooby.WebSocket; +import org.killbill.commons.utils.Strings; import org.slf4j.Logger; import java.util.Set; diff --git a/jooby/src/main/java/org/jooby/internal/AssetSource.java b/jooby/src/main/java/org/jooby/internal/AssetSource.java index 8e19d8665..ec805a03a 100644 --- a/jooby/src/main/java/org/jooby/internal/AssetSource.java +++ b/jooby/src/main/java/org/jooby/internal/AssetSource.java @@ -15,7 +15,7 @@ */ package org.jooby.internal; -import com.google.common.base.Strings; +import org.killbill.commons.utils.Strings; import java.net.MalformedURLException; import java.net.URL; diff --git a/jooby/src/main/java/org/jooby/internal/BodyReferenceImpl.java b/jooby/src/main/java/org/jooby/internal/BodyReferenceImpl.java index 992c30fd5..3d5cb46a7 100644 --- a/jooby/src/main/java/org/jooby/internal/BodyReferenceImpl.java +++ b/jooby/src/main/java/org/jooby/internal/BodyReferenceImpl.java @@ -26,7 +26,7 @@ import org.jooby.Parser; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; import org.jooby.funzy.Try; public class BodyReferenceImpl implements Parser.BodyReference { diff --git a/jooby/src/main/java/org/jooby/internal/BuiltinParser.java b/jooby/src/main/java/org/jooby/internal/BuiltinParser.java index 7ebc11f6e..99ac3f027 100644 --- a/jooby/src/main/java/org/jooby/internal/BuiltinParser.java +++ b/jooby/src/main/java/org/jooby/internal/BuiltinParser.java @@ -22,50 +22,46 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.SortedSet; +import java.util.TreeSet; import java.util.function.Function; -import java.util.function.Supplier; import org.jooby.Parser; -import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; import com.google.inject.TypeLiteral; @SuppressWarnings({"unchecked", "rawtypes" }) public enum BuiltinParser implements Parser { Basic { - private final Map, Function> parsers = ImmutableMap - ., Function> builder() - .put(BigDecimal.class, NOT_EMPTY.andThen(BigDecimal::new)) - .put(BigInteger.class, NOT_EMPTY.andThen(BigInteger::new)) - .put(Byte.class, NOT_EMPTY.andThen(Byte::valueOf)) - .put(byte.class, NOT_EMPTY.andThen(Byte::valueOf)) - .put(Double.class, NOT_EMPTY.andThen(Double::valueOf)) - .put(double.class, NOT_EMPTY.andThen(Double::valueOf)) - .put(Float.class, NOT_EMPTY.andThen(Float::valueOf)) - .put(float.class, NOT_EMPTY.andThen(Float::valueOf)) - .put(Integer.class, NOT_EMPTY.andThen(Integer::valueOf)) - .put(int.class, NOT_EMPTY.andThen(Integer::valueOf)) - .put(Long.class, NOT_EMPTY.andThen(this::toLong)) - .put(long.class, NOT_EMPTY.andThen(this::toLong)) - .put(Short.class, NOT_EMPTY.andThen(Short::valueOf)) - .put(short.class, NOT_EMPTY.andThen(Short::valueOf)) - .put(Boolean.class, NOT_EMPTY.andThen(this::toBoolean)) - .put(boolean.class, NOT_EMPTY.andThen(this::toBoolean)) - .put(Character.class, NOT_EMPTY.andThen(this::toCharacter)) - .put(char.class, NOT_EMPTY.andThen(this::toCharacter)) - .put(String.class, this::toString) - .build(); + private final Map, Function> parsers = Map.ofEntries( + Map.entry(BigDecimal.class, NOT_EMPTY.andThen(BigDecimal::new)), + Map.entry(BigInteger.class, NOT_EMPTY.andThen(BigInteger::new)), + Map.entry(Byte.class, NOT_EMPTY.andThen(Byte::valueOf)), + Map.entry(byte.class, NOT_EMPTY.andThen(Byte::valueOf)), + Map.entry(Double.class, NOT_EMPTY.andThen(Double::valueOf)), + Map.entry(double.class, NOT_EMPTY.andThen(Double::valueOf)), + Map.entry(Float.class, NOT_EMPTY.andThen(Float::valueOf)), + Map.entry(float.class, NOT_EMPTY.andThen(Float::valueOf)), + Map.entry(Integer.class, NOT_EMPTY.andThen(Integer::valueOf)), + Map.entry(int.class, NOT_EMPTY.andThen(Integer::valueOf)), + Map.entry(Long.class, NOT_EMPTY.andThen(this::toLong)), + Map.entry(long.class, NOT_EMPTY.andThen(this::toLong)), + Map.entry(Short.class, NOT_EMPTY.andThen(Short::valueOf)), + Map.entry(short.class, NOT_EMPTY.andThen(Short::valueOf)), + Map.entry(Boolean.class, NOT_EMPTY.andThen(this::toBoolean)), + Map.entry(boolean.class, NOT_EMPTY.andThen(this::toBoolean)), + Map.entry(Character.class, NOT_EMPTY.andThen(this::toCharacter)), + Map.entry(char.class, NOT_EMPTY.andThen(this::toCharacter)), + Map.entry(String.class, (Function) this::toString) + ); @Override public Object parse(final TypeLiteral type, final Parser.Context ctx) throws Throwable { @@ -112,14 +108,10 @@ private Long toLong(final String value) { }, Collection { - private final Map, Supplier>> parsers = ImmutableMap., Supplier>> builder() - .put(List.class, ImmutableList.Builder::new) - .put(Set.class, ImmutableSet.Builder::new) - .put(SortedSet.class, ImmutableSortedSet::naturalOrder) - .build(); + private static final Set> SUPPORTED = Set.of(List.class, Set.class, SortedSet.class); private boolean matches(final TypeLiteral toType) { - return parsers.containsKey(toType.getRawType()) + return SUPPORTED.contains(toType.getRawType()) && toType.getType() instanceof ParameterizedType; } @@ -127,13 +119,27 @@ private boolean matches(final TypeLiteral toType) { public Object parse(final TypeLiteral type, final Parser.Context ctx) throws Throwable { if (matches(type)) { return ctx.param(values -> { - ImmutableCollection.Builder builder = parsers.get(type.getRawType()).get(); + Class rawType = type.getRawType(); + java.util.Collection result; + if (SortedSet.class.isAssignableFrom(rawType)) { + result = new TreeSet<>(); + } else if (Set.class.isAssignableFrom(rawType)) { + result = new java.util.LinkedHashSet<>(); + } else { + result = new ArrayList<>(); + } TypeLiteral paramType = TypeLiteral.get(((ParameterizedType) type.getType()) .getActualTypeArguments()[0]); for (Object value : values) { - builder.add(ctx.next(paramType, value)); + result.add(ctx.next(paramType, value)); + } + if (SortedSet.class.isAssignableFrom(rawType)) { + return Collections.unmodifiableSortedSet((SortedSet) result); + } else if (Set.class.isAssignableFrom(rawType)) { + return Collections.unmodifiableSet((Set) result); + } else { + return Collections.unmodifiableList((List) result); } - return builder.build(); }); } else { return ctx.next(); @@ -196,7 +202,7 @@ Object toEnum(final Class type, final String value) { Bytes { @Override public Object parse(final TypeLiteral type, final Parser.Context ctx) throws Throwable { - if (type.getRawType() == byte[].class) { + if (byte[].class.equals(type.getRawType())) { return ctx.body(body -> body.bytes()); } return ctx.next(); diff --git a/jooby/src/main/java/org/jooby/internal/ByteRange.java b/jooby/src/main/java/org/jooby/internal/ByteRange.java index 335b03835..82c6987bb 100644 --- a/jooby/src/main/java/org/jooby/internal/ByteRange.java +++ b/jooby/src/main/java/org/jooby/internal/ByteRange.java @@ -15,7 +15,7 @@ */ package org.jooby.internal; -import com.google.common.base.Splitter; +import org.killbill.commons.utils.Splitter; import org.jooby.Err; import org.jooby.Status; diff --git a/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java b/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java index 931ad7cb2..ab7fd8dc3 100644 --- a/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java +++ b/jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java @@ -15,12 +15,10 @@ */ package org.jooby.internal; -import com.google.common.base.Strings; -import com.google.common.base.Throwables; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.collect.Sets; +import org.killbill.commons.utils.Strings; +import org.killbill.commons.utils.Throwables; +import org.killbill.commons.utils.cache.Cache; +import org.killbill.commons.utils.cache.CacheBuilder; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.name.Names; @@ -163,7 +161,7 @@ public boolean equals(final Object obj) { private List locales; - private final LoadingCache routeCache; + private final Cache routeCache; private final String redirectHttps; @@ -301,7 +299,7 @@ public void handle(final NativeRequest request, final NativeResponse response) t // usual req/rsp Route[] routes = routeCache - .getUnchecked(new RouteKey(method, path, type, req.accept())); + .get(new RouteKey(method, path, type, req.accept())); RouteChain chain = new RouteChain(req, rsp, routes); scope.put(CHAIN, chain); @@ -484,7 +482,7 @@ private static Err handle405(final Set routeDefs, final String private static List alternative(final Set routeDefs, final String verb, final String uri) { List routes = new LinkedList<>(); - Set verbs = Sets.newHashSet(Route.METHODS); + Set verbs = new java.util.HashSet<>(Route.METHODS); verbs.remove(verb); for (String alt : verbs) { findRoutes(routeDefs, alt, uri, MediaType.all, MediaType.ALL) @@ -525,15 +523,10 @@ private static String method(final String methodParam, final NativeRequest reque return param.size() == 0 ? request.method() : param.get(0); } - private static LoadingCache routeCache(final Set routes, + private static Cache routeCache(final Set routes, final Config conf) { - return CacheBuilder.from(conf.getString("server.routes.Cache")) - .build(new CacheLoader() { - @Override - public Route[] load(final RouteKey key) throws Exception { - return routes(routes, key.method, key.path, key.consumes, key.produces); - } - }); + return CacheBuilder.from(conf.getString("server.routes.Cache")) + .build(key -> routes(routes, key.method, key.path, key.consumes, key.produces)); } private static Function rootpath(final String applicationPath) { diff --git a/jooby/src/main/java/org/jooby/internal/HttpRendererContext.java b/jooby/src/main/java/org/jooby/internal/HttpRendererContext.java index faf64831e..80965a891 100644 --- a/jooby/src/main/java/org/jooby/internal/HttpRendererContext.java +++ b/jooby/src/main/java/org/jooby/internal/HttpRendererContext.java @@ -15,7 +15,7 @@ */ package org.jooby.internal; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; import org.jooby.Err; import org.jooby.MediaType; import org.jooby.Renderer; diff --git a/jooby/src/main/java/org/jooby/internal/MutantImpl.java b/jooby/src/main/java/org/jooby/internal/MutantImpl.java index 56ef6594c..5a97b9a7c 100644 --- a/jooby/src/main/java/org/jooby/internal/MutantImpl.java +++ b/jooby/src/main/java/org/jooby/internal/MutantImpl.java @@ -15,7 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableMap; import com.google.inject.TypeLiteral; import org.jooby.Err; import org.jooby.MediaType; @@ -95,7 +94,7 @@ public Map toMap() { if (data instanceof Map) { return (Map) data; } - return ImmutableMap.of((String) md()[0], this); + return Map.of((String) md()[0], this); } @SuppressWarnings("rawtypes") diff --git a/jooby/src/main/java/org/jooby/internal/RequestImpl.java b/jooby/src/main/java/org/jooby/internal/RequestImpl.java index 50ed8cf8c..431b4c581 100644 --- a/jooby/src/main/java/org/jooby/internal/RequestImpl.java +++ b/jooby/src/main/java/org/jooby/internal/RequestImpl.java @@ -15,7 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableList; import com.google.inject.Injector; import com.google.inject.Key; import com.typesafe.config.Config; @@ -248,8 +247,8 @@ public Map headers() { public Mutant cookie(final String name) { List values = req.cookies().stream().filter(c -> c.name().equalsIgnoreCase(name)) .findFirst() - .map(cookie -> ImmutableList.of(cookie.value().orElse(""))) - .orElse(ImmutableList.of()); + .map(cookie -> List.of(cookie.value().orElse(""))) + .orElse(Collections.emptyList()); return new MutantImpl(require(ParserExecutor.class), new StrParamReferenceImpl("cookie", name, values)); @@ -298,12 +297,12 @@ public long length() { public List locales( final BiFunction, List, List> filter) { return lang.map(h -> filter.apply(LocaleUtils.range(h), locales)) - .orElseGet(() -> filter.apply(ImmutableList.of(), locales)); + .orElseGet(() -> filter.apply(Collections.emptyList(), locales)); } @Override public Locale locale(final BiFunction, List, Locale> filter) { - Supplier def = () -> filter.apply(ImmutableList.of(), locales); + Supplier def = () -> filter.apply(Collections.emptyList(), locales); // don't fail on bad Accept-Language header, just fallback to default locale. return lang.map(h -> Try.apply(() -> filter.apply(LocaleUtils.range(h), locales)).orElseGet(def)) .orElseGet(def); diff --git a/jooby/src/main/java/org/jooby/internal/ResponseImpl.java b/jooby/src/main/java/org/jooby/internal/ResponseImpl.java index 67515f2f2..01fd282d5 100644 --- a/jooby/src/main/java/org/jooby/internal/ResponseImpl.java +++ b/jooby/src/main/java/org/jooby/internal/ResponseImpl.java @@ -15,7 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableList; import static java.util.Objects.requireNonNull; import org.jooby.Asset; import org.jooby.Cookie; @@ -347,7 +346,7 @@ public void send(final Result result) throws Throwable { /** * Do we need to figure it out Content-Length? */ - List produces = this.type == null ? route.produces() : ImmutableList.of(type); + List produces = this.type == null ? route.produces() : List.of(type); Object value = finalResult.get(produces); if (value != null) { diff --git a/jooby/src/main/java/org/jooby/internal/RouteMetadata.java b/jooby/src/main/java/org/jooby/internal/RouteMetadata.java index 3ee0a0586..9be220be0 100644 --- a/jooby/src/main/java/org/jooby/internal/RouteMetadata.java +++ b/jooby/src/main/java/org/jooby/internal/RouteMetadata.java @@ -24,7 +24,8 @@ import java.util.Map; import org.jooby.Env; -import org.jooby.funzy.Try; +import org.killbill.commons.utils.annotation.VisibleForTesting; +import org.killbill.commons.utils.io.Closeables; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; @@ -32,27 +33,25 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import com.google.common.base.Throwables; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import com.google.common.io.Closeables; -import com.google.common.io.Resources; -import com.google.common.util.concurrent.UncheckedExecutionException; +import org.killbill.commons.utils.cache.Cache; +import org.killbill.commons.utils.cache.CacheBuilder; +import org.killbill.commons.utils.io.Resources; public class RouteMetadata implements ParameterNameProvider { private static final String[] NO_ARG = new String[0]; - private final LoadingCache, Map> cache; + private final Cache, Map> cache; public RouteMetadata(final Env env) { - CacheLoader, Map> loader = CacheLoader - .from(RouteMetadata::extractMetadata); - cache = env.name().equals("dev") - ? CacheBuilder.newBuilder().maximumSize(0).build(loader) - : CacheBuilder.newBuilder().build(loader); + ? CacheBuilder., Map>newBuilder().maximumSize(0).build(RouteMetadata::extractMetadata) + : CacheBuilder., Map>newBuilder().build(RouteMetadata::extractMetadata); + } + + @VisibleForTesting + public RouteMetadata(final Cache, Map> cache) { + this.cache = cache; } @Override @@ -68,12 +67,11 @@ public int startAt(final Executable exec) { } private Map md(final Executable exec) { - return Try.apply(() -> cache.getUnchecked(exec.getDeclaringClass())) - .unwrap(UncheckedExecutionException.class) - .get(); + return cache.get(exec.getDeclaringClass()); } - private static Map extractMetadata(final Class owner) { + @VisibleForTesting + public static Map extractMetadata(final Class owner) { InputStream stream = null; try { Map md = new HashMap<>(); diff --git a/jooby/src/main/java/org/jooby/internal/ServerExecutorProvider.java b/jooby/src/main/java/org/jooby/internal/ServerExecutorProvider.java index 1592d2317..74c4b490e 100644 --- a/jooby/src/main/java/org/jooby/internal/ServerExecutorProvider.java +++ b/jooby/src/main/java/org/jooby/internal/ServerExecutorProvider.java @@ -15,7 +15,7 @@ */ package org.jooby.internal; -import com.google.common.util.concurrent.MoreExecutors; +import org.killbill.commons.utils.concurrent.DirectExecutor; import com.google.inject.Inject; import com.google.inject.Provider; import org.jooby.spi.Server; @@ -34,8 +34,8 @@ public ServerExecutorProvider(final ServerHolder serverHolder) { requireNonNull(serverHolder, "Server holder is required."); executor = (serverHolder.server != null) ? - serverHolder.server.executor().orElse(MoreExecutors.directExecutor()) : - MoreExecutors.directExecutor(); + serverHolder.server.executor().orElse(DirectExecutor.INSTANCE) : + DirectExecutor.INSTANCE; } @Override diff --git a/jooby/src/main/java/org/jooby/internal/SessionImpl.java b/jooby/src/main/java/org/jooby/internal/SessionImpl.java index 5dbb64011..bc92031ea 100644 --- a/jooby/src/main/java/org/jooby/internal/SessionImpl.java +++ b/jooby/src/main/java/org/jooby/internal/SessionImpl.java @@ -29,7 +29,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableList; import static java.util.Objects.requireNonNull; import org.jooby.Mutant; import org.jooby.Session; @@ -153,7 +152,7 @@ public long expiryAt() { @Override public Mutant get(final String name) { String value = attributes.get(name); - List values = value == null ? Collections.emptyList() : ImmutableList.of(value); + List values = value == null ? Collections.emptyList() : List.of(value); return new MutantImpl(resolver, new StrParamReferenceImpl("session attribute", name, values)); } @@ -181,7 +180,7 @@ public Mutant unset(final String name) { String value = attributes.remove(name); List values = Collections.emptyList(); if (value != null) { - values = ImmutableList.of(value); + values = List.of(value); dirty = true; } return new MutantImpl(resolver, new StrParamReferenceImpl("session attribute", name, values)); diff --git a/jooby/src/main/java/org/jooby/internal/SseRenderer.java b/jooby/src/main/java/org/jooby/internal/SseRenderer.java index 1ee18acfc..b5510c36b 100644 --- a/jooby/src/main/java/org/jooby/internal/SseRenderer.java +++ b/jooby/src/main/java/org/jooby/internal/SseRenderer.java @@ -15,7 +15,7 @@ */ package org.jooby.internal; -import com.google.common.io.ByteSource; +import org.killbill.commons.utils.io.ByteSource; import org.jooby.MediaType; import org.jooby.Renderer; import org.jooby.Sse; diff --git a/jooby/src/main/java/org/jooby/internal/StaticMethodTypeConverter.java b/jooby/src/main/java/org/jooby/internal/StaticMethodTypeConverter.java index 94ceab79a..be717121e 100644 --- a/jooby/src/main/java/org/jooby/internal/StaticMethodTypeConverter.java +++ b/jooby/src/main/java/org/jooby/internal/StaticMethodTypeConverter.java @@ -17,7 +17,7 @@ import org.jooby.internal.parser.StaticMethodParser; -import com.google.common.primitives.Primitives; +import org.killbill.commons.utils.Primitives; import com.google.inject.TypeLiteral; import com.google.inject.matcher.AbstractMatcher; import com.google.inject.spi.TypeConverter; diff --git a/jooby/src/main/java/org/jooby/internal/StringConstructTypeConverter.java b/jooby/src/main/java/org/jooby/internal/StringConstructTypeConverter.java index 9278991e4..c2a17887a 100644 --- a/jooby/src/main/java/org/jooby/internal/StringConstructTypeConverter.java +++ b/jooby/src/main/java/org/jooby/internal/StringConstructTypeConverter.java @@ -19,7 +19,7 @@ import org.jooby.internal.parser.StringConstructorParser; -import com.google.common.primitives.Primitives; +import org.killbill.commons.utils.Primitives; import com.google.inject.TypeLiteral; import com.google.inject.matcher.AbstractMatcher; import com.google.inject.spi.TypeConverter; diff --git a/jooby/src/main/java/org/jooby/internal/URLAsset.java b/jooby/src/main/java/org/jooby/internal/URLAsset.java index 5bf057aa7..d883f0334 100644 --- a/jooby/src/main/java/org/jooby/internal/URLAsset.java +++ b/jooby/src/main/java/org/jooby/internal/URLAsset.java @@ -27,9 +27,7 @@ import org.jooby.Asset; import org.jooby.MediaType; - -import com.google.common.io.Closeables; -import org.jooby.funzy.Try; +import org.killbill.commons.utils.io.Closeables; public class URLAsset implements Asset { diff --git a/jooby/src/main/java/org/jooby/internal/WebSocketImpl.java b/jooby/src/main/java/org/jooby/internal/WebSocketImpl.java index 269f46464..b26985465 100644 --- a/jooby/src/main/java/org/jooby/internal/WebSocketImpl.java +++ b/jooby/src/main/java/org/jooby/internal/WebSocketImpl.java @@ -15,7 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableList; import com.google.inject.Injector; import com.google.inject.Key; @@ -208,7 +207,7 @@ public void connect(final Injector injector, final Request req, final NativeWebS this.injector = requireNonNull(injector, "Injector required."); this.ws = requireNonNull(ws, "WebSocket is required."); this.locale = req.locale(); - renderers = ImmutableList.copyOf(injector.getInstance(Renderer.KEY)); + renderers = List.copyOf(injector.getInstance(Renderer.KEY)); /** * Bind callbacks @@ -220,7 +219,7 @@ public void connect(final Injector injector, final Request req, final NativeWebS ws.onTextMessage(message -> Try .run(sync(() -> messageCallback.onMessage( new MutantImpl(injector.getInstance(ParserExecutor.class), consumes, - new StrParamReferenceImpl("body", "message", ImmutableList.of(message)))))) + new StrParamReferenceImpl("body", "message", List.of(message)))))) .onFailure(this::handleErr)); ws.onCloseMessage((code, reason) -> { diff --git a/jooby/src/main/java/org/jooby/internal/WebSocketRendererContext.java b/jooby/src/main/java/org/jooby/internal/WebSocketRendererContext.java index b6f9b2297..f503ea9ea 100644 --- a/jooby/src/main/java/org/jooby/internal/WebSocketRendererContext.java +++ b/jooby/src/main/java/org/jooby/internal/WebSocketRendererContext.java @@ -29,8 +29,6 @@ import org.jooby.WebSocket.SuccessCallback; import org.jooby.spi.NativeWebSocket; -import com.google.common.collect.ImmutableList; - public class WebSocketRendererContext extends AbstractRendererContext { private NativeWebSocket ws; @@ -44,7 +42,7 @@ public class WebSocketRendererContext extends AbstractRendererContext { public WebSocketRendererContext(final List renderers, final NativeWebSocket ws, final MediaType type, final Charset charset, Locale locale, final SuccessCallback success, final OnError err) { - super(renderers, ImmutableList.of(type), charset, locale, Collections.emptyMap()); + super(renderers, List.of(type), charset, locale, Collections.emptyMap()); this.ws = ws; this.type = type; this.success = success; diff --git a/jooby/src/main/java/org/jooby/internal/WsBinaryMessage.java b/jooby/src/main/java/org/jooby/internal/WsBinaryMessage.java index 1a1c29077..2a28eb236 100644 --- a/jooby/src/main/java/org/jooby/internal/WsBinaryMessage.java +++ b/jooby/src/main/java/org/jooby/internal/WsBinaryMessage.java @@ -31,8 +31,7 @@ import org.jooby.Mutant; import org.jooby.Status; -import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableMap; +import java.nio.charset.StandardCharsets; import com.google.inject.TypeLiteral; public class WsBinaryMessage implements Mutant { @@ -132,14 +131,14 @@ public T to(final TypeLiteral type, final MediaType mtype) { return (T) new ByteArrayInputStream(buffer.array()); } if (rawType == Reader.class) { - return (T) new InputStreamReader(new ByteArrayInputStream(buffer.array()), Charsets.UTF_8); + return (T) new InputStreamReader(new ByteArrayInputStream(buffer.array()), StandardCharsets.UTF_8); } throw typeError(rawType); } @Override public Map toMap() { - return ImmutableMap.of("message", this); + return Map.of("message", this); } @Override diff --git a/jooby/src/main/java/org/jooby/internal/handlers/OptionsHandler.java b/jooby/src/main/java/org/jooby/internal/handlers/OptionsHandler.java index 6390a0c25..50909e28f 100644 --- a/jooby/src/main/java/org/jooby/internal/handlers/OptionsHandler.java +++ b/jooby/src/main/java/org/jooby/internal/handlers/OptionsHandler.java @@ -27,7 +27,7 @@ import org.jooby.Route.Definition; import org.jooby.Status; -import com.google.common.base.Joiner; +import org.killbill.commons.utils.Joiner; import com.google.inject.Inject; public class OptionsHandler implements Route.Handler { diff --git a/jooby/src/main/java/org/jooby/internal/jetty/JettyServer.java b/jooby/src/main/java/org/jooby/internal/jetty/JettyServer.java index 04861918b..fbfd1089d 100644 --- a/jooby/src/main/java/org/jooby/internal/jetty/JettyServer.java +++ b/jooby/src/main/java/org/jooby/internal/jetty/JettyServer.java @@ -15,7 +15,7 @@ */ package org.jooby.internal.jetty; -import com.google.common.primitives.Primitives; +import org.killbill.commons.utils.Primitives; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; diff --git a/jooby/src/main/java/org/jooby/internal/mvc/MvcRoutes.java b/jooby/src/main/java/org/jooby/internal/mvc/MvcRoutes.java index 35b4aef14..1b7a48156 100644 --- a/jooby/src/main/java/org/jooby/internal/mvc/MvcRoutes.java +++ b/jooby/src/main/java/org/jooby/internal/mvc/MvcRoutes.java @@ -15,8 +15,7 @@ */ package org.jooby.internal.mvc; -import com.google.common.base.CaseFormat; -import com.google.common.collect.ImmutableSet; +import org.killbill.commons.utils.CaseFormat; import org.jooby.Env; import org.jooby.MediaType; import org.jooby.Route; @@ -43,8 +42,10 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -57,17 +58,29 @@ public class MvcRoutes { private static final String[] EMPTY = new String[0]; - private static final Set> VERBS = ImmutableSet.of(GET.class, - POST.class, PUT.class, DELETE.class, PATCH.class, HEAD.class, OPTIONS.class, TRACE.class, - CONNECT.class); + private static final Set> VERBS; + static { + Set> verbs = new LinkedHashSet<>(); + verbs.add(GET.class); + verbs.add(POST.class); + verbs.add(PUT.class); + verbs.add(DELETE.class); + verbs.add(PATCH.class); + verbs.add(HEAD.class); + verbs.add(OPTIONS.class); + verbs.add(TRACE.class); + verbs.add(CONNECT.class); + VERBS = Collections.unmodifiableSet(verbs); + } - private static final Set> IGNORE = ImmutableSet - .>builder() - .addAll(VERBS) - .add(Path.class) - .add(Produces.class) - .add(Consumes.class) - .build(); + private static final Set> IGNORE; + static { + Set> ignore = new java.util.LinkedHashSet<>(VERBS); + ignore.add(Path.class); + ignore.add(Produces.class); + ignore.add(Consumes.class); + IGNORE = Collections.unmodifiableSet(ignore); + } public static List routes(final Env env, final RouteMetadata classInfo, final String rpath, boolean caseSensitiveRouting, final Class routeClass) { diff --git a/jooby/src/main/java/org/jooby/internal/mvc/RequestParam.java b/jooby/src/main/java/org/jooby/internal/mvc/RequestParam.java index d4ce8a873..f90bfaa90 100644 --- a/jooby/src/main/java/org/jooby/internal/mvc/RequestParam.java +++ b/jooby/src/main/java/org/jooby/internal/mvc/RequestParam.java @@ -15,9 +15,7 @@ */ package org.jooby.internal.mvc; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; +import org.killbill.commons.utils.Strings; import com.google.inject.TypeLiteral; import com.google.inject.util.Types; import org.jooby.Cookie; @@ -38,6 +36,8 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Parameter; import java.lang.reflect.Type; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -62,7 +62,7 @@ private interface GetValue { private static final Map injector; static { - Builder builder = ImmutableMap. builder(); + Map builder = new LinkedHashMap<>(); /** * Body */ @@ -144,7 +144,7 @@ private interface GetValue { return param.optional ? req.ifFlash(param.name) : req.flash(param.name); }); - injector = builder.build(); + injector = Collections.unmodifiableMap(builder); } public final String name; diff --git a/jooby/src/main/java/org/jooby/internal/mvc/RequestParamProviderImpl.java b/jooby/src/main/java/org/jooby/internal/mvc/RequestParamProviderImpl.java index 1788bd0ad..fab7577b0 100644 --- a/jooby/src/main/java/org/jooby/internal/mvc/RequestParamProviderImpl.java +++ b/jooby/src/main/java/org/jooby/internal/mvc/RequestParamProviderImpl.java @@ -19,12 +19,10 @@ import java.lang.reflect.Executable; import java.lang.reflect.Parameter; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; - public class RequestParamProviderImpl implements RequestParamProvider { private RequestParamNameProviderImpl provider; @@ -40,11 +38,11 @@ public List parameters(final Executable exec) { return Collections.emptyList(); } - Builder builder = ImmutableList.builder(); + List builder = new ArrayList<>(); for (Parameter parameter : parameters) { builder.add(new RequestParam(parameter, provider.name(parameter))); } - return builder.build(); + return Collections.unmodifiableList(builder); } } diff --git a/jooby/src/main/java/org/jooby/internal/parser/BeanParser.java b/jooby/src/main/java/org/jooby/internal/parser/BeanParser.java index fd9280e4d..b94dc5361 100644 --- a/jooby/src/main/java/org/jooby/internal/parser/BeanParser.java +++ b/jooby/src/main/java/org/jooby/internal/parser/BeanParser.java @@ -15,8 +15,7 @@ */ package org.jooby.internal.parser; -import com.google.common.primitives.Primitives; -import com.google.common.reflect.Reflection; +import org.killbill.commons.utils.Primitives; import com.google.inject.TypeLiteral; import org.jooby.Err; import org.jooby.Mutant; @@ -29,6 +28,7 @@ import org.jooby.internal.parser.bean.BeanPlan; import org.jooby.funzy.Try; +import java.lang.reflect.Proxy; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -94,7 +94,7 @@ private Object newBean(final Request req, final Response rsp, final Route.Chain private Object newBeanInterface(final Request req, final Response rsp, final Route.Chain chain, final Class beanType) { - return Reflection.newProxy(beanType, (proxy, method, args) -> { + return Proxy.newProxyInstance(beanType.getClassLoader(), new Class[]{beanType}, (proxy, method, args) -> { StringBuilder name = new StringBuilder(method.getName() .replace("get", "") .replace("is", "")); diff --git a/jooby/src/main/java/org/jooby/internal/parser/ParserBuilder.java b/jooby/src/main/java/org/jooby/internal/parser/ParserBuilder.java index ba3bc77a8..59a6375d0 100644 --- a/jooby/src/main/java/org/jooby/internal/parser/ParserBuilder.java +++ b/jooby/src/main/java/org/jooby/internal/parser/ParserBuilder.java @@ -15,6 +15,8 @@ */ package org.jooby.internal.parser; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; import org.jooby.Mutant; @@ -25,14 +27,12 @@ import org.jooby.internal.EmptyBodyReference; import org.jooby.internal.StrParamReferenceImpl; -import com.google.common.collect.ImmutableMap; import com.google.inject.TypeLiteral; @SuppressWarnings("rawtypes") public class ParserBuilder implements Parser.Builder { - private ImmutableMap.Builder, Parser.Callback> strategies = ImmutableMap - .builder(); + private Map, Parser.Callback> strategies = new LinkedHashMap<>(); public final TypeLiteral toType; @@ -92,7 +92,7 @@ public Builder ifparams(final Callback> callback) { @SuppressWarnings("unchecked") public Object parse() throws Throwable { - Map, Callback> map = strategies.build(); + Map, Callback> map = Collections.unmodifiableMap(strategies); Callback callback = map.get(type); if (callback == null) { return ctx.next(toType, value); diff --git a/jooby/src/main/java/org/jooby/internal/parser/ParserExecutor.java b/jooby/src/main/java/org/jooby/internal/parser/ParserExecutor.java index 3dd8811a1..f3b1fa3e0 100644 --- a/jooby/src/main/java/org/jooby/internal/parser/ParserExecutor.java +++ b/jooby/src/main/java/org/jooby/internal/parser/ParserExecutor.java @@ -32,7 +32,6 @@ import org.jooby.internal.StatusCodeProvider; import org.jooby.internal.StrParamReferenceImpl; -import com.google.common.collect.ImmutableList; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; @@ -51,7 +50,7 @@ public class ParserExecutor { public ParserExecutor(final Injector injector, final Set parsers, final StatusCodeProvider sc) { this.injector = injector; - this.parsers = ImmutableList.copyOf(parsers); + this.parsers = List.copyOf(parsers); this.sc = sc; } @@ -155,7 +154,7 @@ private Object wrap(final Object nextval, final Object value) { if (nextval instanceof String) { ParamReference pref = (ParamReference) value; return new StrParamReferenceImpl(pref.type(), pref.name(), - ImmutableList.of((String) nextval)); + List.of((String) nextval)); } return nextval; } diff --git a/jooby/src/main/java/org/jooby/internal/parser/bean/BeanPlan.java b/jooby/src/main/java/org/jooby/internal/parser/bean/BeanPlan.java index d9a9c0f23..9aa896d37 100644 --- a/jooby/src/main/java/org/jooby/internal/parser/bean/BeanPlan.java +++ b/jooby/src/main/java/org/jooby/internal/parser/bean/BeanPlan.java @@ -15,8 +15,8 @@ */ package org.jooby.internal.parser.bean; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; +import org.killbill.commons.utils.CharMatcher; +import org.killbill.commons.utils.Splitter; import com.google.inject.TypeLiteral; import org.jooby.Request; import org.jooby.internal.ParameterNameProvider; diff --git a/jooby/src/main/java/org/jooby/internal/ssl/PemReader.java b/jooby/src/main/java/org/jooby/internal/ssl/PemReader.java index 0aa867e90..cb61f8e24 100644 --- a/jooby/src/main/java/org/jooby/internal/ssl/PemReader.java +++ b/jooby/src/main/java/org/jooby/internal/ssl/PemReader.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.KeyException; import java.security.KeyStore; import java.security.cert.CertificateException; @@ -27,8 +28,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.google.common.io.BaseEncoding; -import com.google.common.io.Files; +import java.util.Base64; /** * Reads a PEM file and converts it into a list of DERs so that they are imported into a @@ -51,16 +51,18 @@ final class PemReader { "-+END\\s+.*PRIVATE\\s+KEY[^-]*-+", // Footer Pattern.CASE_INSENSITIVE); + private static final Base64.Decoder BASE64_DECODER = Base64.getMimeDecoder(); + static List readCertificates(final File file) throws CertificateException, IOException { - String content = Files.toString(file, StandardCharsets.US_ASCII); + String content = Files.readString(file.toPath(), StandardCharsets.US_ASCII); - BaseEncoding base64 = base64(); - List certs = new ArrayList(); + // Originally, we have local variables: base64 = return BaseEncoding.base64().withSeparator("\n", '\n'); + List certs = new ArrayList<>(); Matcher m = CERT_PATTERN.matcher(content); int start = 0; while (m.find(start)) { - ByteBuffer buffer = ByteBuffer.wrap(base64.decode(m.group(1))); + ByteBuffer buffer = ByteBuffer.wrap(BASE64_DECODER.decode(m.group(1))); certs.add(buffer); start = m.end(); @@ -73,12 +75,8 @@ static List readCertificates(final File file) return certs; } - private static BaseEncoding base64() { - return BaseEncoding.base64().withSeparator("\n", '\n'); - } - static ByteBuffer readPrivateKey(final File file) throws KeyException, IOException { - String content = Files.toString(file, StandardCharsets.US_ASCII); + String content = Files.readString(file.toPath(), StandardCharsets.US_ASCII); Matcher m = KEY_PATTERN.matcher(content); if (!m.find()) { @@ -86,7 +84,7 @@ static ByteBuffer readPrivateKey(final File file) throws KeyException, IOExcepti } String value = m.group(1); - return ByteBuffer.wrap(base64().decode(value)); + return ByteBuffer.wrap(BASE64_DECODER.decode(value)); } private PemReader() { diff --git a/jooby/src/main/java/org/jooby/servlet/ServletServletRequest.java b/jooby/src/main/java/org/jooby/servlet/ServletServletRequest.java index ca75d8a21..57431672e 100644 --- a/jooby/src/main/java/org/jooby/servlet/ServletServletRequest.java +++ b/jooby/src/main/java/org/jooby/servlet/ServletServletRequest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -39,9 +40,7 @@ import org.jooby.spi.NativeRequest; import org.jooby.spi.NativeUpload; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; +import org.killbill.commons.utils.Strings; public class ServletServletRequest implements NativeRequest { @@ -107,11 +106,11 @@ public List paramNames() { } private List toList(final Enumeration enumeration) { - Builder result = ImmutableList.builder(); + List result = new ArrayList<>(); while (enumeration.hasMoreElements()) { result.add(enumeration.nextElement()); } - return result.build(); + return Collections.unmodifiableList(result); } @Override @@ -152,7 +151,7 @@ public List headerNames() { public List cookies() { jakarta.servlet.http.Cookie[] cookies = req.getCookies(); if (cookies == null) { - return ImmutableList.of(); + return Collections.emptyList(); } return Arrays.stream(cookies) .map(c -> { diff --git a/jooby/src/main/java/org/jooby/servlet/ServletServletResponse.java b/jooby/src/main/java/org/jooby/servlet/ServletServletResponse.java index 7abb88771..11ee84c38 100644 --- a/jooby/src/main/java/org/jooby/servlet/ServletServletResponse.java +++ b/jooby/src/main/java/org/jooby/servlet/ServletServletResponse.java @@ -15,8 +15,7 @@ */ package org.jooby.servlet; -import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; import static java.util.Objects.requireNonNull; import org.jooby.funzy.Try; @@ -55,7 +54,7 @@ public List headers(final String name) { if (headers == null || headers.size() == 0) { return Collections.emptyList(); } - return ImmutableList.copyOf(headers); + return List.copyOf(headers); } @Override diff --git a/jooby/src/main/java/org/jooby/servlet/ServletUpload.java b/jooby/src/main/java/org/jooby/servlet/ServletUpload.java index 8f3c0c2dc..a1fcd6e3e 100644 --- a/jooby/src/main/java/org/jooby/servlet/ServletUpload.java +++ b/jooby/src/main/java/org/jooby/servlet/ServletUpload.java @@ -27,7 +27,6 @@ import org.jooby.spi.NativeUpload; -import com.google.common.collect.ImmutableList; public class ServletUpload implements NativeUpload { @@ -61,7 +60,7 @@ public List headers(final String name) { if (headers == null) { return Collections.emptyList(); } - return ImmutableList.copyOf(headers); + return List.copyOf(headers); } @Override diff --git a/jooby/src/main/java/org/jooby/test/MockRouter.java b/jooby/src/main/java/org/jooby/test/MockRouter.java index 0d16bd2ee..789bed7e7 100644 --- a/jooby/src/main/java/org/jooby/test/MockRouter.java +++ b/jooby/src/main/java/org/jooby/test/MockRouter.java @@ -15,9 +15,6 @@ */ package org.jooby.test; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.reflect.Reflection; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.name.Names; @@ -37,8 +34,10 @@ import org.jooby.funzy.Try; import java.lang.reflect.Field; +import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -251,7 +250,7 @@ public class MockRouter { @Override public List routes() { - return ImmutableList.of(); + return Collections.emptyList(); } @Override @@ -435,7 +434,7 @@ private Jooby hackInjector(final Jooby app) { @SuppressWarnings("rawtypes") private static Injector proxyInjector(final ClassLoader loader, final Map registry) { - return Reflection.newProxy(Injector.class, (proxy, method, args) -> { + return (Injector) Proxy.newProxyInstance(Injector.class.getClassLoader(), new Class[]{Injector.class}, (proxy, method, args) -> { if (method.getName().equals("getInstance")) { Key key = (Key) args[0]; Object value = registry.get(key); @@ -445,7 +444,7 @@ private static Injector proxyInjector(final ClassLoader loader, final Map { StackTraceElement[] stacktrace = iex.getStackTrace(); - return Lists.newArrayList(stacktrace).subList(CLEAN_STACK, stacktrace.length); + return new ArrayList<>(Arrays.asList(stacktrace)).subList(CLEAN_STACK, stacktrace.length); }).onSuccess(stacktrace -> iex .setStackTrace(stacktrace.toArray(new StackTraceElement[stacktrace.size()]))); throw iex; @@ -465,8 +464,9 @@ private void traverse(final Class type, final Consumer set) { } } + @SuppressWarnings({"rawtypes", "unchecked"}) private static T empty(final Class type) { - return Reflection.newProxy(type, (proxy, method, args) -> { + return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (proxy, method, args) -> { throw new UnsupportedOperationException(method.toString()); }); } diff --git a/jooby/src/test/java/jetty/H2Jetty.java b/jooby/src/test/java/jetty/H2Jetty.java index 7335dbd28..451751e71 100644 --- a/jooby/src/test/java/jetty/H2Jetty.java +++ b/jooby/src/test/java/jetty/H2Jetty.java @@ -15,7 +15,7 @@ */ package jetty; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; import org.jooby.Jooby; import org.jooby.MediaType; import org.jooby.Results; diff --git a/jooby/src/test/java/org/jooby/AssetTest.java b/jooby/src/test/java/org/jooby/AssetTest.java new file mode 100644 index 000000000..691b04c08 --- /dev/null +++ b/jooby/src/test/java/org/jooby/AssetTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.jooby; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import java.net.URL; + +import org.junit.Test; + +public class AssetTest { + + private Asset createAsset(final URL resource, final long lastModified, final long length) { + return new Asset() { + @Override + public String path() { + return resource.getPath(); + } + + @Override + public URL resource() { + return resource; + } + + @Override + public long lastModified() { + return lastModified; + } + + @Override + public long length() { + return length; + } + + @Override + public InputStream stream() { + return null; + } + + @Override + public MediaType type() { + return MediaType.js; + } + }; + } + + @Test + public void etagFormat() throws Exception { + URL url = new URL("file:///assets/app.js"); + Asset asset = createAsset(url, 1700000000000L, 4096L); + + String etag = asset.etag(); + // Weak etag format: W/"" + assertTrue("Should start with W/\"", etag.startsWith("W/\"")); + assertTrue("Should end with \"", etag.endsWith("\"")); + } + + @Test + public void etagDeterministic() throws Exception { + URL url = new URL("file:///assets/app.js"); + Asset a1 = createAsset(url, 1700000000000L, 4096L); + Asset a2 = createAsset(url, 1700000000000L, 4096L); + + assertEquals("Same inputs must produce same etag", a1.etag(), a2.etag()); + } + + @Test + public void etagSensitiveToLastModified() throws Exception { + URL url = new URL("file:///assets/app.js"); + Asset a1 = createAsset(url, 1700000000000L, 4096L); + Asset a2 = createAsset(url, 1700000001000L, 4096L); + + assertNotEquals("Different lastModified must produce different etag", a1.etag(), a2.etag()); + } + + @Test + public void etagSensitiveToLength() throws Exception { + URL url = new URL("file:///assets/app.js"); + Asset a1 = createAsset(url, 1700000000000L, 4096L); + Asset a2 = createAsset(url, 1700000000000L, 8192L); + + assertNotEquals("Different length must produce different etag", a1.etag(), a2.etag()); + } + + @Test + public void etagSensitiveToResource() throws Exception { + URL url1 = new URL("file:///assets/app.js"); + URL url2 = new URL("file:///assets/vendor.js"); + Asset a1 = createAsset(url1, 1700000000000L, 4096L); + Asset a2 = createAsset(url2, 1700000000000L, 4096L); + + assertNotEquals("Different resource must produce different etag", a1.etag(), a2.etag()); + } + + @Test + public void etagExactValue() throws Exception { + // Pin a specific input and verify exact output to detect regressions. + // This value was captured from the original Guava (BaseEncoding + Longs) implementation. + URL url = new URL("file:///assets/app.js"); + Asset asset = createAsset(url, 1700000000000L, 4096L); + + String expected = "W/\"///+dC0YuPc=/////+L9wPc=\""; + assertEquals(expected, asset.etag()); + } +} diff --git a/jooby/src/test/java/org/jooby/CookieCodecTest.java b/jooby/src/test/java/org/jooby/CookieCodecTest.java index 26a4b90fd..549652184 100644 --- a/jooby/src/test/java/org/jooby/CookieCodecTest.java +++ b/jooby/src/test/java/org/jooby/CookieCodecTest.java @@ -19,28 +19,31 @@ import org.junit.Test; -import com.google.common.collect.ImmutableMap; +import java.util.LinkedHashMap; +import java.util.Map; public class CookieCodecTest { @Test public void encode() { - assertEquals("success=OK", Cookie.URL_ENCODER.apply(ImmutableMap.of("success", "OK"))); + assertEquals("success=OK", Cookie.URL_ENCODER.apply(Map.of("success", "OK"))); assertEquals("success=semi%3Bcolon", - Cookie.URL_ENCODER.apply(ImmutableMap.of("success", "semi;colon"))); + Cookie.URL_ENCODER.apply(Map.of("success", "semi;colon"))); assertEquals("success=eq%3Duals", - Cookie.URL_ENCODER.apply(ImmutableMap.of("success", "eq=uals"))); + Cookie.URL_ENCODER.apply(Map.of("success", "eq=uals"))); - assertEquals("success=OK&error=404", - Cookie.URL_ENCODER.apply(ImmutableMap.of("success", "OK", "error", "404"))); + Map map = new LinkedHashMap<>(); + map.put("success", "OK"); + map.put("error", "404"); + assertEquals("success=OK&error=404", Cookie.URL_ENCODER.apply(map)); } @Test public void decode() { - assertEquals(ImmutableMap.of("success", "OK"), Cookie.URL_DECODER.apply("success=OK")); - assertEquals(ImmutableMap.of("success", "OK", "foo", "bar"), + assertEquals(Map.of("success", "OK"), Cookie.URL_DECODER.apply("success=OK")); + assertEquals(Map.of("success", "OK", "foo", "bar"), Cookie.URL_DECODER.apply("success=OK&foo=bar")); - assertEquals(ImmutableMap.of("semicolon", "semi;colon"), + assertEquals(Map.of("semicolon", "semi;colon"), Cookie.URL_DECODER.apply("semicolon=semi%3Bcolon")); } } diff --git a/jooby/src/test/java/org/jooby/CorsTest.java b/jooby/src/test/java/org/jooby/CorsTest.java index 2c5fe28e1..2b2c8173b 100644 --- a/jooby/src/test/java/org/jooby/CorsTest.java +++ b/jooby/src/test/java/org/jooby/CorsTest.java @@ -19,13 +19,13 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import java.util.ArrayList; import java.util.Arrays; import java.util.function.Consumer; import org.jooby.handlers.Cors; import org.junit.Test; -import com.google.common.collect.Lists; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -138,10 +138,10 @@ private Config baseconf() { .withValue("enabled", fromAnyRef(true)) .withValue("credentials", fromAnyRef(true)) .withValue("maxAge", fromAnyRef("30m")) - .withValue("origin", fromAnyRef(Lists.newArrayList())) - .withValue("exposedHeaders", fromAnyRef(Lists.newArrayList("X"))) - .withValue("allowedMethods", fromAnyRef(Lists.newArrayList())) - .withValue("allowedHeaders", fromAnyRef(Lists.newArrayList())); + .withValue("origin", fromAnyRef(new ArrayList<>())) + .withValue("exposedHeaders", fromAnyRef(new ArrayList<>(Arrays.asList("X")))) + .withValue("allowedMethods", fromAnyRef(new ArrayList<>())) + .withValue("allowedHeaders", fromAnyRef(new ArrayList<>())); return config; } diff --git a/jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java b/jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java index c35792cac..d9aeb79c9 100644 --- a/jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java +++ b/jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java @@ -15,9 +15,7 @@ */ package org.jooby; -import com.google.common.collect.ImmutableList; -import com.google.common.escape.Escapers; -import com.google.common.html.HtmlEscapers; +import org.killbill.commons.utils.html.HtmlEscapers; import com.typesafe.config.Config; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; @@ -32,6 +30,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -58,7 +57,7 @@ public void handleNoErrMessage() throws Exception { new Err.DefHandler().handle(req, rsp, ex); }, unit -> { Result result = capturedResult.get(); - View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get(); + View view = (View) result.ifGet(List.of(MediaType.html)).get(); assertEquals("err", view.name()); checkErr(stacktrace, "Server Error(500)", (Map) view.model() .get("err")); @@ -120,7 +119,7 @@ public void handleWithErrMessage() throws Exception { }, unit -> { Result result = capturedResult.get(); - View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get(); + View view = (View) result.ifGet(List.of(MediaType.html)).get(); assertEquals("err", view.name()); checkErr(stacktrace, "Server Error(500): Something something dark", (Map) view.model() @@ -151,7 +150,7 @@ public void handleWithHtmlErrMessage() throws Exception { }, unit -> { Result result = capturedResult.get(); - View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get(); + View view = (View) result.ifGet(List.of(MediaType.html)).get(); assertEquals("err", view.name()); checkErr(stacktrace, "Server Error(500): Something something <em>dark</em>", (Map) view.model() diff --git a/jooby/src/test/java/org/jooby/EnvTest.java b/jooby/src/test/java/org/jooby/EnvTest.java index 3eeef1ca7..ce5ca379c 100644 --- a/jooby/src/test/java/org/jooby/EnvTest.java +++ b/jooby/src/test/java/org/jooby/EnvTest.java @@ -15,7 +15,6 @@ */ package org.jooby; -import com.google.common.collect.ImmutableMap; import com.google.inject.Key; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -97,7 +96,7 @@ public void resolveMap() { Config config = ConfigFactory.empty(); Env env = Env.DEFAULT.build(config); - assertEquals("foo.bar - foo.bar", env.resolver().source(ImmutableMap.of("var", "foo.bar")) + assertEquals("foo.bar - foo.bar", env.resolver().source(Map.of("var", "foo.bar")) .resolve("${var} - ${var}")); } @@ -107,7 +106,7 @@ public void resolveMapIgnore() { Env env = Env.DEFAULT.build(config); assertEquals("${varx} - ${varx}", - env.resolver().ignoreMissing().source(ImmutableMap.of("var", "foo.bar")) + env.resolver().ignoreMissing().source(Map.of("var", "foo.bar")) .resolve("${varx} - ${varx}")); } diff --git a/jooby/src/test/java/org/jooby/JoobyTest.java b/jooby/src/test/java/org/jooby/JoobyTest.java index 22d7a445e..959ca7901 100644 --- a/jooby/src/test/java/org/jooby/JoobyTest.java +++ b/jooby/src/test/java/org/jooby/JoobyTest.java @@ -15,9 +15,9 @@ */ package org.jooby; -import com.google.common.escape.Escaper; -import com.google.common.html.HtmlEscapers; -import com.google.common.net.UrlEscapers; +import org.killbill.commons.utils.escape.Escaper; +import org.killbill.commons.utils.html.HtmlEscapers; +import org.killbill.commons.utils.net.UrlEscapers; import com.google.inject.Binder; import com.google.inject.Guice; import com.google.inject.Injector; diff --git a/jooby/src/test/java/org/jooby/RequestForwardingTest.java b/jooby/src/test/java/org/jooby/RequestForwardingTest.java index 327a1febc..022f295d6 100644 --- a/jooby/src/test/java/org/jooby/RequestForwardingTest.java +++ b/jooby/src/test/java/org/jooby/RequestForwardingTest.java @@ -31,8 +31,7 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.base.Charsets; -import com.google.common.collect.ImmutableMap; +import java.nio.charset.StandardCharsets; import com.google.inject.Key; import com.google.inject.TypeLiteral; @@ -415,10 +414,10 @@ public void charset() throws Exception { new MockUnit(Request.class) .expect(unit -> { Request req = unit.get(Request.class); - when(req.charset()).thenReturn(Charsets.UTF_8); + when(req.charset()).thenReturn(StandardCharsets.UTF_8); }) .run(unit -> { - assertEquals(Charsets.UTF_8, new Request.Forwarding(unit.get(Request.class)).charset()); + assertEquals(StandardCharsets.UTF_8, new Request.Forwarding(unit.get(Request.class)).charset()); }); } @@ -675,11 +674,11 @@ public void push() throws Exception { new MockUnit(Request.class) .expect(unit -> { Request req = unit.get(Request.class); - when(req.push("/path", ImmutableMap.of("k", "v"))).thenReturn(req); + when(req.push("/path", Map.of("k", "v"))).thenReturn(req); }) .run(unit -> { Forwarding req = new Request.Forwarding(unit.get(Request.class)); - assertEquals(req, req.push("/path", ImmutableMap.of("k", "v"))); + assertEquals(req, req.push("/path", Map.of("k", "v"))); }); } diff --git a/jooby/src/test/java/org/jooby/ResponseForwardingTest.java b/jooby/src/test/java/org/jooby/ResponseForwardingTest.java index 81fc90087..82dd203c5 100644 --- a/jooby/src/test/java/org/jooby/ResponseForwardingTest.java +++ b/jooby/src/test/java/org/jooby/ResponseForwardingTest.java @@ -23,14 +23,15 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Optional; import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; +import java.nio.charset.StandardCharsets; public class ResponseForwardingTest { @@ -173,15 +174,15 @@ public void charset() throws Exception { new MockUnit(Response.class) .expect(unit -> { Response rsp = unit.get(Response.class); - when(rsp.charset()).thenReturn(Charsets.UTF_8); + when(rsp.charset()).thenReturn(StandardCharsets.UTF_8); - when(rsp.charset(Charsets.US_ASCII)).thenReturn(null); + when(rsp.charset(StandardCharsets.US_ASCII)).thenReturn(null); }) .run(unit -> { Response rsp = new Response.Forwarding(unit.get(Response.class)); - assertEquals(Charsets.UTF_8, rsp.charset()); + assertEquals(StandardCharsets.UTF_8, rsp.charset()); - assertEquals(rsp, rsp.charset(Charsets.US_ASCII)); + assertEquals(rsp, rsp.charset(StandardCharsets.US_ASCII)); }); } @@ -337,12 +338,12 @@ public void listHeader() throws Exception { .expect(unit -> { Response rsp = unit.get(Response.class); - when(rsp.header("h", Lists. newArrayList("v1", 2))).thenReturn(rsp); + when(rsp.header("h", new ArrayList<>(Arrays.asList("v1", 2)))).thenReturn(rsp); }) .run(unit -> { Response rsp = new Response.Forwarding(unit.get(Response.class)); - assertEquals(rsp, rsp.header("h", Lists. newArrayList("v1", 2))); + assertEquals(rsp, rsp.header("h", new ArrayList<>(Arrays.asList("v1", 2)))); }); } diff --git a/jooby/src/test/java/org/jooby/ResultTest.java b/jooby/src/test/java/org/jooby/ResultTest.java index b1411dc68..222699728 100644 --- a/jooby/src/test/java/org/jooby/ResultTest.java +++ b/jooby/src/test/java/org/jooby/ResultTest.java @@ -17,13 +17,14 @@ import static org.junit.Assert.assertEquals; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Optional; import org.junit.Test; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; public class ResultTest { @@ -134,7 +135,7 @@ public void header() { assertEquals(7.0f, result.headers().get("float")); assertEquals(8.0d, result.headers().get("double")); assertEquals(date, result.headers().get("date")); - assertEquals(Lists.newArrayList(1, 2, 3), result.headers().get("list")); + assertEquals(new ArrayList<>(Arrays.asList(1, 2, 3)), result.headers().get("list")); } @Test @@ -214,7 +215,7 @@ public void whenGet() { .when(MediaType.all, () -> value); Result clone = result.clone(); assertEquals(json, result.get()); - assertEquals(value, clone.get(ImmutableList.of(MediaType.html))); + assertEquals(value, clone.get(List.of(MediaType.html))); } @Test diff --git a/jooby/src/test/java/org/jooby/RouteDefinitionTest.java b/jooby/src/test/java/org/jooby/RouteDefinitionTest.java index cd332cdf5..9e20ab0cd 100644 --- a/jooby/src/test/java/org/jooby/RouteDefinitionTest.java +++ b/jooby/src/test/java/org/jooby/RouteDefinitionTest.java @@ -15,7 +15,6 @@ */ package org.jooby; -import com.google.common.collect.ImmutableMap; import issues.RouteSourceLocation; import org.jooby.Route.Definition; import org.jooby.internal.RouteImpl; @@ -28,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -315,7 +315,7 @@ public void reverse() throws Exception { assertEquals("/cat/5", route.apply("/{type}/{id}").reverse("cat", 5)); assertEquals("/ccat/1", - route.apply("/c{type}/{id}").reverse(ImmutableMap.of("type", "cat", "id", 1))); + route.apply("/c{type}/{id}").reverse(Map.of("type", "cat", "id", 1))); assertEquals("/cat/tom", route.apply("/cat/tom").reverse("cat", 1)); } diff --git a/jooby/src/test/java/org/jooby/SseTest.java b/jooby/src/test/java/org/jooby/SseTest.java index cae84fe1b..418e57d73 100644 --- a/jooby/src/test/java/org/jooby/SseTest.java +++ b/jooby/src/test/java/org/jooby/SseTest.java @@ -15,8 +15,6 @@ */ package org.jooby; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; @@ -38,6 +36,7 @@ import java.nio.channels.ClosedChannelException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -59,10 +58,10 @@ public class SseTest { when(request.require(Injector.class)).thenReturn(injector); when(request.route()).thenReturn(route); - when(request.attributes()).thenReturn(ImmutableMap.of()); + when(request.attributes()).thenReturn(Map.of()); when(request.header("Last-Event-ID")).thenReturn(lastEventId); - when(injector.getInstance(Renderer.KEY)).thenReturn(Sets.newHashSet()); + when(injector.getInstance(Renderer.KEY)).thenReturn(new HashSet<>()); }; private Block locale = unit -> { diff --git a/jooby/src/test/java/org/jooby/ViewTest.java b/jooby/src/test/java/org/jooby/ViewTest.java index 2298d8e74..f12f5fc49 100644 --- a/jooby/src/test/java/org/jooby/ViewTest.java +++ b/jooby/src/test/java/org/jooby/ViewTest.java @@ -19,7 +19,8 @@ import org.junit.Test; -import com.google.common.collect.ImmutableMap; +import java.util.Map; + public class ViewTest { @@ -64,15 +65,15 @@ public void viewBuildModel() { @Test public void viewBuildModelMap() { - View view = Results.html("v").put("m", ImmutableMap.of("k", "v")); + View view = Results.html("v").put("m", Map.of("k", "v")); assertEquals("v", view.name()); assertEquals(1, view.model().size()); - assertEquals(ImmutableMap.of("k", "v"), view.model().get("m")); + assertEquals(Map.of("k", "v"), view.model().get("m")); } @Test public void viewPutMap() { - View view = Results.html("v").put(ImmutableMap.of("k", "v")); + View view = Results.html("v").put(Map.of("k", "v")); assertEquals("v", view.name()); assertEquals(1, view.model().size()); assertEquals("v", view.model().get("k")); diff --git a/jooby/src/test/java/org/jooby/internal/AbstractRendererContextTest.java b/jooby/src/test/java/org/jooby/internal/AbstractRendererContextTest.java index d44cd6cf6..97e7bf231 100644 --- a/jooby/src/test/java/org/jooby/internal/AbstractRendererContextTest.java +++ b/jooby/src/test/java/org/jooby/internal/AbstractRendererContextTest.java @@ -32,14 +32,13 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.ImmutableList; public class AbstractRendererContextTest { @Test(expected = Err.class) public void norenderer() throws Throwable { List renderers = new ArrayList<>(); - List produces = ImmutableList.of(MediaType.json); + List produces = List.of(MediaType.json); View value = Results.html("view"); new MockUnit() .run(unit -> { diff --git a/jooby/src/test/java/org/jooby/internal/AppPrinterTest.java b/jooby/src/test/java/org/jooby/internal/AppPrinterTest.java index 52f04f779..4f233110a 100644 --- a/jooby/src/test/java/org/jooby/internal/AppPrinterTest.java +++ b/jooby/src/test/java/org/jooby/internal/AppPrinterTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; +import java.util.LinkedHashSet; import org.jooby.Route; import org.jooby.Route.Before; @@ -25,7 +26,6 @@ import org.junit.Test; import org.slf4j.LoggerFactory; -import com.google.common.collect.Sets; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigValueFactory; @@ -38,9 +38,9 @@ public class AppPrinterTest { @Test public void print() { String setup = new AppPrinter( - Sets.newLinkedHashSet( + new LinkedHashSet<>( Arrays.asList(before("/"), beforeSend("/"), after("/"), route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), config("/")) + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/")) .toString(); assertEquals(" GET {before}/ [*/*] [*/*] (/anonymous)\n" + " GET {after}/ [*/*] [*/*] (/anonymous)\n" + @@ -56,9 +56,9 @@ public void print() { @Test public void printConfig() { AppPrinter printer = new AppPrinter( - Sets.newLinkedHashSet( + new LinkedHashSet<>( Arrays.asList(before("/"), beforeSend("/"), after("/"), route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), config("/")); + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/")); Logger log = (Logger) LoggerFactory.getLogger(AppPrinterTest.class); log.setLevel(Level.DEBUG); printer.printConf(log, config("/")); @@ -67,8 +67,8 @@ public void printConfig() { @Test public void printHttps() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/").withValue("application.securePort", ConfigValueFactory.fromAnyRef(8443))) .toString(); assertEquals(" GET / [*/*] [*/*] (/anonymous)\n" + @@ -83,8 +83,8 @@ public void printHttps() { @Test public void printHttp2() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/") .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(true)) .withValue("application.securePort", ConfigValueFactory.fromAnyRef(8443))) @@ -101,8 +101,8 @@ public void printHttp2() { @Test public void printHttp2Https() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/") .withValue("server.http2.cleartext", ConfigValueFactory.fromAnyRef(false)) .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(true)) @@ -120,8 +120,8 @@ public void printHttp2Https() { @Test public void printHttp2ClearText() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/") .withValue("server.http2.cleartext", ConfigValueFactory.fromAnyRef(true)) .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(true))) @@ -146,8 +146,8 @@ private Config config(final String path) { @Test public void printWithPath() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(Arrays.asList(socket("/ws"))), config("/app")) + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(Arrays.asList(socket("/ws"))), config("/app")) .toString(); assertEquals(" GET / [*/*] [*/*] (/anonymous)\n" + " GET /home [*/*] [*/*] (/anonymous)\n" + @@ -160,8 +160,8 @@ public void printWithPath() { @Test public void printNoSockets() { String setup = new AppPrinter( - Sets.newLinkedHashSet(Arrays.asList(route("/"), route("/home"))), - Sets.newLinkedHashSet(), config("/app")) + new LinkedHashSet<>(Arrays.asList(route("/"), route("/home"))), + new LinkedHashSet<>(), config("/app")) .toString(); assertEquals(" GET / [*/*] [*/*] (/anonymous)\n" + " GET /home [*/*] [*/*] (/anonymous)\n" + diff --git a/jooby/src/test/java/org/jooby/internal/BodyReferenceImplTest.java b/jooby/src/test/java/org/jooby/internal/BodyReferenceImplTest.java index c17714853..84cfb8062 100644 --- a/jooby/src/test/java/org/jooby/internal/BodyReferenceImplTest.java +++ b/jooby/src/test/java/org/jooby/internal/BodyReferenceImplTest.java @@ -17,6 +17,8 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -34,7 +36,7 @@ import org.jooby.test.MockUnit.Block; import org.junit.Test; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; public class BodyReferenceImplTest { @@ -77,7 +79,7 @@ public void inErr() throws Exception { .expect(unit -> { InputStream in = unit.get(InputStream.class); - when(in.read(unit.capture(byte[].class))).thenThrow(new IOException()); + when(in.transferTo(any(OutputStream.class))).thenThrow(new IOException()); OutputStream out = unit.get(ByteArrayOutputStream.class); diff --git a/jooby/src/test/java/org/jooby/internal/BuiltinParserTest.java b/jooby/src/test/java/org/jooby/internal/BuiltinParserTest.java index 8593f76ff..4c83ddd40 100644 --- a/jooby/src/test/java/org/jooby/internal/BuiltinParserTest.java +++ b/jooby/src/test/java/org/jooby/internal/BuiltinParserTest.java @@ -15,20 +15,152 @@ */ package org.jooby.internal; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.jooby.Parser; +import org.jooby.internal.parser.ParserExecutor; +import org.junit.Before; import org.junit.Test; +import com.google.inject.TypeLiteral; +import com.google.inject.util.Types; +import com.typesafe.config.ConfigFactory; + public class BuiltinParserTest { + private ParserExecutor executor; + + public enum Color { + RED, + GREEN, + BLUE + } + + @Before + public void setup() { + executor = parser(BuiltinParser.Basic, BuiltinParser.Collection, BuiltinParser.Optional, + BuiltinParser.Enum, BuiltinParser.Bytes); + } + + @Test + public void valuesAreRegisteredInParserOrder() { + assertArrayEquals(new BuiltinParser[]{ + BuiltinParser.Basic, + BuiltinParser.Collection, + BuiltinParser.Optional, + BuiltinParser.Enum, + BuiltinParser.Bytes }, BuiltinParser.values()); + } + @Test - public void values() { - assertEquals(5, BuiltinParser.values().length); + public void bytesParserName() { + assertEquals("byte[]", BuiltinParser.Bytes.toString()); } @Test - public void bytesValueOf() { - assertEquals(BuiltinParser.Bytes, BuiltinParser.valueOf("Bytes")); + public void bytesParserReadsBody() throws Throwable { + byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8); + + assertArrayEquals(bytes, executor.convert(TypeLiteral.get(byte[].class), + new BodyReferenceImpl(bytes.length, StandardCharsets.UTF_8, + new File("target/BuiltinParserTest/body.tmp"), + new ByteArrayInputStream(bytes), bytes.length + 1L))); + } + + @Test + public void longParserAcceptsHttpDate() throws Throwable { + LocalDateTime dateTime = LocalDateTime.of(2024, 1, 15, 10, 30, 0); + + assertEquals(dateTime.toInstant(ZoneOffset.UTC).toEpochMilli(), + (long) executor.convert(TypeLiteral.get(long.class), data(dateTime.format(Headers.fmt)))); + } + + @Test + public void basicParserAcceptsEmptyStringButRejectsEmptyScalarValues() throws Throwable { + assertEquals("", executor.convert(TypeLiteral.get(String.class), data(""))); + + try { + executor.convert(TypeLiteral.get(int.class), data("")); + fail("Expected NoSuchElementException"); + } catch (NoSuchElementException expected) { + // expected + } + } + + @Test + public void collectionParserBuildsUnmodifiableCollections() throws Throwable { + List list = executor.convert(TypeLiteral.get(Types.listOf(String.class)), + data("b", "a", "b")); + assertEquals(Arrays.asList("b", "a", "b"), list); + assertUnsupported(() -> list.add("c")); + + Set set = executor.convert(TypeLiteral.get(Types.setOf(String.class)), + data("b", "a", "b")); + assertEquals(new LinkedHashSet<>(Arrays.asList("b", "a")), set); + assertEquals(2, set.size()); + assertUnsupported(() -> set.add("c")); + + SortedSet sorted = executor.convert( + TypeLiteral.get(Types.newParameterizedType(SortedSet.class, String.class)), + data("b", "a", "b")); + assertEquals(new TreeSet<>(Arrays.asList("a", "b")), sorted); + assertUnsupported(() -> sorted.add("c")); + } + + @Test + public void collectionAndOptionalParsersIgnoreRawOrUnsupportedTypes() throws Throwable { + ParserExecutor collectionOnly = parser(BuiltinParser.Collection); + assertSame(ParserExecutor.NO_PARSER, + collectionOnly.convert(TypeLiteral.get(List.class), data("a"))); + assertSame(ParserExecutor.NO_PARSER, + collectionOnly.convert(TypeLiteral.get(Types.mapOf(String.class, String.class)), data("a"))); + + ParserExecutor optionalOnly = parser(BuiltinParser.Optional); + assertSame(ParserExecutor.NO_PARSER, + optionalOnly.convert(TypeLiteral.get(Optional.class), data("a"))); + } + + @Test + public void enumParserIsCaseInsensitive() throws Throwable { + assertEquals(Color.RED, executor.convert(TypeLiteral.get(Color.class), data("red"))); + assertEquals(Color.GREEN, executor.convert(TypeLiteral.get(Color.class), data("Green"))); + assertEquals(Color.BLUE, executor.convert(TypeLiteral.get(Color.class), data("bLuE"))); + } + + private Object data(final String... values) { + return new StrParamReferenceImpl("parameter", "p", new ArrayList<>(Arrays.asList(values))); + } + + private ParserExecutor parser(final Parser... parsers) { + return new ParserExecutor(null, new LinkedHashSet<>(Arrays.asList(parsers)), + new StatusCodeProvider(ConfigFactory.empty())); + } + + private static void assertUnsupported(final Runnable mutation) { + try { + mutation.run(); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + // expected + } } } diff --git a/jooby/src/test/java/org/jooby/internal/CookieSessionManagerTest.java b/jooby/src/test/java/org/jooby/internal/CookieSessionManagerTest.java index bc717e157..99731a1e2 100644 --- a/jooby/src/test/java/org/jooby/internal/CookieSessionManagerTest.java +++ b/jooby/src/test/java/org/jooby/internal/CookieSessionManagerTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.when; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -37,7 +38,6 @@ import static org.mockito.Mockito.doAnswer; -import com.google.common.collect.ImmutableMap; import com.typesafe.config.Config; public class CookieSessionManagerTest { @@ -138,7 +138,7 @@ public void saveAfter() throws Exception { .expect(unit -> { SessionImpl session = unit.get(SessionImpl.class); - when(session.attributes()).thenReturn(ImmutableMap.of("foo", "2")); + when(session.attributes()).thenReturn(Map.of("foo", "2")); Request req = unit.get(Request.class); when(req.ifSession()).thenReturn(Optional.of(session)); @@ -214,7 +214,7 @@ public void saveAfterTouchSession() throws Exception { .expect(unit -> { SessionImpl session = unit.get(SessionImpl.class); - when(session.attributes()).thenReturn(ImmutableMap.of("foo", "1")); + when(session.attributes()).thenReturn(Map.of("foo", "1")); Request req = unit.get(Request.class); when(req.ifSession()).thenReturn(Optional.of(session)); @@ -302,7 +302,7 @@ public void getSession() throws Exception { .expect(sessionBuilder(Session.COOKIE_SESSION, false, -1)) .expect(unit -> { Session.Builder builder = unit.get(Session.Builder.class); - when(builder.set(ImmutableMap.of("foo", "1"))).thenReturn(builder); + when(builder.set(Map.of("foo", "1"))).thenReturn(builder); when(builder.build()).thenReturn(unit.get(SessionImpl.class)); }) .expect(push) diff --git a/jooby/src/test/java/org/jooby/internal/FallbackRouteTest.java b/jooby/src/test/java/org/jooby/internal/FallbackRouteTest.java index ba970e2e5..0ab68d426 100644 --- a/jooby/src/test/java/org/jooby/internal/FallbackRouteTest.java +++ b/jooby/src/test/java/org/jooby/internal/FallbackRouteTest.java @@ -17,6 +17,8 @@ import static org.junit.Assert.assertEquals; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.jooby.MediaType; @@ -24,8 +26,6 @@ import org.jooby.Route.Filter; import org.junit.Test; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; public class FallbackRouteTest { @@ -35,7 +35,7 @@ public void props() throws Throwable { Filter filter = (req, rsp, chain) -> { handled.set(true); }; - FallbackRoute route = new FallbackRoute("foo", "GET", "/x", ImmutableList.of(MediaType.json), + FallbackRoute route = new FallbackRoute("foo", "GET", "/x", List.of(MediaType.json), filter); assertEquals(true, route.apply(null)); @@ -46,8 +46,8 @@ public void props() throws Throwable { assertEquals("foo", route.name()); assertEquals("/x", route.path()); assertEquals("/x", route.pattern()); - assertEquals(ImmutableList.of(MediaType.json), route.produces()); - assertEquals("/x", route.reverse(ImmutableMap.of())); + assertEquals(List.of(MediaType.json), route.produces()); + assertEquals("/x", route.reverse(Map.of())); assertEquals("/x", route.reverse("a", "b")); assertEquals(Route.Source.BUILTIN, route.source()); route.handle(null, null, null); diff --git a/jooby/src/test/java/org/jooby/internal/MutantImplTest.java b/jooby/src/test/java/org/jooby/internal/MutantImplTest.java index d5a3ba770..87c61697b 100644 --- a/jooby/src/test/java/org/jooby/internal/MutantImplTest.java +++ b/jooby/src/test/java/org/jooby/internal/MutantImplTest.java @@ -21,6 +21,7 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; @@ -36,10 +37,6 @@ import org.jooby.internal.parser.StringConstructorParser; import org.junit.Test; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Sets; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import com.typesafe.config.ConfigFactory; @@ -67,32 +64,32 @@ public void asBoolean() throws Exception { @Test public void asBooleanList() throws Exception { - assertEquals(ImmutableList.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(List.of(Boolean.TRUE, Boolean.FALSE), newMutant("true", "false").toList(boolean.class)); - assertEquals(ImmutableList.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(List.of(Boolean.TRUE, Boolean.FALSE), newMutant("true", "false").toList(Boolean.class)); - assertEquals(ImmutableList.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(List.of(Boolean.TRUE, Boolean.FALSE), newMutant("true", "false").to(new TypeLiteral>() { })); } @Test public void asBooleanSet() throws Exception { - assertEquals(ImmutableSet.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(Set.of(Boolean.TRUE, Boolean.FALSE), newMutant("true", "false").toSet(boolean.class)); - assertEquals(ImmutableSet.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(Set.of(Boolean.TRUE, Boolean.FALSE), newMutant("true", "false").toSet(Boolean.class)); } @Test public void asBooleanSortedSet() throws Exception { - assertEquals(ImmutableSet.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(Set.of(Boolean.TRUE, Boolean.FALSE), newMutant("false", "true").toSortedSet(boolean.class)); - assertEquals(ImmutableSet.of(Boolean.TRUE, Boolean.FALSE), + assertEquals(Set.of(Boolean.TRUE, Boolean.FALSE), newMutant("false", "true").toSortedSet(Boolean.class)); } @@ -138,28 +135,28 @@ public void byteOverflow() throws Exception { @Test public void asByteList() throws Exception { - assertEquals(ImmutableList.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(List.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toList(byte.class)); - assertEquals(ImmutableList.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(List.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toList(Byte.class)); } @Test public void asByteSet() throws Exception { - assertEquals(ImmutableSet.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(Set.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toSet(byte.class)); - assertEquals(ImmutableSet.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(Set.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toSet(Byte.class)); } @Test public void asByteSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(Set.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toSortedSet(byte.class)); - assertEquals(ImmutableSortedSet.of((byte) 1, (byte) 2, (byte) 3), + assertEquals(Set.of((byte) 1, (byte) 2, (byte) 3), newMutant("1", "2", "3").toSortedSet(Byte.class)); } @@ -191,28 +188,28 @@ public void shortOverflow() throws Exception { @Test public void asShortList() throws Exception { - assertEquals(ImmutableList.of((short) 1, (short) 2, (short) 3), + assertEquals(List.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toList(short.class)); - assertEquals(ImmutableList.of((short) 1, (short) 2, (short) 3), + assertEquals(List.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toList(Short.class)); } @Test public void asShortSet() throws Exception { - assertEquals(ImmutableSet.of((short) 1, (short) 2, (short) 3), + assertEquals(Set.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toSet(short.class)); - assertEquals(ImmutableSet.of((short) 1, (short) 2, (short) 3), + assertEquals(Set.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toSet(Short.class)); } @Test public void asShortSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of((short) 1, (short) 2, (short) 3), + assertEquals(Set.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toSortedSet(short.class)); - assertEquals(ImmutableSortedSet.of((short) 1, (short) 2, (short) 3), + assertEquals(Set.of((short) 1, (short) 2, (short) 3), newMutant("1", "2", "3").toSortedSet(Short.class)); } @@ -236,32 +233,32 @@ public void asInt() throws Exception { @Test public void asIntList() throws Exception { - assertEquals(ImmutableList.of(1, 2, 3), + assertEquals(List.of(1, 2, 3), newMutant("1", "2", "3").toList(int.class)); - assertEquals(ImmutableList.of(1, 2, 3), + assertEquals(List.of(1, 2, 3), newMutant("1", "2", "3").toList(Integer.class)); } @Test public void asIntSet() throws Exception { - assertEquals(ImmutableSet.of(1, 2, 3), + assertEquals(Set.of(1, 2, 3), newMutant("1", "2", "3").toSet(int.class)); - assertEquals(ImmutableSet.of(1, 2, 3), + assertEquals(Set.of(1, 2, 3), newMutant("1", "2", "3").toSet(Integer.class)); - assertEquals(ImmutableSet.of(1, 2, 3), + assertEquals(Set.of(1, 2, 3), newMutant("1", "2", "3").to(new TypeLiteral>() { })); } @Test public void asIntSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of(1, 2, 3), + assertEquals(Set.of(1, 2, 3), newMutant("1", "2", "3").toSortedSet(int.class)); - assertEquals(ImmutableSet.of(1, 2, 3), + assertEquals(Set.of(1, 2, 3), newMutant("1", "2", "3").toSortedSet(Integer.class)); } @@ -295,34 +292,34 @@ public void notALong() throws Exception { @Test public void asLongList() throws Exception { - assertEquals(ImmutableList.of(1l, 2l, 3l), + assertEquals(List.of(1l, 2l, 3l), newMutant("1", "2", "3").toList(long.class)); - assertEquals(ImmutableList.of(1l, 2l, 3l), + assertEquals(List.of(1l, 2l, 3l), newMutant("1", "2", "3").toList(Long.class)); } @Test public void asMediaTypeList() throws Exception { - assertEquals(ImmutableList.of(MediaType.valueOf("application/json")), + assertEquals(List.of(MediaType.valueOf("application/json")), newMutant("application/json").toList(MediaType.class)); } @Test public void asLongSet() throws Exception { - assertEquals(ImmutableSet.of(1l, 2l, 3l), + assertEquals(Set.of(1l, 2l, 3l), newMutant("1", "2", "3").toSet(long.class)); - assertEquals(ImmutableSet.of(1l, 2l, 3l), + assertEquals(Set.of(1l, 2l, 3l), newMutant("1", "2", "3").toSet(Long.class)); } @Test public void asLongSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of(1l, 2l, 3l), + assertEquals(Set.of(1l, 2l, 3l), newMutant("1", "2", "3").toSortedSet(long.class)); - assertEquals(ImmutableSortedSet.of(1l, 2l, 3l), + assertEquals(Set.of(1l, 2l, 3l), newMutant("1", "2", "3").toSortedSet(Long.class)); } @@ -349,29 +346,29 @@ public void notAFloat() throws Exception { @Test public void asFloatList() throws Exception { - assertEquals(ImmutableList.of(1f, 2f, 3f), + assertEquals(List.of(1f, 2f, 3f), newMutant("1", "2", "3").toList(float.class)); - assertEquals(ImmutableList.of(1f, 2f, 3f), + assertEquals(List.of(1f, 2f, 3f), newMutant("1", "2", "3").toList(Float.class)); } @Test public void asFloatSet() throws Exception { - assertEquals(ImmutableSet.of(1f, 2f, 3f), + assertEquals(Set.of(1f, 2f, 3f), newMutant("1", "2", "3").toSet(float.class)); Set asSet = newMutant("1", "2", "3").toSet(Float.class); - assertEquals(ImmutableSet.of(1f, 2f, 3f), + assertEquals(Set.of(1f, 2f, 3f), asSet); } @Test public void asFloatSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of(1f, 2f, 3f), + assertEquals(Set.of(1f, 2f, 3f), newMutant("1", "2", "3").toSortedSet(float.class)); - assertEquals(ImmutableSortedSet.of(1f, 2f, 3f), + assertEquals(Set.of(1f, 2f, 3f), newMutant("1", "2", "3").toSortedSet(Float.class)); } @@ -398,28 +395,28 @@ public void notADouble() throws Exception { @Test public void asDoubleList() throws Exception { - assertEquals(ImmutableList.of(1d, 2d, 3d), + assertEquals(List.of(1d, 2d, 3d), newMutant("1", "2", "3").toList(double.class)); - assertEquals(ImmutableList.of(1d, 2d, 3d), + assertEquals(List.of(1d, 2d, 3d), newMutant("1", "2", "3").toList(Double.class)); } @Test public void asDoubleSet() throws Exception { - assertEquals(ImmutableSet.of(1d, 2d, 3d), + assertEquals(Set.of(1d, 2d, 3d), newMutant("1", "2", "3").toSet(double.class)); - assertEquals(ImmutableSet.of(1d, 2d, 3d), + assertEquals(Set.of(1d, 2d, 3d), newMutant("1", "2", "3").toSet(Double.class)); } @Test public void asDoubleSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of(1d, 2d, 3d), + assertEquals(Set.of(1d, 2d, 3d), newMutant("1", "2", "3").toSortedSet(double.class)); - assertEquals(ImmutableSortedSet.of(1d, 2d, 3d), + assertEquals(Set.of(1d, 2d, 3d), newMutant("1", "2", "3").toSortedSet(Double.class)); } @@ -443,19 +440,19 @@ public void asEnum() throws Exception { @Test public void asEnumList() throws Exception { - assertEquals(ImmutableList.of(LETTER.A, LETTER.B), + assertEquals(List.of(LETTER.A, LETTER.B), newMutant("A", "B").toList(LETTER.class)); } @Test public void asEnumSet() throws Exception { - assertEquals(ImmutableSet.of(LETTER.A, LETTER.B), + assertEquals(Set.of(LETTER.A, LETTER.B), newMutant("A", "B").toSet(LETTER.class)); } @Test public void asEnumSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of(LETTER.A, LETTER.B), + assertEquals(Set.of(LETTER.A, LETTER.B), newMutant("A", "B").toSortedSet(LETTER.class)); } @@ -486,7 +483,7 @@ public void asString() throws Exception { @Test public void asStringList() throws Exception { - assertEquals(ImmutableList.of("aa", "bb"), + assertEquals(List.of("aa", "bb"), newMutant("aa", "bb").toList(String.class)); assertEquals("[aa, bb]", newMutant("aa", "bb").toString()); @@ -494,13 +491,13 @@ public void asStringList() throws Exception { @Test public void asStringSet() throws Exception { - assertEquals(ImmutableSet.of("aa", "bb"), + assertEquals(Set.of("aa", "bb"), newMutant("aa", "bb", "bb").toSet()); } @Test public void asStringSortedSet() throws Exception { - assertEquals(ImmutableSortedSet.of("aa", "bb"), + assertEquals(Set.of("aa", "bb"), newMutant("aa", "bb", "bb").toSortedSet()); } @@ -537,13 +534,13 @@ private Mutant newMutant(final String... values) { private Mutant newMutant(final String value) { StrParamReferenceImpl reference = new StrParamReferenceImpl("parameter", "test", value == null ? Collections.emptyList() - : ImmutableList.of(value)); + : List.of(value)); return new MutantImpl(newConverter(), reference); } private ParserExecutor newConverter() { return new ParserExecutor(mock(Injector.class), - Sets.newLinkedHashSet( + new LinkedHashSet<>( Arrays.asList( BuiltinParser.Basic, BuiltinParser.Collection, diff --git a/jooby/src/test/java/org/jooby/internal/ParamConverterTest.java b/jooby/src/test/java/org/jooby/internal/ParamConverterTest.java index 753450738..7b08d4ce1 100644 --- a/jooby/src/test/java/org/jooby/internal/ParamConverterTest.java +++ b/jooby/src/test/java/org/jooby/internal/ParamConverterTest.java @@ -15,9 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import com.google.inject.util.Types; @@ -128,7 +125,7 @@ public void shouldConvertToDateFromString() throws Throwable { } private Object data(final String... value) { - return new StrParamReferenceImpl("parameter", "test", ImmutableList.copyOf(value)); + return new StrParamReferenceImpl("parameter", "test", new ArrayList<>(Arrays.asList(value))); } @Test @@ -171,7 +168,7 @@ public void shouldConvertBeanWithStringConstructor() throws Throwable { @Test public void shouldConvertListOfBeanWithStringConstructor() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Lists.newArrayList(new StringBean("231")), + assertEquals(new ArrayList<>(Arrays.asList(new StringBean("231"))), resolver.convert(TypeLiteral.get(Types.listOf(StringBean.class)), data("231"))); } @@ -293,14 +290,14 @@ public void shouldConvertToByte() throws Throwable { @Test public void shouldConvertToListOfBytes() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Lists.newArrayList((byte) 23, (byte) 45), + assertEquals(new ArrayList<>(Arrays.asList((byte) 23, (byte) 45)), resolver.convert(TypeLiteral.get(Types.listOf(Byte.class)), data("23", "45"))); } @Test public void shouldConvertToSetOfBytes() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Sets.newHashSet((byte) 23, (byte) 45), + assertEquals(new HashSet<>(Arrays.asList((byte) 23, (byte) 45)), resolver.convert(TypeLiteral.get(Types.setOf(Byte.class)), data("23", "45", "23"))); } @@ -343,11 +340,11 @@ public void shouldConvertToSortedSet() throws Throwable { @Test public void shouldConvertToListOfBoolean() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Lists.newArrayList(true, false), + assertEquals(new ArrayList<>(Arrays.asList(true, false)), resolver.convert(TypeLiteral.get(Types.listOf(Boolean.class)), data("true", "false"))); - assertEquals(Lists.newArrayList(false, false), + assertEquals(new ArrayList<>(Arrays.asList(false, false)), resolver.convert(TypeLiteral.get(Types.listOf(Boolean.class)), data("false", "false"))); } @@ -355,11 +352,11 @@ public void shouldConvertToListOfBoolean() throws Throwable { @Test public void shouldConvertToSetOfBoolean() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Sets.newHashSet(true, false), + assertEquals(new HashSet<>(Arrays.asList(true, false)), resolver.convert(TypeLiteral.get(Types.setOf(Boolean.class)), data("true", "false"))); - assertEquals(Sets.newHashSet(false), + assertEquals(new HashSet<>(Collections.singletonList(false)), resolver.convert(TypeLiteral.get(Types.setOf(Boolean.class)), data("false", "false"))); } @@ -388,14 +385,14 @@ public void shouldConvertToOptionalBoolean() throws Throwable { @Test public void shouldConvertToMediaType() throws Throwable { ParserExecutor resolver = newParser(); - assertEquals(Lists.newArrayList(MediaType.valueOf("text/html")), + assertEquals(new ArrayList<>(Arrays.asList(MediaType.valueOf("text/html"))), resolver.convert(TypeLiteral.get(Types.listOf(MediaType.class)), data("text/html"))); } private ParserExecutor newParser() { return new ParserExecutor(mock(Injector.class), - Sets.newLinkedHashSet( + new LinkedHashSet<>( Arrays.asList( BuiltinParser.Basic, BuiltinParser.Collection, diff --git a/jooby/src/test/java/org/jooby/internal/ParamReferenceImplTest.java b/jooby/src/test/java/org/jooby/internal/ParamReferenceImplTest.java index 19f2fd7f0..efa9720e9 100644 --- a/jooby/src/test/java/org/jooby/internal/ParamReferenceImplTest.java +++ b/jooby/src/test/java/org/jooby/internal/ParamReferenceImplTest.java @@ -26,7 +26,6 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.ImmutableList; public class ParamReferenceImplTest { @@ -43,7 +42,7 @@ public void first() throws Exception { new MockUnit() .run(unit -> { assertEquals("first", - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("first")).first()); + new StrParamReferenceImpl("parameter", "name", List.of("first")).first()); }); } @@ -52,7 +51,7 @@ public void last() throws Exception { new MockUnit() .run(unit -> { assertEquals("last", - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("last")).last()); + new StrParamReferenceImpl("parameter", "name", List.of("last")).last()); }); } @@ -61,9 +60,9 @@ public void get() throws Exception { new MockUnit() .run(unit -> { assertEquals("0", - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0")).get(0)); + new StrParamReferenceImpl("parameter", "name", List.of("0")).get(0)); assertEquals("1", - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0", "1")).get(1)); + new StrParamReferenceImpl("parameter", "name", List.of("0", "1")).get(1)); }); } @@ -71,7 +70,7 @@ public void get() throws Exception { public void missing() throws Exception { new MockUnit() .run(unit -> { - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0")).get(1); + new StrParamReferenceImpl("parameter", "name", List.of("0")).get(1); }); } @@ -79,7 +78,7 @@ public void missing() throws Exception { public void missingLowIndex() throws Exception { new MockUnit() .run(unit -> { - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0")).get(-1); + new StrParamReferenceImpl("parameter", "name", List.of("0")).get(-1); }); } @@ -88,9 +87,9 @@ public void size() throws Exception { new MockUnit() .run(unit -> { assertEquals(1, - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0")).size()); + new StrParamReferenceImpl("parameter", "name", List.of("0")).size()); assertEquals(2, - new StrParamReferenceImpl("parameter", "name", ImmutableList.of("0", "1")).size()); + new StrParamReferenceImpl("parameter", "name", List.of("0", "1")).size()); }); } diff --git a/jooby/src/test/java/org/jooby/internal/RequestImplTest.java b/jooby/src/test/java/org/jooby/internal/RequestImplTest.java index d4339f596..aa90c70ec 100644 --- a/jooby/src/test/java/org/jooby/internal/RequestImplTest.java +++ b/jooby/src/test/java/org/jooby/internal/RequestImplTest.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import org.jooby.Err; @@ -31,8 +33,6 @@ import org.jooby.test.MockUnit.Block; import org.junit.Test; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.inject.Injector; public class RequestImplTest { @@ -60,8 +60,8 @@ public void defaults() throws Exception { .expect(contentType) .run(unit -> { new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), ImmutableMap.of(), 1L); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), Map.of(), 1L); }); } @@ -79,9 +79,9 @@ public void matches() throws Exception { .run(unit -> { RequestImpl req = new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), - ImmutableMap.of(), 1L); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), + Map.of(), 1L); assertEquals(true, req.matches("/path/**")); }); } @@ -98,9 +98,9 @@ public void lang() throws Exception { .run(unit -> { RequestImpl req = new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), - ImmutableMap.of(), 1L); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), + Map.of(), 1L); assertEquals(Locale.ENGLISH, req.locale()); }); } @@ -119,9 +119,9 @@ public void files() throws Exception { .run(unit -> { try { new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), - ImmutableMap.of(), 1L).file("f"); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), + Map.of(), 1L).file("f"); fail("expecting error"); } catch (IOException ex) { assertEquals(cause, ex); @@ -138,7 +138,7 @@ public void paramNames() throws Exception { .expect(contentType) .expect(unit -> { Route route = unit.get(Route.class); - when(route.vars()).thenReturn(ImmutableMap.of()); + when(route.vars()).thenReturn(Map.of()); NativeRequest req = unit.get(NativeRequest.class); when(req.paramNames()).thenThrow(cause); @@ -146,9 +146,9 @@ public void paramNames() throws Exception { .run(unit -> { try { new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), - ImmutableMap.of(), 1L).params(); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), + Map.of(), 1L).params(); fail("expecting error"); } catch (Err ex) { assertEquals(400, ex.statusCode()); @@ -166,7 +166,7 @@ public void params() throws Exception { .expect(contentType) .expect(unit -> { Route route = unit.get(Route.class); - when(route.vars()).thenReturn(ImmutableMap.of()); + when(route.vars()).thenReturn(Map.of()); NativeRequest req = unit.get(NativeRequest.class); when(req.params("p")).thenThrow(cause); @@ -174,9 +174,9 @@ public void params() throws Exception { .run(unit -> { try { new RequestImpl(unit.get(Injector.class), unit.get(NativeRequest.class), "/", 8080, - unit.get(Route.class), StandardCharsets.UTF_8, ImmutableList.of(Locale.ENGLISH), - ImmutableMap.of(), - ImmutableMap.of(), 1L).param("p"); + unit.get(Route.class), StandardCharsets.UTF_8, List.of(Locale.ENGLISH), + Map.of(), + Map.of(), 1L).param("p"); fail("expecting error"); } catch (Err ex) { assertEquals(400, ex.statusCode()); diff --git a/jooby/src/test/java/org/jooby/internal/RouteMetadataTest.java b/jooby/src/test/java/org/jooby/internal/RouteMetadataTest.java index 43f155ca9..16689bfdd 100644 --- a/jooby/src/test/java/org/jooby/internal/RouteMetadataTest.java +++ b/jooby/src/test/java/org/jooby/internal/RouteMetadataTest.java @@ -35,7 +35,7 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; -import com.google.common.io.Resources; +import org.killbill.commons.utils.io.Resources; import com.typesafe.config.Config; public class RouteMetadataTest { diff --git a/jooby/src/test/java/org/jooby/internal/URLAssetTest.java b/jooby/src/test/java/org/jooby/internal/URLAssetTest.java index a5dce8488..097cd5dff 100644 --- a/jooby/src/test/java/org/jooby/internal/URLAssetTest.java +++ b/jooby/src/test/java/org/jooby/internal/URLAssetTest.java @@ -30,7 +30,7 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; public class URLAssetTest { diff --git a/jooby/src/test/java/org/jooby/internal/WebSocketImplTest.java b/jooby/src/test/java/org/jooby/internal/WebSocketImplTest.java index 972769dfd..0c59c88e5 100644 --- a/jooby/src/test/java/org/jooby/internal/WebSocketImplTest.java +++ b/jooby/src/test/java/org/jooby/internal/WebSocketImplTest.java @@ -15,7 +15,6 @@ */ package org.jooby.internal; -import com.google.common.collect.ImmutableMap; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; @@ -291,16 +290,16 @@ public void attributes() throws Exception { .run(unit -> { WebSocketImpl ws = new WebSocketImpl( unit.get(WebSocket.OnOpen1.class), path, pattern, vars, consumes, produces); - assertEquals(ImmutableMap.of(), ws.attributes()); + assertEquals(Map.of(), ws.attributes()); ws.set("foo", "bar"); assertEquals("bar", ws.get("foo")); assertEquals(Optional.empty(), ws.ifGet("bar")); assertEquals(Optional.of("bar"), ws.unset("foo")); - assertEquals(ImmutableMap.of(), ws.attributes()); + assertEquals(Map.of(), ws.attributes()); ws.set("foo", "bar"); ws.unset(); - assertEquals(ImmutableMap.of(), ws.attributes()); + assertEquals(Map.of(), ws.attributes()); try { ws.get("foo"); diff --git a/jooby/src/test/java/org/jooby/internal/WebSocketRendererContextTest.java b/jooby/src/test/java/org/jooby/internal/WebSocketRendererContextTest.java index 743416b74..8c5d82cba 100644 --- a/jooby/src/test/java/org/jooby/internal/WebSocketRendererContextTest.java +++ b/jooby/src/test/java/org/jooby/internal/WebSocketRendererContextTest.java @@ -17,6 +17,8 @@ import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; @@ -33,7 +35,6 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.Lists; public class WebSocketRendererContextTest { @@ -44,7 +45,7 @@ public void fileChannel() throws Exception { WebSocket.OnError.class) .run(unit -> { WebSocketRendererContext ctx = new WebSocketRendererContext( - Lists.newArrayList(unit.get(Renderer.class)), + new ArrayList<>(Collections.singletonList(unit.get(Renderer.class))), unit.get(NativeWebSocket.class), produces, StandardCharsets.UTF_8, @@ -62,7 +63,7 @@ public void inputStream() throws Exception { WebSocket.OnError.class, InputStream.class) .run(unit -> { WebSocketRendererContext ctx = new WebSocketRendererContext( - Lists.newArrayList(unit.get(Renderer.class)), + new ArrayList<>(Collections.singletonList(unit.get(Renderer.class))), unit.get(NativeWebSocket.class), produces, StandardCharsets.UTF_8, diff --git a/jooby/src/test/java/org/jooby/internal/WsBinaryMessageTest.java b/jooby/src/test/java/org/jooby/internal/WsBinaryMessageTest.java index b6d601c34..73eba6667 100644 --- a/jooby/src/test/java/org/jooby/internal/WsBinaryMessageTest.java +++ b/jooby/src/test/java/org/jooby/internal/WsBinaryMessageTest.java @@ -35,7 +35,7 @@ import org.junit.Test; import org.mockito.Mockito; -import com.google.common.base.Charsets; +import java.nio.charset.StandardCharsets; public class WsBinaryMessageTest { @@ -82,7 +82,7 @@ public void toReader() throws Exception { new Class[]{byte[].class }, bytes); unit.mockConstructor(InputStreamReader.class, new Class[]{ - InputStream.class, Charset.class }, null, Charsets.UTF_8); + InputStream.class, Charset.class }, null, StandardCharsets.UTF_8); }) .run(unit -> { Reader result = new WsBinaryMessage(buffer).to(Reader.class); diff --git a/jooby/src/test/java/org/jooby/internal/handlers/HeadHandlerTest.java b/jooby/src/test/java/org/jooby/internal/handlers/HeadHandlerTest.java index 2d6bc58bf..02e3d853c 100644 --- a/jooby/src/test/java/org/jooby/internal/handlers/HeadHandlerTest.java +++ b/jooby/src/test/java/org/jooby/internal/handlers/HeadHandlerTest.java @@ -17,6 +17,8 @@ import static org.mockito.Mockito.when; +import java.util.Collections; +import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -31,7 +33,6 @@ import org.jooby.test.MockUnit.Block; import org.junit.Test; -import com.google.common.collect.Sets; public class HeadHandlerTest { @@ -68,7 +69,7 @@ public void handle() throws Exception { }) .expect(len) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new HeadHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); @@ -89,7 +90,7 @@ public void noRoute() throws Exception { }) .expect(next) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new HeadHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); @@ -106,7 +107,7 @@ public void ignoreGlob() throws Exception { }) .expect(next) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new HeadHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); @@ -119,7 +120,7 @@ public void noroutes() throws Exception { .expect(path) .expect(next) .run(unit -> { - Set routes = Sets.newHashSet(); + Set routes = new HashSet<>(); new HeadHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); diff --git a/jooby/src/test/java/org/jooby/internal/handlers/OptionsHandlerTest.java b/jooby/src/test/java/org/jooby/internal/handlers/OptionsHandlerTest.java index baa951937..311a537e6 100644 --- a/jooby/src/test/java/org/jooby/internal/handlers/OptionsHandlerTest.java +++ b/jooby/src/test/java/org/jooby/internal/handlers/OptionsHandlerTest.java @@ -17,6 +17,8 @@ import static org.mockito.Mockito.when; +import java.util.Collections; +import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -32,7 +34,6 @@ import org.jooby.test.MockUnit.Block; import org.junit.Test; -import com.google.common.collect.Sets; public class OptionsHandlerTest { @@ -68,7 +69,7 @@ public void handle() throws Exception { when(rsp.status(Status.OK)).thenReturn(rsp); }) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new OptionsHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); @@ -98,7 +99,7 @@ public void handleSome() throws Exception { when(rsp.status(Status.OK)).thenReturn(rsp); }) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new OptionsHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); @@ -111,7 +112,7 @@ public void handleNone() throws Exception { .expect(next) .expect(allow(true)) .run(unit -> { - Set routes = Sets.newHashSet(unit.get(Route.Definition.class)); + Set routes = new HashSet<>(Collections.singleton(unit.get(Route.Definition.class))); new OptionsHandler(routes) .handle(unit.get(Request.class), unit.get(Response.class), unit.get(Route.Chain.class)); diff --git a/jooby/src/test/java/org/jooby/internal/mvc/MvcWebSocketTest.java b/jooby/src/test/java/org/jooby/internal/mvc/MvcWebSocketTest.java index f035f5c3b..6d03dde57 100644 --- a/jooby/src/test/java/org/jooby/internal/mvc/MvcWebSocketTest.java +++ b/jooby/src/test/java/org/jooby/internal/mvc/MvcWebSocketTest.java @@ -28,7 +28,6 @@ import org.jooby.test.MockUnit.Block; import org.junit.Test; -import com.google.common.collect.ImmutableList; import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Module; @@ -168,10 +167,10 @@ public void onListPojoMessage() throws Exception { Pojo pojo = new Pojo(); new MockUnit(WebSocket.class, Injector.class, PojoListSocket.class, Binder.class, Mutant.class) .expect(childInjector(PojoListSocket.class)) - .expect(mutant(TypeLiteral.get(Types.listOf(Pojo.class)), ImmutableList.of(pojo))) + .expect(mutant(TypeLiteral.get(Types.listOf(Pojo.class)), List.of(pojo))) .expect(unit -> { PojoListSocket socket = unit.get(PojoListSocket.class); - socket.onMessage(ImmutableList.of(pojo)); + socket.onMessage(List.of(pojo)); }) .run(unit -> { new MvcWebSocket(unit.get(WebSocket.class), PojoListSocket.class) diff --git a/jooby/src/test/java/org/jooby/internal/parser/BeanPlanTest.java b/jooby/src/test/java/org/jooby/internal/parser/BeanPlanTest.java index 5fd8cf525..819f5c0ca 100644 --- a/jooby/src/test/java/org/jooby/internal/parser/BeanPlanTest.java +++ b/jooby/src/test/java/org/jooby/internal/parser/BeanPlanTest.java @@ -19,12 +19,14 @@ import jakarta.inject.Inject; +import java.util.Arrays; +import java.util.HashSet; + import org.jooby.internal.ParameterNameProvider; import org.jooby.internal.parser.bean.BeanPlan; import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.Sets; public class BeanPlanTest { @@ -105,7 +107,7 @@ public void shouldFindMemberOnSuperclass() throws Exception { new MockUnit(ParameterNameProvider.class) .run(unit -> { BeanPlan plan = new BeanPlan(unit.get(ParameterNameProvider.class), Ext.class); - Ext bean = (Ext) plan.newBean(p -> p.name, Sets.newHashSet("foo", "bar")); + Ext bean = (Ext) plan.newBean(p -> p.name, new HashSet<>(Arrays.asList("foo", "bar"))); assertEquals("foo", bean.foo); assertEquals("bar", bean.bar); }); @@ -116,7 +118,7 @@ public void shouldFavorSetterLikeMethod() throws Exception { new MockUnit(ParameterNameProvider.class) .run(unit -> { BeanPlan plan = new BeanPlan(unit.get(ParameterNameProvider.class), SetterLike.class); - SetterLike bean = (SetterLike) plan.newBean(p -> p.name, Sets.newHashSet("bar")); + SetterLike bean = (SetterLike) plan.newBean(p -> p.name, new HashSet<>(Arrays.asList("bar"))); assertEquals("^bar", bean.bar); }); } @@ -126,7 +128,7 @@ public void shouldIgnoreSetterMethodWithZeroOrMoreArg() throws Exception { new MockUnit(ParameterNameProvider.class) .run(unit -> { BeanPlan plan = new BeanPlan(unit.get(ParameterNameProvider.class), BadSetter.class); - BadSetter bean = (BadSetter) plan.newBean(p -> p.name, Sets.newHashSet("bar")); + BadSetter bean = (BadSetter) plan.newBean(p -> p.name, new HashSet<>(Arrays.asList("bar"))); assertEquals("bar", bean.bar); }); } @@ -136,7 +138,7 @@ public void shouldTraverseGraphMethod() throws Exception { new MockUnit(ParameterNameProvider.class) .run(unit -> { BeanPlan plan = new BeanPlan(unit.get(ParameterNameProvider.class), GraphMethod.class); - GraphMethod bean = (GraphMethod) plan.newBean(p -> p.name, Sets.newHashSet("base[foo]")); + GraphMethod bean = (GraphMethod) plan.newBean(p -> p.name, new HashSet<>(Arrays.asList("base[foo]"))); assertEquals("base[foo]", bean.base.foo); }); } diff --git a/jooby/src/test/java/org/jooby/internal/reqparam/ParserExecutorTest.java b/jooby/src/test/java/org/jooby/internal/reqparam/ParserExecutorTest.java index 5dc6f7ce9..70bd74b60 100644 --- a/jooby/src/test/java/org/jooby/internal/reqparam/ParserExecutorTest.java +++ b/jooby/src/test/java/org/jooby/internal/reqparam/ParserExecutorTest.java @@ -17,7 +17,9 @@ import static org.junit.Assert.assertEquals; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -27,7 +29,6 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.Sets; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import com.typesafe.config.ConfigFactory; @@ -38,7 +39,7 @@ public class ParserExecutorTest { public void params() throws Exception { new MockUnit(Injector.class) .run(unit -> { - Set parsers = Sets.newHashSet((Parser) (type, ctx) -> ctx.params(up -> "p")); + Set parsers = new HashSet<>(Collections.singleton((Parser) (type, ctx) -> ctx.params(up -> "p"))); Object converted = new ParserExecutor(unit.get(Injector.class), parsers, new StatusCodeProvider(ConfigFactory.empty())) .convert(TypeLiteral.get(Map.class), new HashMap<>()); diff --git a/jooby/src/test/java/org/jooby/servlet/ServletServletRequestTest.java b/jooby/src/test/java/org/jooby/servlet/ServletServletRequestTest.java index 2aecca0e8..a6081f918 100644 --- a/jooby/src/test/java/org/jooby/servlet/ServletServletRequestTest.java +++ b/jooby/src/test/java/org/jooby/servlet/ServletServletRequestTest.java @@ -19,7 +19,10 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Map; import java.util.UUID; import jakarta.servlet.ServletException; @@ -29,9 +32,6 @@ import org.jooby.test.MockUnit; import org.junit.Test; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; public class ServletServletRequestTest { @@ -159,11 +159,11 @@ public void paramNames() throws IOException, Exception { when(req.getContentType()).thenReturn("text/html"); when(req.getPathInfo()).thenReturn("/"); when(req.getParameterNames()).thenReturn( - Iterators.asEnumeration(Lists.newArrayList("p1", "p2").iterator())); + Collections.enumeration(Arrays.asList("p1", "p2"))); when(req.getContextPath()).thenReturn(""); }) .run(unit -> { - assertEquals(Lists.newArrayList("p1", "p2"), + assertEquals(new ArrayList<>(Arrays.asList("p1", "p2")), new ServletServletRequest(unit.get(HttpServletRequest.class), tmpdir) .paramNames()); }); @@ -182,7 +182,7 @@ public void params() throws IOException, Exception { when(req.getContextPath()).thenReturn(""); }) .run(unit -> { - assertEquals(Lists.newArrayList("a", "b"), + assertEquals(new ArrayList<>(Arrays.asList("a", "b")), new ServletServletRequest(unit.get(HttpServletRequest.class), tmpdir) .params("x")); }); @@ -201,7 +201,7 @@ public void noparams() throws IOException, Exception { when(req.getContextPath()).thenReturn(""); }) .run(unit -> { - assertEquals(Lists.newArrayList(), + assertEquals(new ArrayList<>(), new ServletServletRequest(unit.get(HttpServletRequest.class), tmpdir) .params("x")); }); @@ -223,7 +223,7 @@ public void attributes() throws Exception { when(req.getAttribute("server.attribute")).thenReturn(serverAttribute); }) .run(unit -> { - assertEquals(ImmutableMap.of("server.attribute", serverAttribute), + assertEquals(Map.of("server.attribute", serverAttribute), new ServletServletRequest(unit.get(HttpServletRequest.class), tmpdir) .attributes()); }); @@ -278,7 +278,7 @@ public void noupgrade() throws IOException, Exception { when(req.getContextPath()).thenReturn(""); }) .run(unit -> { - assertEquals(Lists.newArrayList(), + assertEquals(new ArrayList<>(), new ServletServletRequest(unit.get(HttpServletRequest.class), tmpdir) .upgrade(ServletServletRequest.class)); }); diff --git a/jooby/src/test/java/org/jooby/servlet/ServletServletResponseTest.java b/jooby/src/test/java/org/jooby/servlet/ServletServletResponseTest.java index 4572b84e6..83a53329a 100644 --- a/jooby/src/test/java/org/jooby/servlet/ServletServletResponseTest.java +++ b/jooby/src/test/java/org/jooby/servlet/ServletServletResponseTest.java @@ -15,7 +15,7 @@ */ package org.jooby.servlet; -import com.google.common.io.ByteStreams; +import org.killbill.commons.utils.io.ByteStreams; import static org.mockito.Mockito.when; import org.jooby.funzy.Throwing; import org.jooby.test.MockUnit; diff --git a/jooby/src/test/java/org/jooby/servlet/WebXmlTest.java b/jooby/src/test/java/org/jooby/servlet/WebXmlTest.java index 6438fb238..f5f1ff48d 100644 --- a/jooby/src/test/java/org/jooby/servlet/WebXmlTest.java +++ b/jooby/src/test/java/org/jooby/servlet/WebXmlTest.java @@ -23,7 +23,7 @@ import org.junit.Test; -import com.google.common.io.CharStreams; +import org.killbill.commons.utils.io.CharStreams; public class WebXmlTest { diff --git a/jooby/src/test/java/org/jooby/test/JoobySuite.java b/jooby/src/test/java/org/jooby/test/JoobySuite.java index df74451a7..d792ec0a9 100644 --- a/jooby/src/test/java/org/jooby/test/JoobySuite.java +++ b/jooby/src/test/java/org/jooby/test/JoobySuite.java @@ -24,8 +24,7 @@ import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; -import com.google.common.base.Predicate; -import com.google.common.base.Predicates; +import java.util.function.Predicate; /** * JUnit suite for Jooby. Internal use only. @@ -49,7 +48,7 @@ public JoobySuite(final Class klass) throws InitializationError { @SuppressWarnings("rawtypes") private List runners(final Class klass) throws InitializationError { List runners = new ArrayList<>(); - Predicate filter = Predicates.alwaysTrue(); + Predicate filter = c -> true; OnServer onserver = klass.getAnnotation(OnServer.class); if (onserver != null) { List> server = Arrays.asList(onserver.value()); @@ -60,7 +59,7 @@ private List runners(final Class klass) throws InitializationError { for (String server : servers) { try { Class serverClass = getClass().getClassLoader().loadClass(server); - if (filter.apply(serverClass)) { + if (filter.test(serverClass)) { runners.add(new JoobyRunner(getTestClass().getJavaClass(), serverClass)); } } catch (ClassNotFoundException ex) { diff --git a/jooby/src/test/java/org/jooby/test/MockUnit.java b/jooby/src/test/java/org/jooby/test/MockUnit.java index 92d9abca5..10cbd4d25 100644 --- a/jooby/src/test/java/org/jooby/test/MockUnit.java +++ b/jooby/src/test/java/org/jooby/test/MockUnit.java @@ -15,8 +15,6 @@ */ package org.jooby.test; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; import static java.util.Objects.requireNonNull; import org.jooby.funzy.Try; import org.mockito.ArgumentCaptor; @@ -96,7 +94,7 @@ public interface Block { private List mocks = new LinkedList<>(); - private Multimap globalMock = ArrayListMultimap.create(); + private Map> globalMock = new LinkedHashMap<>(); private Map>> captures = new LinkedHashMap<>(); @@ -228,18 +226,18 @@ public T mock(final Class type, final boolean strict) { public T registerMock(final Class type) { T mock = mock(type); - globalMock.put(type, mock); + globalMock.computeIfAbsent(type, k -> new ArrayList<>()).add(mock); return mock; } public T registerMock(final Class type, final T mock) { - globalMock.put(type, mock); + globalMock.computeIfAbsent(type, k -> new ArrayList<>()).add(mock); return mock; } public T get(final Class type) { try { - List collection = (List) requireNonNull(globalMock.get(type)); + List collection = requireNonNull(globalMock.get(type), "Mock not found: " + type); Object result = collection.get(collection.size() - 1); // If this is a pre-mock that has been replaced by a construction mock, return the latter Object constructed = preMockToConstructed.get(result); @@ -250,7 +248,7 @@ public T get(final Class type) { } public T first(final Class type) { - List collection = (List) requireNonNull(globalMock.get(type), + List collection = requireNonNull(globalMock.get(type), "Mock not found: " + type); Object result = collection.get(0); Object constructed = preMockToConstructed.get(result); diff --git a/jooby/src/test/java/org/jooby/test/ServerFeature.java b/jooby/src/test/java/org/jooby/test/ServerFeature.java index 295973fad..daef07326 100644 --- a/jooby/src/test/java/org/jooby/test/ServerFeature.java +++ b/jooby/src/test/java/org/jooby/test/ServerFeature.java @@ -15,7 +15,7 @@ */ package org.jooby.test; -import static com.google.common.base.Preconditions.checkState; +import static org.killbill.commons.utils.Preconditions.checkState; import java.io.IOException; @@ -25,7 +25,7 @@ import org.junit.Rule; import org.junit.runner.RunWith; -import com.google.common.base.Joiner; +import org.killbill.commons.utils.Joiner; /** * Internal use only. diff --git a/metrics/pom.xml b/metrics/pom.xml index 25e1ed8f7..3fb0f5b71 100644 --- a/metrics/pom.xml +++ b/metrics/pom.xml @@ -40,10 +40,6 @@ com.fasterxml.jackson.core jackson-databind - - com.google.code.findbugs - jsr305 - com.google.inject guice @@ -56,6 +52,10 @@ io.dropwizard.metrics metrics-core + + jakarta.annotation + jakarta.annotation-api + jakarta.inject jakarta.inject-api diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/CountedListener.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/CountedListener.java index 6a9c9fe78..27457faa5 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/CountedListener.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/CountedListener.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.aopalliance.intercept.MethodInterceptor; import org.killbill.commons.metrics.api.Counter; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaredMethodsTypeListener.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaredMethodsTypeListener.java index c6c92cab3..b3f21e410 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaredMethodsTypeListener.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaredMethodsTypeListener.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.aopalliance.intercept.MethodInterceptor; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaringClassMetricNamer.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaringClassMetricNamer.java index e7634c7b3..afe7c9f29 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaringClassMetricNamer.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/DeclaringClassMetricNamer.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import org.killbill.commons.metrics.api.annotation.Counted; import org.killbill.commons.metrics.api.annotation.ExceptionMetered; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/ExceptionMeteredListener.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/ExceptionMeteredListener.java index 173a7de2b..a0a53c217 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/ExceptionMeteredListener.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/ExceptionMeteredListener.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.aopalliance.intercept.MethodInterceptor; import org.killbill.commons.metrics.api.Meter; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/GaugeInstanceClassMetricNamer.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/GaugeInstanceClassMetricNamer.java index 9c35c4a1d..0099b9bc8 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/GaugeInstanceClassMetricNamer.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/GaugeInstanceClassMetricNamer.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import org.killbill.commons.metrics.api.annotation.Gauge; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/MeteredListener.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/MeteredListener.java index 43ef843b2..794cfb473 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/MeteredListener.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/MeteredListener.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.aopalliance.intercept.MethodInterceptor; import org.killbill.commons.metrics.api.Meter; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricNamer.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricNamer.java index 543492bba..7f7ba81f3 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricNamer.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricNamer.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import org.killbill.commons.metrics.api.annotation.Counted; import org.killbill.commons.metrics.api.annotation.ExceptionMetered; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricsInstrumentationModule.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricsInstrumentationModule.java index e56268542..537c4810d 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricsInstrumentationModule.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/MetricsInstrumentationModule.java @@ -17,7 +17,7 @@ package org.killbill.commons.metrics.guice; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import org.killbill.commons.metrics.api.MetricRegistry; import org.killbill.commons.metrics.api.annotation.Counted; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/TimedListener.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/TimedListener.java index 5b107017a..9079ebb76 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/TimedListener.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/TimedListener.java @@ -19,7 +19,7 @@ import java.lang.reflect.Method; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.aopalliance.intercept.MethodInterceptor; import org.killbill.commons.metrics.api.MetricRegistry; diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/AnnotationResolver.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/AnnotationResolver.java index 704af3e33..9b7ad811c 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/AnnotationResolver.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/AnnotationResolver.java @@ -20,8 +20,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * Finds annotations, if any, pertaining to a particular Method. Extension point for customizing annotation lookup. diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ClassAnnotationResolver.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ClassAnnotationResolver.java index bb42d4dce..012562d4f 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ClassAnnotationResolver.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ClassAnnotationResolver.java @@ -20,8 +20,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * Looks for annotations on the enclosing class of the method. diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ListAnnotationResolver.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ListAnnotationResolver.java index 7134a2fe2..8be25d6fa 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ListAnnotationResolver.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/ListAnnotationResolver.java @@ -22,8 +22,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * Delegates to the provided list of resolvers, applying each resolver in turn. diff --git a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/MethodAnnotationResolver.java b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/MethodAnnotationResolver.java index 75057f5d3..a698bb09b 100644 --- a/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/MethodAnnotationResolver.java +++ b/metrics/src/main/java/org/killbill/commons/metrics/guice/annotation/MethodAnnotationResolver.java @@ -20,8 +20,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; /** * Looks for annotations on the method itself. diff --git a/pom.xml b/pom.xml index fa694a84d..aceabf26c 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.kill-bill.billing killbill-oss-parent - 0.146.63 + 0.147.0-6d4ed8a-SNAPSHOT org.kill-bill.commons killbill-commons @@ -30,11 +30,11 @@ pom killbill-commons Kill Bill reusable Java components - http://github.com/killbill/killbill-commons + https://github.com/killbill/killbill-commons Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html + https://www.apache.org/licenses/LICENSE-2.0.html repo @@ -66,108 +66,10 @@ true - - 1.12.0 - - 7.0.0 - - 3.0.6 - - 2.13.4 - - 2.1.0 - - 2.1.1 - - 2.0.1 - - 5.0.0 - - 3.0.2 - - 3.0.0 - - 3.30.2-GA - - 4.0.0 - - 4.0.0 - - 3.0.18 - - 11.0.24 - 21 - - 4.9.8.3 -Xmx${build.jvmsize} --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED - - com.fasterxml.jackson.jakarta.rs - jackson-jakarta-rs-json-provider - ${jackson.jakarta.rs.version} - - - com.google.inject - guice - ${guice.version} - - - com.google.inject.extensions - guice-servlet - ${guice.version} - - - jakarta.activation - jakarta.activation-api - ${jakarta.activation-api.version} - - - jakarta.inject - jakarta.inject-api - ${jakarta.inject-api.version} - - - jakarta.xml.bind - jakarta.xml.bind-api - ${jaxb-api.version} - - - org.eclipse.jetty - jetty-alpn-server - ${jetty.version} - - - org.eclipse.jetty.http2 - http2-server - ${jetty.version} - - - org.eclipse.jetty.websocket - websocket-jetty-api - ${jetty.version} - - - org.glassfish.jaxb - jaxb-runtime - ${jaxb-runtime.version} - - - - com.sun.activation - jakarta.activation - - - - - org.javassist - javassist - ${javassist.version} - org.kill-bill.commons killbill-automaton @@ -281,26 +183,4 @@ - - - - com.github.spotbugs - spotbugs-maven-plugin - ${spotbugs-maven-plugin.version} - - - org.apache.maven.plugins - maven-enforcer-plugin - - - - org.codehaus.mojo - extra-enforcer-rules - ${extra-enforcer-rules.version} - - - - - diff --git a/queue/pom.xml b/queue/pom.xml index d845ed2a4..2252ad8e8 100644 --- a/queue/pom.xml +++ b/queue/pom.xml @@ -48,8 +48,8 @@ jackson-datatype-joda - com.google.code.findbugs - jsr305 + jakarta.annotation + jakarta.annotation-api com.google.inject diff --git a/queue/src/main/java/org/killbill/bus/DefaultPersistentBus.java b/queue/src/main/java/org/killbill/bus/DefaultPersistentBus.java index e2cace836..14b6a387b 100644 --- a/queue/src/main/java/org/killbill/bus/DefaultPersistentBus.java +++ b/queue/src/main/java/org/killbill/bus/DefaultPersistentBus.java @@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import jakarta.inject.Inject; import jakarta.inject.Named; import javax.sql.DataSource; diff --git a/queue/src/main/java/org/killbill/bus/dao/PersistentBusSqlDao.java b/queue/src/main/java/org/killbill/bus/dao/PersistentBusSqlDao.java index cfc4842e8..e151ac8e1 100644 --- a/queue/src/main/java/org/killbill/bus/dao/PersistentBusSqlDao.java +++ b/queue/src/main/java/org/killbill/bus/dao/PersistentBusSqlDao.java @@ -23,7 +23,7 @@ import java.util.Iterator; import java.util.List; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.joda.time.DateTime; import org.killbill.commons.jdbi.statement.SmartFetchSize; diff --git a/queue/src/main/java/org/killbill/notificationq/DefaultNotificationQueue.java b/queue/src/main/java/org/killbill/notificationq/DefaultNotificationQueue.java index 37225b862..7d0b27eb6 100644 --- a/queue/src/main/java/org/killbill/notificationq/DefaultNotificationQueue.java +++ b/queue/src/main/java/org/killbill/notificationq/DefaultNotificationQueue.java @@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.joda.time.DateTime; import org.killbill.CreatorName; diff --git a/queue/src/main/java/org/killbill/queue/dao/QueueSqlDao.java b/queue/src/main/java/org/killbill/queue/dao/QueueSqlDao.java index 3d9c493e7..942c6874a 100644 --- a/queue/src/main/java/org/killbill/queue/dao/QueueSqlDao.java +++ b/queue/src/main/java/org/killbill/queue/dao/QueueSqlDao.java @@ -23,7 +23,7 @@ import java.util.Date; import java.util.List; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.killbill.commons.jdbi.binder.SmartBindBean; import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate; diff --git a/skeleton/pom.xml b/skeleton/pom.xml index a1724a576..e8631104e 100644 --- a/skeleton/pom.xml +++ b/skeleton/pom.xml @@ -54,10 +54,6 @@ jackson-jakarta-rs-json-provider true - - com.google.code.findbugs - jsr305 - com.google.inject guice @@ -73,6 +69,10 @@ metrics-core test + + jakarta.annotation + jakarta.annotation-api + jakarta.inject jakarta.inject-api diff --git a/skeleton/src/main/java/org/killbill/commons/skeleton/metrics/ResourceTimer.java b/skeleton/src/main/java/org/killbill/commons/skeleton/metrics/ResourceTimer.java index a90932659..1a419832f 100644 --- a/skeleton/src/main/java/org/killbill/commons/skeleton/metrics/ResourceTimer.java +++ b/skeleton/src/main/java/org/killbill/commons/skeleton/metrics/ResourceTimer.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.killbill.commons.metrics.api.MetricRegistry; import org.killbill.commons.metrics.api.Timer; diff --git a/utils/pom.xml b/utils/pom.xml index 7d7b2bb70..1fdbfc60c 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -33,8 +33,20 @@ - com.google.code.findbugs - jsr305 + com.github.ben-manes.caffeine + caffeine + 3.2.4 + compile + + + com.google.errorprone + error_prone_annotations + + + + + jakarta.annotation + jakarta.annotation-api org.slf4j diff --git a/utils/spotbugs-exclude.xml b/utils/spotbugs-exclude.xml index eb23b1392..2d8a19534 100644 --- a/utils/spotbugs-exclude.xml +++ b/utils/spotbugs-exclude.xml @@ -15,4 +15,40 @@ ~ under the License. --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utils/src/main/java/org/killbill/commons/eventbus/Subscriber.java b/utils/src/main/java/org/killbill/commons/eventbus/Subscriber.java index 14656cce7..5e7db1782 100644 --- a/utils/src/main/java/org/killbill/commons/eventbus/Subscriber.java +++ b/utils/src/main/java/org/killbill/commons/eventbus/Subscriber.java @@ -22,7 +22,7 @@ import java.lang.reflect.Method; import java.util.concurrent.Executor; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; import org.killbill.commons.utils.annotation.VisibleForTesting; @@ -129,7 +129,7 @@ public final int hashCode() { } @Override - public final boolean equals(@CheckForNull final Object obj) { + public final boolean equals(@Nullable final Object obj) { if (obj instanceof Subscriber) { final Subscriber that = (Subscriber) obj; // Use == so that different equal instances will still receive events. diff --git a/utils/src/main/java/org/killbill/commons/eventbus/SubscriberRegistry.java b/utils/src/main/java/org/killbill/commons/eventbus/SubscriberRegistry.java index be3fe697b..453656f2a 100644 --- a/utils/src/main/java/org/killbill/commons/eventbus/SubscriberRegistry.java +++ b/utils/src/main/java/org/killbill/commons/eventbus/SubscriberRegistry.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -34,15 +33,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; -import javax.annotation.CheckForNull; + +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; import org.killbill.commons.utils.Primitives; import org.killbill.commons.utils.TypeToken; import org.killbill.commons.utils.annotation.VisibleForTesting; import org.killbill.commons.utils.cache.Cache; -import org.killbill.commons.utils.cache.DefaultCache; -import org.killbill.commons.utils.cache.DefaultSynchronizedCache; +import org.killbill.commons.utils.cache.CacheBuilder; import org.killbill.commons.utils.collect.Iterators; import org.killbill.commons.utils.collect.MultiValueHashMap; import org.killbill.commons.utils.collect.MultiValueMap; @@ -141,11 +140,10 @@ Iterator getSubscribers(final Object event) { * instances of this class; this greatly improves performance if multiple EventBus instances are * created and objects of the same class are registered on all of them. */ - private static final Cache, List> subscriberMethodsCache = new DefaultSynchronizedCache<>( - Integer.MAX_VALUE, - DefaultCache.NO_TIMEOUT, - SubscriberRegistry::getAnnotatedMethodsNotCached - ); + private static final Cache, List> subscriberMethodsCache = + CacheBuilder., List>newBuilder() + .weakKeys() + .build(SubscriberRegistry::getAnnotatedMethodsNotCached); /** * Returns all subscribers for the given listener grouped by the type of event they subscribe to. @@ -197,15 +195,11 @@ private static List getAnnotatedMethodsNotCached(final Class clazz) { * * Guava version * */ - private static final Cache, Set>> flattenHierarchyCache = new DefaultSynchronizedCache<>( - // max size in our cache is mandatory. OTOH, guava version of flattenHierarchyCache have no maxSize. - Integer.MAX_VALUE, - DefaultCache.NO_TIMEOUT, - // Note Issue: 1615: Originally, flattenHierarchyCache data type was "LoadingCache" from Guava: - // https://github.com/google/guava/blob/master/guava/src/com/google/common/eventbus/SubscriberRegistry.java#L219 - // CacheLoader used ImmutableSet as return value. Somehow ImmutableSet maintains its order, where - // HashSet isn't. This is why we have LinkedHashSet here. - key -> new LinkedHashSet<>(TypeToken.getRawTypes(key))); + /** Global cache of classes to their flattened hierarchy of supertypes. */ + private static final Cache, Set>> flattenHierarchyCache = + CacheBuilder., Set>>newBuilder() + .weakKeys() + .build(TypeToken::getRawTypes); /** * Flattens a class's type hierarchy into a set of {@code Class} objects including all @@ -232,7 +226,7 @@ public int hashCode() { } @Override - public boolean equals(@CheckForNull final Object o) { + public boolean equals(@Nullable final Object o) { if (o instanceof MethodIdentifier) { final MethodIdentifier ident = (MethodIdentifier) o; return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes); diff --git a/utils/src/main/java/org/killbill/commons/utils/Ascii.java b/utils/src/main/java/org/killbill/commons/utils/Ascii.java new file mode 100644 index 000000000..f2cccb42f --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/Ascii.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2010 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.killbill.commons.utils; + +/** + * Verbatim copy of guava's com.google.common.base.Ascii (v.31.0.1) — case-conversion methods only. + * See More. + * + *

Static methods pertaining to ASCII characters (those in the range of values {@code 0x00} + * through {@code 0x7F}), and to strings containing such characters. + * + * @author Catherine Berry + * @author Gregory Kick + * @since 7.0 + */ +public final class Ascii { + + private Ascii() {} + + /** + * The difference between the ASCII values of an uppercase character and its lowercase + * counterpart. The value is 0x20 (decimal: 32). + */ + private static final char CASE_MASK = 0x20; + + /** + * Returns a copy of the input string in which all {@linkplain #isUpperCase(char) uppercase ASCII + * characters} have been converted to lowercase. All other characters are copied without + * modification. + */ + public static String toLowerCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isUpperCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isUpperCase(c)) { + chars[i] = (char) (c ^ CASE_MASK); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isUpperCase(char) + * uppercase ASCII characters} have been converted to lowercase. All other characters are copied + * without modification. + * + * @since 14.0 + */ + public static String toLowerCase(CharSequence chars) { + if (chars instanceof String) { + return toLowerCase((String) chars); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toLowerCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character}, returns the + * lowercase equivalent. Otherwise returns the argument. + */ + public static char toLowerCase(char c) { + return isUpperCase(c) ? (char) (c ^ CASE_MASK) : c; + } + + /** + * Returns a copy of the input string in which all {@linkplain #isLowerCase(char) lowercase ASCII + * characters} have been converted to uppercase. All other characters are copied without + * modification. + */ + public static String toUpperCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isLowerCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isLowerCase(c)) { + chars[i] = (char) (c ^ CASE_MASK); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isLowerCase(char) + * lowercase ASCII characters} have been converted to uppercase. All other characters are copied + * without modification. + * + * @since 14.0 + */ + public static String toUpperCase(CharSequence chars) { + if (chars instanceof String) { + return toUpperCase((String) chars); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toUpperCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character}, returns the + * uppercase equivalent. Otherwise returns the argument. + */ + public static char toUpperCase(char c) { + return isLowerCase(c) ? (char) (c ^ CASE_MASK) : c; + } + + /** + * Indicates whether {@code c} is one of the twenty-six lowercase ASCII alphabetic characters + * between {@code 'a'} and {@code 'z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isLowerCase(char c) { + // Note: This was benchmarked against the alternate expression "(char)(c - 'a') < 26" (Nov '13) + // and found to perform at least as well, or better. + return (c >= 'a') && (c <= 'z'); + } + + /** + * Indicates whether {@code c} is one of the twenty-six uppercase ASCII alphabetic characters + * between {@code 'A'} and {@code 'Z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + */ + public static boolean isUpperCase(char c) { + return (c >= 'A') && (c <= 'Z'); + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/CaseFormat.java b/utils/src/main/java/org/killbill/commons/utils/CaseFormat.java new file mode 100644 index 000000000..35ff8dcfd --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/CaseFormat.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2006 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.killbill.commons.utils; + +import static java.util.Objects.requireNonNull; + +/** + * Verbatim copy of guava's com.google.common.base.CaseFormat (v.31.0.1). + * See More. + * + *

Utility class for converting between various ASCII case formats. Behavior is undefined for + * non-ASCII input. + * + * @author Mike Bostock + * @since 1.0 + */ +public enum CaseFormat { + /** Hyphenated variable naming convention, e.g., "lower-hyphen". */ + LOWER_HYPHEN(CharMatcher.is('-'), "-") { + @Override + String normalizeWord(String word) { + return Ascii.toLowerCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_UNDERSCORE) { + return s.replace('-', '_'); + } + if (format == UPPER_UNDERSCORE) { + return Ascii.toUpperCase(s.replace('-', '_')); + } + return super.convert(format, s); + } + }, + + /** C++ variable naming convention, e.g., "lower_underscore". */ + LOWER_UNDERSCORE(CharMatcher.is('_'), "_") { + @Override + String normalizeWord(String word) { + return Ascii.toLowerCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_HYPHEN) { + return s.replace('_', '-'); + } + if (format == UPPER_UNDERSCORE) { + return Ascii.toUpperCase(s); + } + return super.convert(format, s); + } + }, + + /** Java variable naming convention, e.g., "lowerCamel". */ + LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { + @Override + String normalizeWord(String word) { + return firstCharOnlyToUpper(word); + } + + @Override + String normalizeFirstWord(String word) { + return Ascii.toLowerCase(word); + } + }, + + /** Java and C++ class naming convention, e.g., "UpperCamel". */ + UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { + @Override + String normalizeWord(String word) { + return firstCharOnlyToUpper(word); + } + }, + + /** Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE". */ + UPPER_UNDERSCORE(CharMatcher.is('_'), "_") { + @Override + String normalizeWord(String word) { + return Ascii.toUpperCase(word); + } + + @Override + String convert(CaseFormat format, String s) { + if (format == LOWER_HYPHEN) { + return Ascii.toLowerCase(s.replace('_', '-')); + } + if (format == LOWER_UNDERSCORE) { + return Ascii.toLowerCase(s); + } + return super.convert(format, s); + } + }; + + private final CharMatcher wordBoundary; + private final String wordSeparator; + + CaseFormat(CharMatcher wordBoundary, String wordSeparator) { + this.wordBoundary = wordBoundary; + this.wordSeparator = wordSeparator; + } + + /** + * Converts the specified {@code String str} from this format to the specified {@code format}. A + * "best effort" approach is taken; if {@code str} does not conform to the assumed format, then + * the behavior of this method is undefined but we make a reasonable effort at converting anyway. + */ + public final String to(CaseFormat format, String str) { + Preconditions.checkNotNull(format); + Preconditions.checkNotNull(str); + return (format == this) ? str : convert(format, str); + } + + /** Enum values can override for performance reasons. */ + String convert(CaseFormat format, String s) { + // deal with camel conversion + StringBuilder out = null; + int i = 0; + int j = -1; + while ((j = wordBoundary.indexIn(s, ++j)) != -1) { + if (i == 0) { + // include some extra space for separators + out = new StringBuilder(s.length() + 4 * format.wordSeparator.length()); + out.append(format.normalizeFirstWord(s.substring(i, j))); + } else { + requireNonNull(out).append(format.normalizeWord(s.substring(i, j))); + } + out.append(format.wordSeparator); + i = j + wordSeparator.length(); + } + return (i == 0) + ? format.normalizeFirstWord(s) + : requireNonNull(out).append(format.normalizeWord(s.substring(i))).toString(); + } + + abstract String normalizeWord(String word); + + String normalizeFirstWord(String word) { + return normalizeWord(word); + } + + private static String firstCharOnlyToUpper(String word) { + return word.isEmpty() + ? word + : Ascii.toUpperCase(word.charAt(0)) + Ascii.toLowerCase(word.substring(1)); + } +} + diff --git a/utils/src/main/java/org/killbill/commons/utils/CharMatcher.java b/utils/src/main/java/org/killbill/commons/utils/CharMatcher.java new file mode 100644 index 000000000..eb68f07a1 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/CharMatcher.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2008 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.killbill.commons.utils; + +/** + * Verbatim copy of guava's com.google.common.base.CharMatcher (v.31.0.1) — minimal subset used by + * CaseFormat and Splitter. + * + *

Determines a true or false value for any Java {@code char} value, just as {@link + * java.util.function.Predicate} does for any {@link Object}. + * + * @author Kevin Bourrillion + * @since 1.0 + */ +public abstract class CharMatcher { + + /** Returns a {@code char} matcher that matches only one specified BMP character. */ + public static CharMatcher is(final char match) { + return new Is(match); + } + + /** + * Returns a {@code char} matcher that matches any character present in the given character + * sequence. + */ + public static CharMatcher anyOf(final CharSequence sequence) { + return new AnyOf(sequence.toString()); + } + + /** + * Returns a {@code char} matcher that matches any character in a given BMP range (both endpoints + * are inclusive). For example, to match any lowercase letter of the English alphabet, use {@code + * CharMatcher.inRange('a', 'z')}. + * + * @throws IllegalArgumentException if {@code endInclusive < startInclusive} + */ + public static CharMatcher inRange(final char startInclusive, final char endInclusive) { + return new InRange(startInclusive, endInclusive); + } + + /** Determines a true or false value for the given character. */ + public abstract boolean matches(char c); + + /** + * Returns the index of the first matching character in a character sequence, starting from a + * given position, or {@code -1} if no character matches after that position. + */ + public int indexIn(CharSequence sequence, int start) { + int length = sequence.length(); + Preconditions.checkPositionIndex(start, length); + for (int i = start; i < length; i++) { + if (matches(sequence.charAt(i))) { + return i; + } + } + return -1; + } + + // Implementation of is(char) + private static final class Is extends CharMatcher { + + private final char match; + + Is(char match) { + this.match = match; + } + + @Override + public boolean matches(char c) { + return c == match; + } + + @Override + public String toString() { + return "CharMatcher.is('" + match + "')"; + } + } + + // Implementation of inRange(char, char) + private static final class InRange extends CharMatcher { + + private final char startInclusive; + private final char endInclusive; + + InRange(char startInclusive, char endInclusive) { + Preconditions.checkArgument(endInclusive >= startInclusive); + this.startInclusive = startInclusive; + this.endInclusive = endInclusive; + } + + @Override + public boolean matches(char c) { + return startInclusive <= c && c <= endInclusive; + } + + @Override + public String toString() { + return "CharMatcher.inRange('" + startInclusive + "', '" + endInclusive + "')"; + } + } + + // Implementation of anyOf(CharSequence) + private static final class AnyOf extends CharMatcher { + + private final String chars; + + AnyOf(String chars) { + this.chars = chars; + } + + @Override + public boolean matches(char c) { + return chars.indexOf(c) >= 0; + } + + @Override + public String toString() { + return "CharMatcher.anyOf(\"" + chars + "\")"; + } + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/Joiner.java b/utils/src/main/java/org/killbill/commons/utils/Joiner.java index b01a4fe97..cebe70af3 100644 --- a/utils/src/main/java/org/killbill/commons/utils/Joiner.java +++ b/utils/src/main/java/org/killbill/commons/utils/Joiner.java @@ -19,9 +19,10 @@ import java.io.IOException; import java.util.AbstractList; +import java.util.Arrays; import java.util.Iterator; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import static java.util.Objects.requireNonNull; @@ -64,6 +65,10 @@ public String join(final Iterator parts) { return appendTo(new StringBuilder(), parts).toString(); } + public String join(final Object[] obj) { + return join(Arrays.asList(obj)); + } + /** * Appends the string representation of each of {@code parts}, using the previously configured * separator between each, to {@code builder}. @@ -106,7 +111,7 @@ CharSequence toString(final Object part) { * Returns a string containing the string representation of each argument, using the previously * configured separator between each. */ - public String join(@CheckForNull final Object first, @CheckForNull final Object second, final Object... rest) { + public String join(@Nullable final Object first, @Nullable final Object second, final Object... rest) { return join(iterable(first, second, rest)); } @@ -122,7 +127,7 @@ public int size() { } @Override - @CheckForNull + @Nullable public Object get(final int index) { switch (index) { case 0: diff --git a/utils/src/main/java/org/killbill/commons/utils/Preconditions.java b/utils/src/main/java/org/killbill/commons/utils/Preconditions.java index c86506d62..d197bc82c 100644 --- a/utils/src/main/java/org/killbill/commons/utils/Preconditions.java +++ b/utils/src/main/java/org/killbill/commons/utils/Preconditions.java @@ -17,8 +17,7 @@ package org.killbill.commons.utils; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +38,7 @@ public final class Preconditions { * string using {@link String#valueOf(Object)} * @throws IllegalStateException if {@code expression} is false */ - public static void checkState(final boolean expression, @CheckForNull final Object errorMessage) { + public static void checkState(final boolean expression, @Nullable final Object errorMessage) { if (!expression) { throw new IllegalStateException(String.valueOf(errorMessage)); } @@ -79,9 +78,9 @@ public static void checkState(final boolean expression, final String errorMessag * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(@CheckForNull final T reference, + public static T checkNotNull(@Nullable final T reference, final String errorMessageTemplate, - @CheckForNull @Nullable final Object... errorMessageArgs) { + @Nullable final Object... errorMessageArgs) { if (reference == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, errorMessageArgs)); } @@ -94,7 +93,7 @@ public static T checkNotNull(@CheckForNull final T reference, * *

See {@link #checkState(boolean, String, Object...)} for details. */ - public static void checkState(final boolean b, final String errorMessageTemplate, @CheckForNull final Object p1) { + public static void checkState(final boolean b, final String errorMessageTemplate, @Nullable final Object p1) { if (!b) { throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1)); } @@ -124,7 +123,7 @@ public static T checkNotNull(final T reference) { * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null */ - public static T checkNotNull(@CheckForNull final T reference, @CheckForNull final Object errorMessage) { + public static T checkNotNull(@Nullable final T reference, @Nullable final Object errorMessage) { if (reference == null) { throw new NullPointerException(String.valueOf(errorMessage)); } @@ -136,14 +135,14 @@ public static T checkNotNull(@CheckForNull final T reference, @CheckForNull * *

See {@link #checkNotNull(Object, String, Object...)} for details. */ - public static T checkNotNull(@CheckForNull final T obj, final String errorMessageTemplate, @CheckForNull final Object p1) { + public static T checkNotNull(@Nullable final T obj, final String errorMessageTemplate, @Nullable final Object p1) { if (obj == null) { throw new NullPointerException(lenientFormat(errorMessageTemplate, p1)); } return obj; } - private static String lenientFormat(@CheckForNull String template, @CheckForNull Object... args) { + private static String lenientFormat(@Nullable String template, @Nullable Object... args) { template = String.valueOf(template); // null -> "null" if (args == null) { @@ -183,7 +182,7 @@ private static String lenientFormat(@CheckForNull String template, @CheckForNull return builder.toString(); } - private static String lenientToString(@CheckForNull final Object o) { + private static String lenientToString(@Nullable final Object o) { if (o == null) { return "null"; } @@ -258,4 +257,26 @@ public static void checkArgument(final boolean expression, final String errorMes throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, errorMessageArgs)); } } + + public static void checkArgument(final boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, list or string of + * size {@code size}. A position index may range from zero to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} + */ + public static int checkPositionIndex(int index, int size) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("index (" + index + ") must not be negative and must not be greater than size (" + size + ")"); + } + return index; + } } diff --git a/utils/src/main/java/org/killbill/commons/utils/Primitives.java b/utils/src/main/java/org/killbill/commons/utils/Primitives.java index 59a29e457..1f20f6df8 100644 --- a/utils/src/main/java/org/killbill/commons/utils/Primitives.java +++ b/utils/src/main/java/org/killbill/commons/utils/Primitives.java @@ -30,6 +30,9 @@ public final class Primitives { /** A map from primitive types to their corresponding wrapper types. */ private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; + /** A map from wrapper types to their corresponding primitive types. */ + private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; + static { final Map, Class> primToWrap = new LinkedHashMap<>(16); final Map, Class> wrapToPrim = new LinkedHashMap<>(16); @@ -45,6 +48,7 @@ public final class Primitives { add(primToWrap, wrapToPrim, void.class, Void.class); PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); + WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); } private static void add(final Map, Class> forward, final Map, Class> backward, final Class key, final Class value) { @@ -70,4 +74,14 @@ public static Class wrap(final Class type) { @SuppressWarnings("unchecked") final Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); return (wrapped == null) ? type : wrapped; } + + /** + * Returns {@code true} if {@code type} is one of the nine primitive-wrapper types, such as + * {@link Integer}. + * + * @see Class#isPrimitive + */ + public static boolean isWrapperType(final Class type) { + return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(Preconditions.checkNotNull(type)); + } } diff --git a/utils/src/main/java/org/killbill/commons/utils/Splitter.java b/utils/src/main/java/org/killbill/commons/utils/Splitter.java new file mode 100644 index 000000000..9d090a5a1 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/Splitter.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A string splitter with fluent API, inspired by Guava's {@code Splitter}. + * + *

Like Guava's implementation, this does NOT use regex internally. Splitting is done by + * scanning for separator characters directly. This avoids regex compilation overhead and + * eliminates escaping pitfalls (e.g. splitting on '.' with regex would match any character).

+ * + *

Only the subset used by jooby is implemented: {@link #on(char)}, {@link #on(CharMatcher)}, + * {@link #trimResults()}, {@link #omitEmptyStrings()}, {@link #split(CharSequence)}, + * and {@link #splitToList(CharSequence)}.

+ * + * @see + * Original Guava Splitter + */ +public final class Splitter { + + private final CharMatcher separatorMatcher; + private final boolean trimResults; + private final boolean omitEmpty; + + private Splitter(final CharMatcher separatorMatcher, final boolean trimResults, final boolean omitEmpty) { + this.separatorMatcher = separatorMatcher; + this.trimResults = trimResults; + this.omitEmpty = omitEmpty; + } + + /** + * Returns a splitter that splits on the given single character. + * No regex is used — the character is matched literally, consistent with Guava's behavior. + */ + public static Splitter on(final char separator) { + return new Splitter(new CharMatcher() { + @Override + public boolean matches(final char c) { + return c == separator; + } + }, false, false); + } + + /** + * Returns a splitter that splits on any character matched by the given {@link CharMatcher}. + * No regex is used — each character is tested against the matcher directly. + */ + public static Splitter on(final CharMatcher separatorMatcher) { + return new Splitter(separatorMatcher, false, false); + } + + /** + * Returns a splitter that trims whitespace from the beginning and end of each resulting part. + */ + public Splitter trimResults() { + return new Splitter(this.separatorMatcher, true, this.omitEmpty); + } + + /** + * Returns a splitter that omits empty strings from the results (after trimming, if applicable). + */ + public Splitter omitEmptyStrings() { + return new Splitter(this.separatorMatcher, this.trimResults, true); + } + + /** + * Splits the given sequence into parts and returns them as an {@link Iterable}. + */ + public Iterable split(final CharSequence sequence) { + return splitToList(sequence); + } + + /** + * Splits the given sequence into parts and returns them as an unmodifiable {@link List}. + */ + public List splitToList(final CharSequence sequence) { + final List raw = splitRaw(sequence); + final List result = new ArrayList<>(); + for (final String part : raw) { + String value = trimResults ? part.trim() : part; + if (omitEmpty && value.isEmpty()) { + continue; + } + result.add(value); + } + return Collections.unmodifiableList(result); + } + + /** + * Splits the sequence by scanning for separator characters. No regex involved. + */ + private List splitRaw(final CharSequence sequence) { + final List parts = new ArrayList<>(); + int start = 0; + for (int i = 0; i < sequence.length(); i++) { + if (separatorMatcher.matches(sequence.charAt(i))) { + parts.add(sequence.subSequence(start, i).toString()); + start = i + 1; + } + } + parts.add(sequence.subSequence(start, sequence.length()).toString()); + return parts; + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/StandardSystemProperty.java b/utils/src/main/java/org/killbill/commons/utils/StandardSystemProperty.java index 117a535d1..7cf6859af 100644 --- a/utils/src/main/java/org/killbill/commons/utils/StandardSystemProperty.java +++ b/utils/src/main/java/org/killbill/commons/utils/StandardSystemProperty.java @@ -18,7 +18,7 @@ package org.killbill.commons.utils; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; public enum StandardSystemProperty { @@ -148,7 +148,7 @@ public String key() { *
  • {@code jdk.module.*} (added in Java 9, optional) * */ - @CheckForNull + @Nullable public String value() { return System.getProperty(key); } diff --git a/utils/src/main/java/org/killbill/commons/utils/Strings.java b/utils/src/main/java/org/killbill/commons/utils/Strings.java index dde4fbc21..20c6041db 100644 --- a/utils/src/main/java/org/killbill/commons/utils/Strings.java +++ b/utils/src/main/java/org/killbill/commons/utils/Strings.java @@ -26,7 +26,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; /** * Verbatim copy to guava's Strings (v.31.0.1). See more @@ -69,7 +69,7 @@ public static List split(final String string, final String separator) { * @param string the string to test and possibly return * @return {@code string} itself if it is non-null; {@code ""} if it is null */ - public static String nullToEmpty(@CheckForNull final String string) { + public static String nullToEmpty(@Nullable final String string) { return (string == null) ? "" : string; } @@ -184,4 +184,38 @@ public static String toSnakeCase(final String str) { } return result.toString(); } + + /** + * Verbatim copy of Guava's Strings.padEnd(string, int, char). See: + * javadoc + */ + public static String padEnd(final String string, final int minLength, final char padChar) { + Preconditions.checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + sb.append(string); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + return sb.toString(); + } + + /** + * Verbatim copy of Guava's Strings.padStart(string, int, char). See: + * javadoc + */ + public static String padStart(String string, int minLength, char padChar) { + Preconditions.checkNotNull(string); // eager for GWT. + if (string.length() >= minLength) { + return string; + } + StringBuilder sb = new StringBuilder(minLength); + for (int i = string.length(); i < minLength; i++) { + sb.append(padChar); + } + sb.append(string); + return sb.toString(); + } } diff --git a/utils/src/main/java/org/killbill/commons/utils/Throwables.java b/utils/src/main/java/org/killbill/commons/utils/Throwables.java new file mode 100644 index 000000000..82d15206d --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/Throwables.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.killbill.commons.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Verbatim copy of guava's com.google.common.base.Throwables (v.31.0.1). + * Only includes the methods used within killbill. + * + * @see Guava removal tracking + */ +public final class Throwables { + + private Throwables() {} + + /** + * Returns a string containing the result of {@link Throwable#toString() toString()}, followed by + * the full, recursive stack trace of {@code throwable}. Note that you probably should not be + * parsing the resulting string; if you need programmatic access to the stack frames, you can call + * {@link Throwable#getStackTrace()}. + */ + public static String getStackTraceAsString(final Throwable throwable) { + final StringWriter stringWriter = new StringWriter(); + throwable.printStackTrace(new PrintWriter(stringWriter)); + return stringWriter.toString(); + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/TypeToken.java b/utils/src/main/java/org/killbill/commons/utils/TypeToken.java index 75f7045f6..110283586 100644 --- a/utils/src/main/java/org/killbill/commons/utils/TypeToken.java +++ b/utils/src/main/java/org/killbill/commons/utils/TypeToken.java @@ -17,7 +17,12 @@ package org.killbill.commons.utils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -29,31 +34,35 @@ public final class TypeToken { * Mimic the same behavior of Guava's {@code TypeToken.of(clazz).getTypes().rawTypes()}. */ public static Set> getRawTypes(final Class clazz) { - final Set> result = new LinkedHashSet<>(); - result.add(clazz); - result.addAll(getInterfaces(clazz)); - - Class superClass = clazz.getSuperclass(); - while (superClass != null) { - result.addAll(getRawTypes(superClass)); - superClass = superClass.getSuperclass(); - } + final Map, Integer> depths = new HashMap<>(); + collectTypes(clazz, depths); + + final List> types = new ArrayList<>(depths.keySet()); + types.sort((left, right) -> depths.get(right).compareTo(depths.get(left))); - return result; + return Collections.unmodifiableSet(new LinkedHashSet<>(types)); } - static Set> getInterfaces(final Class clazz) { - final Set> result = new LinkedHashSet<>(); + private static int collectTypes(final Class type, final Map, Integer> depths) { + final Integer existing = depths.get(type); + if (existing != null) { + return existing; + } + + int aboveMe = type.isInterface() ? 1 : 0; - Set> interfaces = Set.of(clazz.getInterfaces()); - while (!interfaces.isEmpty()) { - result.addAll(interfaces); - for (final Class anInterface : interfaces) { - interfaces = Set.of(anInterface.getInterfaces()); - result.addAll(interfaces); - } + for (final Class interfaceType : type.getInterfaces()) { + aboveMe = Math.max(aboveMe, collectTypes(interfaceType, depths)); } - return result; + final Class superclass = type.getSuperclass(); + if (superclass != null) { + aboveMe = Math.max(aboveMe, collectTypes(superclass, depths)); + } + + final int depth = aboveMe + 1; + depths.put(type, depth); + return depth; } + } diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/Cache.java b/utils/src/main/java/org/killbill/commons/utils/cache/Cache.java index 27a97941e..8354998bd 100644 --- a/utils/src/main/java/org/killbill/commons/utils/cache/Cache.java +++ b/utils/src/main/java/org/killbill/commons/utils/cache/Cache.java @@ -22,23 +22,31 @@ public interface Cache { /** - * Get the cache value, or null if no value associated with key. Value returned by this method depends on - * implementation logic. + * Get the cache value associated with {@code key}. * - * @param key to load the value - * @return cache value, or null - * @throws NullPointerException is key is null + * Implementations may define additional behavior on a cache miss. For example, a loading implementation may load and + * store a value before returning. + * + * @param key cache key + * @return cache value, or null if no value can be found or loaded + * @throws NullPointerException if key is null */ V get(K key); /** - * Get or load the cache. Note that value returned from loader parameter will not update cache. + * Get or load the cache value associated with {@code key}. * - * Implementation may decide to load it first from any other source/loader before loader parameter called. + * The supplied {@code loader} gives the implementation a way to produce a value when no value is available through + * its normal lookup or loading path. Implementations define: + *
      + *
    • whether any configured loader or read-through source is consulted before {@code loader};
    • + *
    • whether a value returned by {@code loader} is stored in the cache;
    • + *
    • whether concurrent loads for the same key are deduplicated or atomic.
    • + *
    * - * @param key to load the value - * @param loader algorithm to load a value if, not exist in cache - * @return cache value, or depends on {@code loader} algorithm + * @param key cache key + * @param loader algorithm to load a value if this cache cannot find or load one + * @return cache value, or value returned by {@code loader} * @throws NullPointerException if key or loader is null */ V getOrLoad(K key, Function loader); diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/CacheBuilder.java b/utils/src/main/java/org/killbill/commons/utils/cache/CacheBuilder.java new file mode 100644 index 000000000..653cbda46 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/cache/CacheBuilder.java @@ -0,0 +1,203 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.cache; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.killbill.commons.utils.Preconditions; + +import com.github.benmanes.caffeine.cache.Caffeine; + +/** + * Builder for constructing {@link Cache} instances, supporting both programmatic configuration + * and Guava-compatible spec string parsing via {@link #from(String)}. + * + *

    Spec string format: comma-separated {@code key=value} pairs. Supported keys:

    + *
      + *
    • {@code maximumSize=N} — max cache entries, delegated to Caffeine (0 = do not retain entries)
    • + *
    • {@code concurrencyLevel=N} — accepted for compatibility, not used by Caffeine
    • + *
    • {@code expireAfterWrite=Nd|Nh|Nm|Ns} — time-to-live (d=days, h=hours, m=minutes, s=seconds, 0 = immediate expiry)
    • + *
    + * + *

    Example: {@code CacheBuilder.from("maximumSize=200,concurrencyLevel=8").build(loader)}

    + */ +public final class CacheBuilder { + + private Long maxSize; + private Duration expireAfterWrite; + private Strength keyStrength; + + private CacheBuilder() { + } + + /** + * Creates a new builder with default settings (unbounded, no TTL). + */ + public static CacheBuilder newBuilder() { + return new CacheBuilder<>(); + } + + /** + * Creates a builder configured from a Guava-compatible spec string. Supported configurations are: + * maximumSize, concurrencyLevel (implementation dependent) and + * expireAfterWrite. + * + *

    Ex: {@code CacheBuilder.from("maximumSize=200,concurrencyLevel=8,expireAfterWrite=20s").build(loader)}

    + */ + public static CacheBuilder from(final String spec) { + Preconditions.checkNotNull(spec, "spec is null"); + final CacheBuilder builder = new CacheBuilder<>(); + + if (spec.trim().isEmpty()) { + return builder; + } + + for (final String token : spec.split(",")) { + final String trimmed = token.trim(); + if (trimmed.isEmpty()) { + continue; + } + final int eq = trimmed.indexOf('='); + if (eq < 0) { + // Bare key (e.g. "weakKeys") + switch (trimmed) { + case "weakKeys": + builder.weakKeys(); + break; + default: + // Ignore unknown bare keys for forward compatibility + break; + } + continue; + } + + final String key = trimmed.substring(0, eq).trim(); + final String value = trimmed.substring(eq + 1).trim(); + + switch (key) { + case "maximumSize": + builder.maximumSize(Long.parseLong(value)); + break; + case "concurrencyLevel": + builder.concurrencyLevel(Integer.parseInt(value)); + break; + case "expireAfterWrite": + builder.expireAfterWrite(parseDuration(value)); + break; + default: + // Ignore unknown keys for forward compatibility + break; + } + } + return builder; + } + + /** + * Sets the maximum number of entries. 0 means entries are not retained. + */ + public CacheBuilder maximumSize(final long maxSize) { + Preconditions.checkArgument(maxSize >= 0, "maximumSize must be >= 0, got: %s", maxSize); + this.maxSize = maxSize; + return this; + } + + /** + * Accepts the concurrency level setting for Guava spec compatibility. Caffeine does not support this setting and + * the value is ignored. + */ + public CacheBuilder concurrencyLevel(final int concurrencyLevel) { + Preconditions.checkArgument(concurrencyLevel > 0, "concurrencyLevel must be > 0, got: %s", concurrencyLevel); + return this; + } + + /** + * Sets the time-to-live for cache entries. + * + * @param duration TTL duration. 0 means immediate expiry. + */ + public CacheBuilder expireAfterWrite(final Duration duration) { + Preconditions.checkNotNull(duration, "expireAfterWrite duration is null"); + Preconditions.checkArgument(!duration.isNegative(), "expireAfterWrite must be >= 0, got: %s", duration); + this.expireAfterWrite = duration; + return this; + } + + /** + * Sets the time-to-live for cache entries. + * + * @param duration TTL duration. 0 means immediate expiry. + * @param unit TTL duration unit + */ + public CacheBuilder expireAfterWrite(final long duration, final TimeUnit unit) { + Preconditions.checkArgument(duration >= 0, "expireAfterWrite must be >= 0, got: %s", duration); + Preconditions.checkNotNull(unit, "expireAfterWrite unit is null"); + return expireAfterWrite(Duration.ofNanos(unit.toNanos(duration))); + } + + /** + * Implementation behavior will match what described by Caffeine's + * {@code weakKeys()} + * @throws IllegalStateException if the key strength was already set + */ + public CacheBuilder weakKeys() { + Preconditions.checkState(keyStrength == null, "Key strength was already set to: " + keyStrength); + keyStrength = Strength.WEAK; + return this; + } + + /** + * Builds a cache without a loader. Values must be populated via {@link Cache#put} or + * {@link Cache#getOrLoad}. + */ + public Cache build() { + return build(null); + } + + /** + * Builds a cache with a loader that is called on {@link Cache#get} misses. + * + * @param loader function to load values on cache miss. May be null. + */ + public Cache build(final Function loader) { + return new CaffeineCache<>(maxSize, expireAfterWrite, loader, keyStrength); + } + + /** + * Parses a duration string: a number followed by a unit (d=days, h=hours, m=minutes, s=seconds). + * If no unit suffix, assumes seconds. + */ + private static Duration parseDuration(final String value) { + Preconditions.checkArgument(!value.isEmpty(), "expireAfterWrite value is empty"); + + final char last = value.charAt(value.length() - 1); + if (Character.isDigit(last)) { + return Duration.ofSeconds(Long.parseLong(value)); + } + + final long number = Long.parseLong(value.substring(0, value.length() - 1)); + switch (last) { + case 'd': return Duration.ofDays(number); + case 'h': return Duration.ofHours(number); + case 'm': return Duration.ofMinutes(number); + case 's': return Duration.ofSeconds(number); + default: + throw new IllegalArgumentException("Unknown duration unit: " + last + " in '" + value + "'"); + } + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/CaffeineCache.java b/utils/src/main/java/org/killbill/commons/utils/cache/CaffeineCache.java new file mode 100644 index 000000000..61a0ba9ee --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/cache/CaffeineCache.java @@ -0,0 +1,86 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.cache; + +import java.time.Duration; +import java.util.function.Function; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import jakarta.annotation.Nullable; + +import org.killbill.commons.utils.Preconditions; +import org.killbill.commons.utils.annotation.VisibleForTesting; + +@VisibleForTesting +public final class CaffeineCache implements Cache { + + private final LoadingCache delegate; + + CaffeineCache(final Long maxSize, + final Duration expireAfterWrite, + @Nullable final Function cacheLoader, + @Nullable final Strength keyStrength) { + Preconditions.checkArgument(maxSize == null || maxSize >= 0, "cache maxSize should >= 0"); + Preconditions.checkArgument(expireAfterWrite == null || !expireAfterWrite.isNegative(), "cache expireAfterWrite should >= 0"); + + final Caffeine builder = Caffeine.newBuilder(); + builder.executor(Runnable::run); + if (maxSize != null) { + builder.maximumSize(maxSize); + } + if (expireAfterWrite != null) { + builder.expireAfterWrite(expireAfterWrite); + } + // Additional check for weak. Otherwise, SOFT will also create weakKeys(), which weird. + if (keyStrength != null && keyStrength.equals(Strength.WEAK)) { + builder.weakKeys(); + } + + this.delegate = builder.build(cacheLoader == null ? key -> null : cacheLoader::apply); + } + + @Override + public V get(final K key) { + Preconditions.checkNotNull(key, "Cannot #get() cache with key = null"); + + return delegate.get(key); + } + + @Override + public V getOrLoad(final K key, final Function loader) { + Preconditions.checkNotNull(key, "Cannot #getOrLoad() cache with key = null"); + Preconditions.checkNotNull(loader, "loader parameter in #getOrLoad() is null"); + + return delegate.get(key, loader); + } + + @Override + public void put(final K key, final V value) { + Preconditions.checkNotNull(key, "key in #put() is null"); + Preconditions.checkNotNull(value, "value in #put() is null"); + + delegate.put(key, value); + } + + @Override + public void invalidate(final K key) { + Preconditions.checkNotNull(key, "Cannot invalidate. Cache with null key is not allowed"); + + delegate.invalidate(key); + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/DefaultCache.java b/utils/src/main/java/org/killbill/commons/utils/cache/DefaultCache.java index 3128336e1..8a46bdc75 100644 --- a/utils/src/main/java/org/killbill/commons/utils/cache/DefaultCache.java +++ b/utils/src/main/java/org/killbill/commons/utils/cache/DefaultCache.java @@ -26,21 +26,27 @@ import org.killbill.commons.utils.annotation.VisibleForTesting; /** + * deprecated. Use {@link CaffeineCache} instead via {@link CacheBuilder}. *

    - * Default {@link Cache} implementation, that provide ability to: + * Default {@link Cache} implementation, that provides: *

      - *
    1. Add maxSize to the cache. If more entry added, the oldest entry get removed automatically
    2. + *
    3. A maximum size. If more entries are added, the least recently used entry is removed automatically.
    4. *
    5. - * Add lazy-loading capability with {@code cacheLoader} parameter in constructor. This {@code cacheLoader} - * will be called if {@link #get(Object)} return {@code null}. {@code cacheLoader} also will take precedence - * over loader defined in {@link #getOrLoad(Object, Function)}. + * Lazy-loading capability with the {@code cacheLoader} constructor parameter. This {@code cacheLoader} will + * be called if no value is found in the cache. Non-null values returned by {@code cacheLoader} are stored in + * the cache. {@code cacheLoader} also takes precedence over the loader defined in + * {@link #getOrLoad(Object, Function)}. + *
    6. + *
    7. + * Expire-after-write capability. Accessing an entry does not extend its lifetime. Expired entries are + * evicted lazily when accessed. *
    8. - *
    9. Add timout (similar to expire-after-write in Guava and Caffeine) capability
    10. *
    *

    * @param cache key * @param cache value */ +@Deprecated public class DefaultCache implements Cache { public static final long NO_TIMEOUT = 0; @@ -52,9 +58,9 @@ public class DefaultCache implements Cache { private final Function cacheLoader; /** - * Create cache with maximum entry size, without any timout (live forever) and no cache loader. + * Create cache with maximum entry size, no expiration, and no cache loader. * - * @param maxSize max entry that should be existed in cache. + * @param maxSize maximum number of entries to keep in cache */ public DefaultCache(final int maxSize) { this(maxSize, NO_TIMEOUT, noCacheLoader()); @@ -72,11 +78,13 @@ public DefaultCache(final Function cacheLoader) { } /** - * Create cache with maximum entry size, timeout (in second), and cacheLoader capability. + * Create cache with maximum entry size, expire-after-write duration, and cacheLoader capability. * - * @param maxSize cache maximum size. If more entry added, the oldest entry get removed automatically. - * @param timeoutInSecond cache entry expire time. Entry will eventually be removed after specifics time defined - * here. Use {@link DefaultCache#NO_TIMEOUT} to make entry live forever. + * @param maxSize cache maximum size. If more entries are added, the least recently used entry is removed + * automatically. + * @param timeoutInSecond expire-after-write duration in seconds. Entries expire after this duration from the time + * they are written; reads do not extend the expiration time. Expired entries are evicted + * lazily when accessed. Use {@link DefaultCache#NO_TIMEOUT} to make entries live forever. * @param cacheLoader cache loader. Use {@link #noCacheLoader()} to make this cache should not attempt to load * anything if value is null. */ @@ -139,6 +147,10 @@ public V get(final K key) { } } + /** + * Returns the value from {@link #get(Object)} if one can be found or loaded. If {@link #get(Object)} returns + * {@code null}, this method calls {@code loader} and returns its result without storing that result in this cache. + */ @Override public V getOrLoad(final K key, final Function loader) { Preconditions.checkNotNull(loader, "loader parameter in #getOrLoad() is null"); diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/DefaultSynchronizedCache.java b/utils/src/main/java/org/killbill/commons/utils/cache/DefaultSynchronizedCache.java index da1d1e45f..355bccfc3 100644 --- a/utils/src/main/java/org/killbill/commons/utils/cache/DefaultSynchronizedCache.java +++ b/utils/src/main/java/org/killbill/commons/utils/cache/DefaultSynchronizedCache.java @@ -20,8 +20,10 @@ import java.util.function.Function; /** + * Deprecated. Use {@link CaffeineCache} and {@link CacheBuilder} instead. * {@link DefaultCache} that synchronize {@link Cache} methods call. */ +@Deprecated public class DefaultSynchronizedCache extends DefaultCache implements Cache { public DefaultSynchronizedCache(final int maxSize) { diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/Strength.java b/utils/src/main/java/org/killbill/commons/utils/cache/Strength.java new file mode 100644 index 000000000..8e3b622d0 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/cache/Strength.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020-2026 Equinix, Inc + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.cache; + +enum Strength { WEAK, SOFT } diff --git a/utils/src/main/java/org/killbill/commons/utils/cache/TimedValue.java b/utils/src/main/java/org/killbill/commons/utils/cache/TimedValue.java index fa74b3f25..9c0eded31 100644 --- a/utils/src/main/java/org/killbill/commons/utils/cache/TimedValue.java +++ b/utils/src/main/java/org/killbill/commons/utils/cache/TimedValue.java @@ -27,7 +27,8 @@ class TimedValue { private final V value; /** - * @param timeoutMillis timeout in millisecond + * @param timeoutMillis expire-after-write duration in milliseconds. The expiration timestamp is calculated when this + * value is created and is not extended by reads. */ TimedValue(final long timeoutMillis, final V value) { this.expireTime = System.currentTimeMillis() + timeoutMillis; diff --git a/utils/src/main/java/org/killbill/commons/utils/collect/AbstractIterator.java b/utils/src/main/java/org/killbill/commons/utils/collect/AbstractIterator.java index 08506610c..da9994d32 100644 --- a/utils/src/main/java/org/killbill/commons/utils/collect/AbstractIterator.java +++ b/utils/src/main/java/org/killbill/commons/utils/collect/AbstractIterator.java @@ -20,7 +20,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; @@ -47,7 +47,7 @@ private enum State { FAILED, } - @CheckForNull + @Nullable private T next; /** @@ -74,7 +74,7 @@ private enum State { * this method. Any further attempts to use the iterator will result in an {@link * IllegalStateException}. */ - @CheckForNull + @Nullable protected abstract T computeNext(); /** @@ -84,7 +84,7 @@ private enum State { * @return {@code null}; a convenience so your {@code computeNext} implementation can use the * simple statement {@code return endOfData();} */ - @CheckForNull + @Nullable protected final T endOfData() { state = State.DONE; return null; @@ -143,7 +143,7 @@ public final T peek() { /** * See Guava's NullnessCasts javadoc. */ - static T uncheckedCastNullableTToT(@CheckForNull final T t) { + static T uncheckedCastNullableTToT(@Nullable final T t) { return t; } } diff --git a/utils/src/main/java/org/killbill/commons/utils/collect/ConcatenatedIterator.java b/utils/src/main/java/org/killbill/commons/utils/collect/ConcatenatedIterator.java index 8e5ebff44..999ba8e88 100644 --- a/utils/src/main/java/org/killbill/commons/utils/collect/ConcatenatedIterator.java +++ b/utils/src/main/java/org/killbill/commons/utils/collect/ConcatenatedIterator.java @@ -23,7 +23,7 @@ import java.util.Iterator; import java.util.NoSuchElementException; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; @@ -32,7 +32,7 @@ */ class ConcatenatedIterator implements Iterator { /* The last iterator to return an element. Calls to remove() go to this iterator. */ - @CheckForNull + @Nullable private Iterator toRemove; /* The iterator currently returning elements. */ @@ -45,11 +45,11 @@ class ConcatenatedIterator implements Iterator { * operation O(1). */ - @CheckForNull + @Nullable private Iterator> topMetaIterator; // Only becomes nonnull if we encounter nested concatenations. - @CheckForNull + @Nullable private Deque>> metaIterators; ConcatenatedIterator(final Iterator> metaIterator) { @@ -58,7 +58,7 @@ class ConcatenatedIterator implements Iterator { } // Returns a nonempty meta-iterator or, if all meta-iterators are empty, null. - @CheckForNull + @Nullable private Iterator> getTopMetaIterator() { while (topMetaIterator == null || !topMetaIterator.hasNext()) { if (metaIterators != null && !metaIterators.isEmpty()) { diff --git a/utils/src/main/java/org/killbill/commons/utils/collect/Iterators.java b/utils/src/main/java/org/killbill/commons/utils/collect/Iterators.java index 5effecf94..69cb15155 100644 --- a/utils/src/main/java/org/killbill/commons/utils/collect/Iterators.java +++ b/utils/src/main/java/org/killbill/commons/utils/collect/Iterators.java @@ -29,7 +29,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; @@ -132,7 +132,7 @@ public static Stream toStream(final Iterator iterator) { } /** Returns {@code true} if {@code iterator} contains {@code element}. */ - public static boolean contains(final Iterator iterator, @CheckForNull final Object element) { + public static boolean contains(final Iterator iterator, @Nullable final Object element) { if (element == null) { while (iterator.hasNext()) { if (iterator.next() == null) { diff --git a/utils/src/main/java/org/killbill/commons/utils/concurrent/DirectExecutor.java b/utils/src/main/java/org/killbill/commons/utils/concurrent/DirectExecutor.java index 3f6ab09a4..2af48dd03 100644 --- a/utils/src/main/java/org/killbill/commons/utils/concurrent/DirectExecutor.java +++ b/utils/src/main/java/org/killbill/commons/utils/concurrent/DirectExecutor.java @@ -24,6 +24,9 @@ * to avoid circular dependency. * * An {@link Executor} that runs each task in the thread that invokes {@link Executor#execute}. + * + * Changes: + * - Guava implements toString() as "MoreExecutors.directExecutor()", while this enum as "DirectExecutor.INSTANCE" */ public enum DirectExecutor implements Executor { @@ -33,4 +36,10 @@ public enum DirectExecutor implements Executor { public void execute(final Runnable command) { command.run(); } + + @Override + public String toString() { + return "DirectExecutor.INSTANCE"; + } + } diff --git a/utils/src/main/java/org/killbill/commons/utils/concurrent/ThreadFactoryBuilder.java b/utils/src/main/java/org/killbill/commons/utils/concurrent/ThreadFactoryBuilder.java index bfe032651..15a8c3c83 100644 --- a/utils/src/main/java/org/killbill/commons/utils/concurrent/ThreadFactoryBuilder.java +++ b/utils/src/main/java/org/killbill/commons/utils/concurrent/ThreadFactoryBuilder.java @@ -24,7 +24,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; import org.killbill.commons.utils.Preconditions; @@ -51,12 +51,12 @@ * @since 4.0 */ public final class ThreadFactoryBuilder { - @CheckForNull + @Nullable private String nameFormat = null; - @CheckForNull private Boolean daemon = null; - @CheckForNull private Integer priority = null; - @CheckForNull private UncaughtExceptionHandler uncaughtExceptionHandler = null; - @CheckForNull private ThreadFactory backingThreadFactory = null; + @Nullable private Boolean daemon = null; + @Nullable private Integer priority = null; + @Nullable private UncaughtExceptionHandler uncaughtExceptionHandler = null; + @Nullable private ThreadFactory backingThreadFactory = null; /** Creates a new {@link ThreadFactory} builder. */ public ThreadFactoryBuilder() {} diff --git a/utils/src/main/java/org/killbill/commons/utils/escape/Escaper.java b/utils/src/main/java/org/killbill/commons/utils/escape/Escaper.java new file mode 100644 index 000000000..5c2e3f80b --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/escape/Escaper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.escape; + +/** + * An object that converts literal text into a format safe for inclusion in a particular context + * (such as an XML document or a URL). Typically, the reverse process of "unescaping" is performed by + * a separate interface. + * + *

    Replaces Guava's {@code com.google.common.escape.Escaper}. Made a functional interface + * so that {@code escaper::escape} method references work naturally.

    + * + * @see + * Original Guava Escaper + */ +@FunctionalInterface +public interface Escaper { + + /** + * Returns the escaped form of the given input string. + */ + String escape(String input); +} diff --git a/utils/src/main/java/org/killbill/commons/utils/html/HtmlEscapers.java b/utils/src/main/java/org/killbill/commons/utils/html/HtmlEscapers.java new file mode 100644 index 000000000..4631b6ae0 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/html/HtmlEscapers.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.html; + +import org.killbill.commons.utils.escape.Escaper; + +/** + * Provides HTML escaping. Replaces the 5 characters that have special meaning in HTML + * ({@code & < > " '}) with their corresponding HTML entities. + * + * @see + * Original Guava HtmlEscapers + */ +public final class HtmlEscapers { + + private HtmlEscapers() {} + + private static final Escaper HTML_ESCAPER = input -> { + final StringBuilder sb = new StringBuilder(input.length()); + for (int i = 0; i < input.length(); i++) { + final char c = input.charAt(i); + switch (c) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + }; + + /** + * Returns an {@link Escaper} that replaces special HTML characters with their entity + * equivalents. + */ + public static Escaper htmlEscaper() { + return HTML_ESCAPER; + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/io/ByteSource.java b/utils/src/main/java/org/killbill/commons/utils/io/ByteSource.java new file mode 100644 index 000000000..d80e6cd74 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/io/ByteSource.java @@ -0,0 +1,120 @@ +/* + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.io; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +/** + * A lightweight byte source abstraction for lazily concatenating and slicing byte arrays. + * + *

    Inspired by Guava's {@code com.google.common.io.ByteSource}, but intentionally minimal: + * only the subset used by jooby's SseRenderer (wrap, concat, empty, slice, read) is implemented. + * Internally backed by byte arrays and {@link ByteArrayOutputStream} — no streaming, no IO + * dependency tree.

    + * + * @see + * Original Guava ByteSource + */ +public abstract class ByteSource { + + private static final ByteSource EMPTY = new ByteSource() { + @Override + public byte[] read() { + return new byte[0]; + } + + @Override + public String toString() { + return "ByteSource.empty()"; + } + }; + + /** + * Returns a {@code ByteSource} that wraps the given byte array. + */ + public static ByteSource wrap(final byte[] bytes) { + return new ByteSource() { + @Override + public byte[] read() { + return bytes.clone(); + } + + @Override + public String toString() { + return "ByteSource.wrap(" + bytes.length + " bytes)"; + } + }; + } + + /** + * Returns an empty {@code ByteSource}. + */ + public static ByteSource empty() { + return EMPTY; + } + + /** + * Returns a {@code ByteSource} that concatenates all given sources. + * Bytes are materialized on {@link #read()}. + */ + public static ByteSource concat(final ByteSource... sources) { + return new ByteSource() { + @Override + public byte[] read() throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (final ByteSource source : sources) { + out.write(source.read()); + } + return out.toByteArray(); + } + + @Override + public String toString() { + return "ByteSource.concat(" + sources.length + " sources)"; + } + }; + } + + /** + * Returns a view of a sub-range of this source. + * If offset + length exceeds actual size, returns only available bytes from offset. + */ + public ByteSource slice(final long offset, final long length) { + final ByteSource parent = this; + return new ByteSource() { + @Override + public byte[] read() throws IOException { + final byte[] all = parent.read(); + final int start = (int) Math.min(offset, all.length); + final int end = (int) Math.min(offset + length, all.length); + return Arrays.copyOfRange(all, start, end); + } + + @Override + public String toString() { + return parent.toString() + ".slice(" + offset + ", " + length + ")"; + } + }; + } + + /** + * Reads the full contents as a byte array. + */ + public abstract byte[] read() throws IOException; +} diff --git a/utils/src/main/java/org/killbill/commons/utils/io/ByteStreams.java b/utils/src/main/java/org/killbill/commons/utils/io/ByteStreams.java index ed259250c..431f92b33 100644 --- a/utils/src/main/java/org/killbill/commons/utils/io/ByteStreams.java +++ b/utils/src/main/java/org/killbill/commons/utils/io/ByteStreams.java @@ -17,8 +17,10 @@ package org.killbill.commons.utils.io; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayDeque; import java.util.Queue; @@ -26,7 +28,7 @@ import org.killbill.commons.utils.math.IntMath; /** - * Contains verbatim copy to guava's Joiner (v.31.0.1). See More. + * Contains verbatim copy to guava's ByteStreams (v.31.0.1). See More. */ public final class ByteStreams { @@ -95,4 +97,106 @@ private static byte[] combineBuffers(final Queue bufs, final int totalLe } return result; } + + /** + * Copies all bytes from the input stream to the output stream. Does not close or flush either + * stream. + * + * Keep the method signature exist for Jooby's sake, but using `inputStream.transferTo()` as implementation. + * Note that, all other killbill codebase (notably in killbill-platform) use inputStream.transferTo() directly. + * + * @param from the input stream to read from + * @param to the output stream to write to + * @return the number of bytes copied + * @throws IOException if an I/O error occurs + */ + public static long copy(final InputStream from, final OutputStream to) throws IOException { + Preconditions.checkNotNull(from); + Preconditions.checkNotNull(to); + return from.transferTo(to); + } + + /** + * Wraps a {@link InputStream}, limiting the number of bytes which can be read. + * + * @param in the input stream to be wrapped + * @param limit the maximum number of bytes to be read from {@code in} + * @return a length-limited {@link InputStream} + */ + public static InputStream limit(final InputStream in, final long limit) { + return new LimitedInputStream(in, limit); + } + + private static final class LimitedInputStream extends FilterInputStream { + + private long left; + private long mark = -1; + + LimitedInputStream(InputStream in, long limit) { + super(in); + Preconditions.checkNotNull(in); + Preconditions.checkArgument(limit >= 0, "limit must be non-negative"); + left = limit; + } + + @Override + public int available() throws IOException { + return (int) Math.min(in.available(), left); + } + + // it's okay to mark even if mark isn't supported, as reset won't work + @Override + public synchronized void mark(int readLimit) { + in.mark(readLimit); + mark = left; + } + + @Override + public int read() throws IOException { + if (left == 0) { + return -1; + } + + int result = in.read(); + if (result != -1) { + --left; + } + return result; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (left == 0) { + return -1; + } + + len = (int) Math.min(len, left); + int result = in.read(b, off, len); + if (result != -1) { + left -= result; + } + return result; + } + + @Override + public synchronized void reset() throws IOException { + if (!in.markSupported()) { + throw new IOException("Mark not supported"); + } + if (mark == -1) { + throw new IOException("Mark not set"); + } + + in.reset(); + left = mark; + } + + @Override + public long skip(long n) throws IOException { + n = Math.min(n, left); + long skipped = in.skip(n); + left -= skipped; + return skipped; + } + } } diff --git a/utils/src/main/java/org/killbill/commons/utils/io/Closeables.java b/utils/src/main/java/org/killbill/commons/utils/io/Closeables.java new file mode 100644 index 000000000..56606568b --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/io/Closeables.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020-2026 Equinix, Inc + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.io; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.annotation.Nullable; + +/** + * Verbatim copy of com.google.common.io.Closeables. Changes: + * - Use SLF4J instead of JUL + * - add .debug() when closable is null + */ +public final class Closeables { + + private static final Logger logger = LoggerFactory.getLogger(Closeables.class.getName()); + + private Closeables() {} + + /** + * Closes a {@link Closeable}, with control over whether an {@code IOException} may be thrown. + * This is primarily useful in a finally block, where a thrown exception needs to be logged but + * not propagated (otherwise the original exception will be lost). + * + *

    If {@code swallowIOException} is true then we never throw {@code IOException} but merely log + * it. + * + *

    Example: + * + *

    {@code
    +     * public void useStreamNicely() throws IOException {
    +     *   SomeStream stream = new SomeStream("foo");
    +     *   boolean threw = true;
    +     *   try {
    +     *     // ... code which does something with the stream ...
    +     *     threw = false;
    +     *   } finally {
    +     *     // If an exception occurs, rethrow it only if threw==false:
    +     *     Closeables.close(stream, threw);
    +     *   }
    +     * }
    +     * }
    + * + * @param closeable the {@code Closeable} object to be closed, or null, in which case this method + * does nothing + * @param swallowIOException if true, don't propagate IO exceptions thrown by the {@code close} + * methods + * @throws IOException if {@code swallowIOException} is false and {@code close} throws an {@code + * IOException}. + */ + public static void close(@Nullable Closeable closeable, boolean swallowIOException) + throws IOException { + if (closeable == null) { + logger.debug("Closeable is null; skip closing."); + return; + } + try { + closeable.close(); + } catch (IOException e) { + if (swallowIOException) { + logger.warn("IOException thrown while closing Closeable.", e); + } else { + throw e; + } + } + } + + /** + * Closes the given {@link InputStream}, logging any {@code IOException} that's thrown rather than + * propagating it. + * + *

    While it's not safe in the general case to ignore exceptions that are thrown when closing an + * I/O resource, it should generally be safe in the case of a resource that's being used only for + * reading, such as an {@code InputStream}. Unlike with writable resources, there's no chance that + * a failure that occurs when closing the stream indicates a meaningful problem such as a failure + * to flush all bytes to the underlying resource. + * + * @param inputStream the input stream to be closed, or {@code null} in which case this method + * does nothing + * @since 17.0 + */ + public static void closeQuietly(@Nullable InputStream inputStream) { + try { + close(inputStream, true); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } + + /** + * Closes the given {@link Reader}, logging any {@code IOException} that's thrown rather than + * propagating it. + * + *

    While it's not safe in the general case to ignore exceptions that are thrown when closing an + * I/O resource, it should generally be safe in the case of a resource that's being used only for + * reading, such as a {@code Reader}. Unlike with writable resources, there's no chance that a + * failure that occurs when closing the reader indicates a meaningful problem such as a failure to + * flush all bytes to the underlying resource. + * + * @param reader the reader to be closed, or {@code null} in which case this method does nothing + * @since 17.0 + */ + public static void closeQuietly(@Nullable Reader reader) { + try { + close(reader, true); + } catch (IOException impossible) { + throw new AssertionError(impossible); + } + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/io/Resources.java b/utils/src/main/java/org/killbill/commons/utils/io/Resources.java index 4c0e6fafb..9e5e19094 100644 --- a/utils/src/main/java/org/killbill/commons/utils/io/Resources.java +++ b/utils/src/main/java/org/killbill/commons/utils/io/Resources.java @@ -42,4 +42,16 @@ public static URL getResource(final String resourceName) { Preconditions.checkArgument(url != null, "resource %s not found.", resourceName); return url; } + + /** + * Given a {@code resourceName} that is relative to {@code contextClass}, returns a {@code URL} + * pointing to the named resource. + * + * @throws IllegalArgumentException if the resource is not found + */ + public static URL getResource(final Class contextClass, final String resourceName) { + final URL url = contextClass.getResource(resourceName); + Preconditions.checkArgument(url != null, "resource %s relative to %s not found.", resourceName, contextClass.getName()); + return url; + } } diff --git a/utils/src/main/java/org/killbill/commons/utils/net/UrlEscapers.java b/utils/src/main/java/org/killbill/commons/utils/net/UrlEscapers.java new file mode 100644 index 000000000..169d69da0 --- /dev/null +++ b/utils/src/main/java/org/killbill/commons/utils/net/UrlEscapers.java @@ -0,0 +1,122 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.net; + +import org.killbill.commons.utils.escape.Escaper; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; + +/** + * Provides URL escaping (percent-encoding) for different URL components. + * + *

    Each escaper percent-encodes characters that are not allowed in its respective URL component, + * as defined by RFC 3986.

    + * + * @see + * Original Guava UrlEscapers + */ +public final class UrlEscapers { + + private UrlEscapers() {} + + // RFC 3986 unreserved characters: ALPHA / DIGIT / "-" / "." / "_" / "~" + private static final BitSet UNRESERVED = new BitSet(128); + // sub-delims: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + private static final BitSet SUB_DELIMS = new BitSet(128); + + static { + // unreserved + for (char c = 'A'; c <= 'Z'; c++) UNRESERVED.set(c); + for (char c = 'a'; c <= 'z'; c++) UNRESERVED.set(c); + for (char c = '0'; c <= '9'; c++) UNRESERVED.set(c); + UNRESERVED.set('-'); + UNRESERVED.set('.'); + UNRESERVED.set('_'); + UNRESERVED.set('~'); + + // sub-delims + String subDelims = "!$&'()*+,;="; + for (int i = 0; i < subDelims.length(); i++) { + SUB_DELIMS.set(subDelims.charAt(i)); + } + } + + // Path segment safe: unreserved + sub-delims + ":" + "@" + private static final BitSet PATH_SEGMENT_SAFE = new BitSet(128); + + static { + PATH_SEGMENT_SAFE.or(UNRESERVED); + PATH_SEGMENT_SAFE.or(SUB_DELIMS); + PATH_SEGMENT_SAFE.set(':'); + PATH_SEGMENT_SAFE.set('@'); + } + + // Fragment safe: path segment safe + "/" + "?" + private static final BitSet FRAGMENT_SAFE = new BitSet(128); + + static { + FRAGMENT_SAFE.or(PATH_SEGMENT_SAFE); + FRAGMENT_SAFE.set('/'); + FRAGMENT_SAFE.set('?'); + } + + /** + * Returns an {@link Escaper} for URL fragment encoding. Characters not allowed in a URI + * fragment are percent-encoded. + * + *

    Safe characters: unreserved + sub-delims + {@code : @ / ?}

    + */ + public static Escaper urlFragmentEscaper() { + return input -> percentEncode(input, FRAGMENT_SAFE); + } + + /** + * Returns an {@link Escaper} for URL form parameter encoding (application/x-www-form-urlencoded). + * Space is encoded as {@code +}, other unsafe characters as percent-encoded. + */ + public static Escaper urlFormParameterEscaper() { + return input -> URLEncoder.encode(input, StandardCharsets.UTF_8); + } + + /** + * Returns an {@link Escaper} for URL path segment encoding. Characters not allowed in a path + * segment are percent-encoded. + * + *

    Safe characters: unreserved + sub-delims + {@code : @}

    + */ + public static Escaper urlPathSegmentEscaper() { + return input -> percentEncode(input, PATH_SEGMENT_SAFE); + } + + private static String percentEncode(final String input, final BitSet safeChars) { + final StringBuilder sb = new StringBuilder(input.length()); + final byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + for (final byte b : bytes) { + final int unsigned = b & 0xFF; + if (unsigned < 128 && safeChars.get(unsigned)) { + sb.append((char) unsigned); + } else { + sb.append('%'); + sb.append(Character.toUpperCase(Character.forDigit(unsigned >> 4, 16))); + sb.append(Character.toUpperCase(Character.forDigit(unsigned & 0xF, 16))); + } + } + return sb.toString(); + } +} diff --git a/utils/src/main/java/org/killbill/commons/utils/reflect/AbstractInvocationHandler.java b/utils/src/main/java/org/killbill/commons/utils/reflect/AbstractInvocationHandler.java index e67190e7f..dd84676d9 100644 --- a/utils/src/main/java/org/killbill/commons/utils/reflect/AbstractInvocationHandler.java +++ b/utils/src/main/java/org/killbill/commons/utils/reflect/AbstractInvocationHandler.java @@ -23,7 +23,7 @@ import java.lang.reflect.Proxy; import java.util.Arrays; -import javax.annotation.CheckForNull; +import jakarta.annotation.Nullable; /** * Verbatim copy of Guava's {@code com.google.common.reflect.AbstractInvocationHandler}. @@ -64,8 +64,8 @@ public abstract class AbstractInvocationHandler implements InvocationHandler { * */ @Override - @CheckForNull - public final Object invoke(final Object proxy, final Method method, @CheckForNull Object[] args) throws Throwable { + @Nullable + public final Object invoke(final Object proxy, final Method method, @Nullable Object[] args) throws Throwable { if (args == null) { args = NO_ARGS; } @@ -99,7 +99,7 @@ public final Object invoke(final Object proxy, final Method method, @CheckForNul *

    Unlike {@link #invoke}, {@code args} will never be null. When the method has no parameter, * an empty array is passed in. */ - @CheckForNull + @Nullable protected abstract Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable; /** @@ -114,7 +114,7 @@ public final Object invoke(final Object proxy, final Method method, @CheckForNul *

    Subclasses can override this method to provide custom equality. */ @Override - public boolean equals(@CheckForNull final Object obj) { + public boolean equals(@Nullable final Object obj) { return super.equals(obj); } diff --git a/utils/src/test/java/org/killbill/commons/eventbus/StringCatcher.java b/utils/src/test/java/org/killbill/commons/eventbus/StringCatcher.java index 6e6fb26fe..7f22f7f2d 100644 --- a/utils/src/test/java/org/killbill/commons/eventbus/StringCatcher.java +++ b/utils/src/test/java/org/killbill/commons/eventbus/StringCatcher.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.Nullable; +import jakarta.annotation.Nullable; import org.testng.Assert; diff --git a/utils/src/test/java/org/killbill/commons/utils/SplitterTest.java b/utils/src/test/java/org/killbill/commons/utils/SplitterTest.java new file mode 100644 index 000000000..6a61866bd --- /dev/null +++ b/utils/src/test/java/org/killbill/commons/utils/SplitterTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 2014-2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils; + +import java.util.Iterator; +import java.util.List; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class SplitterTest { + + @Test + public void testOnChar_basic() { + List result = Splitter.on(',').splitToList("a,b,c"); + Assert.assertEquals(result, List.of("a", "b", "c")); + } + + @Test + public void testOnChar_singleElement() { + List result = Splitter.on(',').splitToList("hello"); + Assert.assertEquals(result, List.of("hello")); + } + + @Test + public void testOnChar_emptyParts() { + List result = Splitter.on(',').splitToList("a,,b,"); + Assert.assertEquals(result, List.of("a", "", "b", "")); + } + + @Test + public void testOnChar_emptyInput() { + List result = Splitter.on(',').splitToList(""); + Assert.assertEquals(result, List.of("")); + } + + @Test + public void testTrimResults() { + List result = Splitter.on(',').trimResults().splitToList(" a , b , c "); + Assert.assertEquals(result, List.of("a", "b", "c")); + } + + @Test + public void testOmitEmptyStrings() { + List result = Splitter.on(',').omitEmptyStrings().splitToList("a,,b,,c"); + Assert.assertEquals(result, List.of("a", "b", "c")); + } + + @Test + public void testTrimResults_andOmitEmpty() { + List result = Splitter.on(',').trimResults().omitEmptyStrings() + .splitToList(" a , , b , , "); + Assert.assertEquals(result, List.of("a", "b")); + } + + @Test + public void testOmitEmpty_andTrimResults_orderDoesNotMatter() { + List result = Splitter.on(',').omitEmptyStrings().trimResults() + .splitToList(" a , , b , , "); + Assert.assertEquals(result, List.of("a", "b")); + } + + @Test + public void testOnCharMatcher_anyOf() { + List result = Splitter.on(CharMatcher.anyOf("[].")).trimResults() + .omitEmptyStrings().splitToList("foo[0].bar"); + Assert.assertEquals(result, List.of("foo", "0", "bar")); + } + + @Test + public void testOnCharMatcher_anyOf_multipleDelimiters() { + List result = Splitter.on(CharMatcher.anyOf("&=")).splitToList("a=1&b=2"); + Assert.assertEquals(result, List.of("a", "1", "b", "2")); + } + + @Test + public void testSplit_returnsIterable() { + Iterable result = Splitter.on('&').trimResults().omitEmptyStrings() + .split("a & b & c"); + Iterator it = result.iterator(); + Assert.assertTrue(it.hasNext()); + Assert.assertEquals(it.next(), "a"); + Assert.assertEquals(it.next(), "b"); + Assert.assertEquals(it.next(), "c"); + Assert.assertFalse(it.hasNext()); + } + + @Test + public void testSplitToList_unmodifiable() { + List result = Splitter.on(',').splitToList("a,b"); + try { + result.add("c"); + Assert.fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + // ok + } + } + + @Test + public void testOnNewline() { + List result = Splitter.on('\n').splitToList("line1\nline2\nline3"); + Assert.assertEquals(result, List.of("line1", "line2", "line3")); + } + + @Test + public void testOnEquals() { + List result = Splitter.on('=').trimResults().omitEmptyStrings() + .split("key=value").iterator().next().isEmpty() ? List.of() : null; + // More direct test: + Iterator it = Splitter.on('=').trimResults().omitEmptyStrings() + .split("key=value").iterator(); + Assert.assertEquals(it.next(), "key"); + Assert.assertEquals(it.next(), "value"); + Assert.assertFalse(it.hasNext()); + } + + @Test + public void testOnAmpersand_cookiePattern() { + // Matches Cookie.java usage: Splitter.on('&').trimResults().omitEmptyStrings() + List result = Splitter.on('&').trimResults().omitEmptyStrings() + .splitToList("name=val&other=x&"); + Assert.assertEquals(result, List.of("name=val", "other=x")); + } + + @Test + public void testOnComma_corsPattern() { + // Matches CorsHandler.java: Splitter.on(',').trimResults().omitEmptyStrings() + List result = Splitter.on(',').trimResults().omitEmptyStrings() + .splitToList("GET, POST, PUT"); + Assert.assertEquals(result, List.of("GET", "POST", "PUT")); + } + + @Test + public void testImmutability_trimDoesNotModifyOriginal() { + Splitter base = Splitter.on(','); + Splitter trimmed = base.trimResults(); + // base should still not trim + Assert.assertEquals(base.splitToList(" a , b "), List.of(" a ", " b ")); + Assert.assertEquals(trimmed.splitToList(" a , b "), List.of("a", "b")); + } + + @Test + public void testImmutability_omitDoesNotModifyOriginal() { + Splitter base = Splitter.on(','); + Splitter omitting = base.omitEmptyStrings(); + Assert.assertEquals(base.splitToList("a,,b"), List.of("a", "", "b")); + Assert.assertEquals(omitting.splitToList("a,,b"), List.of("a", "b")); + } +} diff --git a/utils/src/test/java/org/killbill/commons/utils/TestTypeToken.java b/utils/src/test/java/org/killbill/commons/utils/TestTypeToken.java index 0bd8baeb3..542b64dc5 100644 --- a/utils/src/test/java/org/killbill/commons/utils/TestTypeToken.java +++ b/utils/src/test/java/org/killbill/commons/utils/TestTypeToken.java @@ -30,6 +30,20 @@ public class TestTypeToken { private static class SuperList extends ArrayList implements List, Collection {} + private interface LeftRoot {} + + private interface LeftParent extends LeftRoot {} + + private interface LeftChild extends LeftParent {} + + private interface RightRoot {} + + private interface RightParent extends RightRoot {} + + private interface RightChild extends RightParent {} + + private static class BranchedInterfaces implements LeftChild, RightChild {} + @Test(groups = "fast") public void testGetRawTypes() { Set> types = TypeToken.getRawTypes(Object.class); @@ -75,6 +89,23 @@ public void testGetRawTypes() { Assert.assertEquals(types, stringExpected); } + @Test(groups = "fast") + public void testGetRawTypesTraversesAllInterfaceBranches() { + final Set> expected = new LinkedHashSet<>(); + expected.add(BranchedInterfaces.class); + expected.add(LeftChild.class); + expected.add(LeftParent.class); + expected.add(LeftRoot.class); + expected.add(RightChild.class); + expected.add(RightParent.class); + expected.add(RightRoot.class); + expected.add(Object.class); + + // Guava's TypeToken.of(BranchedInterfaces.class).getTypes().rawTypes() contains every type + // below. The local implementation loses one root because it continues only one branch. + Assert.assertEquals(TypeToken.getRawTypes(BranchedInterfaces.class), expected); + } + // Newer JDKs add interfaces such as SequencedCollection/Constable/ConstantDesc, so the test // builds the exact expected set while remaining compatible with earlier runtimes that lack them. private void addIfPresent(final Set> types, final String className) { diff --git a/utils/src/test/java/org/killbill/commons/utils/cache/TestCacheBuilder.java b/utils/src/test/java/org/killbill/commons/utils/cache/TestCacheBuilder.java new file mode 100644 index 000000000..bbd503d0a --- /dev/null +++ b/utils/src/test/java/org/killbill/commons/utils/cache/TestCacheBuilder.java @@ -0,0 +1,385 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.cache; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestCacheBuilder { + + // --- newBuilder() --- + + @Test(groups = "fast") + public void testNewBuilderCreatesUnboundedCache() { + final Cache cache = CacheBuilder.newBuilder().build(); + cache.put("a", "1"); + Assert.assertEquals(cache.get("a"), "1"); + } + + @Test(groups = "fast") + public void testNewBuilderWithLoader() { + final Cache cache = CacheBuilder.newBuilder() + .build(key -> "loaded-" + key); + + Assert.assertEquals(cache.get(1), "loaded-1"); + // Second call should serve from cache + Assert.assertEquals(cache.get(1), "loaded-1"); + } + + @Test(groups = "fast") + public void testNewBuilderWithMaximumSize() { + final Cache cache = CacheBuilder.newBuilder() + .maximumSize(2) + .build(); + + cache.put(1, "A"); + cache.put(2, "B"); + cache.put(3, "C"); + + Assert.assertTrue(countPresent(cache, 1, 2, 3) <= 2); + } + + @Test(groups = "fast") + public void testNewBuilderMaximumSizeZero() { + final AtomicInteger loadCount = new AtomicInteger(0); + final Cache cache = CacheBuilder.newBuilder() + .maximumSize(0) + .build(key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }); + + // Always calls loader, never caches + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 2); + } + + @Test(groups = "fast") + public void testNewBuilderWithExpireAfterWrite() throws InterruptedException { + final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(Duration.ofSeconds(1)) + .build(); + + cache.put(1, "A"); + Assert.assertEquals(cache.get(1), "A"); + + Thread.sleep(1100); + Assert.assertNull(cache.get(1)); + } + + @Test(groups = "fast") + public void testNewBuilderWithExpireAfterWriteDurationZero() { + final AtomicInteger loadCount = new AtomicInteger(0); + final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(Duration.ZERO) + .build(key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }); + + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 2); + } + + @Test(groups = "fast") + public void testNewBuilderWithExpireAfterWriteTimeUnit() throws InterruptedException { + final Cache cache = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.SECONDS) + .build(); + + cache.put(1, "A"); + Assert.assertEquals(cache.get(1), "A"); + + Thread.sleep(1100); + Assert.assertNull(cache.get(1)); + } + + @Test(groups = "fast") + public void testNewBuilderWithConcurrencyLevel() { + final Cache cache = CacheBuilder.newBuilder() + .concurrencyLevel(8) + .build(); + + cache.put("x", "y"); + Assert.assertEquals(cache.get("x"), "y"); + } + + // --- from(spec) --- + + @Test(groups = "fast") + public void testFromSpecMaximumSize() { + final Cache cache = CacheBuilder.from("maximumSize=2") + .build(); + + cache.put(1, "A"); + cache.put(2, "B"); + cache.put(3, "C"); + + Assert.assertTrue(countPresent(cache, 1, 2, 3) <= 2); + } + + @Test(groups = "fast") + public void testFromSpecConcurrencyLevel() { + final Cache cache = CacheBuilder.from("concurrencyLevel=4") + .build(); + + cache.put("a", "1"); + Assert.assertEquals(cache.get("a"), "1"); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteSeconds() throws InterruptedException { + final Cache cache = CacheBuilder.from("expireAfterWrite=1s") + .build(); + + cache.put(1, "A"); + Assert.assertEquals(cache.get(1), "A"); + + Thread.sleep(1100); + Assert.assertNull(cache.get(1)); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteMinutes() { + // Just verify parsing doesn't throw — won't actually wait 60s + final Cache cache = CacheBuilder.from("expireAfterWrite=2m") + .build(); + + cache.put("x", "y"); + Assert.assertEquals(cache.get("x"), "y"); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteHours() { + final Cache cache = CacheBuilder.from("expireAfterWrite=1h") + .build(); + cache.put("x", "y"); + Assert.assertEquals(cache.get("x"), "y"); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteDays() { + final Cache cache = CacheBuilder.from("expireAfterWrite=1d") + .build(); + cache.put("x", "y"); + Assert.assertEquals(cache.get("x"), "y"); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteNoUnit() throws InterruptedException { + // No unit suffix = seconds + final Cache cache = CacheBuilder.from("expireAfterWrite=1") + .build(); + + cache.put(1, "A"); + Thread.sleep(1100); + Assert.assertNull(cache.get(1)); + } + + @Test(groups = "fast") + public void testFromSpecExpireAfterWriteZero() { + final AtomicInteger loadCount = new AtomicInteger(0); + final Cache cache = CacheBuilder.from("expireAfterWrite=0") + .build(key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }); + + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 2); + } + + @Test(groups = "fast") + public void testFromSpecMultipleKeys() { + final Cache cache = CacheBuilder.from( + "concurrencyLevel=8,maximumSize=3").build(); + + cache.put(1, "A"); + cache.put(2, "B"); + cache.put(3, "C"); + cache.put(4, "D"); + + Assert.assertTrue(countPresent(cache, 1, 2, 3, 4) <= 3); + } + + @Test(groups = "fast") + public void testFromSpecWithLoader() { + final Cache cache = CacheBuilder.from("maximumSize=100") + .build(key -> "loaded-" + key); + + Assert.assertEquals(cache.get(42), "loaded-42"); + } + + @Test(groups = "fast") + public void testFromSpecEmptyString() { + // Empty spec = default (unbounded, no TTL) + final Cache cache = CacheBuilder.from("").build(); + cache.put("a", "1"); + Assert.assertEquals(cache.get("a"), "1"); + } + + @Test(groups = "fast") + public void testFromSpecIgnoresUnknownKeys() { + // Unknown keys should be silently ignored + final Cache cache = CacheBuilder.from( + "maximumSize=10,recordStats").build(); + cache.put("a", "1"); + Assert.assertEquals(cache.get("a"), "1"); + } + + // --- weakKeys --- + + @Test(groups = "fast") + public void testNewBuilderWithWeakKeys() { + final Cache cache = CacheBuilder.newBuilder() + .weakKeys() + .build(); + + cache.put("key", "value"); + Assert.assertEquals(cache.get("key"), "value"); + + cache.invalidate("key"); + Assert.assertNull(cache.get("key")); + } + + @Test(groups = "fast") + public void testFromSpecWeakKeys() { + final Cache cache = CacheBuilder.from("weakKeys") + .build(); + + cache.put("k", "v"); + Assert.assertEquals(cache.get("k"), "v"); + } + + @Test(groups = "fast") + public void testFromSpecWeakKeysWithOtherSettings() { + final Cache cache = CacheBuilder.from("weakKeys,maximumSize=100") + .build(); + + cache.put("k", "v"); + Assert.assertEquals(cache.get("k"), "v"); + } + + @Test(groups = "fast") + public void testWeakKeysRejectsDoubleSet() { + try { + CacheBuilder.newBuilder().weakKeys().weakKeys(); + Assert.fail("Should reject setting weakKeys twice"); + } catch (final IllegalStateException e) { + Assert.assertTrue(e.getMessage().contains("Key strength was already set")); + } + } + + // --- Validation --- + + @Test(groups = "fast") + public void testMaximumSizeRejectsNegative() { + try { + CacheBuilder.newBuilder().maximumSize(-1); + Assert.fail("Should reject negative maximumSize"); + } catch (final IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("maximumSize must be >= 0")); + } + } + + @Test(groups = "fast") + public void testConcurrencyLevelRejectsZero() { + try { + CacheBuilder.newBuilder().concurrencyLevel(0); + Assert.fail("Should reject zero concurrencyLevel"); + } catch (final IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("concurrencyLevel must be > 0")); + } + } + + @Test(groups = "fast") + public void testExpireAfterWriteRejectsNegative() { + try { + CacheBuilder.newBuilder().expireAfterWrite(-1, TimeUnit.SECONDS); + Assert.fail("Should reject negative expireAfterWrite"); + } catch (final IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("expireAfterWrite must be >= 0")); + } + } + + @Test(groups = "fast") + public void testExpireAfterWriteDurationRejectsNegative() { + try { + CacheBuilder.newBuilder().expireAfterWrite(Duration.ofNanos(-1)); + Assert.fail("Should reject negative expireAfterWrite"); + } catch (final IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("expireAfterWrite must be >= 0")); + } + } + + @Test(groups = "fast") + public void testExpireAfterWriteDurationRejectsNull() { + try { + CacheBuilder.newBuilder().expireAfterWrite((Duration) null); + Assert.fail("Should reject null expireAfterWrite duration"); + } catch (final NullPointerException e) { + Assert.assertEquals(e.getMessage(), "expireAfterWrite duration is null"); + } + } + + @Test(groups = "fast") + public void testExpireAfterWriteTimeUnitRejectsNullUnit() { + try { + CacheBuilder.newBuilder().expireAfterWrite(1, null); + Assert.fail("Should reject null expireAfterWrite unit"); + } catch (final NullPointerException e) { + Assert.assertEquals(e.getMessage(), "expireAfterWrite unit is null"); + } + } + + @Test(groups = "fast") + public void testFromSpecNullThrows() { + try { + CacheBuilder.from(null); + Assert.fail("Should reject null spec"); + } catch (final NullPointerException e) { + Assert.assertEquals(e.getMessage(), "spec is null"); + } + } + + @Test(groups = "fast") + public void testFromSpecInvalidDurationUnit() { + try { + CacheBuilder.from("expireAfterWrite=10x").build(); + Assert.fail("Should reject invalid duration unit"); + } catch (final IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("Unknown duration unit")); + } + } + + private int countPresent(final Cache cache, final int... keys) { + int present = 0; + for (final int key : keys) { + if (cache.get(key) != null) { + present++; + } + } + return present; + } +} diff --git a/utils/src/test/java/org/killbill/commons/utils/cache/TestCaffeineCache.java b/utils/src/test/java/org/killbill/commons/utils/cache/TestCaffeineCache.java new file mode 100644 index 000000000..b7df0478f --- /dev/null +++ b/utils/src/test/java/org/killbill/commons/utils/cache/TestCaffeineCache.java @@ -0,0 +1,218 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.cache; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TestCaffeineCache { + + // --- Basic operations --- + + @Test(groups = "fast") + public void testGetReturnsNullWithoutLoader() { + final Cache cache = new CaffeineCache<>(null, null, null, null); + + Assert.assertNull(cache.get("missing")); + } + + @Test(groups = "fast") + public void testPutGetAndInvalidate() { + final Cache cache = new CaffeineCache<>(null, null, null, null); + + cache.put("key", "value"); + Assert.assertEquals(cache.get("key"), "value"); + + cache.invalidate("key"); + Assert.assertNull(cache.get("key")); + } + + // --- Configured loader --- + + @Test(groups = "fast") + public void testConfiguredLoaderCachesValue() { + final AtomicInteger loadCount = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, null, key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }, null); + + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 1); + } + + // --- getOrLoad --- + + @Test(groups = "fast") + public void testGetOrLoadCachesValue() { + final AtomicInteger loadCount = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, null, null, null); + + Assert.assertEquals(cache.getOrLoad(1, key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 1); + } + + @Test(groups = "fast") + public void testGetOrLoadUsesSuppliedLoaderOnMiss() { + final AtomicInteger configuredLoaderCalls = new AtomicInteger(); + final AtomicInteger suppliedLoaderCalls = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, null, key -> { + configuredLoaderCalls.incrementAndGet(); + return "configured-" + key; + }, null); + + Assert.assertEquals(cache.getOrLoad(1, key -> { + suppliedLoaderCalls.incrementAndGet(); + return "supplied-" + key; + }), "supplied-1"); + Assert.assertEquals(configuredLoaderCalls.get(), 0); + Assert.assertEquals(suppliedLoaderCalls.get(), 1); + } + + @Test(groups = "fast") + public void testGetOrLoadReturnsCachedValue() { + final AtomicInteger suppliedLoaderCalls = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, null, key -> "configured-" + key, null); + + Assert.assertEquals(cache.get(1), "configured-1"); + Assert.assertEquals(cache.getOrLoad(1, key -> { + suppliedLoaderCalls.incrementAndGet(); + return "supplied-" + key; + }), "configured-1"); + Assert.assertEquals(suppliedLoaderCalls.get(), 0); + } + + // --- maximumSize --- + + @Test(groups = "fast") + public void testMaximumSizeBoundsEntries() { + final Cache cache = new CaffeineCache<>(2L, null, null, null); + + cache.put(1, "A"); + cache.put(2, "B"); + cache.put(3, "C"); + + Assert.assertTrue(countPresent(cache, 1, 2, 3) <= 2); + } + + @Test(groups = "fast") + public void testMaximumSizeZeroDoesNotRetainEntries() { + final AtomicInteger loadCount = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(0L, null, key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }, null); + + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 2); + } + + // --- expireAfterWrite --- + + @Test(groups = "fast") + public void testExpireAfterWriteZeroDoesNotRetainEntries() { + final AtomicInteger loadCount = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, Duration.ZERO, key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }, null); + + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(cache.get(1), "loaded-1"); + Assert.assertEquals(loadCount.get(), 2); + } + + // --- weakKeys --- + + @Test(groups = "fast") + public void testWeakKeysConfigurationAccepted() { + // Verifies weakKeys doesn't throw and cache is functional + final Cache cache = new CaffeineCache<>(null, null, null, Strength.WEAK); + + cache.put("key", "value"); + Assert.assertEquals(cache.get("key"), "value"); + + cache.invalidate("key"); + Assert.assertNull(cache.get("key")); + } + + @Test(groups = "fast") + public void testWeakKeysWithLoader() { + final AtomicInteger loadCount = new AtomicInteger(); + final Cache cache = new CaffeineCache<>(null, null, key -> { + loadCount.incrementAndGet(); + return "loaded-" + key; + }, Strength.WEAK); + + Assert.assertEquals(cache.get("myKey"), "loaded-myKey"); + Assert.assertEquals(cache.get("myKey"), "loaded-myKey"); + // Loaded once, served from cache on second call + Assert.assertEquals(loadCount.get(), 1); + } + + @Test(groups = "fast") + public void testWeakKeysWithMaxSizeAndLoader() { + final Cache cache = new CaffeineCache<>(100L, null, key -> "loaded-" + key, Strength.WEAK); + + Assert.assertEquals(cache.get("a"), "loaded-a"); + Assert.assertEquals(cache.get("b"), "loaded-b"); + Assert.assertEquals(cache.get("a"), "loaded-a"); + } + + // --- Null argument validation --- + + @Test(groups = "fast") + public void testNullArgumentsRejected() { + final Cache cache = new CaffeineCache<>(null, null, null, null); + + assertThrowsNullPointerException(() -> cache.get(null), "Cannot #get() cache with key = null"); + assertThrowsNullPointerException(() -> cache.getOrLoad(1, null), "loader parameter in #getOrLoad() is null"); + assertThrowsNullPointerException(() -> cache.put(null, "value"), "key in #put() is null"); + assertThrowsNullPointerException(() -> cache.put(1, null), "value in #put() is null"); + assertThrowsNullPointerException(() -> cache.invalidate(null), "Cannot invalidate. Cache with null key is not allowed"); + } + + // --- Helpers --- + + private int countPresent(final Cache cache, final int... keys) { + int present = 0; + for (final int key : keys) { + if (cache.get(key) != null) { + present++; + } + } + return present; + } + + private void assertThrowsNullPointerException(final Runnable runnable, final String expectedMessage) { + try { + runnable.run(); + Assert.fail("Should reject null argument"); + } catch (final NullPointerException e) { + Assert.assertEquals(e.getMessage(), expectedMessage); + } + } +} diff --git a/utils/src/test/java/org/killbill/commons/utils/escape/EscaperTest.java b/utils/src/test/java/org/killbill/commons/utils/escape/EscaperTest.java new file mode 100644 index 000000000..294780e57 --- /dev/null +++ b/utils/src/test/java/org/killbill/commons/utils/escape/EscaperTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.escape; + +import org.killbill.commons.utils.html.HtmlEscapers; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class EscaperTest { + + @Test + public void testFunctionalInterface() { + Escaper escaper = String::toUpperCase; + Assert.assertEquals(escaper.escape("hello"), "HELLO"); + } + + @Test + public void testMethodReference() { + Escaper html = HtmlEscapers.htmlEscaper(); + java.util.function.Function fn = html::escape; + Assert.assertEquals(fn.apply(""), "<b>"); + } +} diff --git a/utils/src/test/java/org/killbill/commons/utils/html/HtmlEscapersTest.java b/utils/src/test/java/org/killbill/commons/utils/html/HtmlEscapersTest.java new file mode 100644 index 000000000..66ca6525b --- /dev/null +++ b/utils/src/test/java/org/killbill/commons/utils/html/HtmlEscapersTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2026 The Billing Project, LLC + * + * The Billing Project licenses this file to you under the Apache License, version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.killbill.commons.utils.html; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class HtmlEscapersTest { + + @Test + public void testAmpersand() { + Assert.assertEquals(HtmlEscapers.htmlEscaper().escape("a&b"), "a&b"); + } + + @Test + public void testLessThan() { + Assert.assertEquals(HtmlEscapers.htmlEscaper().escape("