|
| 1 | +package de.joshuagleitze.stringnotation |
| 2 | + |
| 3 | +import java.util.* |
| 4 | +import java.util.stream.IntStream |
| 5 | +import javax.lang.model.SourceVersion |
| 6 | + |
| 7 | +/** |
| 8 | + * A notation for Java type names. This notation is like [UpperCamelCase], but when [printing][StringNotation.print], it will drop any |
| 9 | + * character that is not allowed in a Java identifier. If the result is a Java keyword, `_` will be appended to it. |
| 10 | + * |
| 11 | + * Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart]. Keywords are detected |
| 12 | + * using [SourceVersion.isKeyword]. |
| 13 | + */ |
| 14 | +object JavaTypeName: StringNotation by UpperCamelCase { |
| 15 | + override fun print(word: Word) = UpperCamelCase.print( |
| 16 | + Word(word.parts.mapIndexed { index, wordPart -> |
| 17 | + if (index == 0) wordPart.keepOnlyJavaIdentifierChars() |
| 18 | + else wordPart.keepOnlyJavaIdentifierContinuationChars() |
| 19 | + }) |
| 20 | + ) |
| 21 | + .neutralizeJavaReservedKeywords() |
| 22 | + |
| 23 | + override fun toString() = this::class.java.simpleName!! |
| 24 | +} |
| 25 | + |
| 26 | +/** |
| 27 | + * A notation for java member names. This notation is like [LowerCamelCase], but when [printing][StringNotation.print], it will drop any |
| 28 | + * character that is not allowed in a Java identifier. If the result is a Java keyword, `_` will be appended to it. |
| 29 | + * |
| 30 | + * Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart]. Keywords are detected |
| 31 | + * using [SourceVersion.isKeyword]. |
| 32 | + */ |
| 33 | +object JavaMemberName: BaseStringNotation(camelCaseSplitRegex) { |
| 34 | + override fun transformPartAfterParse(index: Int, part: String) = part.toLowerCase(Locale.ROOT) |
| 35 | + |
| 36 | + override fun print(word: Word) = word.parts |
| 37 | + .foldIndexed(StringBuffer()) { index, existing, part -> |
| 38 | + val filteredPart = |
| 39 | + if (index == 0) part.keepOnlyJavaIdentifierChars() |
| 40 | + else part.keepOnlyJavaIdentifierContinuationChars() |
| 41 | + val nextPart = |
| 42 | + if (existing.contains(Regex("[a-zA-Z]"))) filteredPart.toFirstUpperOtherLowerCase() |
| 43 | + else filteredPart.toLowerCase() |
| 44 | + existing.append(printBeforePart(index, nextPart)).append(nextPart) |
| 45 | + }.toString().makeValidJavaIdentifier() |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * A notation for java package parts. When [printing][StringNotation.print], it simply concatenates all word parts and drops any character |
| 50 | + * that is not allowed in a Java identifier. If the result is a Java keyword, `_` will be appended to it. When |
| 51 | + * [parsing][StringNotation.parse], the notation will recognise word parts both in the [LowerCamelCase] and the [SnakeCase] notation. |
| 52 | + * However, neither notation is conventional and parsing will usually yield only one word part on real-world inputs. |
| 53 | + * |
| 54 | + * Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart]. Keywords are detected |
| 55 | + * using [SourceVersion.isKeyword]. |
| 56 | + */ |
| 57 | +object JavaPackagePart: BaseStringNotation(Regex("_|${camelCaseSplitRegex.pattern}")) { |
| 58 | + override fun transformPartAfterParse(index: Int, part: String) = part.toLowerCase(Locale.ROOT) |
| 59 | + |
| 60 | + override fun transformPartToPrint(index: Int, part: String) = part.toLowerCase(Locale.ROOT) |
| 61 | + |
| 62 | + override fun print(word: Word) = super.print(word).makeValidJavaIdentifier() |
| 63 | +} |
| 64 | + |
| 65 | +/** |
| 66 | + * A notation for whole java packages. When [printing][StringNotation.print] parts, it will drop any character that is not allowed in a Java |
| 67 | + * identifier. If the result is a Java keyword, `_` will be appended to it. |
| 68 | + * |
| 69 | + * Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart]. Keywords are detected |
| 70 | + * using [SourceVersion.isKeyword]. |
| 71 | + */ |
| 72 | +object JavaPackageName: BaseStringNotation(Regex("\\.")) { |
| 73 | + override fun transformPartToPrint(index: Int, part: String) = part.toLowerCase(Locale.ROOT).makeValidJavaIdentifier() |
| 74 | + |
| 75 | + override fun printBeforeInnerPart(index: Int, part: String) = "." |
| 76 | +} |
| 77 | + |
| 78 | +/** |
| 79 | + * A notation for `static final` fields in Java. This notation is like [ScreamingSnakeCase], but when [printing][StringNotation.print], it |
| 80 | + * will drop any character that is not allowed in a Java identifier. If the result is a Java keyword, `_` will be appended to it. |
| 81 | + * |
| 82 | + * Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart]. Keywords are detected |
| 83 | + * using [SourceVersion.isKeyword]. |
| 84 | + */ |
| 85 | +object JavaConstantName: StringNotation by ScreamingSnakeCase { |
| 86 | + override fun print(word: Word) = ScreamingSnakeCase.print(word).makeValidJavaIdentifier() |
| 87 | + |
| 88 | + override fun toString() = this::class.java.simpleName!! |
| 89 | +} |
| 90 | + |
| 91 | +private fun String.makeValidJavaIdentifier() = this.keepOnlyJavaIdentifierChars().neutralizeJavaReservedKeywords() |
| 92 | + |
| 93 | +private fun String.keepOnlyJavaIdentifierChars() = this.chars() |
| 94 | + .skipWhile { !Character.isJavaIdentifierStart(it) } |
| 95 | + .keepOnlyJavaIdentifierContinuationChars() |
| 96 | + .collectToString() |
| 97 | + |
| 98 | +private fun String.keepOnlyJavaIdentifierContinuationChars() = this.chars().keepOnlyJavaIdentifierContinuationChars().collectToString() |
| 99 | +private fun IntStream.keepOnlyJavaIdentifierContinuationChars() = this.filter { Character.isJavaIdentifierPart(it) } |
| 100 | +private fun IntStream.collectToString() = |
| 101 | + this.collect({ StringBuilder() }, { left, right -> left.appendCodePoint(right) }, { left, right -> left.append(right) }) |
| 102 | + .toString() |
| 103 | + |
| 104 | +private fun String.neutralizeJavaReservedKeywords() = when { |
| 105 | + this == "" -> "__" |
| 106 | + SourceVersion.isKeyword(this) -> this + "_" |
| 107 | + else -> this |
| 108 | +} |
| 109 | + |
| 110 | +private inline fun IntStream.skipWhile(crossinline condition: (Int) -> Boolean): IntStream { |
| 111 | + var found = false |
| 112 | + return this.filter { |
| 113 | + if (!found) { |
| 114 | + found = !condition(it) |
| 115 | + } |
| 116 | + found |
| 117 | + } |
| 118 | +} |
| 119 | + |
0 commit comments