package org.osgl.util;
import org.osgl.$;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A `Keyword` can be presented in the different ways:
* * CamelCaseStyle
* * underscore_style
* * CONSTANT_STYLE
* * dash-style
* * "readable style"
* * "Http-Header-Style"
*
* When reading a string into a keyword, the following separator chars
* will be ignored and used as separator to construct the keyword
*
* * space `' '`
* * underscore: `_`
* * dash: `-`
* * comma: `,`
* * dot: `.`
* * colon: `:`
* * semi-colon: `;`
* * slash: `\`
* * forward slash: `/`
*
*/
public final class Keyword implements Comparable<Keyword> {
public static final char SEP_SPACE = ' ';
public static final char SEP_UNDERSCORE = '_';
public static final char SEP_DASH = '-';
public static final char SEP_COMMA = ',';
public static final char SEP_COLON = ':';
public static final char SEP_DOT = '.';
public static final char SEP_SEMI_COLON = ';';
public static final char SEP_SLASH = '\\';
public static final char SEP_FORWARD_SLASH = '/';
private static final char[] SEPS = {
SEP_SPACE, SEP_UNDERSCORE, SEP_DASH, SEP_COMMA,
SEP_COLON, SEP_DOT, SEP_SEMI_COLON, SEP_FORWARD_SLASH,
SEP_SLASH
};
static {
Arrays.sort(SEPS);
}
public static enum Style {
/**
* `CamelCaseStyle`
*/
CAMEL_CASE () {
@Override
protected CharSequence processToken(FastStr token, int seq) {
return token.capFirst();
}
},
JAVA_VARIABLE() {
@Override
protected CharSequence processToken(FastStr token, int seq) {
return seq > 0 ? token.capFirst() : token;
}
},
/**
* `underscore_style`
*/
UNDERSCORE(SEP_UNDERSCORE),
/**
* `CONSTANT_NAME_STYLE`
*/
CONSTANT_NAME(SEP_UNDERSCORE) {
@Override
protected CharSequence processToken(FastStr token, int seq) {
return token.toUpperCase();
}
},
/**
* `dashed-style`
*/
DASHED(SEP_DASH),
/**
* `Http-Header-Style`
*/
HTTP_HEADER(SEP_DASH) {
@Override
protected CharSequence processToken(FastStr token, int seq) {
return token.capFirst();
}
},
/**
* `Readable style`
*/
READABLE(SEP_SPACE) {
@Override
protected CharSequence processToken(FastStr token, int seq) {
if (seq == 0) {
return token.capFirst();
}
return token;
}
};
private String separator;
private Style() {
separator = null;
}
private Style(char sep) {
separator = String.valueOf(sep);
}
public String toString(Keyword keyword) {
S.Buffer sb = S.buffer();
int sz = keyword.list.size();
for (int i = 0; i < sz; i++) {
FastStr fs = keyword.list.get(i);
sb.append(processToken(fs, i));
if (i < sz - 1 && null != separator) {
sb.append(separator);
}
}
return sb.toString();
}
protected CharSequence processToken(FastStr token, int seq) {
return token;
}
}
private C.List<FastStr> list = C.newList();
public Keyword(CharSequence chars) {
init(chars);
}
public String camelCase() {
return Style.CAMEL_CASE.toString(this);
}
public String javaVariable() {
return Style.JAVA_VARIABLE.toString(this);
}
public String constantName() {
return Style.CONSTANT_NAME.toString(this);
}
public String underscore() {
return Style.UNDERSCORE.toString(this);
}
public String dashed() {
return Style.DASHED.toString(this);
}
public String httpHeader() {
return Style.HTTP_HEADER.toString(this);
}
public String readable() {
return Style.READABLE.toString(this);
}
public List<String> tokens() {
List<String> list = new ArrayList<String>();
for (FastStr fs : this.list) {
list.add(fs.toString());
}
return list;
}
@Override
public int hashCode() {
return $.hc(list);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Keyword) {
return ((Keyword) obj).list.equals(list);
}
return false;
}
/**
* Returns string representation of this keyword using
* {@link Style#UNDERSCORE underscore style}
* @return the underscore style representation of this keyword
*/
@Override
public String toString() {
return underscore();
}
/**
* Return string representation of this keyword using style specified
* @param style the style used to print this keyword
* @return the printed string of this keyword by style specified
*/
public String toString(Style style) {
return style.toString(this);
}
@Override
public int compareTo(Keyword o) {
return camelCase().compareTo(o.camelCase());
}
public static Keyword of(CharSequence chars) {
return new Keyword(chars);
}
private void init(CharSequence chars) {
final FastStr fs = FastStr.of(chars);
final int sz = fs.length();
int last = nextNonSeparator(fs, 0);
int pos;
while (true) {
pos = locateNextStop(fs, last);
if (pos < 0 || pos == sz) {
FastStr sub = fs.substr(last);
if (!sub.isEmpty()) {
list.add(sub.toLowerCase());
}
break;
}
FastStr sub = fs.subSequence(last, pos);
if (sub.length() == 1 && isUpperCase(sub.charAt(0))) {
pos = nextNonUpperCase(fs, pos);
sub = fs.subSequence(last, pos);
}
if (!sub.isEmpty()) {
list.add(sub.toLowerCase());
}
last = nextNonSeparator(fs, pos);
}
}
/*
* next stop is at:
* 1. Uppercase character that followed a non-uppercase character
* 2. separator
*/
private static int locateNextStop(FastStr str, int start) {
int sz = str.length();
if (start >= sz - 1) {
return -1;
}
int pos = start + 1;
while (pos < sz) {
char ch = str.charAt(pos);
if (isSeparator(ch) || isUpperCase(ch)) {
break;
}
pos++;
}
return pos;
}
private static int nextNonSeparator(FastStr str, int start) {
int sz = str.length();
int pos = start;
while (pos < sz) {
char ch = str.charAt(pos);
if (isSeparator(ch)) {
pos++;
} else {
break;
}
}
return pos;
}
private static int nextNonUpperCase(FastStr str, int start) {
final int sz = str.size();
int pos = start;
while (pos < sz) {
char ch = str.charAt(pos);
if (isUpperCase(ch)) {
pos++;
} else {
break;
}
}
return pos;
}
private static boolean isSeparator(char ch) {
return Arrays.binarySearch(SEPS, ch) >= 0;
}
private static boolean isUpperCase(char ch) {
return ch >= 'A' && ch <= 'Z';
}
}