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 <[email protected]> Authored: Mon Jun 12 15:33:44 2017 -0400 Committer: JamesBognar <[email protected]> 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> --> <js>"2001-07-04T15:30:45-05:00"</js> + * <li><js>"2001-07-04T15:30:45Z"</js> --> <js>"2001-07-04T15:30:45Z"</js> + * <li><js>"2001-07-04T15:30:45.1Z"</js> --> <js>"2001-07-04T15:30:45.1Z"</js> + * <li><js>"2001-07-04T15:30Z"</js> --> <js>"2001-07-04T15:30:00Z"</js> + * <li><js>"2001-07-04T15:30"</js> --> <js>"2001-07-04T15:30:00"</js> + * <li><js>"2001-07-04"</js> --> <li><js>"2001-07-04T00:00:00"</js> + * <li><js>"2001-07"</js> --> <js>"2001-07-01T00:00:00"</js> + * <li><js>"2001"</js> --> <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
