Add tweed5-naming-format
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package de.siphalor.tweed5.namingformat.api;
|
||||
|
||||
public interface NamingFormat {
|
||||
String[] splitIntoWords(String name);
|
||||
String joinToName(String[] words);
|
||||
|
||||
String name();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package de.siphalor.tweed5.namingformat.api;
|
||||
|
||||
import de.siphalor.tweed5.namingformat.impl.NamingFormatImpls;
|
||||
|
||||
public class NamingFormats {
|
||||
public static String convert(String text, NamingFormat sourceFormat, NamingFormat targetFormat) {
|
||||
return targetFormat.joinToName(sourceFormat.splitIntoWords(text));
|
||||
}
|
||||
|
||||
public static NamingFormat camelCase() {
|
||||
return NamingFormatImpls.CAMEL_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat pascalCase() {
|
||||
return NamingFormatImpls.PASCAL_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat kebabCase() {
|
||||
return NamingFormatImpls.KEBAB_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat upperKebabCase() {
|
||||
return NamingFormatImpls.UPPER_KEBAB_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat snakeCase() {
|
||||
return NamingFormatImpls.SNAKE_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat upperSnakeCase() {
|
||||
return NamingFormatImpls.UPPER_SNAKE_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat spaceCase() {
|
||||
return NamingFormatImpls.SPACE_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat upperSpaceCase() {
|
||||
return NamingFormatImpls.UPPER_SPACE_CASE;
|
||||
}
|
||||
|
||||
public static NamingFormat titleCase() {
|
||||
return NamingFormatImpls.TITLE_CASE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package de.siphalor.tweed5.namingformat.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.PrimitiveIterator;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CodePointReader {
|
||||
int EMPTY_PEEK = Integer.MIN_VALUE;
|
||||
|
||||
private final PrimitiveIterator.OfInt codePointIterator;
|
||||
private int peek = EMPTY_PEEK;
|
||||
|
||||
public static CodePointReader ofString(CharSequence input) {
|
||||
return new CodePointReader(input.codePoints().iterator());
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
if (peek != EMPTY_PEEK) {
|
||||
return true;
|
||||
} else {
|
||||
return codePointIterator.hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
public int next() {
|
||||
if (peek != EMPTY_PEEK) {
|
||||
int codepoint = peek;
|
||||
peek = EMPTY_PEEK;
|
||||
return codepoint;
|
||||
} else {
|
||||
return codePointIterator.nextInt();
|
||||
}
|
||||
}
|
||||
|
||||
public int peek() {
|
||||
if (peek == EMPTY_PEEK) {
|
||||
peek = codePointIterator.nextInt();
|
||||
}
|
||||
return peek;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package de.siphalor.tweed5.namingformat.impl;
|
||||
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.PrimitiveIterator;
|
||||
|
||||
public class NamingFormatImpls {
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
public static final NamingFormat CAMEL_CASE = new NamingFormat() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "camelCase";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] splitIntoWords(String name) {
|
||||
return splitAtUpperCase(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String joinToName(String[] words) {
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int totalLength = countTotalWordsLength(words);
|
||||
StringBuilder stringBuilder = new StringBuilder(totalLength);
|
||||
appendAllLower(stringBuilder, words[0]);
|
||||
for (int i = 1; i < words.length; i++) {
|
||||
appendCapitalized(stringBuilder, words[i]);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
|
||||
public static final NamingFormat PASCAL_CASE = new NamingFormat() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "PascalCase";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] splitIntoWords(String name) {
|
||||
return splitAtUpperCase(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String joinToName(String[] words) {
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int totalLength = countTotalWordsLength(words);
|
||||
StringBuilder stringBuilder = new StringBuilder(totalLength);
|
||||
for (String word : words) {
|
||||
appendCapitalized(stringBuilder, word);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
|
||||
public static final NamingFormat KEBAB_CASE = createLowerDelimitedFormat("kebab-case", '-');
|
||||
public static final NamingFormat UPPER_KEBAB_CASE = createUpperDelimitedFormat("UPPER-KEBAB-CASE", '-');
|
||||
|
||||
public static final NamingFormat SNAKE_CASE = createLowerDelimitedFormat("snake_case", '_');
|
||||
public static final NamingFormat UPPER_SNAKE_CASE = createUpperDelimitedFormat("UPPER_SNAKE_CASE", '_');
|
||||
|
||||
public static final NamingFormat SPACE_CASE = createLowerDelimitedFormat("space case", ' ');
|
||||
public static final NamingFormat UPPER_SPACE_CASE = createUpperDelimitedFormat("UPPER SPACE CASE", ' ');
|
||||
public static final NamingFormat TITLE_CASE = createCapitalizedDelimitedFormat("Title Case", ' ');
|
||||
|
||||
private static NamingFormat createLowerDelimitedFormat(String formatName, char delimiter) {
|
||||
return new NamingFormat() {
|
||||
@Override
|
||||
public String name() {
|
||||
return formatName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] splitIntoWords(String name) {
|
||||
return splitAtCharacter(name, delimiter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String joinToName(String[] words) {
|
||||
return joinAllLower(delimiter, words);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static NamingFormat createUpperDelimitedFormat(String formatName, char delimiter) {
|
||||
return new NamingFormat() {
|
||||
@Override
|
||||
public String name() {
|
||||
return formatName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] splitIntoWords(String name) {
|
||||
return splitAtCharacter(name, delimiter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String joinToName(String[] words) {
|
||||
return joinAllUpper(delimiter, words);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static NamingFormat createCapitalizedDelimitedFormat(String formatName, char delimiter) {
|
||||
return new NamingFormat() {
|
||||
@Override
|
||||
public String name() {
|
||||
return formatName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] splitIntoWords(String name) {
|
||||
return splitAtCharacter(name, delimiter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String joinToName(String[] words) {
|
||||
return joinCapitalized(delimiter, words);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String[] splitAtUpperCase(String name) {
|
||||
ArrayList<String> words = new ArrayList<>();
|
||||
|
||||
StringBuilder wordBuilder = new StringBuilder();
|
||||
|
||||
CodePointReader codePointReader = CodePointReader.ofString(name);
|
||||
while (codePointReader.hasNext()) {
|
||||
if (wordBuilder.length() == 0) {
|
||||
wordBuilder.appendCodePoint(codePointReader.next());
|
||||
} else if (Character.isUpperCase(codePointReader.peek())) {
|
||||
words.add(wordBuilder.toString());
|
||||
wordBuilder.setLength(0);
|
||||
} else {
|
||||
wordBuilder.appendCodePoint(codePointReader.next());
|
||||
}
|
||||
}
|
||||
if (wordBuilder.length() > 0) {
|
||||
words.add(wordBuilder.toString());
|
||||
}
|
||||
|
||||
return words.toArray(EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
private static String[] splitAtCharacter(String text, char delimiter) {
|
||||
ArrayList<String> words = new ArrayList<>();
|
||||
|
||||
int index = 0;
|
||||
while (index < text.length()) {
|
||||
int delimiterIndex = text.indexOf(delimiter, index);
|
||||
if (delimiterIndex == -1) {
|
||||
words.add(text.substring(index));
|
||||
break;
|
||||
}
|
||||
|
||||
words.add(text.substring(index, delimiterIndex));
|
||||
index = delimiterIndex + 1;
|
||||
}
|
||||
|
||||
return words.toArray(EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
private static String joinAllLower(char joiner, String[] words) {
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int totalLength = countTotalWordsLength(words) + words.length - 1;
|
||||
StringBuilder stringBuilder = new StringBuilder(totalLength);
|
||||
for (String word : words) {
|
||||
appendAllLower(stringBuilder, word);
|
||||
if (stringBuilder.length() < totalLength) {
|
||||
stringBuilder.append(joiner);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private static String joinAllUpper(char joiner, String[] words) {
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int totalLength = countTotalWordsLength(words) + words.length - 1;
|
||||
StringBuilder stringBuilder = new StringBuilder(totalLength);
|
||||
for (String word : words) {
|
||||
appendAllUpper(stringBuilder, word);
|
||||
if (stringBuilder.length() < totalLength) {
|
||||
stringBuilder.append(joiner);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private static String joinCapitalized(char joiner, String[] words) {
|
||||
if (words.length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int totalLength = countTotalWordsLength(words) + words.length - 1;
|
||||
StringBuilder stringBuilder = new StringBuilder(totalLength);
|
||||
for (String word : words) {
|
||||
appendCapitalized(stringBuilder, word);
|
||||
if (stringBuilder.length() < totalLength) {
|
||||
stringBuilder.append(joiner);
|
||||
}
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private static int countTotalWordsLength(String[] words) {
|
||||
if (words.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int totalLength = 0;
|
||||
for (String word : words) {
|
||||
totalLength += word.length();
|
||||
}
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
private static void appendAllLower(StringBuilder target, CharSequence input) {
|
||||
if (input.length() == 0) {
|
||||
return;
|
||||
}
|
||||
PrimitiveIterator.OfInt codePointIterator = input.codePoints().iterator();
|
||||
while (codePointIterator.hasNext()) {
|
||||
target.appendCodePoint(Character.toLowerCase(codePointIterator.nextInt()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendAllUpper(StringBuilder target, CharSequence input) {
|
||||
if (input.length() == 0) {
|
||||
return;
|
||||
}
|
||||
PrimitiveIterator.OfInt codePointIterator = input.codePoints().iterator();
|
||||
while (codePointIterator.hasNext()) {
|
||||
target.appendCodePoint(Character.toUpperCase(codePointIterator.nextInt()));
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendCapitalized(StringBuilder target, CharSequence input) {
|
||||
if (input.length() == 0) {
|
||||
return;
|
||||
}
|
||||
PrimitiveIterator.OfInt codePointIterator = input.codePoints().iterator();
|
||||
target.appendCodePoint(Character.toUpperCase(codePointIterator.nextInt()));
|
||||
while (codePointIterator.hasNext()) {
|
||||
target.appendCodePoint(Character.toLowerCase(codePointIterator.nextInt()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@ApiStatus.Internal
|
||||
|
||||
package de.siphalor.tweed5.namingformat.impl;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.siphalor.tweed5.namingformat.api;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class NamingFormatsTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allNamingFormats")
|
||||
void splitIntoWordsEmpty(NamingFormat namingFormat) {
|
||||
assertArrayEquals(new String[0], namingFormat.splitIntoWords(""));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allNamingFormats")
|
||||
void joinToNameEmpty(NamingFormat namingFormat) {
|
||||
assertEquals("", namingFormat.joinToName(new String[0]));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allNamingFormats")
|
||||
void isFormatNameStableInOwnFormat(NamingFormat namingFormat) {
|
||||
assertEquals(namingFormat.name(), NamingFormats.convert(namingFormat.name(), namingFormat, namingFormat));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("splitIntoWordsArgs")
|
||||
void splitIntoWords(NamingFormat namingFormat, String name) {
|
||||
String[] result = namingFormat.splitIntoWords(name);
|
||||
// Expecting "not a URL" in any casing
|
||||
assertEquals(3, result.length, "Words should be 3, but got: " + Arrays.toString(result));
|
||||
assertEquals("not", result[0].toLowerCase(Locale.ROOT));
|
||||
assertEquals("a", result[1].toLowerCase(Locale.ROOT));
|
||||
assertEquals("url", result[2].toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> splitIntoWordsArgs() {
|
||||
return Stream.of(
|
||||
Arguments.of(NamingFormats.camelCase(), "notAUrl"),
|
||||
Arguments.of(NamingFormats.pascalCase(), "NotAUrl"),
|
||||
Arguments.of(NamingFormats.kebabCase(), "not-a-url"),
|
||||
Arguments.of(NamingFormats.upperKebabCase(), "NOT-A-URL"),
|
||||
Arguments.of(NamingFormats.snakeCase(), "not_a_url"),
|
||||
Arguments.of(NamingFormats.upperSnakeCase(), "NOT_A_URL"),
|
||||
Arguments.of(NamingFormats.spaceCase(), "not a url"),
|
||||
Arguments.of(NamingFormats.upperSpaceCase(), "NOT A URL"),
|
||||
Arguments.of(NamingFormats.titleCase(), "Not A Url")
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("joinToNameArgs")
|
||||
void joinToName(NamingFormat namingFormat, String expectedName) {
|
||||
assertEquals(expectedName, namingFormat.joinToName(new String[]{ "an", "INTERESTING", "über", "name" }));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("joinToNameArgs")
|
||||
void joinToNameLocaleIndependent(NamingFormat namingFormat, String expectedName) {
|
||||
Locale turkishLocale = Locale.of("tr", "TR");
|
||||
Locale.setDefault(turkishLocale);
|
||||
assertEquals("ı", "I".toLowerCase(), "Turkish locale works");
|
||||
|
||||
assertEquals(expectedName, namingFormat.joinToName(new String[]{ "an", "INTERESTING", "über", "name" }));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> joinToNameArgs() {
|
||||
return Stream.of(
|
||||
Arguments.of(NamingFormats.camelCase(), "anInterestingÜberName"),
|
||||
Arguments.of(NamingFormats.pascalCase(), "AnInterestingÜberName"),
|
||||
Arguments.of(NamingFormats.kebabCase(), "an-interesting-über-name"),
|
||||
Arguments.of(NamingFormats.upperKebabCase(), "AN-INTERESTING-ÜBER-NAME"),
|
||||
Arguments.of(NamingFormats.snakeCase(), "an_interesting_über_name"),
|
||||
Arguments.of(NamingFormats.upperSnakeCase(), "AN_INTERESTING_ÜBER_NAME"),
|
||||
Arguments.of(NamingFormats.spaceCase(), "an interesting über name"),
|
||||
Arguments.of(NamingFormats.upperSpaceCase(), "AN INTERESTING ÜBER NAME"),
|
||||
Arguments.of(NamingFormats.titleCase(), "An Interesting Über Name")
|
||||
);
|
||||
}
|
||||
|
||||
private static NamingFormat[] allNamingFormats() {
|
||||
return new NamingFormat[] {
|
||||
NamingFormats.camelCase(),
|
||||
NamingFormats.pascalCase(),
|
||||
NamingFormats.kebabCase(),
|
||||
NamingFormats.upperKebabCase(),
|
||||
NamingFormats.snakeCase(),
|
||||
NamingFormats.upperSnakeCase(),
|
||||
NamingFormats.spaceCase(),
|
||||
NamingFormats.upperSpaceCase(),
|
||||
NamingFormats.titleCase()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.siphalor.tweed5.namingformat.impl;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class CodePointReaderTest {
|
||||
|
||||
@Test
|
||||
void ofString() {
|
||||
CodePointReader reader = CodePointReader.ofString("Aüc");
|
||||
|
||||
assertTrue(reader.hasNext());
|
||||
assertEquals('A', reader.next());
|
||||
assertTrue(reader.hasNext());
|
||||
assertEquals('ü', reader.peek());
|
||||
assertEquals('ü', reader.peek());
|
||||
assertTrue(reader.hasNext());
|
||||
assertEquals('ü', reader.next());
|
||||
assertTrue(reader.hasNext());
|
||||
assertEquals('c', reader.next());
|
||||
assertFalse(reader.hasNext());
|
||||
assertThrows(Exception.class, reader::next);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user