This is an automated email from the ASF dual-hosted git repository. mattsicker pushed a commit to branch feature/3.x/graalvm-reachability in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit d4829f94c1731f1e548071e129d97940c5e7eb2c Author: Matt Sicker <msic...@apple.com> AuthorDate: Wed Aug 13 17:54:37 2025 -0500 Extract class PluginIndex --- .../{PluginCacheTest.java => PluginIndexTest.java} | 40 ++++------ .../logging/log4j/plugins/model/PluginCache.java | 91 ---------------------- .../logging/log4j/plugins/model/PluginIndex.java | 79 +++++++++++++++++++ .../log4j/plugins/model/PluginRegistry.java | 30 ++++++- 4 files changed, 121 insertions(+), 119 deletions(-) diff --git a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.java b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginIndexTest.java similarity index 50% rename from log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.java rename to log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginIndexTest.java index 6fc33cab54..a338e40d21 100644 --- a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.java +++ b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/processor/PluginIndexTest.java @@ -17,47 +17,39 @@ package org.apache.logging.log4j.plugins.processor; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; -import java.io.IOException; -import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.Objects; -import org.apache.logging.log4j.plugins.model.PluginCache; import org.apache.logging.log4j.plugins.model.PluginEntry; +import org.apache.logging.log4j.plugins.model.PluginIndex; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.Issue; -public class PluginCacheTest { +public class PluginIndexTest { @Test @Issue("https://issues.apache.org/jira/browse/LOG4J2-2735") - public void testOutputIsReproducibleWhenInputOrderingChanges() throws IOException { - final PluginCache cacheA = new PluginCache(); - createCategory(cacheA, "one", Arrays.asList("bravo", "alpha", "charlie")); - createCategory(cacheA, "two", Arrays.asList("alpha", "charlie", "bravo")); - assertEquals(cacheA.getAllNamespaces().size(), 2); - assertEquals(cacheA.getAllNamespaces().get("one").size(), 3); - assertEquals(cacheA.getAllNamespaces().get("two").size(), 3); - final PluginCache cacheB = new PluginCache(); - createCategory(cacheB, "two", Arrays.asList("bravo", "alpha", "charlie")); - createCategory(cacheB, "one", Arrays.asList("alpha", "charlie", "bravo")); - assertEquals(cacheB.getAllNamespaces().size(), 2); - assertEquals(cacheB.getAllNamespaces().get("one").size(), 3); - assertEquals(cacheB.getAllNamespaces().get("two").size(), 3); - assertEquals(Objects.toString(cacheA.getAllNamespaces()), Objects.toString(cacheB.getAllNamespaces())); + public void testOutputIsReproducibleWhenInputOrderingChanges() { + final PluginIndex indexA = new PluginIndex(); + createNamespace(indexA, "one", List.of("bravo", "alpha", "charlie")); + createNamespace(indexA, "two", List.of("alpha", "charlie", "bravo")); + assertEquals(6, indexA.size()); + final PluginIndex indexB = new PluginIndex(); + createNamespace(indexB, "two", List.of("bravo", "alpha", "charlie")); + createNamespace(indexB, "one", List.of("alpha", "charlie", "bravo")); + assertEquals(6, indexB.size()); + assertIterableEquals(indexA, indexB); } - private void createCategory(final PluginCache cache, final String categoryName, final List<String> entryNames) { - final Map<String, PluginEntry> category = cache.getNamespace(categoryName); + private void createNamespace(final PluginIndex index, final String namespace, final List<String> entryNames) { for (String entryName : entryNames) { final PluginEntry entry = PluginEntry.builder() .setKey(entryName) .setName(entryName) .setClassName("com.example.Plugin") - .setNamespace(categoryName) + .setNamespace(namespace) .get(); - category.put(entryName, entry); + index.add(entry); } } } diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginCache.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginCache.java deleted file mode 100644 index 024e931d3a..0000000000 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginCache.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF 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.apache.logging.log4j.plugins.model; - -import java.io.BufferedInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.net.URL; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Map; -import java.util.TreeMap; - -public class PluginCache { - private final Map<String, Map<String, PluginEntry>> namespaces = new TreeMap<>(); - - /** - * Returns all namespaces of plugins in this cache. - * - * @return all namespaces of plugins in this cache. - * @since 2.1 - */ - public Map<String, Map<String, PluginEntry>> getAllNamespaces() { - return namespaces; - } - - /** - * Gets or creates a namespace of plugins. - * - * @param namespace namespace to look up. - * @return plugin mapping of names to plugin entries. - */ - public Map<String, PluginEntry> getNamespace(final String namespace) { - final String key = namespace.toLowerCase(Locale.ROOT); - return namespaces.computeIfAbsent(key, ignored -> new TreeMap<>()); - } - - /** - * Loads and merges all the Log4j plugin cache files specified. Usually, this is obtained via a ClassLoader. - * - * @param resources URLs to all the desired plugin cache files to load. - * @throws IOException if an I/O exception occurs. - */ - public void loadCacheFiles(final Enumeration<URL> resources) throws IOException { - namespaces.clear(); - while (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()))) { - final int count = in.readInt(); - for (int i = 0; i < count; i++) { - final var builder = PluginEntry.builder().setNamespace(in.readUTF()); - final Map<String, PluginEntry> m = getNamespace(builder.getNamespace()); - final int entries = in.readInt(); - for (int j = 0; j < entries; j++) { - // Must always read all parts of the entry, even if not adding, so that the stream progresses - final var entry = builder.setKey(in.readUTF()) - .setClassName(in.readUTF()) - .setName(in.readUTF()) - .setPrintable(in.readBoolean()) - .setDeferChildren(in.readBoolean()) - .get(); - m.putIfAbsent(entry.key(), entry); - } - } - } - } - } - - /** - * Gets the number of plugin namespaces registered. - * - * @return number of plugin namespaces in cache. - */ - public int size() { - return namespaces.size(); - } -} diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginIndex.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginIndex.java new file mode 100644 index 0000000000..f9a6b38aca --- /dev/null +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginIndex.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.logging.log4j.plugins.model; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class PluginIndex extends AbstractCollection<PluginEntry> { + private final Map<String, Map<String, PluginEntry>> index = new TreeMap<>(); + + @Override + public void forEach(Consumer<? super PluginEntry> action) { + for (var namespace : index.values()) { + for (var pluginEntry : namespace.values()) { + action.accept(pluginEntry); + } + } + } + + @Override + public Iterator<PluginEntry> iterator() { + return index.values().stream() + .map(Map::values) + .flatMap(Collection::stream) + .iterator(); + } + + @Override + public int size() { + return index.values().stream().mapToInt(Map::size).sum(); + } + + @Override + public boolean add(PluginEntry entry) { + return getOrCreateNamespace(entry.namespace()).putIfAbsent(entry.key(), entry) == null; + } + + @Override + public boolean isEmpty() { + return index.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return o instanceof PluginEntry entry + && index.containsKey(entry.namespace()) + && index.get(entry.namespace()).containsKey(entry.key()) + && index.get(entry.namespace()).get(entry.key()).equals(entry); + } + + @Override + public void clear() { + index.clear(); + } + + private Map<String, PluginEntry> getOrCreateNamespace(String namespace) { + return index.computeIfAbsent(namespace, ignored -> new TreeMap<>()); + } +} diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginRegistry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginRegistry.java index dbd4fdc8b0..e09b510988 100644 --- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginRegistry.java +++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/model/PluginRegistry.java @@ -20,6 +20,8 @@ import static org.apache.logging.log4j.util.Unbox.box; import aQute.bnd.annotation.Cardinality; import aQute.bnd.annotation.spi.ServiceConsumer; +import java.io.BufferedInputStream; +import java.io.DataInputStream; import java.io.IOException; import java.net.URL; import java.text.DecimalFormat; @@ -101,24 +103,44 @@ public class PluginRegistry { private Namespaces decodeCacheFiles(final ClassLoader classLoader) { final long startTime = System.nanoTime(); - final PluginCache cache = new PluginCache(); + final PluginIndex index = new PluginIndex(); try { final Enumeration<URL> resources = classLoader.getResources(PLUGIN_CACHE_FILE); if (resources == null) { LOGGER.info("Plugin preloads not available from class loader {}", classLoader); } else { - cache.loadCacheFiles(resources); + while (resources.hasMoreElements()) { + final URL url = resources.nextElement(); + try (final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()))) { + final int count = in.readInt(); + for (int i = 0; i < count; i++) { + final var builder = PluginEntry.builder().setNamespace(in.readUTF()); + final int entries = in.readInt(); + for (int j = 0; j < entries; j++) { + // Must always read all parts of the entry, even if not adding, so that the stream + // progresses + final var entry = builder.setKey(in.readUTF()) + .setClassName(in.readUTF()) + .setName(in.readUTF()) + .setPrintable(in.readBoolean()) + .setDeferChildren(in.readBoolean()) + .get(); + index.add(entry); + } + } + } + } } } catch (final IOException ioe) { LOGGER.warn("Unable to preload plugins", ioe); } final Namespaces namespaces = new Namespaces(); final AtomicInteger pluginCount = new AtomicInteger(); - cache.getAllNamespaces().forEach((key, outer) -> outer.values().forEach(entry -> { + index.forEach(entry -> { final PluginType<?> type = new PluginType<>(entry, classLoader); namespaces.add(type); pluginCount.incrementAndGet(); - })); + }); reportLoadTime(classLoader, startTime, pluginCount); return namespaces; }