#61942 - Refactor PackagePartName handling and add getUnusedPartIndex method
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1819708 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
2dc2933f9a
commit
2a18d2d4db
@ -571,7 +571,7 @@ public class POIXMLDocumentPart {
|
||||
* equivalent part names and package implementers shall neither
|
||||
* create nor recognize packages with equivalent part names.
|
||||
*/
|
||||
protected final RelationPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx, boolean noRelation){
|
||||
public final RelationPart createRelationship(POIXMLRelation descriptor, POIXMLFactory factory, int idx, boolean noRelation){
|
||||
try {
|
||||
PackagePartName ppName = PackagingURIHelper.createPartName(descriptor.getFileName(idx));
|
||||
PackageRelationship rel = null;
|
||||
|
@ -1670,4 +1670,19 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
|
||||
this.isDirty = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
return partList.getUnusedPartIndex(nameTemplate);
|
||||
}
|
||||
}
|
||||
|
@ -18,15 +18,21 @@
|
||||
package org.apache.poi.openxml4j.opc;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.BitSet;
|
||||
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 org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||
import org.apache.poi.openxml4j.exceptions.InvalidOperationException;
|
||||
|
||||
/**
|
||||
* A package part collection.
|
||||
*
|
||||
* @author Julien Chable
|
||||
* @version 0.1
|
||||
*/
|
||||
public final class PackagePartCollection implements Serializable {
|
||||
|
||||
@ -36,10 +42,10 @@ public final class PackagePartCollection implements Serializable {
|
||||
* HashSet use to store this collection part names as string for rule
|
||||
* M1.11 optimized checking.
|
||||
*/
|
||||
private HashSet<String> registerPartNameStr = new HashSet<>();
|
||||
private final Set<String> registerPartNameStr = new HashSet<>();
|
||||
|
||||
|
||||
private final HashMap<PackagePartName, PackagePart> packagePartLookup = new HashMap<>();
|
||||
private final TreeMap<String, PackagePart> packagePartLookup =
|
||||
new TreeMap<>(PackagePartName::compare);
|
||||
|
||||
|
||||
/**
|
||||
@ -51,26 +57,32 @@ public final class PackagePartCollection implements Serializable {
|
||||
* Throws if you try to add a part with a name derived from
|
||||
* another part name.
|
||||
*/
|
||||
public PackagePart put(PackagePartName partName, PackagePart part) {
|
||||
String[] segments = partName.getURI().toASCIIString().split(
|
||||
PackagingURIHelper.FORWARD_SLASH_STRING);
|
||||
StringBuilder concatSeg = new StringBuilder();
|
||||
for (String seg : segments) {
|
||||
if (!seg.isEmpty())
|
||||
concatSeg.append(PackagingURIHelper.FORWARD_SLASH_CHAR);
|
||||
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 (this.registerPartNameStr.contains(concatSeg.toString())) {
|
||||
if (registerPartNameStr.contains(concatSeg.toString())) {
|
||||
throw new InvalidOperationException(
|
||||
"You can't add a part with a part name derived from another part ! [M1.11]");
|
||||
}
|
||||
}
|
||||
this.registerPartNameStr.add(partName.getName());
|
||||
return packagePartLookup.put(partName, part);
|
||||
registerPartNameStr.add(ppName);
|
||||
return packagePartLookup.put(ppName, part);
|
||||
}
|
||||
|
||||
public PackagePart remove(PackagePartName key) {
|
||||
this.registerPartNameStr.remove(key.getName());
|
||||
return packagePartLookup.remove(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;
|
||||
}
|
||||
|
||||
|
||||
@ -79,21 +91,49 @@ public final class PackagePartCollection implements Serializable {
|
||||
* avoids paying the high cost of Natural Ordering per insertion.
|
||||
*/
|
||||
public Collection<PackagePart> sortedValues() {
|
||||
ArrayList<PackagePart> packageParts = new ArrayList<>(packagePartLookup.values());
|
||||
Collections.sort(packageParts);
|
||||
return packageParts;
|
||||
return Collections.unmodifiableCollection(packagePartLookup.values());
|
||||
|
||||
}
|
||||
|
||||
public boolean containsKey(PackagePartName partName) {
|
||||
return packagePartLookup.containsKey(partName);
|
||||
return partName != null && packagePartLookup.containsKey(partName.getName());
|
||||
}
|
||||
|
||||
public PackagePart get(PackagePartName partName) {
|
||||
return packagePartLookup.get(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(BitSet::new, BitSet::set, BitSet::or).nextClearBit(1);
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,6 @@ import org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException;
|
||||
/**
|
||||
* An immutable Open Packaging Convention compliant part name.
|
||||
*
|
||||
* @author Julien Chable
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">http://www.ietf.org/rfc/rfc3986.txt</a>
|
||||
*/
|
||||
public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
@ -37,32 +35,31 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
/**
|
||||
* Part name stored as an URI.
|
||||
*/
|
||||
private URI partNameURI;
|
||||
private final URI partNameURI;
|
||||
|
||||
/*
|
||||
* URI Characters definition (RFC 3986)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reserved characters for sub delimitations.
|
||||
* Reserved characters for sub delimiters.
|
||||
*/
|
||||
private static String[] RFC3986_PCHAR_SUB_DELIMS = { "!", "$", "&", "'",
|
||||
"(", ")", "*", "+", ",", ";", "=" };
|
||||
private static final String RFC3986_PCHAR_SUB_DELIMS = "!$&'()*+,;=";
|
||||
|
||||
/**
|
||||
* Unreserved character (+ ALPHA & DIGIT).
|
||||
*/
|
||||
private static String[] RFC3986_PCHAR_UNRESERVED_SUP = { "-", ".", "_", "~" };
|
||||
private static final String RFC3986_PCHAR_UNRESERVED_SUP = "-._~";
|
||||
|
||||
/**
|
||||
* Authorized reserved characters for pChar.
|
||||
*/
|
||||
private static String[] RFC3986_PCHAR_AUTHORIZED_SUP = { ":", "@" };
|
||||
private static final String RFC3986_PCHAR_AUTHORIZED_SUP = ":@";
|
||||
|
||||
/**
|
||||
* Flag to know if this part name is from a relationship part name.
|
||||
*/
|
||||
private boolean isRelationship;
|
||||
private final boolean isRelationship;
|
||||
|
||||
/**
|
||||
* Constructor. Makes a ValidPartName object from a java.net.URI
|
||||
@ -70,7 +67,7 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* @param uri
|
||||
* The URI to validate and to transform into ValidPartName.
|
||||
* @param checkConformance
|
||||
* Flag to specify if the contructor have to validate the OPC
|
||||
* Flag to specify if the constructor have to validate the OPC
|
||||
* conformance. Must be always <code>true</code> except for
|
||||
* special URI like '/' which is needed for internal use by
|
||||
* OpenXML4J but is not valid.
|
||||
@ -99,7 +96,7 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* @param partName
|
||||
* Part name to valid and to create.
|
||||
* @param checkConformance
|
||||
* Flag to specify if the contructor have to validate the OPC
|
||||
* Flag to specify if the constructor have to validate the OPC
|
||||
* conformance. Must be always <code>true</code> except for
|
||||
* special URI like '/' which is needed for internal use by
|
||||
* OpenXML4J but is not valid.
|
||||
@ -138,8 +135,9 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* part naming convention else <code>false</code>.
|
||||
*/
|
||||
private boolean isRelationshipPartURI(URI partUri) {
|
||||
if (partUri == null)
|
||||
if (partUri == null) {
|
||||
throw new IllegalArgumentException("partUri");
|
||||
}
|
||||
|
||||
return partUri.getPath().matches(
|
||||
"^.*/" + PackagingURIHelper.RELATIONSHIP_PART_SEGMENT_NAME + "/.*\\"
|
||||
@ -168,8 +166,9 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
*/
|
||||
private static void throwExceptionIfInvalidPartUri(URI partUri)
|
||||
throws InvalidFormatException {
|
||||
if (partUri == null)
|
||||
if (partUri == null) {
|
||||
throw new IllegalArgumentException("partUri");
|
||||
}
|
||||
// Check if the part name URI is empty [M1.1]
|
||||
throwExceptionIfEmptyURI(partUri);
|
||||
|
||||
@ -197,16 +196,18 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
*/
|
||||
private static void throwExceptionIfEmptyURI(URI partURI)
|
||||
throws InvalidFormatException {
|
||||
if (partURI == null)
|
||||
if (partURI == null) {
|
||||
throw new IllegalArgumentException("partURI");
|
||||
}
|
||||
|
||||
String uriPath = partURI.getPath();
|
||||
if (uriPath.length() == 0
|
||||
|| ((uriPath.length() == 1) && (uriPath.charAt(0) == PackagingURIHelper.FORWARD_SLASH_CHAR)))
|
||||
|| ((uriPath.length() == 1) && (uriPath.charAt(0) == PackagingURIHelper.FORWARD_SLASH_CHAR))) {
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall not be empty [M1.1]: "
|
||||
+ partURI.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the part name has empty segments. [M1.3]
|
||||
@ -240,32 +241,31 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
}
|
||||
|
||||
// Split the URI into several part and analyze each
|
||||
String[] segments = partUri.toASCIIString().split("/");
|
||||
if (segments.length <= 1 || !segments[0].isEmpty())
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall not have empty segments [M1.3]: "
|
||||
+ partUri.getPath());
|
||||
String[] segments = partUri.toASCIIString()
|
||||
.replaceFirst("^"+PackagingURIHelper.FORWARD_SLASH_CHAR,"")
|
||||
.split(PackagingURIHelper.FORWARD_SLASH_STRING);
|
||||
|
||||
for (int i = 1; i < segments.length; ++i) {
|
||||
String seg = segments[i];
|
||||
if (segments.length < 1) {
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall not have empty segments [M1.3]: " + partUri.getPath());
|
||||
}
|
||||
|
||||
for (final String seg : segments) {
|
||||
if (seg == null || seg.isEmpty()) {
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall not have empty segments [M1.3]: "
|
||||
+ partUri.getPath());
|
||||
"A part name shall not have empty segments [M1.3]: " + partUri.getPath());
|
||||
}
|
||||
|
||||
if (seg.endsWith(".")) {
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall not end with a dot ('.') character [M1.9]: "
|
||||
+ partUri.getPath());
|
||||
"A segment shall not end with a dot ('.') character [M1.9]: " + partUri.getPath());
|
||||
}
|
||||
|
||||
if (seg.replaceAll("\\\\.", "").isEmpty()) {
|
||||
// Normally will never been invoked with the previous
|
||||
// implementation rule [M1.9]
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall include at least one non-dot character. [M1.10]: "
|
||||
+ partUri.getPath());
|
||||
"A segment shall include at least one non-dot character. [M1.10]: " + partUri.getPath());
|
||||
}
|
||||
|
||||
// Check for rule M1.6, M1.7, M1.8
|
||||
@ -288,90 +288,57 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
*/
|
||||
private static void checkPCharCompliance(String segment)
|
||||
throws InvalidFormatException {
|
||||
boolean errorFlag;
|
||||
final int length = segment.length();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
char c = segment.charAt(i);
|
||||
errorFlag = true;
|
||||
final char c = segment.charAt(i);
|
||||
|
||||
/* Check rule M1.6 */
|
||||
|
||||
if (
|
||||
// Check for digit or letter
|
||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|
||||
|| (c >= '0' && c <= '9')) {
|
||||
errorFlag = false;
|
||||
} else {
|
||||
isDigitOrLetter(c) ||
|
||||
// Check "-", ".", "_", "~"
|
||||
for (int j = 0; j < RFC3986_PCHAR_UNRESERVED_SUP.length; ++j) {
|
||||
if (c == RFC3986_PCHAR_UNRESERVED_SUP[j].charAt(0)) {
|
||||
errorFlag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RFC3986_PCHAR_UNRESERVED_SUP.indexOf(c) > -1 ||
|
||||
// Check ":", "@"
|
||||
for (int j = 0; errorFlag
|
||||
&& j < RFC3986_PCHAR_AUTHORIZED_SUP.length; ++j) {
|
||||
if (c == RFC3986_PCHAR_AUTHORIZED_SUP[j].charAt(0)) {
|
||||
errorFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
RFC3986_PCHAR_AUTHORIZED_SUP.indexOf(c) > -1 ||
|
||||
// Check "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
|
||||
for (int j = 0; errorFlag
|
||||
&& j < RFC3986_PCHAR_SUB_DELIMS.length; ++j) {
|
||||
if (c == RFC3986_PCHAR_SUB_DELIMS[j].charAt(0)) {
|
||||
errorFlag = false;
|
||||
}
|
||||
}
|
||||
RFC3986_PCHAR_SUB_DELIMS.indexOf(c) > -1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (c != '%') {
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall not hold any characters other than pchar characters. [M1.6]");
|
||||
}
|
||||
|
||||
if (errorFlag && c == '%') {
|
||||
// We certainly found an encoded character, check for length
|
||||
// now ( '%' HEXDIGIT HEXDIGIT)
|
||||
if (((length - i) < 2)) {
|
||||
throw new InvalidFormatException("The segment " + segment
|
||||
+ " contain invalid encoded character !");
|
||||
if ((length - i) < 2 || !isHexDigit(segment.charAt(i+1)) || !isHexDigit(segment.charAt(i+2))) {
|
||||
throw new InvalidFormatException("The segment " + segment + " contain invalid encoded character !");
|
||||
}
|
||||
|
||||
// If not percent encoded character error occur then reset the
|
||||
// flag -> the character is valid
|
||||
errorFlag = false;
|
||||
|
||||
// Decode the encoded character
|
||||
char decodedChar = (char) Integer.parseInt(segment.substring(
|
||||
i + 1, i + 3), 16);
|
||||
final char decodedChar = (char) Integer.parseInt(segment.substring(i + 1, i + 3), 16);
|
||||
i += 2;
|
||||
|
||||
/* Check rule M1.7 */
|
||||
if (decodedChar == '/' || decodedChar == '\\')
|
||||
if (decodedChar == '/' || decodedChar == '\\') {
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall not contain percent-encoded forward slash ('/'), or backward slash ('\') characters. [M1.7]");
|
||||
}
|
||||
|
||||
/* Check rule M1.8 */
|
||||
|
||||
if (
|
||||
// Check for unreserved character like define in RFC3986
|
||||
if ((decodedChar >= 'A' && decodedChar <= 'Z')
|
||||
|| (decodedChar >= 'a' && decodedChar <= 'z')
|
||||
|| (decodedChar >= '0' && decodedChar <= '9'))
|
||||
errorFlag = true;
|
||||
|
||||
isDigitOrLetter(decodedChar) ||
|
||||
// Check for unreserved character "-", ".", "_", "~"
|
||||
for (int j = 0; !errorFlag
|
||||
&& j < RFC3986_PCHAR_UNRESERVED_SUP.length; ++j) {
|
||||
if (c == RFC3986_PCHAR_UNRESERVED_SUP[j].charAt(0)) {
|
||||
errorFlag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (errorFlag)
|
||||
RFC3986_PCHAR_UNRESERVED_SUP.indexOf(decodedChar) > -1
|
||||
) {
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall not contain percent-encoded unreserved characters. [M1.8]");
|
||||
}
|
||||
|
||||
if (errorFlag)
|
||||
throw new InvalidFormatException(
|
||||
"A segment shall not hold any characters other than pchar characters. [M1.6]");
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,11 +356,12 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
URI partUri) throws InvalidFormatException {
|
||||
String uriPath = partUri.getPath();
|
||||
if (uriPath.length() > 0
|
||||
&& uriPath.charAt(0) != PackagingURIHelper.FORWARD_SLASH_CHAR)
|
||||
&& uriPath.charAt(0) != PackagingURIHelper.FORWARD_SLASH_CHAR) {
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall start with a forward slash ('/') character [M1.4]: "
|
||||
+ partUri.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the specified part name ends with a forwar slash
|
||||
@ -409,11 +377,12 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
URI partUri) throws InvalidFormatException {
|
||||
String uriPath = partUri.getPath();
|
||||
if (uriPath.length() > 0
|
||||
&& uriPath.charAt(uriPath.length() - 1) == PackagingURIHelper.FORWARD_SLASH_CHAR)
|
||||
&& uriPath.charAt(uriPath.length() - 1) == PackagingURIHelper.FORWARD_SLASH_CHAR) {
|
||||
throw new InvalidFormatException(
|
||||
"A part name shall not have a forward slash as the last character [M1.5]: "
|
||||
+ partUri.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the specified URI is absolute.
|
||||
@ -423,11 +392,10 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* @throws InvalidFormatException
|
||||
* Throws if the specified URI is absolute.
|
||||
*/
|
||||
private static void throwExceptionIfAbsoluteUri(URI partUri)
|
||||
throws InvalidFormatException {
|
||||
if (partUri.isAbsolute())
|
||||
throw new InvalidFormatException("Absolute URI forbidden: "
|
||||
+ partUri);
|
||||
private static void throwExceptionIfAbsoluteUri(URI partUri) throws InvalidFormatException {
|
||||
if (partUri.isAbsolute()) {
|
||||
throw new InvalidFormatException("Absolute URI forbidden: " + partUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -439,8 +407,7 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* packages with equivalent part names. [M1.12]
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(PackagePartName other)
|
||||
{
|
||||
public int compareTo(PackagePartName other) {
|
||||
// compare with natural sort order
|
||||
return compare(this, other);
|
||||
}
|
||||
@ -456,8 +423,9 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
String fragment = this.partNameURI.getPath();
|
||||
if (fragment.length() > 0) {
|
||||
int i = fragment.lastIndexOf(".");
|
||||
if (i > -1)
|
||||
if (i > -1) {
|
||||
return fragment.substring(i + 1);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -468,7 +436,7 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* @return The name of this part name.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.partNameURI.toASCIIString();
|
||||
return getURI().toASCIIString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -479,20 +447,13 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof PackagePartName) {
|
||||
// String.equals() is compatible with our compareTo(), but cheaper
|
||||
return this.partNameURI.toASCIIString().toLowerCase(Locale.ROOT).equals
|
||||
(
|
||||
((PackagePartName) other).partNameURI.toASCIIString().toLowerCase(Locale.ROOT)
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return (other instanceof PackagePartName) &&
|
||||
compare(this.getName(), ((PackagePartName)other).getName()) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.partNameURI.toASCIIString().toLowerCase(Locale.ROOT).hashCode();
|
||||
return getName().toLowerCase(Locale.ROOT).hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -529,24 +490,10 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* part names and package implementers shall neither create nor recognize
|
||||
* packages with equivalent part names. [M1.12]
|
||||
*/
|
||||
public static int compare(PackagePartName obj1, PackagePartName obj2)
|
||||
{
|
||||
// NOTE could also throw a NullPointerException() if desired
|
||||
if (obj1 == null)
|
||||
{
|
||||
// (null) == (null), (null) < (non-null)
|
||||
return (obj2 == null ? 0 : -1);
|
||||
}
|
||||
else if (obj2 == null)
|
||||
{
|
||||
// (non-null) > (null)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return compare
|
||||
(
|
||||
obj1.getURI().toASCIIString().toLowerCase(Locale.ROOT),
|
||||
obj2.getURI().toASCIIString().toLowerCase(Locale.ROOT)
|
||||
public static int compare(PackagePartName obj1, PackagePartName obj2) {
|
||||
return compare (
|
||||
obj1 == null ? null : obj1.getName(),
|
||||
obj2 == null ? null : obj2.getName()
|
||||
);
|
||||
}
|
||||
|
||||
@ -560,49 +507,48 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
* numerical portion), but sorts "File10.png" before "file2.png"
|
||||
* (lexigraphical sort)
|
||||
*/
|
||||
public static int compare(String str1, String str2)
|
||||
{
|
||||
if (str1 == null)
|
||||
public static int compare(final String str1, final String str2)
|
||||
{
|
||||
if (str1 == null) {
|
||||
// (null) == (null), (null) < (non-null)
|
||||
return (str2 == null ? 0 : -1);
|
||||
}
|
||||
else if (str2 == null)
|
||||
{
|
||||
} else if (str2 == null) {
|
||||
// (non-null) > (null)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int len1 = str1.length();
|
||||
int len2 = str2.length();
|
||||
for (int idx1 = 0, idx2 = 0; idx1 < len1 && idx2 < len2; /*nil*/)
|
||||
{
|
||||
char c1 = str1.charAt(idx1++);
|
||||
char c2 = str2.charAt(idx2++);
|
||||
if (str1.equalsIgnoreCase(str2)) {
|
||||
return 0;
|
||||
}
|
||||
final String name1 = str1.toLowerCase(Locale.ROOT);
|
||||
final String name2 = str2.toLowerCase(Locale.ROOT);
|
||||
|
||||
if (Character.isDigit(c1) && Character.isDigit(c2))
|
||||
{
|
||||
int beg1 = idx1 - 1; // undo previous increment
|
||||
while (idx1 < len1 && Character.isDigit(str1.charAt(idx1)))
|
||||
{
|
||||
++idx1;
|
||||
final int len1 = name1.length();
|
||||
final int len2 = name2.length();
|
||||
for (int idx1 = 0, idx2 = 0; idx1 < len1 && idx2 < len2; /*nil*/) {
|
||||
final char c1 = name1.charAt(idx1++);
|
||||
final char c2 = name2.charAt(idx2++);
|
||||
|
||||
if (Character.isDigit(c1) && Character.isDigit(c2)) {
|
||||
final int beg1 = idx1 - 1; // undo previous increment
|
||||
while (idx1 < len1 && Character.isDigit(name1.charAt(idx1))) {
|
||||
idx1++;
|
||||
}
|
||||
|
||||
int beg2 = idx2 - 1; // undo previous increment
|
||||
while (idx2 < len2 && Character.isDigit(str2.charAt(idx2)))
|
||||
{
|
||||
++idx2;
|
||||
final int beg2 = idx2 - 1; // undo previous increment
|
||||
while (idx2 < len2 && Character.isDigit(name2.charAt(idx2))) {
|
||||
idx2++;
|
||||
}
|
||||
|
||||
// note: BigInteger for extra safety
|
||||
int cmp = new BigInteger(str1.substring(beg1, idx1)).compareTo
|
||||
(
|
||||
new BigInteger(str2.substring(beg2, idx2))
|
||||
);
|
||||
if (cmp != 0) return cmp;
|
||||
final BigInteger b1 = new BigInteger(name1.substring(beg1, idx1));
|
||||
final BigInteger b2 = new BigInteger(name2.substring(beg2, idx2));
|
||||
final int cmp = b1.compareTo(b2);
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
else if (c1 != c2)
|
||||
{
|
||||
}
|
||||
else if (c1 != c2) {
|
||||
return (c1 - c2);
|
||||
}
|
||||
}
|
||||
@ -610,6 +556,11 @@ public final class PackagePartName implements Comparable<PackagePartName> {
|
||||
return (len1 - len2);
|
||||
}
|
||||
|
||||
private static boolean isDigitOrLetter(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
private static boolean isHexDigit(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user