You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2017/06/12 19:33:48 UTC

incubator-juneau git commit: Fix parse exceptions on partial ISO8601 timestamps.

Repository: incubator-juneau
Updated Branches:
  refs/heads/master 4eb151d1a -> 257776b98


Fix parse exceptions on partial ISO8601 timestamps.

Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/257776b9
Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/257776b9
Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/257776b9

Branch: refs/heads/master
Commit: 257776b98d04ece5b45efbaa04b33d6b368eea5f
Parents: 4eb151d
Author: JamesBognar <ja...@apache.org>
Authored: Mon Jun 12 15:33:44 2017 -0400
Committer: JamesBognar <ja...@apache.org>
Committed: Mon Jun 12 15:33:44 2017 -0400

----------------------------------------------------------------------
 .../juneau/ini/ConfigFileInterfaceTest.java     |   4 +-
 .../java/org/apache/juneau/ini/ConfigFile.java  | 171 +++++++++++++++++--
 .../apache/juneau/ini/ConfigFileContext.java    |  60 +++++++
 .../org/apache/juneau/ini/ConfigFileImpl.java   |  48 ++++--
 .../apache/juneau/ini/ConfigFileWrapped.java    |  12 +-
 .../org/apache/juneau/internal/ArrayUtils.java  |  21 +++
 .../org/apache/juneau/internal/DateUtils.java   |  60 +++++++
 .../org/apache/juneau/internal/StringUtils.java |  11 +-
 .../apache/juneau/transforms/CalendarSwap.java  |   5 +-
 .../org/apache/juneau/transforms/DateSwap.java  |   5 +-
 juneau-core/src/main/javadoc/overview.html      |   7 +
 11 files changed, 361 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core-test/src/test/java/org/apache/juneau/ini/ConfigFileInterfaceTest.java
----------------------------------------------------------------------
diff --git a/juneau-core-test/src/test/java/org/apache/juneau/ini/ConfigFileInterfaceTest.java b/juneau-core-test/src/test/java/org/apache/juneau/ini/ConfigFileInterfaceTest.java
index 4d34568..7bc4163 100644
--- a/juneau-core-test/src/test/java/org/apache/juneau/ini/ConfigFileInterfaceTest.java
+++ b/juneau-core-test/src/test/java/org/apache/juneau/ini/ConfigFileInterfaceTest.java
@@ -182,7 +182,7 @@ public class ConfigFileInterfaceTest {
 	public void testBeanList() throws Exception {
 		proxy.setBeanList(Arrays.asList(new ABean().init()));
 		assertObjectEquals("[{a:1,b:'foo'}]", proxy.getBeanList());
-		assertEquals("[{a:1,b:'foo'}]", cf.get("A", "beanList"));
+		assertEquals("\n[\n\t{a:1,b:'foo'}\n]", cf.get("A", "beanList"));
 		assertType(ABean.class, proxy.getBeanList().get(0));
 	}
 
@@ -248,7 +248,7 @@ public class ConfigFileInterfaceTest {
 	public void testTypedBeanList() throws Exception {
 		proxy.setTypedBeanList(Arrays.asList((TypedBean)new TypedBeanImpl().init()));
 		assertObjectEquals("[{_type:'TypedBeanImpl',a:1,b:'foo'}]", proxy.getTypedBeanList());
-		assertEquals("[{_type:'TypedBeanImpl',a:1,b:'foo'}]", cf.get("A", "typedBeanList"));
+		assertEquals("\n[\n\t{_type:'TypedBeanImpl',a:1,b:'foo'}\n]", cf.get("A", "typedBeanList"));
 		assertType(TypedBeanImpl.class, proxy.getTypedBeanList().get(0));
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFile.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFile.java b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFile.java
index d2a7e6a..172d3d9 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFile.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFile.java
@@ -57,15 +57,17 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @param sectionName The section name.  Must not be <jk>null</jk>.
 	 * @param sectionKey The section key.  Must not be <jk>null</jk>.
 	 * @param value The new value.
+	 * @param serializer The serializer to use for serializing the object.
+	 * 	If <jk>null</jk>, then uses the predefined serializer on the config file.
 	 * @param encoded
 	 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
 	 * @throws SerializeException If the object value could not be converted to a JSON string for some reason.
 	 * @throws UnsupportedOperationException If config file is read only.
 	 */
-	public abstract String put(String sectionName, String sectionKey, Object value, boolean encoded) throws SerializeException;
+	public abstract String put(String sectionName, String sectionKey, Object value, Serializer serializer, boolean encoded) throws SerializeException;
 
 	/**
-	 * Identical to {@link #put(String, String, Object, boolean)} except used when the value is a simple string
+	 * Identical to {@link #put(String, String, Object, Serializer, boolean)} except used when the value is a simple string
 	 * to avoid having to catch a {@link SerializeException}.
 	 *
 	 * @param sectionName The section name.  Must not be <jk>null</jk>.
@@ -192,21 +194,25 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * </ul>
 	 *
 	 * @param o The object to serialize.
+	 * @param serializer The serializer to use for serializing the object.
+	 * 	If <jk>null</jk>, then uses the predefined serializer on the config file.
 	 * @return The serialized object.
 	 * @throws SerializeException
 	 */
-	protected abstract String serialize(Object o) throws SerializeException;
+	protected abstract String serialize(Object o, Serializer serializer) throws SerializeException;
 
 	/**
 	 * Converts the specified string to an object of the specified type.
 	 *
 	 * @param s The string to parse.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
 	 * @param type The data type to create.
 	 * @param args The generic type arguments if the type is a {@link Collection} or {@link Map}
 	 * @return The parsed object.
 	 * @throws ParseException
 	 */
-	protected abstract <T> T parse(String s, Type type, Type...args) throws ParseException;
+	protected abstract <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException;
 
 	/**
 	 * Places a read lock on this config file.
@@ -301,9 +307,28 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @return The value, or <jk>null</jk> if the section or key does not exist.
 	 */
 	public final <T> T getObject(String key, Type type, Type...args) throws ParseException {
+		return getObject(key, (Parser)null, type, args);
+	}
+
+	/**
+	 * Same as {@link #getObject(String, Type, Type...)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param type The object type to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * @param args The type arguments of the class if it's a collection or map.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 *
+	 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file.
+	 * @return The value, or <jk>null</jk> if the section or key does not exist.
+	 */
+	public final <T> T getObject(String key, Parser parser, Type type, Type...args) throws ParseException {
 		assertFieldNotNull(key, "key");
 		assertFieldNotNull(type, "type");
-		return parse(getString(key), type, args);
+		return parse(getString(key), parser, type, args);
 	}
 
 	/**
@@ -338,12 +363,27 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
 	 */
 	public final <T> T getObject(String key, Class<T> type) throws ParseException {
+		return getObject(key, (Parser)null, type);
+	}
+
+	/**
+	 * Same as {@link #getObject(String, Class)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param <T> The class type of the object being created.
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param type The object type to create.
+	 * @return The parsed object.
+	 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type.
+	 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections.
+	 */
+	public final <T> T getObject(String key, Parser parser, Class<T> type) throws ParseException {
 		assertFieldNotNull(key, "key");
 		assertFieldNotNull(type, "c");
-		return parse(getString(key), type);
+		return parse(getString(key), parser, type);
 	}
 
-
 	/**
 	 * Gets the entry with the specified key and converts it to the specified value.
 	 * <p>
@@ -357,9 +397,25 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @return The value, or <jk>null</jk> if the section or key does not exist.
 	 */
 	public final <T> T getObjectWithDefault(String key, T def, Class<T> type) throws ParseException {
+		return getObjectWithDefault(key, null, def, type);
+	}
+
+	/**
+	 * Same as {@link #getObjectWithDefault(String, Object, Class)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param def The default value if section or key does not exist.
+	 * @param type The class to convert the value to.
+	 *
+	 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file.
+	 * @return The value, or <jk>null</jk> if the section or key does not exist.
+	 */
+	public final <T> T getObjectWithDefault(String key, Parser parser, T def, Class<T> type) throws ParseException {
 		assertFieldNotNull(key, "key");
 		assertFieldNotNull(type, "c");
-		T t = parse(getString(key), type);
+		T t = parse(getString(key), parser, type);
 		return (t == null ? def : t);
 	}
 
@@ -380,9 +436,29 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @return The value, or <jk>null</jk> if the section or key does not exist.
 	 */
 	public final <T> T getObjectWithDefault(String key, T def, Type type, Type...args) throws ParseException {
+		return getObjectWithDefault(key, null, def, type, args);
+	}
+
+	/**
+	 * Same as {@link #getObjectWithDefault(String, Object, Type, Type...)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param def The default value if section or key does not exist.
+	 * @param type The object type to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * @param args The type arguments of the class if it's a collection or map.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 *
+	 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file.
+	 * @return The value, or <jk>null</jk> if the section or key does not exist.
+	 */
+	public final <T> T getObjectWithDefault(String key, Parser parser, T def, Type type, Type...args) throws ParseException {
 		assertFieldNotNull(key, "key");
 		assertFieldNotNull(type, "type");
-		T t = parse(getString(key), type, args);
+		T t = parse(getString(key), parser, type, args);
 		return (t == null ? def : t);
 	}
 
@@ -399,9 +475,25 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @return The value, or the default value if the section or value doesn't exist.
 	 */
 	public final <T> T getObject(String sectionName, String sectionKey, Class<T> c) throws ParseException {
+		return getObject(sectionName, sectionKey, null, c);
+	}
+
+	/**
+	 * Same as {@link #getObject(String, String, Class)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param sectionName The section name.  Must not be <jk>null</jk>.
+	 * @param sectionKey The section key.  Must not be <jk>null</jk>.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param c The class to convert the value to.
+	 *
+	 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file.
+	 * @return The value, or the default value if the section or value doesn't exist.
+	 */
+	public final <T> T getObject(String sectionName, String sectionKey, Parser parser, Class<T> c) throws ParseException {
 		assertFieldNotNull(sectionName, "sectionName");
 		assertFieldNotNull(sectionKey, "sectionKey");
-		return parse(get(sectionName, sectionKey), c);
+		return parse(get(sectionName, sectionKey), parser, c);
 	}
 
 	/**
@@ -421,9 +513,29 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @return The value, or <jk>null</jk> if the section or key does not exist.
 	 */
 	public final <T> T getObject(String sectionName, String sectionKey, Type type, Type...args) throws ParseException {
+		return getObject(sectionName, sectionKey, null, type, args);
+	}
+
+	/**
+	 * Same as {@link #getObject(String, String, Type, Type...)} but allows you to specify the parser to use to parse the value.
+	 *
+	 * @param sectionName The section name.  Must not be <jk>null</jk>.
+	 * @param sectionKey The section key.  Must not be <jk>null</jk>.
+	 * @param parser The parser to use for parsing the object.
+	 * 	If <jk>null</jk>, then uses the predefined parser on the config file.
+	 * @param type The object type to create.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * @param args The type arguments of the class if it's a collection or map.
+	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
+	 * 	<br>Ignored if the main type is not a map or collection.
+	 *
+	 * @throws ParseException If parser could not parse the value or if a parser is not registered with this config file.
+	 * @return The value, or <jk>null</jk> if the section or key does not exist.
+	 */
+	public final <T> T getObject(String sectionName, String sectionKey, Parser parser, Type type, Type...args) throws ParseException {
 		assertFieldNotNull(sectionName, "sectionName");
 		assertFieldNotNull(sectionKey, "sectionKey");
-		return parse(get(sectionName, sectionKey), type, args);
+		return parse(get(sectionName, sectionKey), parser, type, args);
 	}
 
 	/**
@@ -533,7 +645,22 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @throws UnsupportedOperationException If config file is read only.
 	 */
 	public final String put(String key, Object value) throws SerializeException {
-		return put(key, value, isEncoded(key));
+		return put(key, value, null, isEncoded(key));
+	}
+
+	/**
+	 * Same as {@link #put(String, Object)} but allows you to specify the serializer to use to serialize the value.
+	 *
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param value The new value POJO.
+	 * @param serializer The serializer to use for serializing the object.
+	 * 	If <jk>null</jk>, then uses the predefined serializer on the config file.
+	 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
+	 * @throws SerializeException If serializer could not serialize the value or if a serializer is not registered with this config file.
+	 * @throws UnsupportedOperationException If config file is read only.
+	 */
+	public final String put(String key, Object value, Serializer serializer) throws SerializeException {
+		return put(key, value, serializer, isEncoded(key));
 	}
 
 	/**
@@ -556,8 +683,24 @@ public abstract class ConfigFile implements Map<String,Section> {
 	 * @throws UnsupportedOperationException If config file is read only.
 	 */
 	public final String put(String key, Object value, boolean encoded) throws SerializeException {
+		return put(key, value, null, encoded);
+	}
+
+	/**
+	 * Same as {@link #put(String, Object, boolean)} but allows you to specify the serializer to use to serialize the value.
+	 *
+	 * @param key The key.  See {@link #getString(String)} for a description of the key.
+	 * @param value The new value.
+	 * @param serializer The serializer to use for serializing the object.
+	 * 	If <jk>null</jk>, then uses the predefined serializer on the config file.
+	 * @param encoded If <jk>true</jk>, value is encoded by the registered encoder when the config file is persisted to disk.
+	 * @return The previous value, or <jk>null</jk> if the section or key did not previously exist.
+	 * @throws SerializeException If serializer could not serialize the value or if a serializer is not registered with this config file.
+	 * @throws UnsupportedOperationException If config file is read only.
+	 */
+	public final String put(String key, Object value, Serializer serializer, boolean encoded) throws SerializeException {
 		assertFieldNotNull(key, "key");
-		return put(getSectionName(key), getSectionKey(key), serialize(value), encoded);
+		return put(getSectionName(key), getSectionKey(key), serialize(value, serializer), encoded);
 	}
 
 	/**
@@ -770,7 +913,7 @@ public abstract class ConfigFile implements Map<String,Section> {
 					if (method.equals(rm))
 						return ConfigFile.this.getObject(sectionName, pd.getName(), rm.getGenericReturnType());
 					if (method.equals(wm))
-						return ConfigFile.this.put(sectionName, pd.getName(), args[0], false);
+						return ConfigFile.this.put(sectionName, pd.getName(), args[0], null, false);
 				}
 				throw new UnsupportedOperationException("Unsupported interface method.  method=[ " + method + " ]");
 			}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileContext.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileContext.java b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileContext.java
new file mode 100644
index 0000000..593a7e9
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileContext.java
@@ -0,0 +1,60 @@
+// ***************************************************************************************************************************
+// * 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.ini;
+
+import org.apache.juneau.*;
+
+/**
+ * TODO
+ */
+public class ConfigFileContext extends Context {
+
+	/**
+	 * TODO
+	 *
+	 * @param propertyStore
+	 */
+	public ConfigFileContext(PropertyStore propertyStore) {
+		super(propertyStore);
+	}
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_serializer = "ConfigFile.serializer";
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_parser = "ConfigFile.parser";
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_encoder = "ConfigFile.encoder";
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_readonly = "ConfigFile.readonly";
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_createIfNotExists = "ConfigFile.createIfNotExists";
+
+	/**
+	 * TODO
+	 */
+	public static final String CONFIGFILE_wsDepth = "ConfigFile.wsDepth";
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileImpl.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileImpl.java b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileImpl.java
index 0e08db2..44353f3 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileImpl.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileImpl.java
@@ -197,22 +197,46 @@ public final class ConfigFileImpl extends ConfigFile {
 		return this;
 	}
 
+	@SuppressWarnings("hiding")
 	@Override /* ConfigFile */
-	protected String serialize(Object value) throws SerializeException {
+	protected String serialize(Object value, Serializer serializer) throws SerializeException {
 		if (value == null)
 			return "";
+		if (serializer == null)
+			serializer = this.serializer;
 		Class<?> c = value.getClass();
 		if (isSimpleType(c))
 			return value.toString();
-		String s = serializer.toString(value);
-		if (s.startsWith("'"))
-			return s.substring(1, s.length()-1);
-		return s;
+
+		BeanContext bc = serializer.getBeanContext();
+		ClassMeta<?> cm = bc.getClassMetaForObject(value);
+		String r = null;
+
+		// For arrays of bean/collections, we have special formatting.
+		if (cm.isCollectionOrArray()) {
+			Object v1 = ArrayUtils.getFirst(value);
+			if (bc.getClassMetaForObject(v1).isMapOrBean()) {
+				List<Object> l = new ArrayList<Object>();
+				if (cm.isCollection()) {
+					for (Object o : (Collection<?>)value)
+						l.add(serializer.serialize(o));
+				} else {
+					for (int i = 0; i < Array.getLength(value); i++)
+						l.add(serializer.serialize(Array.get(value, i)));
+				}
+				return "\n[\n\t" + StringUtils.join(l, ",\n\t") + "\n]";
+			}
+		}
+
+		r = (String)serializer.serialize(value);
+		if (r.startsWith("'"))
+			return r.substring(1, r.length()-1);
+		return r;
 	}
 
 	@Override /* ConfigFile */
-	@SuppressWarnings({ "unchecked" })
-	protected <T> T parse(String s, Type type, Type...args) throws ParseException {
+	@SuppressWarnings({ "unchecked", "hiding" })
+	protected <T> T parse(String s, Parser parser, Type type, Type...args) throws ParseException {
 
 		if (StringUtils.isEmpty(s))
 			return null;
@@ -220,10 +244,13 @@ public final class ConfigFileImpl extends ConfigFile {
 		if (isSimpleType(type))
 			return (T)pBeanSession.convertToType(s, (Class<?>)type);
 
-		char s1 = charAt(s, 0);
+		char s1 = firstNonWhitespaceChar(s);
 		if (s1 != '[' && s1 != '{' && ! "null".equals(s))
 			s = '\'' + s + '\'';
 
+		if (parser == null)
+			parser = this.parser;
+
 		return parser.parse(s, type, args);
 	}
 
@@ -469,11 +496,12 @@ public final class ConfigFileImpl extends ConfigFile {
 		return (s2 == null ? null : s2.toString());
 	}
 
+	@SuppressWarnings("hiding")
 	@Override /* ConfigFile */
-	public String put(String sectionName, String sectionKey, Object value, boolean encoded) throws SerializeException {
+	public String put(String sectionName, String sectionKey, Object value, Serializer serializer, boolean encoded) throws SerializeException {
 		assertFieldNotNull(sectionKey, "sectionKey");
 		Section s = getSection(sectionName, true);
-		return s.put(sectionKey, serialize(value), encoded);
+		return s.put(sectionKey, serialize(value, serializer), encoded);
 	}
 
 	@Override /* ConfigFile */

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWrapped.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWrapped.java b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWrapped.java
index 5407c0f..dd8e2ee 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWrapped.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWrapped.java
@@ -252,8 +252,8 @@ public final class ConfigFileWrapped extends ConfigFile {
 	}
 
 	@Override /* ConfigFile */
-	public String put(String sectionName, String sectionKey, Object value, boolean encoded) throws SerializeException {
-		return cf.put(sectionName, sectionKey, value, encoded);
+	public String put(String sectionName, String sectionKey, Object value, Serializer serializer, boolean encoded) throws SerializeException {
+		return cf.put(sectionName, sectionKey, value, serializer, encoded);
 	}
 
 	@Override /* ConfigFile */
@@ -277,12 +277,12 @@ public final class ConfigFileWrapped extends ConfigFile {
 	}
 
 	@Override /* ConfigFile */
-	protected String serialize(Object o) throws SerializeException {
-		return cf.serialize(o);
+	protected String serialize(Object o, Serializer s) throws SerializeException {
+		return cf.serialize(o, s);
 	}
 
 	@Override /* ConfigFile */
-	protected <T> T parse(String s, Type type, Type... args) throws ParseException {
-		return cf.parse(s, type, args);
+	protected <T> T parse(String s, Parser parser, Type type, Type... args) throws ParseException {
+		return cf.parse(s, parser, type, args);
 	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
index 3fbaeaa..4f1a7d8 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
@@ -363,4 +363,25 @@ public final class ArrayUtils {
 			l.add(i2.next());
 		return l;
 	}
+
+	/**
+	 * Returns the first object in the specified collection or array.
+	 *
+	 * @param val The collection or array object.
+	 * @return The first object, or <jk>null</jk> if the collection or array is empty or <jk>null</jk> or the value
+	 * 	isn't a collection or array.
+	 */
+	public static Object getFirst(Object val) {
+		if (val != null) {
+			if (val instanceof Collection) {
+				Collection<?> c = (Collection<?>)val;
+				if (c.isEmpty())
+					return null;
+				return c.iterator().next();
+			}
+			if (val.getClass().isArray())
+				return Array.getLength(val) == 0 ? null : Array.get(val, 0);
+		}
+		return null;
+	}
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/internal/DateUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/DateUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/DateUtils.java
index 48565c4..9ca1b5e 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/DateUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/DateUtils.java
@@ -16,6 +16,8 @@ import java.lang.ref.*;
 import java.text.*;
 import java.util.*;
 
+import javax.xml.bind.*;
+
 /**
  * A utility class for parsing and formatting HTTP dates as used in cookies and
  * other headers.  This class handles dates as defined by RFC 2616 section
@@ -183,4 +185,62 @@ public final class DateUtils {
 			THREADLOCAL_FORMATS.remove();
 		}
 	}
+
+	/**
+	 * Pads out an ISO8601 string so that it can be parsed using {@link DatatypeConverter#parseDateTime(String)}.
+	 * <ul>
+	 * 	<li><js>"2001-07-04T15:30:45-05:00"</js> --&gt; <js>"2001-07-04T15:30:45-05:00"</js>
+	 * 	<li><js>"2001-07-04T15:30:45Z"</js> --&gt; <js>"2001-07-04T15:30:45Z"</js>
+	 * 	<li><js>"2001-07-04T15:30:45.1Z"</js> --&gt; <js>"2001-07-04T15:30:45.1Z"</js>
+	 * 	<li><js>"2001-07-04T15:30Z"</js> --&gt; <js>"2001-07-04T15:30:00Z"</js>
+	 * 	<li><js>"2001-07-04T15:30"</js> --&gt; <js>"2001-07-04T15:30:00"</js>
+	 * 	<li><js>"2001-07-04"</js> --&gt; <li><js>"2001-07-04T00:00:00"</js>
+	 * 	<li><js>"2001-07"</js> --&gt; <js>"2001-07-01T00:00:00"</js>
+	 * 	<li><js>"2001"</js> --&gt; <js>"2001-01-01T00:00:00"</js>
+	 * </ul>
+	 *
+	 * @param in The string to pad.
+	 * @return The padded string.
+	 */
+	public static final String toValidISO8601DT(String in) {
+
+		// "2001-07-04T15:30:45Z"
+		final int
+			S1 = 1, // Looking for -
+			S2 = 2, // Found -, looking for -
+			S3 = 3, // Found -, looking for T
+			S4 = 4, // Found T, looking for :
+			S5 = 5, // Found :, looking for :
+			S6 = 6; // Found :
+
+		int state = 1;
+		for (int i = 0; i < in.length(); i++) {
+			char c = in.charAt(i);
+			if (state == S1) {
+				if (c == '-')
+					state = S2;
+			} else if (state == S2) {
+				if (c == '-')
+					state = S3;
+			} else if (state == S3) {
+				if (c == 'T')
+					state = S4;
+			} else if (state == S4) {
+				if (c == ':')
+					state = S5;
+			} else if (state == S5) {
+				if (c == ':')
+					state = S6;
+			}
+		}
+
+		switch(state) {
+			case S1: return in + "-01-01T00:00:00";
+			case S2: return in + "-01T00:00:00";
+			case S3: return in + "T00:00:00";
+			case S4: return in + ":00:00";
+			case S5: return in + ":00";
+			default: return in;
+		}
+	}
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
index 22b444b..3baf951 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -1319,13 +1319,10 @@ public final class StringUtils {
 	 * @return The first non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed of only whitespace.
 	 */
 	public static char firstNonWhitespaceChar(String s) {
-		if (s != null) {
-			for (int i = 0; i < s.length(); i++) {
-				char c = s.charAt(i);
-				if (! Character.isWhitespace(c))
-					return c;
-			}
-		}
+		if (s != null)
+			for (int i = 0; i < s.length(); i++)
+				if (! Character.isWhitespace(s.charAt(i)))
+					return s.charAt(i);
 		return 0;
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/transforms/CalendarSwap.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/transforms/CalendarSwap.java b/juneau-core/src/main/java/org/apache/juneau/transforms/CalendarSwap.java
index 2490c99..9f45498 100644
--- a/juneau-core/src/main/java/org/apache/juneau/transforms/CalendarSwap.java
+++ b/juneau-core/src/main/java/org/apache/juneau/transforms/CalendarSwap.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.transforms;
 
 import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.DateUtils.*;
 
 import java.text.*;
 import java.util.*;
@@ -133,7 +134,7 @@ public class CalendarSwap extends StringSwap<Calendar> {
 			try {
 				if (isEmpty(o))
 					return null;
-				return convert(DatatypeConverter.parseDateTime(o), hint);
+				return convert(DatatypeConverter.parseDateTime(toValidISO8601DT(o)), hint);
 			} catch (Exception e) {
 				throw new ParseException(e);
 			}
@@ -164,7 +165,7 @@ public class CalendarSwap extends StringSwap<Calendar> {
 			try {
 				if (isEmpty(o))
 					return null;
-				return convert(DatatypeConverter.parseDateTime(o), hint);
+				return convert(DatatypeConverter.parseDateTime(toValidISO8601DT(o)), hint);
 			} catch (Exception e) {
 				throw new ParseException(e);
 			}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/juneau-core/src/main/java/org/apache/juneau/transforms/DateSwap.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/transforms/DateSwap.java b/juneau-core/src/main/java/org/apache/juneau/transforms/DateSwap.java
index 642ba1b..b2fb0b0 100644
--- a/juneau-core/src/main/java/org/apache/juneau/transforms/DateSwap.java
+++ b/juneau-core/src/main/java/org/apache/juneau/transforms/DateSwap.java
@@ -12,6 +12,7 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.transforms;
 
+import static org.apache.juneau.internal.DateUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 
 import java.text.*;
@@ -132,7 +133,7 @@ public class DateSwap extends StringSwap<Date> {
 			try {
 				if (isEmpty(o))
 					return null;
-				return convert(DatatypeConverter.parseDateTime(o).getTime(), hint);
+				return convert(DatatypeConverter.parseDateTime(toValidISO8601DT(o)).getTime(), hint);
 			} catch (Exception e) {
 				throw new ParseException(e);
 			}
@@ -165,7 +166,7 @@ public class DateSwap extends StringSwap<Date> {
 			try {
 				if (isEmpty(o))
 					return null;
-				return convert(DatatypeConverter.parseDateTime(o).getTime(), hint);
+				return convert(DatatypeConverter.parseDateTime(toValidISO8601DT(o)).getTime(), hint);
 			} catch (Exception e) {
 				throw new ParseException(e);
 			}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/257776b9/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 458a70f..81a3f33 100644
--- a/juneau-core/src/main/javadoc/overview.html
+++ b/juneau-core/src/main/javadoc/overview.html
@@ -6192,12 +6192,19 @@
 			<li>New/modified methods on {@link org.apache.juneau.ini.ConfigFile}:
 				<ul>
 					<li>{@link org.apache.juneau.ini.ConfigFile#put(String,String,Object,boolean) put(String,String,Object,boolean)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#put(String,String,Object,Serializer,boolean) put(String,String,Object,boolean)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,Type,Type...) getObject(String,Type,Type...)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,Parser,Type,Type...) getObject(String,Parser,Type,Type...)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,Class) getObject(String,Class)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,Parser,Class) getObject(String,Parser,Class)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,String,Type,Type...) getObject(String,String,Type,Type...)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,String,Parser,Type,Type...) getObject(String,String,Parser,Type,Type...)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,String,Class) getObject(String,String,Class)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObject(String,String,Parser,Class) getObject(String,String,Parser,Class)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObjectWithDefault(String,Object,Type,Type...) getObjectWithDefault(String,Object,Type,Type)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObjectWithDefault(String,Object,Parser,Type,Type...) getObjectWithDefault(String,Object,Type,Type)} 
 					<li>{@link org.apache.juneau.ini.ConfigFile#getObjectWithDefault(String,Object,Class) getObjectWithDefault(String,Object,Class)} 
+					<li>{@link org.apache.juneau.ini.ConfigFile#getObjectWithDefault(String,Object,Parser,Class) getObjectWithDefault(String,Object,Parser,Class)} 
 				</ul>
 			<li>New ability to interact with config file sections with proxy interfaces with new method {@link org.apache.juneau.ini.ConfigFile#getSectionAsInterface(String,Class)}.
 			<li>{@link org.apache.juneau.annotation.BeanProperty @BeanProperty} annotation can now be applied to getters