This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4809-metadata-key-api in repository https://gitbox.apache.org/repos/asf/tika.git
commit ff5715b3d6e249cd4998055c916459e7022ed96c Author: tallison <[email protected]> AuthorDate: Tue Aug 11 14:22:02 2026 -0400 TIKA-4809 metadata-key stage 4: putAll(Metadata) public copy API --- .../java/org/apache/tika/metadata/Metadata.java | 34 ++++++++ .../java/org/apache/tika/utils/ParserUtils.java | 11 +-- .../apache/tika/metadata/MetadataPutAllTest.java | 98 ++++++++++++++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java index 1f20a5ce09..9ea8385850 100644 --- a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java +++ b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java @@ -361,6 +361,40 @@ public class Metadata } } + /** + * Copies every key from {@code other} into this Metadata: for each name in + * {@code other}, this Metadata's values for that name are replaced wholesale with + * {@code other}'s values, in order (multi-values preserved); names absent from + * {@code other} are left untouched. Each value is written via + * {@link #reconstruct(String, String, boolean)}, so reserved {@code tk:} keys copy + * through their trusted route rather than the String-guarded route. + * <p> + * This is the supported replacement for a manual + * {@code for (String n : src.names()) dest.set(n, src.get(n))} copy loop, which + * silently collapses multi-valued keys to a single value today and throws on + * {@code tk:}-prefixed keys once the reserved-key guard is flipped from drop to throw. + * + * @param other the Metadata to copy from; {@code other == this} is a no-op + * @throws NullPointerException if other is null + * @since Apache Tika 4.0 + */ + public void putAll(Metadata other) { + Objects.requireNonNull(other, "other must not be null"); + if (other == this) { + return; + } + for (String n : other.names()) { + String[] vals = other.getValues(n); + if (vals.length == 0) { + continue; + } + reconstruct(n, vals[0], false); + for (int i = 1; i < vals.length; i++) { + reconstruct(n, vals[i], true); + } + } + } + /** * Add a metadata name/value mapping. Add the specified value to the list of * values associated to the specified metadata name. diff --git a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java index e194536076..bf1a845247 100644 --- a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java +++ b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java @@ -43,16 +43,7 @@ public class ParserUtils { */ public static Metadata cloneMetadata(Metadata m) { Metadata clone = new Metadata(); - - for (String n : m.names()) { - if (!m.isMultiValued(n)) { - clone.reconstruct(n, m.get(n), false); - } else { - for (String val : m.getValues(n)) { - clone.reconstruct(n, val, true); - } - } - } + clone.putAll(m); return clone; } diff --git a/tika-core/src/test/java/org/apache/tika/metadata/MetadataPutAllTest.java b/tika-core/src/test/java/org/apache/tika/metadata/MetadataPutAllTest.java new file mode 100644 index 0000000000..003eda4585 --- /dev/null +++ b/tika-core/src/test/java/org/apache/tika/metadata/MetadataPutAllTest.java @@ -0,0 +1,98 @@ +/* + * 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.tika.metadata; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** {@link Metadata#putAll(Metadata)}: the provenance-preserving per-key-replace copy API. */ +public class MetadataPutAllTest { + + @Test + public void testMultiValuedKeyCopiesAllValuesInOrder() { + Metadata src = new Metadata(); + src.add("multi", "a"); + src.add("multi", "b"); + src.add("multi", "c"); + + Metadata dest = new Metadata(); + dest.putAll(src); + + // the manual `dest.set(n, src.get(n))` loop this replaces would collapse to just "a" + assertArrayEquals(new String[] {"a", "b", "c"}, dest.getValues("multi")); + } + + @Test + public void testReservedKeyCopiesThroughTrustedRoute() { + Metadata src = new Metadata(); + src.reconstruct(TikaCoreProperties.TIKA_CONTENT.getName(), "the content", false); + + Metadata dest = new Metadata(); + dest.putAll(src); + + assertEquals("the content", dest.get(TikaCoreProperties.TIKA_CONTENT)); + } + + @Test + public void testPerKeyReplaceSemantics() { + Metadata src = new Metadata(); + src.add("k", "only"); + + Metadata dest = new Metadata(); + dest.add("k", "v1"); + dest.add("k", "v2"); + dest.add("k", "v3"); + + dest.putAll(src); + + assertArrayEquals(new String[] {"only"}, dest.getValues("k")); + } + + @Test + public void testKeysAbsentFromOtherAreUntouched() { + Metadata src = new Metadata(); + src.add("inOther", "x"); + + Metadata dest = new Metadata(); + dest.add("onlyInDest", "keepme"); + + dest.putAll(src); + + assertEquals("keepme", dest.get("onlyInDest")); + assertEquals("x", dest.get("inOther")); + } + + @Test + public void testSelfPutAllIsNoOp() { + Metadata metadata = new Metadata(); + metadata.add("k", "v1"); + metadata.add("k", "v2"); + + metadata.putAll(metadata); + + assertArrayEquals(new String[] {"v1", "v2"}, metadata.getValues("k")); + } + + @Test + public void testNullOtherThrowsNPE() { + Metadata metadata = new Metadata(); + assertThrows(NullPointerException.class, () -> metadata.putAll(null)); + } +}
