| /* ==================================================================== |
| 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.poi.openxml4j.opc; |
| |
| import java.io.Serializable; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.function.ToIntFunction; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import com.zaxxer.sparsebits.SparseBitSet; |
| import org.apache.poi.openxml4j.exceptions.InvalidFormatException; |
| import org.apache.poi.openxml4j.exceptions.InvalidOperationException; |
| |
| /** |
| * A package part collection. |
| */ |
| public final class PackagePartCollection implements Serializable { |
| |
| private static final long serialVersionUID = 2515031135957635517L; |
| |
| /** |
| * HashSet use to store this collection part names as string for rule |
| * M1.11 optimized checking. |
| */ |
| private final Set<String> registerPartNameStr = new HashSet<>(); |
| |
| private final TreeMap<String, PackagePart> packagePartLookup = |
| new TreeMap<>(PackagePartName::compare); |
| |
| |
| /** |
| * Check rule [M1.11]: a package implementer shall neither create nor |
| * recognize a part with a part name derived from another part name by |
| * appending segments to it. |
| * |
| * @param partName name of part |
| * @param part part to put |
| * @return the previous value associated with {@code partName}, or |
| * {@code null} if there was no mapping for {@code partName}. |
| * @exception InvalidOperationException |
| * Throws if you try to add a part with a name derived from |
| * another part name. |
| */ |
| public PackagePart put(final PackagePartName partName, final PackagePart part) { |
| final String ppName = partName.getName(); |
| final StringBuilder concatSeg = new StringBuilder(); |
| // split at slash, but keep leading slash |
| final String delim = "(?=["+PackagingURIHelper.FORWARD_SLASH_STRING+".])"; |
| for (String seg : ppName.split(delim)) { |
| concatSeg.append(seg); |
| if (registerPartNameStr.contains(concatSeg.toString())) { |
| throw new InvalidOperationException( |
| "You can't add a part with a part name derived from another part ! [M1.11]"); |
| } |
| } |
| registerPartNameStr.add(ppName); |
| return packagePartLookup.put(ppName, part); |
| } |
| |
| public PackagePart remove(PackagePartName key) { |
| if (key == null) { |
| return null; |
| } |
| final String ppName = key.getName(); |
| PackagePart pp = packagePartLookup.remove(ppName); |
| if (pp != null) { |
| this.registerPartNameStr.remove(ppName); |
| } |
| return pp; |
| } |
| |
| |
| /** |
| * The values themselves should be returned in sorted order. Doing it here |
| * avoids paying the high cost of Natural Ordering per insertion. |
| * @return unmodifiable collection of parts |
| */ |
| public Collection<PackagePart> sortedValues() { |
| return Collections.unmodifiableCollection(packagePartLookup.values()); |
| |
| } |
| |
| public boolean containsKey(PackagePartName partName) { |
| return partName != null && packagePartLookup.containsKey(partName.getName()); |
| } |
| |
| public PackagePart get(PackagePartName partName) { |
| return partName == null ? null : packagePartLookup.get(partName.getName()); |
| } |
| |
| public int size() { |
| return packagePartLookup.size(); |
| } |
| |
| |
| |
| /** |
| * Get an unused part index based on the namePattern, which doesn't exist yet |
| * and has the lowest positive index |
| * |
| * @param nameTemplate |
| * The template for new part names containing a {@code '#'} for the index, |
| * e.g. "/ppt/slides/slide#.xml" |
| * @return the next available part name index |
| * @throws InvalidFormatException if the nameTemplate is null or doesn't contain |
| * the index char (#) or results in an invalid part name |
| */ |
| public int getUnusedPartIndex(final String nameTemplate) throws InvalidFormatException { |
| if (nameTemplate == null || !nameTemplate.contains("#")) { |
| throw new InvalidFormatException("name template must not be null and contain an index char (#)"); |
| } |
| |
| final Pattern pattern = Pattern.compile(nameTemplate.replace("#", "([0-9]+)")); |
| |
| final ToIntFunction<String> indexFromName = name -> { |
| Matcher m = pattern.matcher(name); |
| return m.matches() ? Integer.parseInt(m.group(1)) : 0; |
| }; |
| |
| return packagePartLookup.keySet().stream() |
| .mapToInt(indexFromName) |
| .collect(SparseBitSet::new, SparseBitSet::set, (s1,s2) -> s1.or(s2)).nextClearBit(1); |
| } |
| } |