blob: da710170c5bbf58b0c396182f3c10c384b9c5da9 [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.common;
import javax.xml.namespace.QName;
import java.util.*;
public class NameUtil {
// punctuation characters
public final static char HYPHEN = '\u002D';
public final static char PERIOD = '\u002E';
public final static char COLON = '\u003A';
public final static char USCORE = '\u005F';
public final static char DOT = '\u00B7';
public final static char TELEIA = '\u0387';
public final static char AYAH = '\u06DD';
public final static char ELHIZB = '\u06DE';
private final static boolean DEBUG = false;
private final static Set javaWords = new HashSet(Arrays.asList(
new String[]
{
"assert",
"abstract",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum", // since JDK1.5
"extends",
"false", // not a keyword
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"null", // not a keyword
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"threadsafe",
"throw",
"throws",
"transient",
"true", // not a keyword
"try",
"void",
"volatile",
"while",
}
));
private final static Set extraWords = new HashSet(Arrays.asList(
new String[]
{
"i", // used for indexes
"target", // used for parameter
"org", // used for package names
"com", // used for package names
}
));
/*
private final static Set javaNames = new HashSet(Arrays.asList(
new String[]
{
"CharSequence",
"Cloneable",
"Comparable",
"Runnable",
"Boolean",
"Byte",
"Character",
"Class",
"ClassLoader",
"Compiler",
"Double",
"Float",
"InheritableThreadLocal",
"Integer",
"Long",
"Math",
"Number",
"Object",
"Package",
"Process",
"Runtime",
"RuntimePermission",
"SecurityManager",
"Short",
"StackTraceElement",
"StrictMath",
"String",
"StringBuffer",
"System",
"Thread",
"ThreadGroup",
"ThreadLocal",
"Throwable",
"Void",
"ArithmeticException",
"ArrayIndexOutOfBoundsException",
"ArrayStoreException",
"ClassCastException",
"ClassNotFoundException",
"CloneNotSupportedException",
"Exception",
"IllegalAccessException",
"IllegalArgumentException",
"IllegalMonitorStateException",
"IllegalStateException",
"IllegalThreadStateException",
"IndexOutOfBoundsException",
"InstantiationException",
"InterruptedException",
"NegativeArraySizeException",
"NoSuchFieldException",
"NoSuchMethodException",
"NullPointerException",
"NumberFormatException",
"RuntimeException",
"SecurityException",
"StringIndexOutOfBoundsException",
"UnsupportedOperationException",
"AbstractMethodError",
"AssertionError",
"ClassCircularityError",
"ClassFormatError",
"Error",
"ExceptionInInitializerError",
"IllegalAccessError",
"IncompatibleClassChangeError",
"InstantiationError",
"InternalError",
"LinkageError",
"NoClassDefFoundError",
"NoSuchFieldError",
"NoSuchMethodError",
"OutOfMemoryError",
"StackOverflowError",
"ThreadDeath",
"UnknownError",
"UnsatisfiedLinkError",
"UnsupportedClassVersionError",
"VerifyError",
"VirtualMachineError",
}
));
*/
private final static Set javaNames = new HashSet(Arrays.asList(
new String[]
{
// 1. all the Java.lang classes [1.4.1 JDK].
"CharSequence",
"Cloneable",
"Comparable",
"Runnable",
"Boolean",
"Byte",
"Character",
"Class",
"ClassLoader",
"Compiler",
"Double",
"Float",
"InheritableThreadLocal",
"Integer",
"Long",
"Math",
"Number",
"Object",
"Package",
"Process",
"Runtime",
"RuntimePermission",
"SecurityManager",
"Short",
"StackTraceElement",
"StrictMath",
"String",
"StringBuffer",
"System",
"Thread",
"ThreadGroup",
"ThreadLocal",
"Throwable",
"Void",
"ArithmeticException",
"ArrayIndexOutOfBoundsException",
"ArrayStoreException",
"ClassCastException",
"ClassNotFoundException",
"CloneNotSupportedException",
"Exception",
"IllegalAccessException",
"IllegalArgumentException",
"IllegalMonitorStateException",
"IllegalStateException",
"IllegalThreadStateException",
"IndexOutOfBoundsException",
"InstantiationException",
"InterruptedException",
"NegativeArraySizeException",
"NoSuchFieldException",
"NoSuchMethodException",
"NullPointerException",
"NumberFormatException",
"RuntimeException",
"SecurityException",
"StringIndexOutOfBoundsException",
"UnsupportedOperationException",
"AbstractMethodError",
"AssertionError",
"ClassCircularityError",
"ClassFormatError",
"Error",
"ExceptionInInitializerError",
"IllegalAccessError",
"IncompatibleClassChangeError",
"InstantiationError",
"InternalError",
"LinkageError",
"NoClassDefFoundError",
"NoSuchFieldError",
"NoSuchMethodError",
"OutOfMemoryError",
"StackOverflowError",
"ThreadDeath",
"UnknownError",
"UnsatisfiedLinkError",
"UnsupportedClassVersionError",
"VerifyError",
"VirtualMachineError",
// 2. other classes used as primitive types by xml beans
"BigInteger",
"BigDecimal",
"Enum",
"Date",
"GDate",
"GDuration",
"QName",
"List",
// 3. the top few org.apache.xmlbeans names
"XmlObject",
"XmlCursor",
"XmlBeans",
"SchemaType",
}
));
public static boolean isValidJavaIdentifier(String id) {
if (id == null) {
throw new IllegalArgumentException("id cannot be null");
}
int len = id.length();
if (len == 0) {
return false;
}
if (javaWords.contains(id)) {
return false;
}
if (!Character.isJavaIdentifierStart(id.charAt(0))) {
return false;
}
for (int i = 1; i < len; i++) {
if (!Character.isJavaIdentifierPart(id.charAt(i))) {
return false;
}
}
return true;
}
public static String getClassNameFromQName(QName qname) {
return getClassNameFromQName(qname, false);
}
public static String getClassNameFromQName(QName qname, boolean useJaxRpcRules) {
String java_type = upperCamelCase(qname.getLocalPart(), useJaxRpcRules);
String uri = qname.getNamespaceURI();
String java_pkg = null;
java_pkg = getPackageFromNamespace(uri, useJaxRpcRules);
if (java_pkg != null) {
return java_pkg + "." + java_type;
} else {
return java_type;
}
}
private static final String JAVA_NS_PREFIX = "java:";
private static final String LANG_PREFIX = "java.";
public static String getNamespaceFromPackage(final Class clazz) {
Class curr_clazz = clazz;
while (curr_clazz.isArray()) {
curr_clazz = curr_clazz.getComponentType();
}
String fullname = clazz.getName();
int lastdot = fullname.lastIndexOf('.');
String pkg_name = lastdot < 0 ? "" : fullname.substring(0, lastdot);
//special case for builtin types
/*
if (curr_clazz.isPrimitive())
{
pkg_name = c.getJavaLanguageNamespaceUri();
}
else if (pkg_name.startsWith(LANG_PREFIX))
{
final String rem_str = pkg_name.substring(LANG_PREFIX.length());
pkg_name = c.getJavaLanguageNamespaceUri() + "." + rem_str;
}
*/
return JAVA_NS_PREFIX + pkg_name;
}
private static boolean isUriSchemeChar(char ch) {
return (ch >= 'a' && ch <= 'z' ||
ch >= 'A' && ch <= 'Z' ||
ch >= '0' && ch <= '9' ||
ch == '-' || ch == '.' || ch == '+');
}
private static boolean isUriAlphaChar(char ch) {
return (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z');
}
private static int findSchemeColon(String uri) {
int len = uri.length();
if (len == 0) {
return -1;
}
if (!isUriAlphaChar(uri.charAt(0))) {
return -1;
}
int i;
for (i = 1; i < len; i++) {
if (!isUriSchemeChar(uri.charAt(i))) {
break;
}
}
if (i == len) {
return -1;
}
if (uri.charAt(i) != ':') {
return -1;
}
// consume consecutive colons
for (; i < len; i++) {
if (uri.charAt(i) != ':') {
break;
}
}
// for the "scheme:::" case, return len-1
return i - 1;
}
private static String jls77String(String name) {
StringBuilder buf = new StringBuilder(name);
for (int i = 0; i < name.length(); i++) {
// We need to also make sure that our package names don't contain the
// "$" character in them, which, although a valid Java identifier part,
// would create confusion when trying to generate fully-qualified names
if (!Character.isJavaIdentifierPart(buf.charAt(i)) || '$' == buf.charAt(i)) {
buf.setCharAt(i, '_');
}
}
if (buf.length() == 0 || !Character.isJavaIdentifierStart(buf.charAt(0))) {
buf.insert(0, '_');
}
if (isJavaReservedWord(name)) {
buf.append('_');
}
return buf.toString();
}
private static List splitDNS(String dns) {
// JAXB says: only split+reverse DNS if TLD matches known TLDs or ISO 3166
// We are ignoring this now (TH)
List result = new ArrayList();
int end = dns.length();
int begin = dns.lastIndexOf('.');
for (; begin != -1; begin--) {
if (dns.charAt(begin) == '.') {
result.add(jls77String(dns.substring(begin + 1, end)));
end = begin;
}
}
result.add(jls77String(dns.substring(0, end)));
// JAXB draft example implies removal of www
if (result.size() >= 3 &&
((String) result.get(result.size() - 1)).toLowerCase().equals("www")) {
result.remove(result.size() - 1);
}
return result;
}
private static String processFilename(String filename) {
// JAXB says: strip 2 or 3 letter extension or ".html"
int i = filename.lastIndexOf('.');
if (i > 0 && (
i + 1 + 2 == filename.length() ||
i + 1 + 3 == filename.length() ||
"html".equals(filename.substring(i + 1).toLowerCase()))) {
return filename.substring(0, i);
}
return filename;
}
public static String getPackageFromNamespace(String uri) {
return getPackageFromNamespace(uri, false);
}
public static String getPackageFromNamespace(String uri, boolean useJaxRpcRules) {
// special case: no namespace -> package "noNamespace"
if (uri == null || uri.length() == 0) {
return "noNamespace";
}
// apply draft JAXB rules
int len = uri.length();
int i = findSchemeColon(uri);
List result = null;
if (i == len - 1) {
// XMLBEANS-57: colon is at end so just use scheme as the package name
result = new ArrayList();
result.add(uri.substring(0, i));
} else if (i >= 0 && uri.substring(0, i).equals("java")) {
result = Arrays.asList(uri.substring(i + 1).split("\\."));
} else {
result = new ArrayList();
outer:
for (i = i + 1; i < len; ) {
while (uri.charAt(i) == '/') {
if (++i >= len) {
break outer;
}
}
int start = i;
while (uri.charAt(i) != '/') {
if (++i >= len) {
break;
}
}
int end = i;
result.add(uri.substring(start, end));
}
if (result.size() > 1) {
result.set(result.size() - 1, processFilename((String) result.get(result.size() - 1)));
}
if (result.size() > 0) {
List splitdns = splitDNS((String) result.get(0));
result.remove(0);
result.addAll(0, splitdns);
}
}
StringBuilder buf = new StringBuilder();
for (Iterator it = result.iterator(); it.hasNext(); ) {
String part = nonJavaKeyword(lowerCamelCase((String) it.next(), useJaxRpcRules, true));
if (part.length() > 0) {
buf.append(part);
buf.append('.');
}
}
if (buf.length() == 0) {
return "noNamespace";
}
if (useJaxRpcRules) {
return buf.substring(0, buf.length() - 1).toLowerCase();
}
return buf.substring(0, buf.length() - 1); // chop off extra dot
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(upperCaseUnderbar(args[i]));
}
}
/**
* Returns a upper-case-and-underbar string using the JAXB rules.
* Always starts with a capital letter that is a valid
* java identifier start. (If JAXB rules don't produce
* one, then "X_" is prepended.)
*/
public static String upperCaseUnderbar(String xml_name) {
StringBuilder buf = new StringBuilder();
List words = splitWords(xml_name, false);
final int sz = words.size() - 1;
if (sz >= 0 && !Character.isJavaIdentifierStart(((String) words.get(0)).charAt(0))) {
buf.append("X_");
}
for (int i = 0; i < sz; i++) {
buf.append((String) words.get(i));
buf.append(USCORE);
}
if (sz >= 0) {
buf.append((String) words.get(sz));
}
//upcase entire buffer
final int len = buf.length();
for (int j = 0; j < len; j++) {
char c = buf.charAt(j);
buf.setCharAt(j, Character.toUpperCase(c));
}
return buf.toString();
}
/**
* Returns a camel-cased string using the JAXB rules.
* Always starts with a capital letter that is a valid
* java identifier start. (If JAXB rules don't produce
* one, then "X" is prepended.)
*/
public static String upperCamelCase(String xml_name) {
return upperCamelCase(xml_name, false);
}
/**
* Returns a camel-cased string, but either JAXB or JAX-RPC rules
* are used
*/
public static String upperCamelCase(String xml_name, boolean useJaxRpcRules) {
StringBuilder buf = new StringBuilder();
List words = splitWords(xml_name, useJaxRpcRules);
if (words.size() > 0) {
if (!Character.isJavaIdentifierStart(((String) words.get(0)).charAt(0))) {
buf.append("X");
}
Iterator itr = words.iterator();
while (itr.hasNext()) {
buf.append((String) itr.next());
}
}
return buf.toString();
}
/**
* Returns a camel-cased string using the JAXB rules,
* where the first component is lowercased. Note that
* if the first component is an acronym, the whole
* thigns gets lowercased.
* Always starts with a lowercase letter that is a valid
* java identifier start. (If JAXB rules don't produce
* one, then "x" is prepended.)
*/
public static String lowerCamelCase(String xml_name) {
return lowerCamelCase(xml_name, false, true);
}
/**
* Returns a camel-cased string using the JAXB or JAX-RPC rules
*/
public static String lowerCamelCase(String xml_name, boolean useJaxRpcRules,
boolean fixGeneratedName) {
StringBuilder buf = new StringBuilder();
List words = splitWords(xml_name, useJaxRpcRules);
if (words.size() > 0) {
String first = ((String) words.get(0)).toLowerCase();
char f = first.charAt(0);
if (!Character.isJavaIdentifierStart(f) && fixGeneratedName) {
buf.append("x");
}
buf.append(first);
Iterator itr = words.iterator();
itr.next(); // skip already-lowercased word
while (itr.hasNext()) {
buf.append((String) itr.next());
}
}
return buf.toString();
}
public static String upperCaseFirstLetter(String s) {
if (s.length() == 0 || Character.isUpperCase(s.charAt(0))) {
return s;
}
StringBuilder buf = new StringBuilder(s);
buf.setCharAt(0, Character.toUpperCase(buf.charAt(0)));
return buf.toString();
}
/**
* split an xml name into words via JAXB approach, upcasing first
* letter of each word as needed, if upcase is true
* <p>
* ncname is xml ncname (i.e. no colons).
*/
private static void addCapped(List list, String str) {
if (str.length() > 0) {
list.add(upperCaseFirstLetter(str));
}
}
public static List splitWords(String name, boolean useJaxRpcRules) {
List list = new ArrayList();
int len = name.length();
int start = 0;
int prefix = START;
for (int i = 0; i < len; i++) {
int current = getCharClass(name.charAt(i), useJaxRpcRules);
if (prefix != PUNCT && current == PUNCT) {
addCapped(list, name.substring(start, i));
while ((current = getCharClass(name.charAt(i), useJaxRpcRules)) == PUNCT) {
if (++i >= len) {
return list;
}
}
start = i;
} else if ((prefix == DIGIT) != (current == DIGIT) ||
(prefix == LOWER && current != LOWER) ||
(isLetter(prefix) != isLetter(current))) {
addCapped(list, name.substring(start, i));
start = i;
} else if (prefix == UPPER && current == LOWER && i > start + 1) {
addCapped(list, name.substring(start, i - 1));
start = i - 1;
}
prefix = current;
}
addCapped(list, name.substring(start));
return list;
}
//char classes
private final static int START = 0;
private final static int PUNCT = 1;
private final static int DIGIT = 2;
private final static int MARK = 3;
private final static int UPPER = 4;
private final static int LOWER = 5;
private final static int NOCASE = 6;
public static int getCharClass(char c, boolean useJaxRpcRules) {
//ordering is important here.
if (isPunctuation(c, useJaxRpcRules)) {
return PUNCT;
} else if (Character.isDigit(c)) {
return DIGIT;
} else if (Character.isUpperCase(c)) {
return UPPER;
} else if (Character.isLowerCase(c)) {
return LOWER;
} else if (Character.isLetter(c)) {
return NOCASE;
} else if (Character.isJavaIdentifierPart(c)) {
return MARK;
} else {
return PUNCT; // not covered by JAXB: treat it as punctuation
}
}
private static boolean isLetter(int state) {
return (state == UPPER
|| state == LOWER
|| state == NOCASE);
}
public static boolean isPunctuation(char c, boolean useJaxRpcRules) {
return (c == HYPHEN
|| c == PERIOD
|| c == COLON
|| c == DOT
|| (c == USCORE && !useJaxRpcRules)
|| c == TELEIA
|| c == AYAH
|| c == ELHIZB);
}
/**
* Intended to be applied to a lowercase-starting identifier that
* may collide with a Java keyword. If it does collide, this
* prepends the letter "x".
*/
public static String nonJavaKeyword(String word) {
if (isJavaReservedWord(word)) {
return 'x' + word;
}
return word;
}
/**
* Intended to be applied to a lowercase-starting identifier that
* may collide with a Java keyword. If it does collide, this
* prepends the letter "x".
*/
public static String nonExtraKeyword(String word) {
if (isExtraReservedWord(word, true)) {
return word + "Value";
}
return word;
}
/**
* Intended to be applied to an uppercase-starting identifier that
* may collide with a java.lang.* classname. If it does collide, this
* prepends the letter "X".
*/
public static String nonJavaCommonClassName(String name) {
if (isJavaCommonClassName(name)) {
return "X" + name;
}
return name;
}
private static boolean isJavaReservedWord(String word) {
return isJavaReservedWord(word, true);
}
private static boolean isJavaReservedWord(String word, boolean ignore_case) {
if (ignore_case) {
word = word.toLowerCase();
}
return javaWords.contains(word);
}
private static boolean isExtraReservedWord(String word, boolean ignore_case) {
if (ignore_case) {
word = word.toLowerCase();
}
return extraWords.contains(word);
}
public static boolean isJavaCommonClassName(String word) {
return javaNames.contains(word);
}
}