1+ package io .cloudquery .caser ;
2+
3+ import lombok .Builder ;
4+
5+ import java .util .ArrayList ;
6+ import java .util .Arrays ;
7+ import java .util .HashMap ;
8+ import java .util .HashSet ;
9+ import java .util .List ;
10+ import java .util .Map ;
11+ import java .util .Set ;
12+
13+ import static io .cloudquery .caser .Initialisms .*;
14+ import static io .cloudquery .caser .Initialisms .COMMON_INITIALISMS ;
15+
16+ @ Builder
17+ public class Caser {
18+ @ Builder .Default
19+ private Set <String > initialisms = new HashSet <>(COMMON_INITIALISMS );
20+
21+ @ Builder .Default
22+ private Map <String , String > snakeToCamelExceptions = new HashMap <>();
23+
24+ @ Builder .Default
25+ private Map <String , String > camelToSnakeExceptions = new HashMap <>();
26+
27+ @ Builder .Default
28+ private Map <String , String > customExceptions = new HashMap <>();
29+
30+ @ Builder .Default
31+ private Set <String > customInitialisms = new HashSet <>();
32+
33+ public Caser (Set <String > initialisms ,
34+ Map <String , String > snakeToCamelExceptions ,
35+ Map <String , String > camelToSnakeExceptions ,
36+ Map <String , String > customExceptions ,
37+ Set <String > customInitialisms ) {
38+ this .initialisms = initialisms ;
39+ this .snakeToCamelExceptions = snakeToCamelExceptions ;
40+ this .camelToSnakeExceptions = camelToSnakeExceptions ;
41+ this .customExceptions = customExceptions ;
42+ this .customInitialisms = customInitialisms ;
43+
44+
45+ HashMap <String , String > combinedExceptions = new HashMap <>(COMMON_EXCEPTIONS );
46+ combinedExceptions .putAll (customExceptions );
47+ for (String key : combinedExceptions .keySet ()) {
48+ snakeToCamelExceptions .put (key , combinedExceptions .get (key ));
49+ camelToSnakeExceptions .put (combinedExceptions .get (key ), key );
50+ }
51+
52+ initialisms .addAll (customInitialisms );
53+ }
54+
55+ public String toSnake (String s ) {
56+ List <String > words = new ArrayList <>();
57+ int lastPos = 0 ;
58+ for (int i = 0 ; i < s .length (); i ++) {
59+ if (i > 0 && Character .isUpperCase (s .charAt (i ))) {
60+
61+ String initialism = startsWithInitialism (s .substring (lastPos ));
62+ if (!initialism .isEmpty ()) {
63+ words .add (initialism );
64+ i = lastPos + initialism .length ();
65+ lastPos = i ;
66+ continue ;
67+ }
68+
69+ String capWord = getCapWord (s .substring (lastPos ));
70+ if (!capWord .isEmpty ()) {
71+ words .add (capWord );
72+ i = lastPos + capWord .length ();
73+ lastPos = i ;
74+ continue ;
75+ }
76+
77+ words .add (s .substring (lastPos , i ));
78+ lastPos = i ;
79+ }
80+ }
81+
82+ if (!s .substring (lastPos ).isEmpty ()) {
83+ String w = s .substring (lastPos );
84+ if (w .equals ("s" )) {
85+ String lastWord = words .remove (words .size () - 1 );
86+ words .add (lastWord + w );
87+ } else {
88+ words .add (s .substring (lastPos ));
89+ }
90+ }
91+
92+ return String .join ("_" , words ).toLowerCase ();
93+ }
94+
95+ /**
96+ * Returns a string converted from snake case to camel case.
97+ * <p>
98+ *
99+ * @param s The input string
100+ * @return The string converted to camel case
101+ */
102+ public String toCamel (String s ) {
103+ if (s .isEmpty ()) {
104+ return s ;
105+ }
106+ List <String > words = Arrays .asList (s .split ("_" ));
107+ return String .join ("" , capitalize (words ));
108+ }
109+
110+ /**
111+ * Returns a string converted from snake case to title case.
112+ * <p>
113+ * Title case is similar to camel case, but spaces are used in between words.
114+ *
115+ * @param s The input string
116+ * @return The string converted to title case
117+ */
118+ public String toTitle (String s ) {
119+ if (s .isEmpty ()) {
120+ return s ;
121+ }
122+ String [] words = s .split ("_" );
123+ if (!snakeToCamelExceptions .containsKey (words [0 ].toLowerCase ())) {
124+ words [0 ] = words [0 ].substring (0 , 1 ).toUpperCase () + words [0 ].substring (1 ).toLowerCase ();
125+ }
126+ return String .join (" " , capitalize (Arrays .asList (words )));
127+ }
128+
129+ /**
130+ * Returns a string converted from snake case to pascal case
131+ *
132+ * @param s The input string
133+ * @return The string converted to pascal case
134+ */
135+ public String toPascal (String s ) {
136+ if (s .isEmpty ()) {
137+ return s ;
138+ }
139+ String camel = toCamel (s );
140+ return camel .substring (0 , 1 ).toUpperCase () + camel .substring (1 );
141+ }
142+
143+ /**
144+ * gets the next sequence of capitalized letters as a single word.
145+ * <p>
146+ * If there is a word after capitalized sequence it leaves one letter as beginning of the next word
147+ *
148+ * @param s The input string
149+ * @return A single word
150+ */
151+ private String getCapWord (String s ) {
152+ for (int i = 0 ; i < s .length (); i ++) {
153+ if (!Character .isUpperCase (s .charAt (i ))) {
154+ if (i == 0 ) {
155+ return "" ;
156+ }
157+ return s .substring (0 , i - 1 );
158+ }
159+ }
160+ return s ;
161+ }
162+
163+ /**
164+ * Returns the initialism if the given string begins with it
165+ *
166+ * @param s The input string
167+ * @return The initialism if the given string begins with it, otherwise an empty string
168+ */
169+ private String startsWithInitialism (String s ) {
170+ String initialism = "" ;
171+
172+ // the longest initialism is 5 char, the shortest 2 we choose the longest match
173+ for (int i = 1 ; i <= s .length () && i <= 5 ; i ++) {
174+ if (s .length () > i - 1 && this .initialisms .contains (s .substring (0 , i )) && s .substring (0 , i ).length () > initialism .length ()) {
175+ initialism = s .substring (0 , i );
176+ }
177+ }
178+
179+ return initialism ;
180+ }
181+
182+ private List <String > capitalize (List <String > words ) {
183+ int n = words .stream ().map (String ::length ).reduce (0 , Integer ::sum );
184+
185+ List <String > results = new ArrayList <>();
186+ for (int i = 0 ; i < words .size (); i ++) {
187+ if (snakeToCamelExceptions .containsKey (words .get (i ))) {
188+ results .add (snakeToCamelExceptions .get (words .get (i )));
189+ continue ;
190+ }
191+
192+ if (i > 0 ) {
193+ String upper = words .get (i ).toUpperCase ();
194+ if (n > i - 1 && initialisms .contains (upper )) {
195+ results .add (upper );
196+ continue ;
197+ }
198+ }
199+
200+ if (i > 0 && !words .get (i ).isEmpty ()) {
201+ results .add (words .get (i ).substring (0 , 1 ).toUpperCase ()+words .get (i ).substring (1 ));
202+ } else {
203+ results .add (words .get (i ));
204+ }
205+ }
206+ return results ;
207+ }
208+ }
0 commit comments