NameFormatter.java
package space.sunqian.common.base.string;
import space.sunqian.annotations.Nonnull;
import space.sunqian.common.base.value.Span;
import java.util.Objects;
/**
* Name formatter, used to parse or format a given name. A name formatter represents a formatting scheme, such as
* {@code Camel Case}, {@code Snake Case}, {@code File Naming}, etc.
*
* @author sunqian
*/
public interface NameFormatter {
/**
* Returns a new {@link NameFormatter} for lower camel case (e.g. {@code someName}). The returned instance applies
* the lower camel case to parse letters in {@code a-z} and {@code A-Z}, but treats digits ({@code 0-9}) and other
* characters as separate words.
*
* @return a new {@link NameFormatter} for lower camel case (e.g. {@code someName})
*/
static @Nonnull NameFormatter lowerCamel() {
return NameFormatterBack.camelCase(false);
}
/**
* Returns a new {@link NameFormatter} for upper camel case (e.g. {@code SomeName}), also called pascal case. The
* returned instance applies the upper camel case to parse letters in {@code a-z} and {@code A-Z}, but treats digits
* ({@code 0-9}) and other characters as separate words.
*
* @return a new {@link NameFormatter} for upper camel case (e.g. {@code SomeName})
*/
static @Nonnull NameFormatter upperCamel() {
return NameFormatterBack.camelCase(true);
}
/**
* Returns a new {@link NameFormatter} base on the specified delimiter (e.g. {@code some-name}, {@code some_name}).
*
* @param delimiter the specified delimiter
* @return a new {@link NameFormatter} base on the specified delimiter (e.g. {@code some-name}, {@code some_name})
* @throws IllegalArgumentException if the delimiter is empty
*/
static @Nonnull NameFormatter delimiterCase(
@Nonnull CharSequence delimiter
) throws IllegalArgumentException {
return delimiterCase(delimiter, simpleAppender());
}
/**
* Returns a new {@link NameFormatter} base on the specified delimiter (e.g. {@code some-name}, {@code some_name}).
*
* @param delimiter the specified delimiter, can not be empty
* @param appender the appender used to append each word into the destination appendable object
* @return a new {@link NameFormatter} base on the specified delimiter (e.g. {@code some-name}, {@code some_name})
* @throws IllegalArgumentException if the delimiter is empty
*/
static @Nonnull NameFormatter delimiterCase(
@Nonnull CharSequence delimiter, @Nonnull NameFormatter.Appender appender
) throws IllegalArgumentException {
return NameFormatterBack.delimiterCase(delimiter, appender);
}
/**
* Returns a new {@link NameFormatter} used to separate file base name and file extension (e.g.
* {@code some-name.txt} split to {@code some-name} and {@code txt}).
*
* @return a new {@link NameFormatter} used to separate file base name and file extension (e.g.
* {@code some-name.txt} split to {@code some-name} and {@code txt})
*/
static @Nonnull NameFormatter fileNaming() {
return NameFormatterBack.fileNaming();
}
/**
* Returns an instance of {@link NameFormatter.Appender} which simply adds the word without any modifications.
*
* @return an instance of {@link NameFormatter.Appender} which simply adds the word without any modifications
*/
static @Nonnull NameFormatter.Appender simpleAppender() {
return NameFormatterBack.simpleAppender();
}
/**
* Parses the given name, splits it into an array of words by scheme of this formatter.
*
* @param name the given name
* @return an array of words split from the given name
* @throws NameFormatException if failed to parse the given name
*/
default @Nonnull String @Nonnull [] parse(@Nonnull CharSequence name) throws NameFormatException {
Span[] spans = tokenize(name);
if (spans.length <= 1) {
return new String[]{name.toString()};
}
String[] ret = new String[spans.length];
for (int i = 0; i < spans.length; i++) {
ret[i] = name.subSequence(spans[i].startIndex(), spans[i].endIndex()).toString();
}
return ret;
}
/**
* Parses the given name, splits it into an array of words by scheme of this formatter. Returns an array of
* {@link Span}s define the range of each word within the given name.
*
* @param name the given name
* @return an array of {@link Span}s define the range of each word within the given name
* @throws NameFormatException if failed to parse the given name
*/
@Nonnull
Span @Nonnull [] tokenize(@Nonnull CharSequence name) throws NameFormatException;
/**
* Joins the given words into a name by rules of this name formatter.
*
* @param words the given words
* @return a name joined by the given words by rules of this name formatter
* @throws NameFormatException if failed to join the words
*/
default @Nonnull String format(@Nonnull CharSequence @Nonnull ... words) throws NameFormatException {
StringBuilder sb = new StringBuilder();
format(words, sb);
return sb.toString();
}
/**
* Joins the given words into a name by rules of this name formatter. The joined name will be appended to the
* specified destination appendable.
*
* @param words the given words
* @param dst the specified destination appendable
* @throws NameFormatException if failed to join the words
*/
void format(
@Nonnull CharSequence @Nonnull [] words,
@Nonnull Appendable dst
) throws NameFormatException;
/**
* Joins the words into a name by rules of this name formatter. The words are specified by the array of {@link Span}
* that define the range of each word within the given original name.
*
* @param originalName the given original name where the word spans are derived
* @param wordSpans the array of {@link Span} that define the range of each word within the given original name
* @return a name joined from the words by rules of this name formatter
* @throws NameFormatException if failed to join the words
*/
default @Nonnull String format(
@Nonnull CharSequence originalName, @Nonnull Span @Nonnull [] wordSpans
) throws NameFormatException {
StringBuilder sb = new StringBuilder(originalName.length());
format(originalName, wordSpans, sb);
return sb.toString();
}
/**
* Joins the words into a name by rules of this name formatter. The words are specified by the array of {@link Span}
* that define the range of each word within the given original name. The joined name will be appended to the
* specified destination appendable.
*
* @param originalName the given original name where the word spans are derived
* @param wordSpans the array of {@link Span} that define the range of each word within the given original name
* @param dst the specified destination appendable
* @throws NameFormatException if failed to join the words
*/
void format(
@Nonnull CharSequence originalName,
@Nonnull Span @Nonnull [] wordSpans,
@Nonnull Appendable dst
) throws NameFormatException;
/**
* Converts the given name from this name formatter to the other specified name formatter. This method is equivalent
* to: {@code otherFormatter.format(name, tokenize(name))}.
*
* @param name the given name
* @param otherFormatter the other specified name formatter
* @return the converted name
* @throws NameFormatException if the conversion is not supported for the given name
*/
default @Nonnull String format(
@Nonnull CharSequence name, @Nonnull NameFormatter otherFormatter
) throws NameFormatException {
if (Objects.equals(this, otherFormatter)) {
return name.toString();
}
return otherFormatter.format(name, tokenize(name));
}
/**
* Appender for appending each word split by {@link #tokenize(CharSequence)} into the specified {@link Appendable}.
*/
interface Appender {
/**
* Appends the word into the specified {@link Appendable}, the word is specified by the given span that define
* the range of the word within the given original name.
*
* @param dst the specified {@link Appendable}
* @param originalName the given original name where the word span is derived
* @param span the span that define the range of the word within the given original name
* @param index the index of the word in the returned list of {@link #tokenize(CharSequence)}
* @throws Exception if failed to append
*/
void append(
@Nonnull Appendable dst,
@Nonnull CharSequence originalName,
@Nonnull Span span,
int index
) throws Exception;
}
}