001: package org.objectweb.celtix.jaxb;
002:
003: import java.lang.reflect.Method;
004: import java.net.URI;
005: import java.net.URISyntaxException;
006:
007: import java.util.ArrayList;
008: import java.util.Arrays;
009: import java.util.HashMap;
010: import java.util.HashSet;
011: import java.util.List;
012: import java.util.Map;
013: import java.util.Set;
014: import java.util.StringTokenizer;
015: import java.util.concurrent.Future;
016:
017: import javax.xml.ws.AsyncHandler;
018: import javax.xml.ws.Response;
019:
020: public final class JAXBUtils {
021:
022: public enum IdentifierType {
023: CLASS, INTERFACE, GETTER, SETTER, VARIABLE, CONSTANT
024: };
025:
026: public static final String JAXB_URI = "http://java.sun.com/xml/ns/jaxb";
027:
028: private static final Set<String> KEYWORDS = new HashSet<String>(
029: Arrays.asList("abstract", "continue", "for", "new",
030: "switch", "assert", "default", "if", "package",
031: "synchronized", "boolean", "do", "goto", "private",
032: "this", "break", "double", "implements",
033: "protected", "throw", "byte", "else", "import",
034: "public", "throws", "case", "enum", "instanceof",
035: "return", "transient", "catch", "extends", "int",
036: "short", "try", "char", "final", "interface",
037: "static", "void", "class", "finally", "long",
038: "strictfp", "volatile", "const", "float", "native",
039: "super", "while"));
040:
041: private static final char[] XML_NAME_PUNCTUATION_CHARS = new char[] {
042: /* hyphen */'\u002D',
043: /* period */'\u002E',
044: /* colon */'\u003A',
045: /* dot */'\u00B7',
046: /* greek ano teleia */'\u0387',
047: /* arabic end of ayah */'\u06DD',
048: /* arabic start of rub el hizb */'\u06DE',
049: /* underscore */'\u005F', };
050:
051: private static final String XML_NAME_PUNCTUATION_STRING = new String(
052: XML_NAME_PUNCTUATION_CHARS);
053:
054: private static final Map<String, String> BUILTIN_DATATYPES_MAP;
055: private static final Map<String, Class> HOLDER_TYPES_MAP;
056:
057: static {
058: BUILTIN_DATATYPES_MAP = new HashMap<String, String>();
059: BUILTIN_DATATYPES_MAP.put("string", "java.lang.String");
060: BUILTIN_DATATYPES_MAP.put("integer", "java.math.BigInteger");
061: BUILTIN_DATATYPES_MAP.put("int", "int");
062: BUILTIN_DATATYPES_MAP.put("long", "long");
063: BUILTIN_DATATYPES_MAP.put("short", "short");
064: BUILTIN_DATATYPES_MAP.put("decimal", "java.math.BigDecimal");
065: BUILTIN_DATATYPES_MAP.put("float", "float");
066: BUILTIN_DATATYPES_MAP.put("double", "double");
067: BUILTIN_DATATYPES_MAP.put("boolean", "boolean");
068: BUILTIN_DATATYPES_MAP.put("byte", "byte");
069: BUILTIN_DATATYPES_MAP.put("QName", "javax.xml.namespace.QName");
070: BUILTIN_DATATYPES_MAP.put("dateTime",
071: "javax.xml.datatype.XMLGregorianCalendar");
072: BUILTIN_DATATYPES_MAP.put("base64Binary", "byte[]");
073: BUILTIN_DATATYPES_MAP.put("hexBinary", "byte[]");
074: BUILTIN_DATATYPES_MAP.put("unsignedInt", "long");
075: BUILTIN_DATATYPES_MAP.put("unsignedShort", "short");
076: BUILTIN_DATATYPES_MAP.put("unsignedByte", "byte");
077: BUILTIN_DATATYPES_MAP.put("time",
078: "javax.xml.datatype.XMLGregorianCalendar");
079: BUILTIN_DATATYPES_MAP.put("date",
080: "javax.xml.datatype.XMLGregorianCalendar");
081: BUILTIN_DATATYPES_MAP.put("gYear",
082: "javax.xml.datatype.XMLGregorianCalendar");
083: BUILTIN_DATATYPES_MAP.put("gYearMonth",
084: "javax.xml.datatype.XMLGregorianCalendar");
085: BUILTIN_DATATYPES_MAP.put("gMonth",
086: "javax.xml.datatype.XMLGregorianCalendar");
087: BUILTIN_DATATYPES_MAP.put("gMonthDay",
088: "javax.xml.datatype.XMLGregorianCalendar");
089: BUILTIN_DATATYPES_MAP.put("gDay",
090: "javax.xml.datatype.XMLGregorianCalendar");
091: BUILTIN_DATATYPES_MAP.put("duration",
092: "javax.xml.datatype.Duration");
093: BUILTIN_DATATYPES_MAP.put("NOTATION",
094: "javax.xml.namespace.QName");
095: BUILTIN_DATATYPES_MAP.put("string", "java.lang.String");
096:
097: HOLDER_TYPES_MAP = new HashMap<String, Class>();
098: HOLDER_TYPES_MAP.put("int", java.lang.Integer.class);
099: HOLDER_TYPES_MAP.put("long", java.lang.Long.class);
100: HOLDER_TYPES_MAP.put("short", java.lang.Short.class);
101: HOLDER_TYPES_MAP.put("float", java.lang.Float.class);
102: HOLDER_TYPES_MAP.put("double", java.lang.Double.class);
103: HOLDER_TYPES_MAP.put("boolean", java.lang.Boolean.class);
104: HOLDER_TYPES_MAP.put("byte", java.lang.Byte.class);
105: }
106:
107: /**
108: * prevents instantiation
109: *
110: */
111: private JAXBUtils() {
112: }
113:
114: public static String builtInTypeToJavaType(String type) {
115: return BUILTIN_DATATYPES_MAP.get(type);
116: }
117:
118: public static Class holderClass(String type) {
119: return HOLDER_TYPES_MAP.get(type);
120: }
121:
122: /**
123: * Checks if the specified word is a Java keyword (as of 1.5).
124: *
125: * @param word the word to check.
126: * @return true if the word is a keyword.
127: */
128: public static boolean isJavaKeyword(String word) {
129: return KEYWORDS.contains(word);
130: }
131:
132: /**
133: * Generates a Java package name from a URI according to the
134: * algorithm outlined in JAXB 2.0.
135: *
136: * @param namespaceURI the namespace URI.
137: * @return the package name.
138: */
139: public static String namespaceURIToPackage(String namespaceURI) {
140: try {
141: return nameSpaceURIToPackage(new URI(namespaceURI));
142: } catch (URISyntaxException ex) {
143: return null;
144: }
145: }
146:
147: /**
148: * Generates a Java package name from a URI according to the
149: * algorithm outlined in JAXB 2.0.
150: *
151: * @param namespaceURI the namespace URI.
152: * @return the package name.
153: */
154: public static String nameSpaceURIToPackage(URI uri) {
155:
156: StringBuffer packageName = new StringBuffer();
157:
158: String authority = uri.getAuthority();
159:
160: if (null != authority && !"".equals(authority)) {
161: if ("urn".equals(uri.getScheme())) {
162: packageName.append(authority);
163: for (int i = 0; i < packageName.length(); i++) {
164: if (packageName.charAt(i) == '-') {
165: packageName.setCharAt(i, '.');
166: }
167: }
168: authority = packageName.toString();
169: packageName.setLength(0);
170: }
171:
172: StringTokenizer st = new StringTokenizer(authority, ".");
173: if (st.hasMoreTokens()) {
174: String token = null;
175: while (st.hasMoreTokens()) {
176: token = st.nextToken();
177: if (packageName.length() == 0) {
178: if ("www".equals(token)) {
179: continue;
180: }
181: } else {
182: packageName.insert(0, ".");
183: }
184: packageName.insert(0, token);
185: }
186:
187: if (!("com".equals(token) || "gov".equals(token)
188: || "net".equals(token) || "org".equals(token) || "edu"
189: .equals(token))) {
190: packageName.setLength(0);
191:
192: }
193: }
194: }
195:
196: String path = uri.getPath();
197: int index = path.lastIndexOf('.');
198: if (index < 0) {
199: index = path.length();
200: }
201: StringTokenizer st = new StringTokenizer(path.substring(0,
202: index), "/");
203: while (st.hasMoreTokens()) {
204: String token = st.nextToken();
205: if (packageName.length() > 0) {
206: packageName.append('.');
207: }
208: packageName.append(normalizePackageNamePart(token));
209: }
210: return packageName.toString();
211: }
212:
213: public static boolean isAsync(Method method) {
214: return method.getName().endsWith("Async")
215: && (method.getReturnType().equals(Response.class) || method
216: .getReturnType().equals(Future.class));
217: }
218:
219: public static boolean isAsyncPolling(Method method) {
220: return method.getName().endsWith("Async")
221: && (method.getReturnType().equals(Response.class));
222: }
223:
224: public static boolean isAsyncCallback(Method method) {
225: Class[] paramTypes = method.getParameterTypes();
226: return method.getName().endsWith("Async")
227: && (method.getReturnType().equals(Future.class) && AsyncHandler.class
228: .isAssignableFrom(paramTypes[paramTypes.length - 1]));
229: }
230:
231: private static String normalizePackageNamePart(String name) {
232: StringBuffer sname = new StringBuffer(name.toLowerCase());
233:
234: for (int i = 0; i < sname.length(); i++) {
235: sname.setCharAt(i, Character.toLowerCase(sname.charAt(i)));
236: }
237:
238: for (int i = 0; i < sname.length(); i++) {
239: if (!Character.isJavaIdentifierPart(sname.charAt(i))) {
240: sname.setCharAt(i, '_');
241: }
242: }
243:
244: if (isJavaKeyword(sname.toString())) {
245: sname.insert(0, '_');
246: }
247:
248: if (!Character.isJavaIdentifierStart(sname.charAt(0))) {
249: sname.insert(0, '_');
250: }
251:
252: return sname.toString();
253: }
254:
255: /**
256: * Converts an XML name to a Java identifier according to the mapping
257: * algorithm outlines in the JAXB specification
258: *
259: * @param name the XML name
260: * @return the Java identifier
261: */
262: public static String nameToIdentifier(String name,
263: IdentifierType type) {
264:
265: if (null == name || name.length() == 0) {
266: return name;
267: }
268:
269: // algorithm will not change an XML name that is already a legal and
270: // conventional (!) Java class, method, or constant identifier
271:
272: boolean legalIdentifier = false;
273: StringBuffer buf = new StringBuffer(name);
274: legalIdentifier = Character
275: .isJavaIdentifierStart(buf.charAt(0));
276:
277: for (int i = 1; i < name.length() && legalIdentifier; i++) {
278: legalIdentifier = legalIdentifier
279: && Character.isJavaIdentifierPart(buf.charAt(i));
280: }
281:
282: boolean conventionalIdentifier = isConventionalIdentifier(buf,
283: type);
284: if (legalIdentifier && conventionalIdentifier) {
285: if (JAXBUtils.isJavaKeyword(name)
286: && type == IdentifierType.VARIABLE) {
287: name = normalizePackageNamePart(name.toString());
288: }
289: return name;
290: }
291:
292: // split into words
293:
294: List<String> words = new ArrayList<String>();
295:
296: StringTokenizer st = new StringTokenizer(name,
297: XML_NAME_PUNCTUATION_STRING);
298: while (st.hasMoreTokens()) {
299: words.add(st.nextToken());
300: }
301:
302: for (int i = 0; i < words.size(); i++) {
303: splitWord(words, i);
304: }
305:
306: return makeConventionalIdentifier(words, type);
307: }
308:
309: private static void splitWord(List<String> words, int listIndex) {
310: String word = words.get(listIndex);
311: if (word.length() <= 1) {
312: return;
313: }
314: int index = listIndex + 1;
315: StringBuffer sword = new StringBuffer(word);
316: int first = 0;
317: char firstChar = sword.charAt(first);
318: if (Character.isLowerCase(firstChar)) {
319: sword.setCharAt(first, Character.toUpperCase(firstChar));
320: }
321: int i = 1;
322:
323: while (i < sword.length()) {
324: if (Character.isDigit(firstChar)) {
325: while (i < sword.length()
326: && Character.isDigit(sword.charAt(i))) {
327: i++;
328: }
329: } else if (isCasedLetter(firstChar)) {
330: boolean previousIsLower = Character
331: .isLowerCase(firstChar);
332: while (i < sword.length()
333: && isCasedLetter(sword.charAt(i))) {
334: if (Character.isUpperCase(sword.charAt(i))
335: && previousIsLower) {
336: break;
337: }
338: previousIsLower = Character.isLowerCase(sword
339: .charAt(i));
340: i++;
341: }
342: } else {
343: // first must be a mark or an uncased letter
344: while (i < sword.length()
345: && (isMark(sword.charAt(i)) || !isCasedLetter(sword
346: .charAt(i)))) {
347: i++;
348: }
349: }
350:
351: // characters from first to i are all either
352: // * digits
353: // * upper or lower case letters, with only the first one an upper
354: // * uncased letters or marks
355:
356: String newWord = sword.substring(first, i);
357: words.add(index, newWord);
358: index++;
359: if (i >= sword.length()) {
360: break;
361: } else {
362: first = i;
363: firstChar = sword.charAt(first);
364: }
365: }
366:
367: if (index > (listIndex + 1)) {
368: words.remove(listIndex);
369: }
370: }
371:
372: private static boolean isMark(char c) {
373: return Character.isJavaIdentifierPart(c)
374: && !Character.isLetter(c) && !Character.isDigit(c);
375: }
376:
377: private static boolean isCasedLetter(char c) {
378: return Character.isUpperCase(c) || Character.isLowerCase(c);
379: }
380:
381: private static boolean isConventionalIdentifier(StringBuffer buf,
382: IdentifierType type) {
383: if (null == buf || buf.length() == 0) {
384: return false;
385: }
386: boolean result = false;
387: if (IdentifierType.CONSTANT == type) {
388: for (int i = 0; i < buf.length(); i++) {
389: if (Character.isLowerCase(buf.charAt(i))) {
390: return false;
391: }
392: }
393: result = true;
394: } else if (IdentifierType.VARIABLE == type) {
395: result = Character.isLowerCase(buf.charAt(0));
396: } else {
397: int pos = 3;
398: if (IdentifierType.GETTER == type
399: && !(buf.length() >= pos && "get".equals(buf
400: .subSequence(0, 3)))) {
401: return false;
402: } else if (IdentifierType.SETTER == type
403: && !(buf.length() >= pos && "set".equals(buf
404: .subSequence(0, 3)))) {
405: return false;
406: } else {
407: pos = 0;
408: }
409: result = Character.isUpperCase(buf.charAt(pos));
410: }
411: return result;
412: }
413:
414: private static String makeConventionalIdentifier(
415: List<String> words, IdentifierType type) {
416: StringBuffer buf = new StringBuffer();
417: boolean firstWord = true;
418: if (IdentifierType.GETTER == type) {
419: buf.append("get");
420: } else if (IdentifierType.SETTER == type) {
421: buf.append("set");
422: }
423: for (String w : words) {
424: int l = buf.length();
425: if (l > 0 && IdentifierType.CONSTANT == type) {
426: buf.append('_');
427: l++;
428: }
429: buf.append(w);
430: if (IdentifierType.CONSTANT == type) {
431: for (int i = l; i < buf.length(); i++) {
432: if (Character.isLowerCase(buf.charAt(i))) {
433: buf.setCharAt(i, Character.toUpperCase(buf
434: .charAt(i)));
435: }
436: }
437: } else if (IdentifierType.VARIABLE == type) {
438: if (firstWord && Character.isUpperCase(buf.charAt(l))) {
439: buf.setCharAt(l, Character.toLowerCase(buf
440: .charAt(l)));
441: }
442: } else {
443: if (firstWord && Character.isLowerCase(buf.charAt(l))) {
444: buf.setCharAt(l, Character.toUpperCase(buf
445: .charAt(l)));
446: }
447: }
448: firstWord = false;
449: }
450: return buf.toString();
451: }
452:
453: }
|