blob: 6d0ff04693ca2c706211aa14c24f15044d144a9e [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() ||
filename.substring(i + 1).toLowerCase() == "html"))
{
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;
StringBuffer buf = new StringBuffer(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
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);
}
}