Skip to content

Commit 69c5bad

Browse files
authored
Merge pull request #6 from jGleitz/refactor
Restructure files
2 parents e527a61 + f90bce7 commit 69c5bad

File tree

8 files changed

+292
-231
lines changed

8 files changed

+292
-231
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package de.joshuagleitze.stringnotation
2+
3+
import java.util.*
4+
5+
/**
6+
* Base class for implementing string notations.
7+
*
8+
* @constructor Creates a string notation that will use the provided regular expression to [split][String.split] parts when parsing.
9+
*/
10+
abstract class BaseStringNotation(private val splitAt: Regex): StringNotation {
11+
/**
12+
* Transforms a parsed part after it has been read. The default implementation is to convert the part to lowercase to discard possibly
13+
* wrong case information.
14+
*/
15+
protected open fun transformPartAfterParse(index: Int, part: String) = part.toLowerCase(Locale.ROOT)
16+
17+
override fun parse(sourceString: String): Word = Word(sourceString.split(splitAt).asSequence().mapIndexed(::transformPartAfterParse))
18+
19+
/**
20+
* Allows to transform a part before it is being printed. The default implementation does not modify the part in any way.
21+
*/
22+
protected open fun transformPartToPrint(index: Int, part: String) = part
23+
24+
/**
25+
* Allows to print characters in front of parts. The default implementation will print nothing before the first part and delegate to
26+
* [printBeforeInnerPart] for the remaining parts.
27+
*/
28+
protected open fun printBeforePart(index: Int, part: String) = if (index == 0) "" else printBeforeInnerPart(index, part)
29+
30+
/**
31+
* Allows to print characters in front of parts that are not the first part. The default implementation prints nothing.
32+
*/
33+
protected open fun printBeforeInnerPart(index: Int, part: String) = ""
34+
35+
override fun print(word: Word) = word.parts
36+
.mapIndexed(::transformPartToPrint)
37+
.foldIndexed(StringBuffer()) { index, left, right -> left.append(printBeforePart(index, right)).append(right) }
38+
.toString()
39+
40+
override fun toString() = this::class.java.simpleName!!
41+
}

src/main/kotlin/Notations.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package de.joshuagleitze.stringnotation
2+
3+
import java.util.stream.IntStream
4+
5+
private val camelCaseSplitRegex = Regex("(?<=.)(?=\\p{Lu})")
6+
7+
/**
8+
* The `UpperCamelCase` notation.
9+
*
10+
* @see JavaTypeName
11+
*/
12+
object UpperCamelCase: BaseStringNotation(camelCaseSplitRegex) {
13+
public override fun transformPartToPrint(index: Int, part: String) = part.toFirstUpperOtherLowerCase()
14+
}
15+
16+
/**
17+
* The `lowerCamelCase` notation.
18+
*
19+
* @see JavaMemberName
20+
*/
21+
object LowerCamelCase: BaseStringNotation(camelCaseSplitRegex) {
22+
override fun transformPartToPrint(index: Int, part: String) = if (index == 0) part.toLowerCase() else part.toFirstUpperOtherLowerCase()
23+
}
24+
25+
/**
26+
* The `SCREAMING_SNAKE_CASE` notation.
27+
*/
28+
object ScreamingSnakeCase: BaseStringNotation(Regex("_")) {
29+
override fun printBeforeInnerPart(index: Int, part: String) = "_"
30+
31+
override fun transformPartToPrint(index: Int, part: String) = part.toUpperCase()
32+
}
33+
34+
/**
35+
* The `snake_case` notation.
36+
*/
37+
object SnakeCase: BaseStringNotation(Regex("_")) {
38+
override fun transformPartAfterParse(index: Int, part: String) = part
39+
override fun printBeforeInnerPart(index: Int, part: String) = "_"
40+
}
41+
42+
/**
43+
* A notation for java type names. This notation is like [UpperCamelCase], but will drop any character that is not allowed in a Java
44+
* identifier when [printing][StringNotation.print].
45+
*
46+
* Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart].
47+
*/
48+
object JavaTypeName: BaseStringNotation(camelCaseSplitRegex) {
49+
override fun transformPartToPrint(index: Int, part: String) = part.toFirstUpperOtherLowerCase()
50+
override fun print(word: Word) = super.print(word).keepOnlyJavaIdentifierChars()
51+
}
52+
53+
/**
54+
* A notation for java member names. This notation is like [LowerCamelCase], but will drop any character that is not allowed in a Java
55+
* identifier when [printing][StringNotation.print].
56+
*
57+
* Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart].
58+
*/
59+
object JavaMemberName: BaseStringNotation(camelCaseSplitRegex) {
60+
override fun print(word: Word) = word.parts
61+
.foldIndexed(StringBuffer()) { index, left, right ->
62+
val rightPart =
63+
if (left.contains(Regex("[a-zA-Z]"))) right.toFirstUpperOtherLowerCase()
64+
else right.toLowerCase()
65+
left.append(printBeforePart(index, rightPart)).append(rightPart)
66+
}.toString().keepOnlyJavaIdentifierChars()
67+
68+
}
69+
70+
/**
71+
* Notation for words written like in normal language. [Parsing][StringNotation.parse] will recognise all substrings that are separated by
72+
* one or more characters of whitespace as a [part][Word.parts]. [Printing][StringNotation.print] will print the parts separated by one
73+
* space.
74+
*/
75+
object NormalWords: BaseStringNotation(Regex("[\\s]+")) {
76+
override fun transformPartAfterParse(index: Int, part: String) = part
77+
override fun printBeforeInnerPart(index: Int, part: String) = " "
78+
}
79+
80+
internal fun String.toFirstUpperOtherLowerCase() = if (isNotEmpty()) this[0].toUpperCase() + substring(1).toLowerCase() else this
81+
82+
fun String.keepOnlyJavaIdentifierChars() = this.chars()
83+
.skipWhile { !Character.isJavaIdentifierStart(it) }
84+
.filter { Character.isJavaIdentifierPart(it) }
85+
.collect({ StringBuilder() }, { left, right -> left.appendCodePoint(right) }, { left, right -> left.append(right) })
86+
.toString()
87+
88+
internal inline fun IntStream.skipWhile(crossinline condition: (Int) -> Boolean): IntStream {
89+
var found = false
90+
return this.filter {
91+
if (!found) {
92+
found = !condition(it)
93+
}
94+
found
95+
}
96+
}
97+

src/main/kotlin/StringNotation.kt

Lines changed: 0 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -20,131 +20,4 @@ interface StringNotation {
2020
fun print(word: Word): String
2121
}
2222

23-
/**
24-
* Base class for implementing string notations.
25-
*
26-
* @constructor Creates a string notation that will use the provided regular expression to [split][String.split] parts when parsing.
27-
*/
28-
abstract class BaseStringNotation(private val splitAt: Regex): StringNotation {
29-
/**
30-
* Transforms a parsed part after it has been read. The default implementation is to convert the part to lowercase to discard possibly
31-
* wrong case information.
32-
*/
33-
protected open fun transformPartAfterParse(index: Int, part: String) = part.toLowerCase(Locale.ROOT)
34-
35-
override fun parse(sourceString: String): Word = Word(sourceString.split(splitAt).asSequence().mapIndexed(::transformPartAfterParse))
36-
37-
/**
38-
* Allows to transform a part before it is being printed. The default implementation does not modify the part in any way.
39-
*/
40-
protected open fun transformPartToPrint(index: Int, part: String) = part
41-
42-
/**
43-
* Allows to print characters in front of parts. The default implementation will print nothing before the first part and delegate to
44-
* [printBeforeInnerPart] for the remaining parts.
45-
*/
46-
protected open fun printBeforePart(index: Int, part: String) = if (index == 0) "" else printBeforeInnerPart(index, part)
47-
48-
/**
49-
* Allows to print characters in front of parts that are not the first part. The default implementation prints nothing.
50-
*/
51-
protected open fun printBeforeInnerPart(index: Int, part: String) = ""
52-
53-
override fun print(word: Word) = word.parts
54-
.mapIndexed(::transformPartToPrint)
55-
.foldIndexed(StringBuffer()) { index, left, right -> left.append(printBeforePart(index, right)).append(right) }
56-
.toString()
57-
}
58-
59-
internal fun String.capitalizeOnlyFirst() = if (isNotEmpty()) this[0].toUpperCase() + substring(1).toLowerCase() else this
60-
61-
private val camelCaseSplitRegex = Regex("(?<=.)(?=\\p{Lu})")
62-
63-
/**
64-
* The `UpperCamelCase` notation.
65-
*
66-
* @see JavaTypeName
67-
*/
68-
object UpperCamelCase: BaseStringNotation(camelCaseSplitRegex) {
69-
public override fun transformPartToPrint(index: Int, part: String) = part.capitalizeOnlyFirst()
70-
}
71-
72-
/**
73-
* The `lowerCamelCase` notation.
74-
*
75-
* @see JavaMemberName
76-
*/
77-
object LowerCamelCase: BaseStringNotation(camelCaseSplitRegex) {
78-
override fun transformPartToPrint(index: Int, part: String) = if (index == 0) part.toLowerCase() else part.capitalizeOnlyFirst()
79-
}
80-
81-
/**
82-
* The `SCREAMING_SNAKE_CASE` notation.
83-
*/
84-
object ScreamingSnakeCase: BaseStringNotation(Regex("_")) {
85-
override fun printBeforeInnerPart(index: Int, part: String) = "_"
8623

87-
override fun transformPartToPrint(index: Int, part: String) = part.toUpperCase()
88-
}
89-
90-
/**
91-
* The `snake_case` notation.
92-
*/
93-
object SnakeCase: BaseStringNotation(Regex("_")) {
94-
override fun transformPartAfterParse(index: Int, part: String) = part
95-
override fun printBeforeInnerPart(index: Int, part: String) = "_"
96-
}
97-
98-
/**
99-
* A notation for java type names. This notation is like [UpperCamelCase], but will drop any character that is not allowed in a Java
100-
* identifier when [printing][StringNotation.print].
101-
*
102-
* Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart].
103-
*/
104-
object JavaTypeName: BaseStringNotation(camelCaseSplitRegex) {
105-
override fun transformPartToPrint(index: Int, part: String) = part.keepOnlyJavaIdentifierChars().capitalizeOnlyFirst()
106-
}
107-
108-
/**
109-
* A notation for java member names. This notation is like [LowerCamelCase], but will drop any character that is not allowed in a Java
110-
* identifier when [printing][StringNotation.print].
111-
*
112-
* Allowed characters are determined using [Character.isJavaIdentifierStart] and [Character.isJavaIdentifierPart].
113-
*/
114-
object JavaMemberName: BaseStringNotation(camelCaseSplitRegex) {
115-
override fun print(word: Word) = word.parts
116-
.foldIndexed(StringBuffer()) { index, left, right ->
117-
val rightPart = right.keepOnlyJavaIdentifierChars().run {
118-
if (left.contains(Regex("[a-zA-Z]"))) capitalizeOnlyFirst()
119-
else this.toLowerCase()
120-
}
121-
left.append(printBeforePart(index, rightPart)).append(rightPart)
122-
}.toString()
123-
124-
}
125-
126-
/**
127-
* Notation for words written like in normal language. [Parsing][StringNotation.parse] will recognise all substrings that are separated by
128-
* one or more characters of whitespace as a [part][Word.parts]. [Printing][StringNotation.print] will print the parts separated by one
129-
* space.
130-
*/
131-
object NormalWords: BaseStringNotation(Regex("[\\s]+")) {
132-
override fun transformPartAfterParse(index: Int, part: String) = part
133-
override fun printBeforeInnerPart(index: Int, part: String) = " "
134-
}
135-
136-
internal fun String.keepOnlyJavaIdentifierChars() = this.chars()
137-
.skipWhile { !isJavaIdentifierStart(it) }
138-
.filter { isJavaIdentifierPart(it) }
139-
.collect({ StringBuilder() }, { left, right -> left.appendCodePoint(right) }, { left, right -> left.append(right) })
140-
.toString()
141-
142-
internal fun IntStream.skipWhile(condition: (Int) -> Boolean): IntStream {
143-
var found = false
144-
return this.filter {
145-
if (!found) {
146-
found = !condition(it)
147-
}
148-
found
149-
}
150-
}

src/main/kotlin/Word.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
@file:JvmName("StringToWord")
2+
13
package de.joshuagleitze.stringnotation
24

35
/**
@@ -30,10 +32,12 @@ class Word(val parts: Sequence<String>) {
3032
* Appends all parts of the given [word] to this word.
3133
*/
3234
operator fun plus(word: Word) = Word(parts + word.parts)
35+
36+
override fun toString() = "Word(${parts.joinToString { "\"$it\"" }})"
3337
}
3438

3539
/**
3640
* Converts `this` string, into a [Word] according to the provided [notation]. This method expects that the input is formatted according to
3741
* the [notation] and creates a notation-agnostic representation of it.
3842
*/
39-
fun String.inNotation(notation: StringNotation): Word = notation.parse(this)
43+
fun String.fromNotation(notation: StringNotation): Word = notation.parse(this)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package de.joshuagleitze.stringnotation.example;
2+
3+
import de.joshuagleitze.stringnotation.JavaTypeName;
4+
import de.joshuagleitze.stringnotation.LowerCamelCase;
5+
import de.joshuagleitze.stringnotation.NormalWords;
6+
import de.joshuagleitze.stringnotation.UpperCamelCase;
7+
8+
import static de.joshuagleitze.stringnotation.StringToWord.fromNotation;
9+
10+
public class CallingFromJavaExample {
11+
public static void main(String... args) {
12+
// myVariable -> MY_VARIABLE
13+
fromNotation("myVariable", UpperCamelCase.INSTANCE).toNotation(LowerCamelCase.INSTANCE);
14+
15+
// 1 Type Name 4 You! -> TypeName4You
16+
fromNotation("1 Type Name 4 You!", NormalWords.INSTANCE).toNotation(JavaTypeName.INSTANCE);
17+
}
18+
}

0 commit comments

Comments
 (0)