Skip to content

Commit ce34880

Browse files
committed
Sort members
1 parent 7810325 commit ce34880

2 files changed

Lines changed: 343 additions & 343 deletions

File tree

src/main/java/org/apache/commons/io/FileSystem.java

Lines changed: 170 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,136 @@ public enum FileSystem {
113113
}, true, true, '\\', NameLengthStrategy.UTF16_CODE_UNITS);
114114
// @formatter:on
115115

116+
/**
117+
* Strategy for measuring and truncating file or path names in different units.
118+
* Implementations measure length and can truncate to a specified limit.
119+
*/
120+
enum NameLengthStrategy {
121+
/** Length measured as encoded bytes. */
122+
BYTES {
123+
@Override
124+
int getLength(final CharSequence value, final Charset charset) {
125+
final CharsetEncoder enc = charset.newEncoder()
126+
.onMalformedInput(CodingErrorAction.REPORT)
127+
.onUnmappableCharacter(CodingErrorAction.REPORT);
128+
try {
129+
return enc.encode(CharBuffer.wrap(value)).remaining();
130+
} catch (CharacterCodingException e) {
131+
// Unencodable, does not fit any byte limit.
132+
return Integer.MAX_VALUE;
133+
}
134+
}
135+
136+
@Override
137+
CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
138+
final CharsetEncoder encoder = charset.newEncoder()
139+
.onMalformedInput(CodingErrorAction.REPORT)
140+
.onUnmappableCharacter(CodingErrorAction.REPORT);
141+
142+
if (!encoder.canEncode(value)) {
143+
throw new IllegalArgumentException(
144+
"The value " + value + " cannot be encoded using " + charset.name());
145+
}
146+
147+
// Fast path: if even the worst-case expansion fits, we're done.
148+
if (value.length() <= Math.floor(limit / encoder.maxBytesPerChar())) {
149+
return value;
150+
}
151+
152+
// Slow path: encode into a fixed-size byte buffer.
153+
// 1. Compute length of extension in bytes (if any).
154+
final CharSequence[] parts = splitExtension(value);
155+
final int extensionLength = getLength(parts[1], charset);
156+
if (extensionLength > 0 && extensionLength >= limit) {
157+
// Extension itself does not fit
158+
throw new IllegalArgumentException(
159+
"The extension of " + value + " is too long to fit within " + limit + " bytes");
160+
}
161+
162+
// 2. Compute the character part that fits within the remaining byte budget.
163+
final ByteBuffer byteBuffer = ByteBuffer.allocate(limit - extensionLength);
164+
final CharBuffer charBuffer = CharBuffer.wrap(parts[0]);
165+
166+
// Encode until the first character that would exceed the byte budget.
167+
final CoderResult cr = encoder.encode(charBuffer, byteBuffer, true);
168+
169+
if (cr.isUnderflow()) {
170+
// Entire candidate fit within maxFileNameLength bytes.
171+
return value;
172+
}
173+
174+
final CharSequence truncated = safeTruncate(value, charBuffer.position());
175+
return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
176+
}
177+
},
178+
179+
/** Length measured as UTF-16 code units (i.e., {@code CharSequence.length()}). */
180+
UTF16_CODE_UNITS {
181+
@Override
182+
int getLength(final CharSequence value, final Charset charset) {
183+
return value.length();
184+
}
185+
186+
@Override
187+
CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
188+
if (!UTF_16.newEncoder().canEncode(value)) {
189+
throw new IllegalArgumentException(
190+
"The value " + value + " can not be encoded using " + UTF_16.name());
191+
}
192+
193+
// Fast path: no truncation needed.
194+
if (value.length() <= limit) {
195+
return value;
196+
}
197+
198+
// Slow path: truncate to limit.
199+
// 1. Compute length of extension in chars (if any).
200+
final CharSequence[] parts = splitExtension(value);
201+
final int extensionLength = parts[1].length();
202+
if (extensionLength > 0 && extensionLength >= limit) {
203+
// Extension itself does not fit
204+
throw new IllegalArgumentException(
205+
"The extension of " + value + " is too long to fit within " + limit + " characters");
206+
}
207+
208+
// 2. Truncate the non-extension part and append the extension (if any).
209+
final CharSequence truncated = safeTruncate(value, limit - extensionLength);
210+
return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
211+
}
212+
};
213+
214+
/**
215+
* Gets the measured length in this strategy’s unit.
216+
*
217+
* @param value The value to measure, not null.
218+
* @param charset The charset to use when measuring in bytes.
219+
* @return The length in this strategy’s unit.
220+
*/
221+
abstract int getLength(CharSequence value, Charset charset);
222+
223+
/**
224+
* Tests if the measured length is less or equal the {@code limit}.
225+
*
226+
* @param value The value to measure, not null.
227+
* @param limit The limit to compare to.
228+
* @param charset The charset to use when measuring in bytes.
229+
* @return {@code true} if the measured length is less or equal the {@code limit}, {@code false} otherwise.
230+
*/
231+
final boolean isWithinLimit(final CharSequence value, final int limit, final Charset charset) {
232+
return getLength(value, charset) <= limit;
233+
}
234+
235+
/**
236+
* Truncates to {@code limit} in this strategy’s unit (no-op if already within limit).
237+
*
238+
* @param value The value to truncate, not null.
239+
* @param limit The limit to truncate to.
240+
* @param charset The charset to use when measuring in bytes.
241+
* @return The truncated value, not null.
242+
*/
243+
abstract CharSequence truncate(CharSequence value, int limit, Charset charset);
244+
}
245+
116246
/**
117247
* <p>
118248
* Is {@code true} if this is Linux.
@@ -260,7 +390,42 @@ private static boolean isOsNameMatch(final String osName, final String osNamePre
260390
private static String replace(final String path, final char oldChar, final char newChar) {
261391
return path == null ? null : path.replace(oldChar, newChar);
262392
}
263-
393+
/**
394+
* Truncates a string respecting grapheme cluster boundaries.
395+
*
396+
* @param value The value to truncate.
397+
* @param limit The maximum length.
398+
* @return The truncated value.
399+
* @throws IllegalArgumentException If the first grapheme cluster is longer than the limit.
400+
*/
401+
private static CharSequence safeTruncate(final CharSequence value, final int limit) {
402+
if (value.length() <= limit) {
403+
return value;
404+
}
405+
final BreakIterator boundary = BreakIterator.getCharacterInstance(Locale.ROOT);
406+
final String text = value.toString();
407+
boundary.setText(text);
408+
final int end = boundary.preceding(limit + 1);
409+
assert end != BreakIterator.DONE;
410+
if (end == 0) {
411+
final String limitMessage = limit <= 1 ? "1 character" : limit + " characters";
412+
throw new IllegalArgumentException("The value " + value + " can not be truncated to " + limitMessage
413+
+ " without breaking the first codepoint or grapheme cluster");
414+
}
415+
return text.substring(0, end);
416+
}
417+
static CharSequence[] splitExtension(final CharSequence value) {
418+
final int index = indexOfFirstDot(value);
419+
// An initial dot is not an extension
420+
return index < 1
421+
? new CharSequence[] {value, ""}
422+
: new CharSequence[] {value.subSequence(0, index), value.subSequence(index, value.length())};
423+
}
424+
static CharSequence trimExtension(final CharSequence cs) {
425+
final int index = indexOfFirstDot(cs);
426+
// An initial dot is not an extension
427+
return index < 1 ? cs : cs.subSequence(0, index);
428+
}
264429
private final int blockSize;
265430
private final boolean casePreserving;
266431
private final boolean caseSensitive;
@@ -269,9 +434,13 @@ private static String replace(final String path, final char oldChar, final char
269434
private final int maxPathLength;
270435
private final String[] reservedFileNames;
271436
private final boolean reservedFileNamesExtensions;
437+
272438
private final boolean supportsDriveLetter;
439+
273440
private final char nameSeparator;
441+
274442
private final char nameSeparatorOther;
443+
275444
private final NameLengthStrategy nameLengthStrategy;
276445

277446
/**
@@ -571,173 +740,4 @@ public String toLegalFileName(final String candidate, final char replacement, fi
571740
return new String(array, 0, array.length);
572741
}
573742

574-
static CharSequence trimExtension(final CharSequence cs) {
575-
final int index = indexOfFirstDot(cs);
576-
// An initial dot is not an extension
577-
return index < 1 ? cs : cs.subSequence(0, index);
578-
}
579-
580-
static CharSequence[] splitExtension(final CharSequence value) {
581-
final int index = indexOfFirstDot(value);
582-
// An initial dot is not an extension
583-
return index < 1
584-
? new CharSequence[] {value, ""}
585-
: new CharSequence[] {value.subSequence(0, index), value.subSequence(index, value.length())};
586-
}
587-
588-
/**
589-
* Truncates a string respecting grapheme cluster boundaries.
590-
*
591-
* @param value The value to truncate.
592-
* @param limit The maximum length.
593-
* @return The truncated value.
594-
* @throws IllegalArgumentException If the first grapheme cluster is longer than the limit.
595-
*/
596-
private static CharSequence safeTruncate(final CharSequence value, final int limit) {
597-
if (value.length() <= limit) {
598-
return value;
599-
}
600-
final BreakIterator boundary = BreakIterator.getCharacterInstance(Locale.ROOT);
601-
final String text = value.toString();
602-
boundary.setText(text);
603-
final int end = boundary.preceding(limit + 1);
604-
assert end != BreakIterator.DONE;
605-
if (end == 0) {
606-
final String limitMessage = limit <= 1 ? "1 character" : limit + " characters";
607-
throw new IllegalArgumentException("The value " + value + " can not be truncated to " + limitMessage
608-
+ " without breaking the first codepoint or grapheme cluster");
609-
}
610-
return text.substring(0, end);
611-
}
612-
613-
/**
614-
* Strategy for measuring and truncating file or path names in different units.
615-
* Implementations measure length and can truncate to a specified limit.
616-
*/
617-
enum NameLengthStrategy {
618-
/** Length measured as encoded bytes. */
619-
BYTES {
620-
@Override
621-
int getLength(final CharSequence value, final Charset charset) {
622-
final CharsetEncoder enc = charset.newEncoder()
623-
.onMalformedInput(CodingErrorAction.REPORT)
624-
.onUnmappableCharacter(CodingErrorAction.REPORT);
625-
try {
626-
return enc.encode(CharBuffer.wrap(value)).remaining();
627-
} catch (CharacterCodingException e) {
628-
// Unencodable, does not fit any byte limit.
629-
return Integer.MAX_VALUE;
630-
}
631-
}
632-
633-
@Override
634-
CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
635-
final CharsetEncoder encoder = charset.newEncoder()
636-
.onMalformedInput(CodingErrorAction.REPORT)
637-
.onUnmappableCharacter(CodingErrorAction.REPORT);
638-
639-
if (!encoder.canEncode(value)) {
640-
throw new IllegalArgumentException(
641-
"The value " + value + " cannot be encoded using " + charset.name());
642-
}
643-
644-
// Fast path: if even the worst-case expansion fits, we're done.
645-
if (value.length() <= Math.floor(limit / encoder.maxBytesPerChar())) {
646-
return value;
647-
}
648-
649-
// Slow path: encode into a fixed-size byte buffer.
650-
// 1. Compute length of extension in bytes (if any).
651-
final CharSequence[] parts = splitExtension(value);
652-
final int extensionLength = getLength(parts[1], charset);
653-
if (extensionLength > 0 && extensionLength >= limit) {
654-
// Extension itself does not fit
655-
throw new IllegalArgumentException(
656-
"The extension of " + value + " is too long to fit within " + limit + " bytes");
657-
}
658-
659-
// 2. Compute the character part that fits within the remaining byte budget.
660-
final ByteBuffer byteBuffer = ByteBuffer.allocate(limit - extensionLength);
661-
final CharBuffer charBuffer = CharBuffer.wrap(parts[0]);
662-
663-
// Encode until the first character that would exceed the byte budget.
664-
final CoderResult cr = encoder.encode(charBuffer, byteBuffer, true);
665-
666-
if (cr.isUnderflow()) {
667-
// Entire candidate fit within maxFileNameLength bytes.
668-
return value;
669-
}
670-
671-
final CharSequence truncated = safeTruncate(value, charBuffer.position());
672-
return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
673-
}
674-
},
675-
676-
/** Length measured as UTF-16 code units (i.e., {@code CharSequence.length()}). */
677-
UTF16_CODE_UNITS {
678-
@Override
679-
int getLength(final CharSequence value, final Charset charset) {
680-
return value.length();
681-
}
682-
683-
@Override
684-
CharSequence truncate(final CharSequence value, final int limit, final Charset charset) {
685-
if (!UTF_16.newEncoder().canEncode(value)) {
686-
throw new IllegalArgumentException(
687-
"The value " + value + " can not be encoded using " + UTF_16.name());
688-
}
689-
690-
// Fast path: no truncation needed.
691-
if (value.length() <= limit) {
692-
return value;
693-
}
694-
695-
// Slow path: truncate to limit.
696-
// 1. Compute length of extension in chars (if any).
697-
final CharSequence[] parts = splitExtension(value);
698-
final int extensionLength = parts[1].length();
699-
if (extensionLength > 0 && extensionLength >= limit) {
700-
// Extension itself does not fit
701-
throw new IllegalArgumentException(
702-
"The extension of " + value + " is too long to fit within " + limit + " characters");
703-
}
704-
705-
// 2. Truncate the non-extension part and append the extension (if any).
706-
final CharSequence truncated = safeTruncate(value, limit - extensionLength);
707-
return extensionLength == 0 ? truncated : truncated.toString() + parts[1];
708-
}
709-
};
710-
711-
/**
712-
* Gets the measured length in this strategy’s unit.
713-
*
714-
* @param value The value to measure, not null.
715-
* @param charset The charset to use when measuring in bytes.
716-
* @return The length in this strategy’s unit.
717-
*/
718-
abstract int getLength(CharSequence value, Charset charset);
719-
720-
/**
721-
* Tests if the measured length is less or equal the {@code limit}.
722-
*
723-
* @param value The value to measure, not null.
724-
* @param limit The limit to compare to.
725-
* @param charset The charset to use when measuring in bytes.
726-
* @return {@code true} if the measured length is less or equal the {@code limit}, {@code false} otherwise.
727-
*/
728-
final boolean isWithinLimit(final CharSequence value, final int limit, final Charset charset) {
729-
return getLength(value, charset) <= limit;
730-
}
731-
732-
/**
733-
* Truncates to {@code limit} in this strategy’s unit (no-op if already within limit).
734-
*
735-
* @param value The value to truncate, not null.
736-
* @param limit The limit to truncate to.
737-
* @param charset The charset to use when measuring in bytes.
738-
* @return The truncated value, not null.
739-
*/
740-
abstract CharSequence truncate(CharSequence value, int limit, Charset charset);
741-
}
742-
743743
}

0 commit comments

Comments
 (0)