com.sun.jersey.json.impl.NameUtil Maven / Gradle / Ivy
Show all versions of jersey-json Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jersey.json.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
* Methods that convert strings into various formats.
*
*
* What JAX-RPC name binding tells us is that even such basic method
* like "isLetter" can be different depending on the situation.
*
* For this reason, a whole lot of methods are made non-static,
* even though they look like they should be static.
*/
class NameUtil {
protected static boolean isPunct(char c) {
return c == '-' || c == '.' || c == ':' || c == '_' || c == '\u00b7' || c == '\u0387' || c == '\u06dd' || c == '\u06de';
}
protected static boolean isDigit(char c) {
return c >= '0' && c <= '9' || Character.isDigit(c);
}
protected static boolean isUpper(char c) {
return c >= 'A' && c <= 'Z' || Character.isUpperCase(c);
}
protected static boolean isLower(char c) {
return c >= 'a' && c <= 'z' || Character.isLowerCase(c);
}
protected boolean isLetter(char c) {
return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || Character.isLetter(c);
}
/**
* Capitalizes the first character of the specified string,
* and de-capitalize the rest of characters.
*/
protected static String capitalize(String s) {
if (!isLower(s.charAt(0))) {
return s;
}
StringBuilder sb = new StringBuilder(s.length());
sb.append(Character.toUpperCase(s.charAt(0)));
sb.append(s.substring(1).toLowerCase());
return sb.toString();
}
// Precondition: s[start] is not punctuation
private static int nextBreak(String s, int start) {
int n = s.length();
char c1 = s.charAt(start);
int t1 = classify(c1);
for (int i = start + 1; i < n; i++) {
// shift (c1,t1) into (c0,t0)
// char c0 = c1; --- conceptually, but c0 won't be used
int t0 = t1;
c1 = s.charAt(i);
t1 = classify(c1);
switch (actionTable[t0 * 5 + t1]) {
case ACTION_CHECK_PUNCT:
if (isPunct(c1)) {
return i;
}
break;
case ACTION_CHECK_C2:
if (i < n - 1) {
char c2 = s.charAt(i + 1);
if (isLower(c2)) {
return i;
}
}
break;
case ACTION_BREAK:
return i;
}
}
return -1;
}
// the 5-category classification that we use in this code
// to find work breaks
static protected final int UPPER_LETTER = 0;
static protected final int LOWER_LETTER = 1;
static protected final int OTHER_LETTER = 2;
static protected final int DIGIT = 3;
static protected final int OTHER = 4;
/**
* Look up table for actions.
* type0*5+type1 would yield the action to be taken.
*/
private static final byte[] actionTable = new byte[5 * 5];
// action constants. see nextBreak for the meaning
static private final byte ACTION_CHECK_PUNCT = 0;
static private final byte ACTION_CHECK_C2 = 1;
static private final byte ACTION_BREAK = 2;
static private final byte ACTION_NOBREAK = 3;
/**
* Decide the action to be taken given
* the classification of the preceding character 't0' and
* the classification of the next character 't1'.
*/
private static byte decideAction(int t0, int t1) {
if (t0 == OTHER && t1 == OTHER) {
return ACTION_CHECK_PUNCT;
}
if (!xor(t0 == DIGIT, t1 == DIGIT)) {
return ACTION_BREAK;
}
if (t0 == LOWER_LETTER && t1 != LOWER_LETTER) {
return ACTION_BREAK;
}
if (!xor(t0 <= OTHER_LETTER, t1 <= OTHER_LETTER)) {
return ACTION_BREAK;
}
if (!xor(t0 == OTHER_LETTER, t1 == OTHER_LETTER)) {
return ACTION_BREAK;
}
if (t0 == UPPER_LETTER && t1 == UPPER_LETTER) {
return ACTION_CHECK_C2;
}
return ACTION_NOBREAK;
}
private static boolean xor(boolean x, boolean y) {
return (x && y) || (!x && !y);
}
static {
// initialize the action table
for (int t0 = 0; t0 < 5; t0++) {
for (int t1 = 0; t1 < 5; t1++) {
actionTable[t0 * 5 + t1] = decideAction(t0, t1);
}
}
}
/**
* Classify a character into 5 categories that determine the word break.
*/
protected static int classify(char c0) {
switch (Character.getType(c0)) {
case Character.UPPERCASE_LETTER:
return UPPER_LETTER;
case Character.LOWERCASE_LETTER:
return LOWER_LETTER;
case Character.TITLECASE_LETTER:
case Character.MODIFIER_LETTER:
case Character.OTHER_LETTER:
return OTHER_LETTER;
case Character.DECIMAL_DIGIT_NUMBER:
return DIGIT;
default:
return OTHER;
}
}
/**
* Tokenizes a string into words and capitalizes the first
* character of each word.
*
*
* This method uses a change in character type as a splitter
* of two words. For example, "abc100ghi" will be splitted into
* {"Abc", "100","Ghi"}.
*/
protected static List toWordList(String s) {
ArrayList ss = new ArrayList();
int n = s.length();
for (int i = 0; i < n;) {
// Skip punctuation
while (i < n) {
if (!isPunct(s.charAt(i))) {
break;
}
i++;
}
if (i >= n) {
break;
}
// Find next break and collect word
int b = nextBreak(s, i);
String w = (b == -1) ? s.substring(i) : s.substring(i, b);
ss.add(escape(capitalize(w)));
if (b == -1) {
break;
}
i = b;
}
// we can't guarantee a valid Java identifier anyway,
// so there's not much point in rejecting things in this way.
// if (ss.size() == 0)
// throw new IllegalArgumentException("Zero-length identifier");
return ss;
}
protected static String toMixedCaseName(List ss, boolean startUpper) {
StringBuilder sb = new StringBuilder();
if (!ss.isEmpty()) {
sb.append(startUpper ? ss.get(0) : ss.get(0).toLowerCase());
for (int i = 1; i < ss.size(); i++) {
sb.append(ss.get(i));
}
}
return sb.toString();
}
protected static String toMixedCaseVariableName(String[] ss,
boolean startUpper,
boolean cdrUpper) {
if (cdrUpper) {
for (int i = 1; i < ss.length; i++) {
ss[i] = capitalize(ss[i]);
}
}
StringBuilder sb = new StringBuilder();
if (ss.length > 0) {
sb.append(startUpper ? ss[0] : ss[0].toLowerCase());
for (int i = 1; i < ss.length; i++) {
sb.append(ss[i]);
}
}
return sb.toString();
}
/**
* Formats a string into "THIS_KIND_OF_FORMAT_ABC_DEF".
*
* @return
* Always return a string but there's no guarantee that
* the generated code is a valid Java identifier.
*/
protected static String toConstantName(String s) {
return toConstantName(toWordList(s));
}
/**
* Formats a string into "THIS_KIND_OF_FORMAT_ABC_DEF".
*
* @return
* Always return a string but there's no guarantee that
* the generated code is a valid Java identifier.
*/
protected static String toConstantName(List ss) {
StringBuilder sb = new StringBuilder();
if (!ss.isEmpty()) {
sb.append(ss.get(0).toUpperCase());
for (int i = 1; i < ss.size(); i++) {
sb.append('_');
sb.append(ss.get(i).toUpperCase());
}
}
return sb.toString();
}
/**
* Escapes characters is the given string so that they can be
* printed by only using US-ASCII characters.
*
* The escaped characters will be appended to the given
* StringBuffer.
*
* @param sb
* StringBuffer that receives escaped string.
* @param s
* String to be escaped. s.substring(start)
* will be escaped and copied to the string buffer.
*/
protected static void escape(StringBuilder sb, String s, int start) {
int n = s.length();
for (int i = start; i < n; i++) {
char c = s.charAt(i);
if (Character.isJavaIdentifierPart(c)) {
sb.append(c);
} else {
sb.append('_');
if (c <= '\u000f') {
sb.append("000");
} else if (c <= '\u00ff') {
sb.append("00");
} else if (c <= '\u0fff') {
sb.append('0');
}
sb.append(Integer.toString(c, 16));
}
}
}
/**
* Escapes characters that are unusable as Java identifiers
* by replacing unsafe characters with safe characters.
*/
private static String escape(String s) {
int n = s.length();
for (int i = 0; i < n; i++) {
if (!Character.isJavaIdentifierPart(s.charAt(i))) {
StringBuilder sb = new StringBuilder(s.substring(0, i));
escape(sb, s, i);
return sb.toString();
}
}
return s;
}
/**
* Checks if a given string is usable as a Java identifier.
*/
protected static boolean isJavaIdentifier(String s) {
if (s.length() == 0) {
return false;
}
if (reservedKeywords.contains(s)) {
return false;
}
if (!Character.isJavaIdentifierStart(s.charAt(0))) {
return false;
}
for (int i = 1; i < s.length(); i++) {
if (!Character.isJavaIdentifierPart(s.charAt(i))) {
return false;
}
}
return true;
}
/**
* Checks if the given string is a valid Java package name.
*/
protected static boolean isJavaPackageName(String s) {
while (s.length() != 0) {
int idx = s.indexOf('.');
if (idx == -1) {
idx = s.length();
}
if (!isJavaIdentifier(s.substring(0, idx))) {
return false;
}
s = s.substring(idx);
if (s.length() != 0) {
s = s.substring(1); // remove '.'
}
}
return true;
}
/** All reserved keywords of Java. */
private static HashSet reservedKeywords = new HashSet();
static {
// see http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html
String[] words = new String[]{
"abstract",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
// technically these are not reserved words but they cannot be used as identifiers.
"true",
"false",
"null",
// and I believe assert is also a new keyword
"assert",
// and 5.0 keywords
"enum"
};
for (String word : words) {
reservedKeywords.add(word);
}
}
}