Repository: incubator-juneau
Updated Branches:
  refs/heads/master 24209d4b8 -> c79850318


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java
new file mode 100644
index 0000000..6b72fdf
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/utils/CalendarUtils.java
@@ -0,0 +1,697 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you 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.juneau.utils;
+
+import static org.apache.juneau.internal.DateUtils.*;
+
+import java.text.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+import javax.xml.bind.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Utility class for converting {@link Calendar} and {@link Date} objects to 
common serialized forms.
+ */
+public class CalendarUtils {
+
+       private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+       /**
+        * Valid conversion formats.
+        */
+       public static enum Format {
+
+               /**
+                * Transform to ISO8601 date-time-local strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"2001-07-04T15:30:45"</js>
+                * </ul>
+                *
+                * <h6 class='topic'>Example input:</h6>
+                * <ul>
+                *      <li><js>"2001-07-04T15:30:45"</js>
+                *      <li><js>"2001-07-04T15:30:45.1"</js>
+                *      <li><js>"2001-07-04T15:30"</js>
+                *      <li><js>"2001-07-04"</js>
+                *      <li><js>"2001-07"</js>
+                *      <li><js>"2001"</js>
+                * </ul>
+                */
+               ISO8601_DTL,
+
+               /**
+                * Transform to ISO8601 date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"2001-07-04T15:30:45-05:00"</js>
+                *      <li><js>"2001-07-04T15:30:45Z"</js>
+                * </ul>
+                *
+                * <h6 class='topic'>Example input:</h6>
+                * <ul>
+                *      <li><js>"2001-07-04T15:30:45-05:00"</js>
+                *      <li><js>"2001-07-04T15:30:45Z"</js>
+                *      <li><js>"2001-07-04T15:30:45.1Z"</js>
+                *      <li><js>"2001-07-04T15:30Z"</js>
+                *      <li><js>"2001-07-04"</js>
+                *      <li><js>"2001-07"</js>
+                *      <li><js>"2001"</js>
+                * </ul>
+                */
+               ISO8601_DT,
+
+               /**
+                * Same as {@link CalendarUtils.Format#ISO8601_DT}, except 
always serializes in GMT.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <js>"2001-07-04T15:30:45Z"</js>
+                */
+               ISO8601_DTZ,
+
+               /**
+                * Same as {@link CalendarUtils.Format#ISO8601_DT} except 
serializes to millisecond precision.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <js>"2001-07-04T15:30:45.123Z"</js>
+                */
+               ISO8601_DTP,
+
+               /**
+                * Same as {@link CalendarUtils.Format#ISO8601_DTZ} except 
serializes to millisecond precision.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <js>"2001-07-04T15:30:45.123"</js>
+                */
+               ISO8601_DTPZ,
+
+               /**
+                * ISO8601 date only.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <js>"2001-07-04"</js>
+                */
+               ISO8601_D,
+
+               /**
+                * Transform to {@link String Strings} using the {@code 
Date.toString()} method.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Wed Jul 04 15:30:45 EST 2001"</js>
+                * </ul>
+                */
+               TO_STRING,
+
+               /**
+                * Transform to RFC2822 date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Sat, 03 Mar 2001 10:11:12 +0000"</js> <jc>// 
en_US</jc>
+                *      <li><js>"土, 03 3 2001 10:11:12 +0000"</js> <jc>// 
ja_JP</jc>
+                *      <li><js>"토, 03 3월 2001 10:11:12 +0000"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               RFC2822_DT,
+
+               /**
+                * Same as {@link CalendarUtils.Format#RFC2822_DT}, except 
always serializes in GMT.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Sat, 03 Mar 2001 10:11:12 GMT"</js> <jc>// 
en_US</jc>
+                *      <li><js>"土, 03 3 2001 10:11:12 GMT"</js> <jc>// 
ja_JP</jc>
+                *      <li><js>"토, 03 3월 2001 10:11:12 GMT"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               RFC2822_DTZ,
+
+               /**
+                * Transform to RFC2822 date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"03 Mar 2001"</js> <jc>// en_US</jc>
+                *      <li><js>"03 3 2001"</js> <jc>// ja_JP</jc>
+                *      <li><js>"03 3월 2001"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               RFC2822_D,
+
+               /**
+                * Transform to simple <js>"yyyy/MM/dd HH:mm:ss"</js> date-time 
strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"2001/03/03 10:11:12"</js>
+                * </ul>
+                */
+               SIMPLE_DT,
+
+               /**
+                * Transform to simple <js>"yyyy/MM/dd"</js> date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"2001/03/03"</js>
+                * </ul>
+                */
+               SIMPLE_D,
+
+               /**
+                * Transform to simple <js>"HH:mm:ss"</js> time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"10:11:12"</js>
+                * </ul>
+                */
+               SIMPLE_T,
+
+               /**
+                * Transform to {@link DateFormat#FULL} date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Saturday, March 3, 2001"</js> <jc>// en_US</jc>
+                *      <li><js>"2001年3月3日"</js> <jc>// ja_JP</jc>
+                *      <li><js>"2001년 3월 3일 토요일"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               FULL_D,
+
+               /**
+                * Transform to {@link DateFormat#LONG} date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"March 3, 2001"</js> <jc>// en_US</jc>
+                *      <li><js>"2001/03/03"</js> <jc>// ja_JP</jc>
+                *      <li><js>"2001년 3월 3일 (토)"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               LONG_D,
+
+               /**
+                * Transform to {@link DateFormat#MEDIUM} date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Mar 3, 2001"</js> <jc>// en_US</jc>
+                *      <li><js>"2001/03/03"</js> <jc>// ja_JP</jc>
+                *      <li><js>"2001. 3. 3"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               MEDIUM_D,
+
+               /**
+                * Transform to {@link DateFormat#SHORT} date strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"3/3/01"</js> <jc>// en_US</jc>
+                *      <li><js>"01/03/03"</js> <jc>// ja_JP</jc>
+                *      <li><js>"01. 3. 3"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               SHORT_D,
+
+               /**
+                * Transform to {@link DateFormat#FULL} time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc>
+                *      <li><js>"10時11分12秒 GMT"</js> <jc>// ja_JP</jc>
+                *      <li><js>"오전 10시 11분 12초 GMT"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               FULL_T,
+
+               /**
+                * Transform to {@link DateFormat#LONG} time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"10:11:12 AM GMT"</js> <jc>// en_US</jc>
+                *      <li><js>"10:11:12 GMT"</js> <jc>// ja_JP</jc>
+                *      <li><js>"오전 10시 11분 12초"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               LONG_T,
+
+               /**
+                * Transform to {@link DateFormat#MEDIUM} time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"10:11:12 AM"</js> <jc>// en_US</jc>
+                *      <li><js>"10:11:12"</js> <jc>// ja_JP</jc>
+                *      <li><js>"오전 10:11:12"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               MEDIUM_T,
+
+               /**
+                * Transform to {@link DateFormat#SHORT} time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"10:11 AM"</js> <jc>// en_US</jc>
+                *      <li><js>"10:11 AM"</js> <jc>// ja_JP</jc>
+                *      <li><js>"오전 10:11"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               SHORT_T,
+
+               /**
+                * Transform to {@link DateFormat#FULL} date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Saturday, March 3, 2001 10:11:12 AM GMT"</js> 
<jc>// en_US</jc>
+                *      <li><js>"2001年3月3日 10時11分12秒 GMT"</js> 
<jc>// ja_JP</jc>
+                *      <li><js>"2001년 3월 3일 토요일 오전 10시 11분 
12초 GMT"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               FULL_DT,
+
+               /**
+                * Transform to {@link DateFormat#LONG} date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"March 3, 2001 10:11:12 AM GMT"</js> <jc>// 
en_US</jc>
+                *      <li><js>"2001/03/03 10:11:12 GMT"</js> <jc>// ja_JP</jc>
+                *      <li><js>"2001년 3월 3일 (토) 오전 10시 11분 
12초"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               LONG_DT,
+
+               /**
+                * Transform to {@link DateFormat#MEDIUM} date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"Mar 3, 2001 10:11:12 AM"</js> <jc>// en_US</jc>
+                *      <li><js>"2001/03/03 10:11:12"</js> <jc>// ja_JP</jc>
+                *      <li><js>"2001. 3. 3 오전 10:11:12"</js> <jc>// 
ko_KR</jc>
+                * </ul>
+                */
+               MEDIUM_DT,
+
+               /**
+                * Transform to {@link DateFormat#SHORT} date-time strings.
+                *
+                * <h5 class='section'>Example output:</h5>
+                * <ul>
+                *      <li><js>"3/3/01 10:11 AM"</js> <jc>// en_US</jc>
+                *      <li><js>"01/03/03 10:11"</js> <jc>// ja_JP</jc>
+                *      <li><js>"01. 3. 3 오전 10:11"</js> <jc>// ko_KR</jc>
+                * </ul>
+                */
+               SHORT_DT
+       }
+
+       private static ThreadLocal<Map<DateFormatKey,DateFormat>> patternCache 
= new ThreadLocal<Map<DateFormatKey,DateFormat>>();
+
+       static class DateFormatKey {
+               final CalendarUtils.Format format;
+               final Locale locale;
+               final TimeZone timeZone;
+               final int hashCode;
+
+               DateFormatKey(CalendarUtils.Format format, Locale locale, 
TimeZone timeZone) {
+                       this.format = format;
+                       this.locale = locale;
+                       this.timeZone = timeZone;
+                       this.hashCode = format.hashCode() + locale.hashCode() + 
timeZone.hashCode();
+               }
+
+               @Override
+               public int hashCode() {
+                       return hashCode;
+               }
+
+               @Override
+               public boolean equals(Object o) {
+                       if (o == null)
+                               return false;
+                       DateFormatKey key = (DateFormatKey)o;
+                       return format.equals(key.format) && 
locale.equals(key.locale) && timeZone.equals(key.timeZone);
+               }
+       }
+
+
+       private static DateFormat getFormat(CalendarUtils.Format format, Locale 
locale, TimeZone timeZone) {
+
+               if (locale == null)
+                       locale = Locale.getDefault();
+
+               if (timeZone == null)
+                       timeZone = TimeZone.getDefault();
+
+               DateFormatKey key = new DateFormatKey(format, locale, timeZone);
+
+               Map<DateFormatKey,DateFormat> m1 = patternCache.get();
+               if (m1 == null) {
+                       m1 = new ConcurrentHashMap<DateFormatKey,DateFormat>();
+                       patternCache.set(m1);
+               }
+
+               DateFormat df = m1.get(key);
+
+               if (df == null) {
+                       String p = null;
+                       switch (format) {
+                               case ISO8601_DTL: p = "yyyy-MM-dd'T'HH:mm:ss"; 
break;
+                               case ISO8601_D: p = "yyyy-MM-dd"; break;
+                               case TO_STRING: p = "EEE MMM dd HH:mm:ss zzz 
yyyy"; break;
+                               case RFC2822_DT: p = "EEE, dd MMM yyyy HH:mm:ss 
Z"; break;
+                               case RFC2822_DTZ: p = "EEE, dd MMM yyyy 
HH:mm:ss 'GMT'"; break;
+                               case RFC2822_D: p = "dd MMM yyyy"; break;
+                               case SIMPLE_DT: p = "yyyy/MM/dd HH:mm:ss"; 
break;
+                               case SIMPLE_D: p = "yyyy/MM/dd"; break;
+                               case SIMPLE_T: p = "HH:mm:ss"; break;
+                               case FULL_D: df = 
DateFormat.getDateInstance(DateFormat.FULL, locale); break;
+                               case LONG_D: df = 
DateFormat.getDateInstance(DateFormat.LONG, locale); break;
+                               case MEDIUM_D: df = 
DateFormat.getDateInstance(DateFormat.MEDIUM, locale); break;
+                               case SHORT_D: df = 
DateFormat.getDateInstance(DateFormat.SHORT, locale); break;
+                               case FULL_T: df = 
DateFormat.getTimeInstance(DateFormat.FULL, locale); break;
+                               case LONG_T: df = 
DateFormat.getTimeInstance(DateFormat.LONG, locale); break;
+                               case MEDIUM_T: df = 
DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); break;
+                               case SHORT_T: df = 
DateFormat.getTimeInstance(DateFormat.SHORT, locale); break;
+                               case FULL_DT: df = 
DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, locale); break;
+                               case LONG_DT: df = 
DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale); break;
+                               case MEDIUM_DT: df = 
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale); 
break;
+                               case SHORT_DT: df = 
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); 
break;
+                               default: return null;
+                       }
+                       if (p != null) {
+                               df = new SimpleDateFormat(p, locale);
+                       }
+                       if (df != null)
+                               df.setTimeZone(timeZone);
+                       m1.put(key, df);
+               }
+
+               return df;
+       }
+
+       /**
+        * Converts the specified calendar to a string of the specified format.
+        *
+        * @param c The calendar to serialize.
+        * @param format The date format.
+        * @param locale The locale to use.  If <jk>null</jk>, uses {@link 
Locale#getDefault()}.
+        * @param timeZone The time zone to use.  If <jk>null</jk>, uses {@link 
TimeZone#getDefault()}.
+        * @return The serialized date, or <jk>null</jk> if the calendar was 
<jk>null</jk>.
+        * @throws Exception
+        */
+       public static final String serialize(Calendar c, CalendarUtils.Format 
format, Locale locale, TimeZone timeZone) throws Exception {
+               if (c == null)
+                       return null;
+               if (timeZone == null)
+                       timeZone = c.getTimeZone();
+               switch(format) {
+                       case ISO8601_DTL:
+                       case ISO8601_D:
+                       case RFC2822_D:
+                       case RFC2822_DT:
+                       case TO_STRING:
+                       case FULL_D:
+                       case FULL_DT:
+                       case FULL_T:
+                       case LONG_D:
+                       case LONG_DT:
+                       case LONG_T:
+                       case MEDIUM_D:
+                       case MEDIUM_DT:
+                       case MEDIUM_T:
+                       case SHORT_D:
+                       case SHORT_DT:
+                       case SHORT_T:
+                       case SIMPLE_D:
+                       case SIMPLE_DT:
+                       case SIMPLE_T:
+                               return serializeFromDateFormat(c.getTime(), 
format, locale, timeZone);
+                       case ISO8601_DT:
+                               return 
DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
+                       case ISO8601_DTP:
+                               String s = 
DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
+                               return String.format("%s.%03d%s", 
s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
+                       case ISO8601_DTZ:
+                               if (c.getTimeZone().getRawOffset() != 0) {
+                                       Calendar c2 = Calendar.getInstance(GMT);
+                                       c2.setTime(c.getTime());
+                                       c = c2;
+                               }
+                               return DatatypeConverter.printDateTime(c);
+                       case ISO8601_DTPZ:
+                               if (c.getTimeZone().getRawOffset() != 0) {
+                                       Calendar c2 = Calendar.getInstance(GMT);
+                                       c2.setTime(c.getTime());
+                                       c = c2;
+                               }
+                               s = DatatypeConverter.printDateTime(c);
+                               return String.format("%s.%03d%s", 
s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
+                       case RFC2822_DTZ:
+                               return serializeFromDateFormat(c.getTime(), 
format, locale, GMT);
+               default:
+                       break;
+               }
+               return null;
+       }
+
+       /**
+        * Converts the specified date to a string of the specified format.
+        *
+        * @param format The date format.
+        * @param d The date to serialize.
+        * @param locale The locale to use.  If <jk>null</jk>, uses {@link 
Locale#getDefault()}.
+        * @param timeZone The time zone to use.  If <jk>null</jk>, uses {@link 
TimeZone#getDefault()}.
+        * @return The serialized date, or <jk>null</jk> if the calendar was 
<jk>null</jk>.
+        * @throws Exception
+        */
+       public static final String serialize(Date d, CalendarUtils.Format 
format, Locale locale, TimeZone timeZone) throws Exception {
+               if (d == null)
+                       return null;
+               if (timeZone == null)
+                       timeZone = TimeZone.getDefault();
+               switch(format) {
+                       case ISO8601_DTL:
+                       case ISO8601_D:
+                       case RFC2822_D:
+                       case RFC2822_DT:
+                       case TO_STRING:
+                       case FULL_D:
+                       case FULL_DT:
+                       case FULL_T:
+                       case LONG_D:
+                       case LONG_DT:
+                       case LONG_T:
+                       case MEDIUM_D:
+                       case MEDIUM_DT:
+                       case MEDIUM_T:
+                       case SHORT_D:
+                       case SHORT_DT:
+                       case SHORT_T:
+                       case SIMPLE_D:
+                       case SIMPLE_DT:
+                       case SIMPLE_T:
+                               return serializeFromDateFormat(d, format, 
locale, timeZone);
+                       case ISO8601_DT:
+                               Calendar c = new GregorianCalendar();
+                               c.setTime(d);
+                               c.setTimeZone(timeZone);
+                               return DatatypeConverter.printDateTime(c);
+                       case ISO8601_DTP:
+                               c = new GregorianCalendar();
+                               c.setTime(d);
+                               c.setTimeZone(timeZone);
+                               String s = 
DatatypeConverter.printDateTime(setTimeZone(c, timeZone));
+                               return String.format("%s.%03d%s", 
s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
+                       case ISO8601_DTZ:
+                               c = new GregorianCalendar();
+                               c.setTime(d);
+                               c.setTimeZone(GMT);
+                               return DatatypeConverter.printDateTime(c);
+                       case ISO8601_DTPZ:
+                               c = new GregorianCalendar();
+                               c.setTime(d);
+                               c.setTimeZone(GMT);
+                               s = DatatypeConverter.printDateTime(c);
+                               return String.format("%s.%03d%s", 
s.substring(0, 19), c.get(Calendar.MILLISECOND), s.substring(19));
+                       case RFC2822_DTZ:
+                               return serializeFromDateFormat(d, format, 
locale, GMT);
+               }
+               return null;
+       }
+
+
+       /**
+        * Converts the specified serialized date back into a {@link Calendar} 
object.
+        *
+        * @param format The date format.
+        * @param in The serialized date.
+        * @param locale
+        *      The locale to use.
+        *      If <jk>null</jk>, uses {@link Locale#getDefault()}.
+        * @param timeZone
+        *      The timezone to assume if input string doesn't contain timezone 
info.
+        *      If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
+        * @return The date as a {@link Calendar}, or <jk>null</jk> if the 
input was <jk>null</jk> or empty.
+        * @throws Exception
+        */
+       public static final Calendar parseCalendar(String in, 
CalendarUtils.Format format, Locale locale, TimeZone timeZone) throws Exception 
{
+               if (StringUtils.isEmpty(in))
+                       return null;
+               if (timeZone == null)
+                       timeZone = TimeZone.getDefault();
+               Date d = null;
+               switch(format) {
+
+                       // These use DatatypeConverter to parse the date.
+                       case ISO8601_DTL:
+                       case ISO8601_DT:
+                       case ISO8601_DTZ:
+                       case ISO8601_DTP:
+                       case ISO8601_DTPZ:
+                       case ISO8601_D:
+                               return 
DatatypeConverter.parseDateTime(toValidISO8601DT(in));
+
+                       // These don't specify timezones, so we have to assume 
the timezone is whatever is specified.
+                       case RFC2822_D:
+                       case SIMPLE_DT:
+                       case SIMPLE_D:
+                       case SIMPLE_T:
+                       case FULL_D:
+                       case LONG_D:
+                       case MEDIUM_D:
+                       case SHORT_D:
+                       case MEDIUM_T:
+                       case SHORT_T:
+                       case MEDIUM_DT:
+                       case SHORT_DT:
+                               d = getFormat(format, locale, 
TimeZone.getDefault()).parse(in);
+                               d.setTime(d.getTime() - 
timeZone.getRawOffset());
+                               break;
+
+                       // This is always in GMT.
+                       case RFC2822_DTZ:
+                               d = getFormat(format, locale, 
TimeZone.getDefault()).parse(in);
+                               d.setTime(d.getTime() + 
TimeZone.getDefault().getRawOffset());
+                               break;
+
+                       // These specify timezones in the strings, so we don't 
use the specified timezone.
+                       case TO_STRING:
+                       case FULL_DT:
+                       case FULL_T:
+                       case LONG_DT:
+                       case LONG_T:
+                       case RFC2822_DT:
+                               d = getFormat(format, locale, 
timeZone).parse(in);
+                               break;
+               }
+               if (d == null)
+                       return null;
+               Calendar c = new GregorianCalendar();
+               c.setTime(d);
+               c.setTimeZone(timeZone);
+               return c;
+       }
+
+       /**
+        * Converts the specified serialized date back into a {@link Date} 
object.
+        *
+        * @param format The date format.
+        * @param in The serialized date.
+        * @param locale
+        *      The locale to use.
+        *      If <jk>null</jk>, uses {@link Locale#getDefault()}.
+        * @param timeZone
+        *      The timezone to assume if input string doesn't contain timezone 
info.
+        *      If <jk>null</jk>, uses {@link TimeZone#getDefault()}.
+        * @return The date as a {@link Date}, or <jk>null</jk> if the input 
was <jk>null</jk> or empty.
+        * @throws Exception
+        */
+       public static final Date parseDate(String in, CalendarUtils.Format 
format, Locale locale, TimeZone timeZone) throws Exception {
+               if (StringUtils.isEmpty(in))
+                       return null;
+               if (timeZone == null)
+                       timeZone = TimeZone.getDefault();
+               switch(format) {
+
+                       // These use DatatypeConverter to parse the date.
+                       case ISO8601_DTL:
+                       case ISO8601_D:
+                       case ISO8601_DT:
+                       case ISO8601_DTZ:
+                       case ISO8601_DTP:
+                       case ISO8601_DTPZ:
+                               return 
DatatypeConverter.parseDateTime(toValidISO8601DT(in)).getTime();
+
+                       // These don't specify timezones, so we have to assume 
the timezone is whatever is specified.
+                       case FULL_D:
+                       case LONG_D:
+                       case MEDIUM_D:
+                       case MEDIUM_DT:
+                       case MEDIUM_T:
+                       case RFC2822_D:
+                       case SHORT_D:
+                       case SHORT_DT:
+                       case SHORT_T:
+                       case SIMPLE_D:
+                       case SIMPLE_DT:
+                       case SIMPLE_T:
+                               return getFormat(format, locale, 
timeZone).parse(in);
+
+                       // This is always in GMT.
+                       case RFC2822_DTZ:
+                               Date d = getFormat(format, locale, 
TimeZone.getDefault()).parse(in);
+                               d.setTime(d.getTime() + 
TimeZone.getDefault().getRawOffset());
+                               return d;
+
+                       // These specify timezones in the strings, so we don't 
use the specified timezone.
+                       case TO_STRING:
+                       case FULL_DT:
+                       case FULL_T:
+                       case LONG_DT:
+                       case LONG_T:
+                       case RFC2822_DT:
+                               return getFormat(format, locale, 
timeZone).parse(in);
+
+               }
+               return null;
+       }
+
+       private static String serializeFromDateFormat(Date date, 
CalendarUtils.Format format, Locale locale, TimeZone timeZone) {
+               DateFormat df = getFormat(format, locale, timeZone);
+               String s = df.format(date);
+               return s;
+       }
+
+       private static Calendar setTimeZone(Calendar c, TimeZone tz) {
+               if (tz != null && ! tz.equals(c.getTimeZone())) {
+                       c = (Calendar)c.clone();
+                       c.setTimeZone(tz);
+               }
+               return c;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-core/src/main/javadoc/overview.html
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/javadoc/overview.html 
b/juneau-core/src/main/javadoc/overview.html
index 2f41325..e61feaf 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -6911,6 +6911,15 @@
        }                       
                                </p>
                                This is mostly an internal change and doesn't 
affect the existing APIs.
+                       <li>
+                               {@link 
org.apache.juneau.dto.html5.HtmlElementMixed#children(Object...)} can now take 
in collections
+                               of objects.
+                       <li>
+                               {@link 
org.apache.juneau.transform.PojoSwap#swap(BeanSession,Object)} and {@link 
org.apache.juneau.transform.PojoSwap#unswap(BeanSession,Object,ClassMeta)} 
+                               can now throw arbitrary exceptions instead of 
having to wrap them in 
<code>SerializeException</code>/<code>ParseException</code>.
+                       <li>
+                               New {@link 
org.apache.juneau.utils.CalendarUtils} class that encapsulates 
serialization/parsing logic from {@link 
org.apache.juneau.transforms.CalendarSwap} and 
+                               {@link org.apache.juneau.transforms.DateSwap}.
                </ul>
                
                <h6 class='topic'>org.apache.juneau.rest</h6>
@@ -6928,6 +6937,10 @@
                                New setting: {@link 
org.apache.juneau.rest.RestContext#REST_resourceResolver}.  
                                <br>Allows you to specify a resource resolver 
on the servlet context to make it easier to work with
                                dependency injection frameworks.
+                       <li>
+                               {@link org.apache.juneau.rest.widget.Widget} 
classes can now be defined as inner classes of servlets/resources.
+                       <li>
+                               New tooltip template: {@link 
org.apache.juneau.rest.widget.Tooltip}.
                </ul>
 
                <h6 class='topic'>org.apache.juneau.rest.microservice</h6>
@@ -6941,6 +6954,14 @@
                                        <li>{@link 
org.apache.juneau.microservice.RestMicroservice#getServletContextHandler() 
getServletContextHandler()}
                                </ul>
                </ul>
+
+               <h6 class='topic'>org.apache.juneau.rest.examples</h6>
+               <ul class='spaced-list'>
+                       <li>
+                               New example of adding a menu-item widget to the 
Pet Store resource (including tooltips):
+                               <br><img 
src='doc-files/ReleaseNotes_632_PetStoreAdd.png'>
+               </ul>
+       
        </div>
        
        <!-- 
========================================================================================================
 -->

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
----------------------------------------------------------------------
diff --git 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
index c67371e..b5475b7 100644
--- 
a/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
+++ 
b/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/PetStoreResource.java
@@ -12,6 +12,8 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.examples.rest;
 
+import static org.apache.juneau.dto.html5.HtmlBuilder.*;
+
 import java.util.*;
 import java.util.Map;
 
@@ -23,6 +25,7 @@ import org.apache.juneau.json.*;
 import org.apache.juneau.microservice.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.annotation.Body;
 import org.apache.juneau.rest.converters.*;
 import org.apache.juneau.rest.widget.*;
 import org.apache.juneau.serializer.*;
@@ -38,14 +41,16 @@ import org.apache.juneau.transforms.*;
        htmldoc=@HtmlDoc(
                widgets={
                        ContentTypeMenuItem.class,
-                       StyleMenuItem.class
+                       StyleMenuItem.class,
+                       PetStoreResource.AddPet.class
                },
                links={
                        "up: request:/..",
                        "options: servlet:/?method=OPTIONS",
                        "$W{ContentTypeMenuItem}",
                        "$W{StyleMenuItem}",
-                       "source: 
$C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java"
+                       "source: 
$C{Source/gitHub}/org/apache/juneau/examples/rest/$R{servletClassSimple}.java",
+                       "$W{AddPet}"
                },
                aside={
                        "<div style='max-width:400px' class='text'>",
@@ -105,6 +110,12 @@ public class PetStoreResource extends ResourceJena {
                return petDB.get(id);
        }
 
+       @RestMethod(name="POST", path="/")
+       public Redirect addPet(@Body Pet pet) throws Exception {
+               this.petDB.put(pet.id, pet);
+               return new Redirect("servlet:/");
+       }
+       
        // Our bean class.
        public static class Pet {
 
@@ -120,7 +131,7 @@ public class PetStoreResource extends ResourceJena {
                @BeanProperty(format="$%.2f")  // Renders price in dollars.
                public float price;
 
-               @BeanProperty(swap=DateSwap.RFC2822D.class)  // Renders dates 
in RFC2822 format.
+               @BeanProperty(swap=DateSwap.ISO8601D.class)  // Renders dates 
in ISO8601 format.
                public Date birthDate;
 
                public int getAge() {
@@ -145,5 +156,79 @@ public class PetStoreResource extends ResourceJena {
                        return "background-color:#FDF2E9";
                }
        }
+       
+       /**
+        * Renders the "ADD" menu item.
+        */
+       public class AddPet extends MenuItemWidget {
+
+               @Override
+               public String getLabel(RestRequest req) throws Exception {
+                       return "add";
+               }
+
+               @Override
+               public Object getContent(RestRequest req) throws Exception {
+                       return div(
+                               
form().id("form").action("servlet:/").method("POST").children(
+                                       table(
+                                               tr(
+                                                       th("ID:"),
+                                                       
td(input().name("id").type("number").value(getNextAvailableId())),
+                                                       td(new Tooltip("(?)", 
"A unique identifer for the pet.", br(), "Must not conflict with existing IDs"))
+                                               ),
+                                               tr(
+                                                       th("Name:"),
+                                                       
td(input().name("name").type("text")),
+                                                       td(new Tooltip("(?)", 
"The name of the pet.", br(), "e.g. 'Fluffy'")) 
+                                               ),
+                                               tr(
+                                                       th("Kind:"),
+                                                       td(
+                                                               
select().name("kind").children(
+                                                                       
option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), 
option("RABBIT"), option("SNAKE")
+                                                               )
+                                                       ),
+                                                       td(new Tooltip("(?)", 
"The kind of animal.")) 
+                                               ),
+                                               tr(
+                                                       th("Breed:"),
+                                                       
td(input().name("breed").type("text")),
+                                                       td(new Tooltip("(?)", 
"The breed of animal.", br(), "Can be any arbitrary text")) 
+                                               ),
+                                               tr(
+                                                       th("Gets along with:"),
+                                                       
td(input().name("getsAlongWith").type("text")),
+                                                       td(new Tooltip("(?)", 
"A comma-delimited list of other animal types that this animal gets along 
with.")) 
+                                               ),
+                                               tr(
+                                                       th("Price:"),
+                                                       
td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100)),
+                                                       td(new Tooltip("(?)", 
"The price to charge for this pet.")) 
+                                               ),
+                                               tr(
+                                                       th("Birthdate:"),
+                                                       
td(input().name("birthDate").type("date")),
+                                                       td(new Tooltip("(?)", 
"The pets birthday.")) 
+                                               ),
+                                               tr(
+                                                       
td().colspan(2).style("text-align:right").children(
+                                                               button("reset", 
"Reset"),
+                                                               
button("button","Cancel").onclick("window.location.href='/'"),
+                                                               
button("submit", "Submit")
+                                                       )
+                                               )
+                                       ).style("white-space:nowrap")
+                               )
+                       );
+               }
+       }
+       
+       private int getNextAvailableId() {
+               int i = 100;
+               for (Integer k : petDB.keySet())
+                       i = Math.max(i, k);
+               return i+1;
+       }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
----------------------------------------------------------------------
diff --git 
a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
 
b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
index fe7ba7a..4e523f3 100644
--- 
a/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
+++ 
b/juneau-examples-rest/src/main/resources/org/apache/juneau/examples/rest/PetStore.json
@@ -11,11 +11,11 @@
 // * specific language governing permissions and limitations under the 
License.                                              *
 // 
***************************************************************************************************************************
 {
-       101: {kind:'CAT',name:'Mr. 
Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT'],birthDate:'04
 Jul 2012'},
-       102: 
{kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT'],birthDate:'01
 Sep 2014'},
-       103: 
{kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'16
 Apr 2017'},
-       104: {kind:'RABBIT',name:'Hoppy 
2',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'17
 Apr 2017'},
-       105: 
{kind:'FISH',name:'Gorton',price:1.99,breed:'Gold',getsAlongWith:['FISH'],birthDate:'20
 Jun 2017'},
-       106: 
{kind:'MOUSE',name:'Hackwrench',price:4.99,breed:'Gadget',getsAlongWith:['FISH','MOUSE'],birthDate:'20
 Jun 2017'},
-       107: {kind:'SNAKE',name:'Just 
Snake',price:9.99,breed:'Human',getsAlongWith:['SNAKE'],birthDate:'21 Jun 2017'}
+       101: {kind:'CAT',name:'Mr. 
Frisky',price:39.99,breed:'Persian',getsAlongWith:['CAT','FISH','RABBIT'],birthDate:'2012-07-04'},
+       102: 
{kind:'DOG',name:'Kibbles',price:99.99,breed:'Husky',getsAlongWith:['DOG','BIRD','FISH','MOUSE','RABBIT'],birthDate:'2014-09-01'},
+       103: 
{kind:'RABBIT',name:'Hoppy',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'2017-04-16'},
+       104: {kind:'RABBIT',name:'Hoppy 
2',price:49.99,breed:'Angora',getsAlongWith:['DOG','BIRD','FISH','MOUSE'],birthDate:'2017-04-17'},
+       105: 
{kind:'FISH',name:'Gorton',price:1.99,breed:'Gold',getsAlongWith:['FISH'],birthDate:'2017-06-20'},
+       106: 
{kind:'MOUSE',name:'Hackwrench',price:4.99,breed:'Gadget',getsAlongWith:['FISH','MOUSE'],birthDate:'2017-06-20'},
+       107: {kind:'SNAKE',name:'Just 
Snake',price:9.99,breed:'Human',getsAlongWith:['SNAKE'],birthDate:'2017-06-21'}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
index 60d1ff3..59e1cd0 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConfig.java
@@ -127,7 +127,7 @@ public class RestConfig implements ServletConfig {
        Object htmlTemplate = HtmlDocTemplateBasic.class;
 
        Class<?> resourceClass;
-       Map<String,Widget> htmlWidgets = new HashMap<String,Widget>();
+       List<Class<? extends Widget>> htmlWidgets = new ArrayList<Class<? 
extends Widget>>();
 
        /**
         * Constructor for top-level servlets when using dependency injection.
@@ -1381,8 +1381,7 @@ public class RestConfig implements ServletConfig {
         * @return This object (for method chaining).
         */
        public RestConfig addHtmlWidget(Class<? extends Widget> value) {
-               Widget w = ClassUtils.newInstance(Widget.class, value);
-               this.htmlWidgets.put(w.getName(), w);
+               this.htmlWidgets.add(value);
                return this;
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
----------------------------------------------------------------------
diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
index 4a18c33..fbd1f7f 100644
--- a/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -701,7 +701,12 @@ public final class RestContext extends Context {
 
                        fullPath = (sc.parentContext == null ? "" : 
(sc.parentContext.fullPath + '/')) + sc.path;
 
-                       htmlWidgets = sc.htmlWidgets;
+                       this.htmlWidgets = new LinkedHashMap<String,Widget>();
+                       for (Class<? extends Widget> wc : sc.htmlWidgets) {
+                               Widget w = 
ClassUtils.newInstanceFromOuter(resource, Widget.class, wc);
+                               this.htmlWidgets.put(w.getName(), w);
+                       }
+
                        htmlHeader = sc.htmlHeader;
                        htmlLinks = sc.htmlLinks;
                        htmlNav = sc.htmlNav;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java
index efcd579..d551aa3 100644
--- 
a/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java
+++ 
b/juneau-rest/src/main/java/org/apache/juneau/rest/converters/Traversable.java
@@ -16,7 +16,6 @@ import javax.servlet.http.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.rest.*;
-import org.apache.juneau.serializer.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -58,10 +57,10 @@ public final class Traversable implements RestConverter {
                                        o = 
cm.getPojoSwap().swap(req.getBeanSession(), o);
                                PojoRest p = new PojoRest(o, 
req.getBody().getReaderParser());
                                o = p.get(req.getPathMatch().getRemainder());
-                       } catch (SerializeException e) {
-                               throw new 
RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
                        } catch (PojoRestException e) {
                                throw new RestException(e.getStatus(), e);
+                       } catch (Exception e) {
+                               throw new 
RestException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
                        }
                }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java 
b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java
new file mode 100644
index 0000000..829c740
--- /dev/null
+++ b/juneau-rest/src/main/java/org/apache/juneau/rest/widget/Tooltip.java
@@ -0,0 +1,97 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you 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.juneau.rest.widget;
+
+import static org.apache.juneau.dto.html5.HtmlBuilder.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.html5.*;
+
+/**
+ * Simple template for adding tooltips to HTML5 bean constructs, typically in 
menu item widgets.
+ *
+ * <p>
+ * Tooltips depend on the existence of the <code>tooltip</code> and 
<code>tooltiptext</code> styles that should be
+ * present in the stylesheet for the document.
+ *
+ * <p>
+ * The following examples shows how tooltips can be added to a menu item 
widget.
+ *
+ * <p class='bcode'>
+ * <jk>public class</jk> MyFormMenuItem <jk>extends</jk> MenuItemWidget {
+ *
+ *     <ja>@Override</ja>
+ *     <jk>public</jk> String getLabel(RestRequest req) <jk>throws</jk> 
Exception {
+ *             <jk>return</jk> <js>"myform"</js>;
+ *     }
+ *
+ *     <ja>@Override</ja>
+ *     <jk>public</jk> Object getContent(RestRequest req) <jk>throws</jk> 
Exception {
+ *             <jk>return</jk> div(
+ *                     
<jsm>form</jsm>().id(<js>"form"</js>).action(<js>"servlet:/form"</js>).method(<js>"POST"</js>).children(
+ *                             <jsm>table</jsm>(
+ *                                     <jsm>tr</jsm>(
+ *                                             <jsm>th</jsm>(<js>"Field 
1:"</js>),
+ *                                             
<jsm>td</jsm>(<jsm>input</jsm>().name(<js>"field1"</js>).type(<js>"text"</js>)),
+ *                                             <jsm>td</jsm>(<jk>new</jk> 
Tooltip(<js>"(?)"</js>, <js>"This is field #1!"</js>, br(), <js>"(e.g. '"</js>, 
code(<js>"Foo"</js>), <js>"')"</js>))
+ *                                     ),
+ *                                     <jsm>tr</jsm>(
+ *                                             <jsm>th</jsm>(<js>"Field 
2:"</js>),
+ *                                             
<jsm>td</jsm>(<jsm>input</jsm>().name(<js>"field2"</js>).type(<js>"text"</js>)),
+ *                                             <jsm>td</jsm>(<jk>new</jk> 
Tooltip(<js>"(?)"</js>, <js>"This is field #2!"</js>, br(), <js>"(e.g. '"</js>, 
code(<js>"Bar"</js>), <js>"')"</js>))
+ *                                     )
+ *                             )
+ *                     )
+ *             );
+ *     }
+ * }
+ * </p>
+ */
+public class Tooltip {
+
+       private final String display;
+       private final List<Object> content;
+
+   /**
+    * Constructor.
+    *
+    * @param display
+    *  The normal display text.
+    *  This is what gets rendered normally.
+    * @param content
+    *  The hover contents.
+    *  Typically a list of strings, but can also include any HTML5 beans as 
well.
+    */
+   public Tooltip(String display, Object...content) {
+       this.display = display;
+       this.content = new ArrayList<Object>(Arrays.asList(content));
+   }
+
+   /**
+    * The swap method.
+    *
+    * <p>
+    * Converts this bean into a div tag with contents.
+    *
+    * @param session The bean session.
+    * @return The swapped contents of this bean.
+    */
+   public Div swap(BeanSession session) {
+      return div(
+       small(display),
+       span()._class("tooltiptext").children(content)
+      )._class("tooltip");
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css
index ee9a94a..487eae7 100644
--- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css
+++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/devops.css
@@ -72,13 +72,13 @@ nav {
        color: #94A3AB;
 }
 
-nav ol {
+nav>ol {
        list-style-type: none;
        margin: 0px 10px;
        padding: 0px;
 }
 
-nav li {
+nav>ol>li {
        display: inline;
 }
 
@@ -125,7 +125,6 @@ article div.data {
        border-radius: 4px;
        margin: 20px;
        display: inline-block;
-       overflow-x: auto;
        box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
        font-family: sans-serif;
        color: #26343F;
@@ -221,6 +220,43 @@ footer {
 }
 
 
/**********************************************************************************************************************/
+/**  Tooltips                                                                  
                                      **/
+/**********************************************************************************************************************/
+
+.tooltip {
+       position: relative;
+       display: inline-block;      
+}
+
+.tooltip .tooltiptext {
+       visibility: hidden;
+       background-color: #FEF9E7;
+       color: black;
+       padding: 5px;
+       border-radius: 6px;
+       position: absolute;
+       z-index: 1;
+       top: 0;
+       left: 0;
+       margin-left: 30px;
+       box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
+       opacity: 0;
+       transition: opacity 0.5s;
+       font-weight: normal;
+}
+
+.tooltip:hover .tooltiptext {
+       visibility: visible;
+       opacity: 1;
+}      
+
+.tooltiptext {
+       white-space: nowrap;
+       float: left;
+       border: 1px solid black;
+}
+
+/**********************************************************************************************************************/
 /**  Other classes                                                             
                                      **/
 
/**********************************************************************************************************************/
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css
index 0a43064..59a641c 100644
--- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css
+++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/light.css
@@ -79,13 +79,13 @@ nav * {
        font-weight: lighter;
 }
 
-nav ol {
+nav>ol {
        list-style-type: none;
        margin: 0px 10px;
        padding: 0px;
 }
 
-nav li {
+nav>ol>li {
        display: inline;
 }
 
@@ -120,7 +120,6 @@ section {
 article {
        display: table-cell;
        padding: 20px 40px;
-       overflow-x: auto;
 }
 
 article * {
@@ -212,6 +211,43 @@ footer {
 }
 
 
/**********************************************************************************************************************/
+/**  Tooltips                                                                  
                                      **/
+/**********************************************************************************************************************/
+
+.tooltip {
+       position: relative;
+       display: inline-block;      
+}
+
+.tooltip .tooltiptext {
+       visibility: hidden;
+       background-color: #FEF9E7;
+       color: black;
+       padding: 5px;
+       border-radius: 6px;
+       position: absolute;
+       z-index: 1;
+       top: 0;
+       left: 0;
+       margin-left: 30px;
+       box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
+       opacity: 0;
+       transition: opacity 0.5s;
+       font-weight: normal;
+}
+
+.tooltip:hover .tooltiptext {
+       visibility: visible;
+       opacity: 1;
+}      
+
+.tooltiptext {
+       white-space: nowrap;
+       float: left;
+       border: 1px solid black;
+}
+
+/**********************************************************************************************************************/
 /**  Other classes                                                             
                                      **/
 
/**********************************************************************************************************************/
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css
index 690dad4..3b4aa44 100644
--- a/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css
+++ b/juneau-rest/src/main/resources/org/apache/juneau/rest/styles/original.css
@@ -69,13 +69,13 @@ nav {
        margin: 0px 25px;       
 }
 
-nav ol {
+nav>ol {
        list-style-type: none;
        margin: 0px 10px;
        padding: 0px;
 }
 
-nav li {
+nav>ol>li {
        display: inline;
 }
 
@@ -174,6 +174,43 @@ footer {
 }
 
 
/**********************************************************************************************************************/
+/**  Tooltips                                                                  
                                      **/
+/**********************************************************************************************************************/
+
+.tooltip {
+       position: relative;
+       display: inline-block;      
+}
+
+.tooltip .tooltiptext {
+       visibility: hidden;
+       background-color: #FEF9E7;
+       color: black;
+       padding: 5px;
+       border-radius: 6px;
+       position: absolute;
+       z-index: 1;
+       top: 0;
+       left: 0;
+       margin-left: 30px;
+       box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
+       opacity: 0;
+       transition: opacity 0.5s;
+       font-weight: normal;
+}
+
+.tooltip:hover .tooltiptext {
+       visibility: visible;
+       opacity: 1;
+}      
+
+.tooltiptext {
+       white-space: nowrap;
+       float: left;
+       border: 1px solid black;
+}
+
+/**********************************************************************************************************************/
 /**  Other classes                                                             
                                      **/
 
/**********************************************************************************************************************/
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css
 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css
index 5b24e83..430df88 100644
--- 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css
+++ 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/MenuItemWidget.css
@@ -14,4 +14,4 @@
 .menu-item {
        position: relative;
        display: inline-block;
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c7985031/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css
----------------------------------------------------------------------
diff --git 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css
 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css
index a1903e6..cdd96c3 100644
--- 
a/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css
+++ 
b/juneau-rest/src/main/resources/org/apache/juneau/rest/widget/QueryMenuItem.css
@@ -11,35 +11,3 @@
  * specific language governing permissions and limitations under the License.
  *
  
***************************************************************************************************************************/
-.tooltip {
-       position: relative;
-       display: inline-block;      
-}
-
-.tooltip .tooltiptext {
-       visibility: hidden;
-       background-color: #FEF9E7;
-       color: black;
-       padding: 5px;
-       border-radius: 6px;
-       position: absolute;
-       z-index: 1;
-       top: 0;
-       left: 0;
-       margin-left: 30px;
-       box-shadow: 2px 3px 3px 0px rgba(0, 0, 0, 0.5);
-       opacity: 0;
-       transition: opacity 0.5s;
-       font-weight: normal;
-}
-
-.tooltip:hover .tooltiptext {
-       visibility: visible;
-       opacity: 1;
-}      
-
-.tooltiptext {
-       white-space: nowrap;
-       float: left;
-       border: 1px solid black;
-}
\ No newline at end of file

Reply via email to