Repository: incubator-juneau Updated Branches: refs/heads/master ef55eca75 -> f8ee48642
DynaBean support Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/f8ee4864 Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/f8ee4864 Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/f8ee4864 Branch: refs/heads/master Commit: f8ee48642e65ab13e9be483b02e6035e975b84e1 Parents: ef55eca Author: JamesBognar <[email protected]> Authored: Thu May 4 12:56:03 2017 -0400 Committer: JamesBognar <[email protected]> Committed: Thu May 4 12:56:03 2017 -0400 ---------------------------------------------------------------------- .../org/apache/juneau/DynaBeanComboTest.java | 252 ++++++++++++++++++- .../main/java/org/apache/juneau/BeanMap.java | 8 +- .../main/java/org/apache/juneau/BeanMeta.java | 27 +- .../org/apache/juneau/BeanPropertyMeta.java | 18 +- .../org/apache/juneau/BeanPropertyValue.java | 7 +- .../apache/juneau/annotation/BeanProperty.java | 69 +++++ juneau-core/src/main/javadoc/overview.html | 3 +- 7 files changed, 355 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core-test/src/test/java/org/apache/juneau/DynaBeanComboTest.java ---------------------------------------------------------------------- diff --git a/juneau-core-test/src/test/java/org/apache/juneau/DynaBeanComboTest.java b/juneau-core-test/src/test/java/org/apache/juneau/DynaBeanComboTest.java index bb9ac79..3be3753 100644 --- a/juneau-core-test/src/test/java/org/apache/juneau/DynaBeanComboTest.java +++ b/juneau-core-test/src/test/java/org/apache/juneau/DynaBeanComboTest.java @@ -19,6 +19,8 @@ import java.util.*; import org.apache.juneau.annotation.*; import org.apache.juneau.parser.*; import org.apache.juneau.serializer.*; +import org.apache.juneau.transforms.*; +import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; @@ -33,10 +35,10 @@ public class DynaBeanComboTest extends ComboTest { public static Collection<Object[]> getParameters() { return Arrays.asList(new Object[][] { { /* 0 */ - new ComboInput<A>( - "A", - A.class, - new A().init(), + new ComboInput<BeanWithDynaField>( + "BeanWithDynaField", + BeanWithDynaField.class, + new BeanWithDynaField().init(), /* Json */ "{f1:1,f2a:'a',f2b:'b',f3:3}", /* JsonT */ "{f1:1,f2a:'a',f2b:'b',f3:3}", /* JsonR */ "{\n\tf1: 1,\n\tf2a: 'a',\n\tf2b: 'b',\n\tf3: 3\n}", @@ -60,8 +62,142 @@ public class DynaBeanComboTest extends ComboTest { /* RdfXmlR */ "<rdf:RDF>\n <rdf:Description>\n <jp:f1>1</jp:f1>\n <jp:f2a>a</jp:f2a>\n <jp:f2b>b</jp:f2b>\n <jp:f3>3</jp:f3>\n </rdf:Description>\n</rdf:RDF>\n" ) { - public void verify(A o) { - assertType(A.class, o); + public void verify(BeanWithDynaField o) { + assertType(BeanWithDynaField.class, o); + } + } + }, + { /* 1 */ + new ComboInput<BeanWithDynaMethods>( + "BeanWithDynaMethods", + BeanWithDynaMethods.class, + new BeanWithDynaMethods().init(), + /* Json */ "{f1:1,f2a:'a',f2b:'b',f3:3}", + /* JsonT */ "{f1:1,f2a:'a',f2b:'b',f3:3}", + /* JsonR */ "{\n\tf1: 1,\n\tf2a: 'a',\n\tf2b: 'b',\n\tf3: 3\n}", + /* Xml */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* XmlT */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* XmlR */ "<object>\n\t<f1>1</f1>\n\t<f2a>a</f2a>\n\t<f2b>b</f2b>\n\t<f3>3</f3>\n</object>\n", + /* XmlNs */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* Html */ "<table><tr><td>f1</td><td>1</td></tr><tr><td>f2a</td><td>a</td></tr><tr><td>f2b</td><td>b</td></tr><tr><td>f3</td><td>3</td></tr></table>", + /* HtmlT */ "<table><tr><td>f1</td><td>1</td></tr><tr><td>f2a</td><td>a</td></tr><tr><td>f2b</td><td>b</td></tr><tr><td>f3</td><td>3</td></tr></table>", + /* HtmlR */ "<table>\n\t<tr>\n\t\t<td>f1</td>\n\t\t<td>1</td>\n\t</tr>\n\t<tr>\n\t\t<td>f2a</td>\n\t\t<td>a</td>\n\t</tr>\n\t<tr>\n\t\t<td>f2b</td>\n\t\t<td>b</td>\n\t</tr>\n\t<tr>\n\t\t<td>f3</td>\n\t\t<td>3</td>\n\t</tr>\n</table>\n", + /* Uon */ "(f1=1,f2a=a,f2b=b,f3=3)", + /* UonT */ "(f1=1,f2a=a,f2b=b,f3=3)", + /* UonR */ "(\n\tf1=1,\n\tf2a=a,\n\tf2b=b,\n\tf3=3\n)", + /* UrlEnc */ "f1=1&f2a=a&f2b=b&f3=3", + /* UrlEncT */ "f1=1&f2a=a&f2b=b&f3=3", + /* UrlEncR */ "f1=1\n&f2a=a\n&f2b=b\n&f3=3", + /* MsgPack */ "84A2663101A3663261A161A3663262A162A2663303", + /* MsgPackT */ "84A2663101A3663261A161A3663262A162A2663303", + /* RdfXml */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1>1</jp:f1>\n<jp:f2a>a</jp:f2a>\n<jp:f2b>b</jp:f2b>\n<jp:f3>3</jp:f3>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlT */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1>1</jp:f1>\n<jp:f2a>a</jp:f2a>\n<jp:f2b>b</jp:f2b>\n<jp:f3>3</jp:f3>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlR */ "<rdf:RDF>\n <rdf:Description>\n <jp:f1>1</jp:f1>\n <jp:f2a>a</jp:f2a>\n <jp:f2b>b</jp:f2b>\n <jp:f3>3</jp:f3>\n </rdf:Description>\n</rdf:RDF>\n" + ) + { + public void verify(BeanWithDynaMethods o) { + assertType(BeanWithDynaMethods.class, o); + Assert.assertTrue(o.setterCalled); + } + } + }, + { /* 2 */ + new ComboInput<BeanWithDynaGetterOnly>( + "BeanWithDynaGetterOnly", + BeanWithDynaGetterOnly.class, + new BeanWithDynaGetterOnly().init(), + /* Json */ "{f1:1,f2a:'a',f2b:'b',f3:3}", + /* JsonT */ "{f1:1,f2a:'a',f2b:'b',f3:3}", + /* JsonR */ "{\n\tf1: 1,\n\tf2a: 'a',\n\tf2b: 'b',\n\tf3: 3\n}", + /* Xml */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* XmlT */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* XmlR */ "<object>\n\t<f1>1</f1>\n\t<f2a>a</f2a>\n\t<f2b>b</f2b>\n\t<f3>3</f3>\n</object>\n", + /* XmlNs */ "<object><f1>1</f1><f2a>a</f2a><f2b>b</f2b><f3>3</f3></object>", + /* Html */ "<table><tr><td>f1</td><td>1</td></tr><tr><td>f2a</td><td>a</td></tr><tr><td>f2b</td><td>b</td></tr><tr><td>f3</td><td>3</td></tr></table>", + /* HtmlT */ "<table><tr><td>f1</td><td>1</td></tr><tr><td>f2a</td><td>a</td></tr><tr><td>f2b</td><td>b</td></tr><tr><td>f3</td><td>3</td></tr></table>", + /* HtmlR */ "<table>\n\t<tr>\n\t\t<td>f1</td>\n\t\t<td>1</td>\n\t</tr>\n\t<tr>\n\t\t<td>f2a</td>\n\t\t<td>a</td>\n\t</tr>\n\t<tr>\n\t\t<td>f2b</td>\n\t\t<td>b</td>\n\t</tr>\n\t<tr>\n\t\t<td>f3</td>\n\t\t<td>3</td>\n\t</tr>\n</table>\n", + /* Uon */ "(f1=1,f2a=a,f2b=b,f3=3)", + /* UonT */ "(f1=1,f2a=a,f2b=b,f3=3)", + /* UonR */ "(\n\tf1=1,\n\tf2a=a,\n\tf2b=b,\n\tf3=3\n)", + /* UrlEnc */ "f1=1&f2a=a&f2b=b&f3=3", + /* UrlEncT */ "f1=1&f2a=a&f2b=b&f3=3", + /* UrlEncR */ "f1=1\n&f2a=a\n&f2b=b\n&f3=3", + /* MsgPack */ "84A2663101A3663261A161A3663262A162A2663303", + /* MsgPackT */ "84A2663101A3663261A161A3663262A162A2663303", + /* RdfXml */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1>1</jp:f1>\n<jp:f2a>a</jp:f2a>\n<jp:f2b>b</jp:f2b>\n<jp:f3>3</jp:f3>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlT */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1>1</jp:f1>\n<jp:f2a>a</jp:f2a>\n<jp:f2b>b</jp:f2b>\n<jp:f3>3</jp:f3>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlR */ "<rdf:RDF>\n <rdf:Description>\n <jp:f1>1</jp:f1>\n <jp:f2a>a</jp:f2a>\n <jp:f2b>b</jp:f2b>\n <jp:f3>3</jp:f3>\n </rdf:Description>\n</rdf:RDF>\n" + ) + { + public void verify(BeanWithDynaGetterOnly o) { + assertType(BeanWithDynaGetterOnly.class, o); + } + } + }, + { /* 3 */ + new ComboInput<BeanWithDynaFieldSwapped>( + "BeanWithDynaFieldSwapped", + BeanWithDynaFieldSwapped.class, + new BeanWithDynaFieldSwapped().init(), + /* Json */ "{f1a:'1901-03-03T18:11:12Z'}", + /* JsonT */ "{f1a:'1901-03-03T18:11:12Z'}", + /* JsonR */ "{\n\tf1a: '1901-03-03T18:11:12Z'\n}", + /* Xml */ "<object><f1a>1901-03-03T18:11:12Z</f1a></object>", + /* XmlT */ "<object><f1a>1901-03-03T18:11:12Z</f1a></object>", + /* XmlR */ "<object>\n\t<f1a>1901-03-03T18:11:12Z</f1a>\n</object>\n", + /* XmlNs */ "<object><f1a>1901-03-03T18:11:12Z</f1a></object>", + /* Html */ "<table><tr><td>f1a</td><td>1901-03-03T18:11:12Z</td></tr></table>", + /* HtmlT */ "<table><tr><td>f1a</td><td>1901-03-03T18:11:12Z</td></tr></table>", + /* HtmlR */ "<table>\n\t<tr>\n\t\t<td>f1a</td>\n\t\t<td>1901-03-03T18:11:12Z</td>\n\t</tr>\n</table>\n", + /* Uon */ "(f1a=1901-03-03T18:11:12Z)", + /* UonT */ "(f1a=1901-03-03T18:11:12Z)", + /* UonR */ "(\n\tf1a=1901-03-03T18:11:12Z\n)", + /* UrlEnc */ "f1a=1901-03-03T18:11:12Z", + /* UrlEncT */ "f1a=1901-03-03T18:11:12Z", + /* UrlEncR */ "f1a=1901-03-03T18:11:12Z", + /* MsgPack */ "81A3663161B4313930312D30332D30335431383A31313A31325A", + /* MsgPackT */ "81A3663161B4313930312D30332D30335431383A31313A31325A", + /* RdfXml */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1a>1901-03-03T18:11:12Z</jp:f1a>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlT */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1a>1901-03-03T18:11:12Z</jp:f1a>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlR */ "<rdf:RDF>\n <rdf:Description>\n <jp:f1a>1901-03-03T18:11:12Z</jp:f1a>\n </rdf:Description>\n</rdf:RDF>\n" + ) + { + public void verify(BeanWithDynaFieldSwapped o) { + assertType(BeanWithDynaFieldSwapped.class, o); + assertType(Calendar.class, o.f1.get("f1a")); + } + } + }, + { /* 4 */ + new ComboInput<BeanWithDynaFieldStringList>( + "BeanWithDynaFieldStringList", + BeanWithDynaFieldStringList.class, + new BeanWithDynaFieldStringList().init(), + /* Json */ "{f1a:['foo','bar']}", + /* JsonT */ "{f1a:['foo','bar']}", + /* JsonR */ "{\n\tf1a: [\n\t\t'foo',\n\t\t'bar'\n\t]\n}", + /* Xml */ "<object><f1a><string>foo</string><string>bar</string></f1a></object>", + /* XmlT */ "<object><f1a><string>foo</string><string>bar</string></f1a></object>", + /* XmlR */ "<object>\n\t<f1a>\n\t\t<string>foo</string>\n\t\t<string>bar</string>\n\t</f1a>\n</object>\n", + /* XmlNs */ "<object><f1a><string>foo</string><string>bar</string></f1a></object>", + /* Html */ "<table><tr><td>f1a</td><td><ul><li>foo</li><li>bar</li></ul></td></tr></table>", + /* HtmlT */ "<table><tr><td>f1a</td><td><ul><li>foo</li><li>bar</li></ul></td></tr></table>", + /* HtmlR */ "<table>\n\t<tr>\n\t\t<td>f1a</td>\n\t\t<td>\n\t\t\t<ul>\n\t\t\t\t<li>foo</li>\n\t\t\t\t<li>bar</li>\n\t\t\t</ul>\n\t\t</td>\n\t</tr>\n</table>\n", + /* Uon */ "(f1a=@(foo,bar))", + /* UonT */ "(f1a=@(foo,bar))", + /* UonR */ "(\n\tf1a=@(\n\t\tfoo,\n\t\tbar\n\t)\n)", + /* UrlEnc */ "f1a=@(foo,bar)", + /* UrlEncT */ "f1a=@(foo,bar)", + /* UrlEncR */ "f1a=@(\n\tfoo,\n\tbar\n)", + /* MsgPack */ "81A366316192A3666F6FA3626172", + /* MsgPackT */ "81A366316192A3666F6FA3626172", + /* RdfXml */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1a>\n<rdf:Seq>\n<rdf:li>foo</rdf:li>\n<rdf:li>bar</rdf:li>\n</rdf:Seq>\n</jp:f1a>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlT */ "<rdf:RDF>\n<rdf:Description>\n<jp:f1a>\n<rdf:Seq>\n<rdf:li>foo</rdf:li>\n<rdf:li>bar</rdf:li>\n</rdf:Seq>\n</jp:f1a>\n</rdf:Description>\n</rdf:RDF>\n", + /* RdfXmlR */ "<rdf:RDF>\n <rdf:Description>\n <jp:f1a>\n <rdf:Seq>\n <rdf:li>foo</rdf:li>\n <rdf:li>bar</rdf:li>\n </rdf:Seq>\n </jp:f1a>\n </rdf:Description>\n</rdf:RDF>\n" + ) + { + public void verify(BeanWithDynaFieldStringList o) { + assertType(BeanWithDynaFieldStringList.class, o); } } }, @@ -82,17 +218,117 @@ public class DynaBeanComboTest extends ComboTest { return p.builder().build(); } - public static class A { + @Bean(sort=true) + public static class BeanWithDynaField { public int f1; @BeanProperty(name="*") public Map<String,Object> f2 = new LinkedHashMap<String,Object>(); public int f3; - public A init() { + public BeanWithDynaField init() { + this.f1 = 1; + this.f2 = new ObjectMap().append("f2a", "a").append("f2b", "b"); + this.f3 = 3; + return this; + } + } + + @Bean(sort=true) + public static class BeanWithDynaMethods { + + private int f1, f3; + private Map<String,Object> f2 = new LinkedHashMap<String,Object>(); + private boolean setterCalled = false; + + public int getF1() { + return f1; + } + public void setF1(int f1) { + this.f1 = f1; + } + public int getF3() { + return f3; + } + public void setF3(int f3) { + this.f3 = f3; + } + + @BeanProperty(name="*") + public Map<String, Object> xxx() { + return f2; + } + + @BeanProperty(name="*") + public void yyy(String name, Object o) { + setterCalled = true; + this.f2.put(name, o); + } + + public BeanWithDynaMethods init() { + this.f1 = 1; + this.f2 = new ObjectMap().append("f2a", "a").append("f2b", "b"); + this.f3 = 3; + return this; + } + } + + @Bean(sort=true) + public static class BeanWithDynaGetterOnly { + + private int f1, f3; + private Map<String,Object> f2 = new LinkedHashMap<String,Object>(); + + public int getF1() { + return f1; + } + public void setF1(int f1) { + this.f1 = f1; + } + public int getF3() { + return f3; + } + public void setF3(int f3) { + this.f3 = f3; + } + + @BeanProperty(name="*") + public Map<String, Object> xxx() { + return f2; + } + + public BeanWithDynaGetterOnly init() { this.f1 = 1; this.f2 = new ObjectMap().append("f2a", "a").append("f2b", "b"); this.f3 = 3; return this; } } + + private static Calendar singleDate = new GregorianCalendar(TimeZone.getTimeZone("PST")); + static { + singleDate.setTimeInMillis(0); + singleDate.set(1901, 2, 3, 10, 11, 12); + } + + @Bean(sort=true) + public static class BeanWithDynaFieldSwapped { + @BeanProperty(name="*", swap=CalendarSwap.ISO8601DTZ.class) + public Map<String,Calendar> f1 = new LinkedHashMap<String,Calendar>(); + + public BeanWithDynaFieldSwapped init() { + this.f1.put("f1a", singleDate); + return this; + } + } + + @Bean(sort=true) + public static class BeanWithDynaFieldStringList { + @BeanProperty(name="*") + public Map<String,List<String>> f1 = new LinkedHashMap<String,List<String>>(); + + public BeanWithDynaFieldStringList init() { + this.f1.put("f1a", Arrays.asList(new String[]{"foo","bar"})); + return this; + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core/src/main/java/org/apache/juneau/BeanMap.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMap.java b/juneau-core/src/main/java/org/apache/juneau/BeanMap.java index 85e1670..e6a1566 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanMap.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanMap.java @@ -417,10 +417,10 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T for (BeanPropertyMeta bpm : properties) { try { if (bpm.isDyna()) { - for (Map.Entry<String,Object> e : bpm.getDynaMap(bean).entrySet()) { - Object val = e.getValue(); + for (String pName : bpm.getDynaMap(bean).keySet()) { + Object val = bpm.get(this, pName); if (val != null || ! ignoreNulls) - l.add(new BeanPropertyValue(bpm, e.getKey(), val, null)); + l.add(new BeanPropertyValue(bpm, pName, val, null)); } } else { Object val = bpm.get(this, null); @@ -434,6 +434,8 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T l.add(new BeanPropertyValue(bpm, bpm.getName(), null, t)); } } + if (meta.sortProperties && meta.dynaProperty != null) + Collections.sort(l); return l; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java index bfa5246..6ca660c 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java @@ -93,6 +93,7 @@ public class BeanMeta<T> { private final String dictionaryName; // The @Bean.typeName() annotation defined on this bean class. final String notABeanReason; // Readable string explaining why this class wasn't a bean. final BeanRegistry beanRegistry; + final boolean sortProperties; /** * Constructor. @@ -123,6 +124,7 @@ public class BeanMeta<T> { this.beanRegistry = b.beanRegistry; this.typePropertyName = b.typePropertyName; this.typeProperty = new BeanPropertyMeta.Builder(this, typePropertyName, ctx.string(), beanRegistry).build(); + this.sortProperties = b.sortProperties; } private static final class Builder<T> { @@ -142,6 +144,7 @@ public class BeanMeta<T> { PropertyNamer propertyNamer; BeanRegistry beanRegistry; String dictionaryName, typePropertyName; + boolean sortProperties; private Builder(ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames) { this.classMeta = classMeta; @@ -341,7 +344,7 @@ public class BeanMeta<T> { if (beanFilter == null && ctx.beansRequireSomeProperties && normalProps.size() == 0) return "No properties detected on bean class"; - boolean sortProperties = (ctx.sortProperties || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty(); + sortProperties = (ctx.sortProperties || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty(); properties = sortProperties ? new TreeMap<String,BeanPropertyMeta>() : new LinkedHashMap<String,BeanPropertyMeta>(); @@ -493,6 +496,10 @@ public class BeanMeta<T> { if (b == null) return false; + // Don't do further validation if this is the "*" bean property. + if ("*".equals(b.name)) + return true; + // Get the bean property type from the getter/field. Class<?> pt = null; if (b.getter != null) @@ -551,6 +558,7 @@ public class BeanMeta<T> { Class<?> rt = m.getReturnType(); boolean isGetter = false, isSetter = false; BeanProperty bp = m.getAnnotation(BeanProperty.class); + String bpName = bp == null ? "" : bp.name(); if (pt.length == 0) { if (n.startsWith("get") && (! rt.equals(Void.TYPE))) { isGetter = true; @@ -558,23 +566,28 @@ public class BeanMeta<T> { } else if (n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) { isGetter = true; n = n.substring(2); - } else if (bp != null && ! bp.name().isEmpty()) { + } else if (! bpName.isEmpty()) { isGetter = true; - n = bp.name(); + n = bpName; } } else if (pt.length == 1) { if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) { isSetter = true; n = n.substring(3); - } else if (bp != null && ! bp.name().isEmpty()) { + } else if (! bpName.isEmpty()) { + isSetter = true; + n = bpName; + } + } else if (pt.length == 2) { + if ("*".equals(bpName)) { isSetter = true; - n = bp.name(); + n = bpName; } } n = pn.getPropertyName(n); if (isGetter || isSetter) { - if (bp != null && ! bp.name().equals("")) { - n = bp.name(); + if (! bpName.isEmpty()) { + n = bpName; if (! fixedBeanProps.isEmpty()) if (! fixedBeanProps.contains(n)) throw new BeanRuntimeException(c, "Method property ''{0}'' identified in @BeanProperty, but missing from @Bean", n); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java index ca20da7..409373a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java @@ -166,11 +166,6 @@ public class BeanPropertyMeta { isDyna = "*".equals(name); - if (isDyna) - rawTypeMeta = rawTypeMeta.getValueType(); - if (rawTypeMeta == null) - return false; - // Do some annotation validation. Class<?> c = rawTypeMeta.getInnerClass(); if (getter != null) { @@ -187,9 +182,7 @@ public class BeanPropertyMeta { if (pt.length != (isDyna ? 2 : 1)) return false; if (isDyna) { - if (pt[0].equals(String.class)) - return false; - if (! isParentClass(pt[1], c)) + if (! pt[0].equals(String.class)) return false; } else { if (! isParentClass(pt[0], c)) @@ -206,6 +199,11 @@ public class BeanPropertyMeta { } } + if (isDyna) + rawTypeMeta = rawTypeMeta.getValueType(); + if (rawTypeMeta == null) + return false; + if (typeMeta == null) typeMeta = (swap != null ? swap.getSwapClassMeta(beanContext) : rawTypeMeta == null ? beanContext.object() : rawTypeMeta.getSerializedClassMeta()); if (typeMeta == null) @@ -497,7 +495,7 @@ public class BeanPropertyMeta { boolean isMap = rawTypeMeta.isMap(); boolean isCollection = rawTypeMeta.isCollection(); - if (field == null && setter == null && ! (isMap || isCollection)) { + if ((! isDyna) && field == null && setter == null && ! (isMap || isCollection)) { if ((value == null && beanContext.ignoreUnknownNullBeanProperties) || beanContext.ignorePropertiesWithoutSetters) return null; throw new BeanRuntimeException(beanMeta.c, "Setter or public field not defined on property ''{0}''", name); @@ -671,6 +669,8 @@ public class BeanPropertyMeta { Map m = null; if (field != null) m = (Map<String,Object>)field.get(bean); + else if (getter != null) + m = (Map<String,Object>)getter.invoke(bean); else throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, this.getClassMeta().getInnerClass().getName(), findClassName(val)); return (m == null ? null : m.put(pName, val)); http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core/src/main/java/org/apache/juneau/BeanPropertyValue.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyValue.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyValue.java index de9d796..68abb37 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyValue.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyValue.java @@ -16,7 +16,7 @@ package org.apache.juneau; * Represents a simple bean property value and the meta-data associated with it. * <p> */ -public class BeanPropertyValue { +public class BeanPropertyValue implements Comparable<BeanPropertyValue> { private final BeanPropertyMeta pMeta; private final String name; @@ -77,4 +77,9 @@ public class BeanPropertyValue { public final Throwable getThrown() { return thrown; } + + @Override /* Comparable */ + public int compareTo(BeanPropertyValue o) { + return name.compareTo(o.name); + } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/juneau-core/src/main/java/org/apache/juneau/annotation/BeanProperty.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/annotation/BeanProperty.java b/juneau-core/src/main/java/org/apache/juneau/annotation/BeanProperty.java index f934c9e..daf6ca1 100644 --- a/juneau-core/src/main/java/org/apache/juneau/annotation/BeanProperty.java +++ b/juneau-core/src/main/java/org/apache/juneau/annotation/BeanProperty.java @@ -54,6 +54,75 @@ public @interface BeanProperty { * <p> * If the {@link BeanContext#BEAN_beanFieldVisibility} setting on the bean context excludes this field (e.g. the visibility * is set to PUBLIC, but the field is PROTECTED), this annotation can be used to force the field to be identified as a property. + * <p> + * <h6 class='topic'>Dynamic beans</h6> + * The bean property named <js>"*"</js> is the designated "dynamic property" which allows for "extra" bean properties not otherwise defined. + * This is similar in concept to the Jackson <ja>@JsonGetterAll</ja> and <ja>@JsonSetterAll</ja> annotations. + * The primary purpose is for backwards compatibility in parsing newer streams with addition information into older beans. + * <p> + * The following examples show how to define dynamic bean properties. + * <p class='bcode'> + * <jc>// Option #1 - A simple public Map field. + * // The field name can be anything.</jc> + * <jk>public class</jk> BeanWithDynaField { + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>) + * <jk>public</jk> Map<String,Object> extraStuff = <jk>new</jk> LinkedHashMap<String,Object>(); + * } + * + * <jc>// Option #2 - Getters and setters. + * // Method names can be anything. + * // Getter must return a Map with String keys. + * // Setter must take in two arguments.</jc> + * <jk>public class</jk> BeanWithDynaMethods { + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>) + * <jk>public</jk> Map<String,Object> getMyExtraStuff() { + * ... + * } + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>) + * <jk>public void</jk> setAnExtraField(String name, Object value) { + * ... + * } + * } + * + * <jc>// Option #3 - Getter only. + * // Properties will be added through the getter.</jc> + * <jk>public class</jk> BeanWithDynaGetterOnly { + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>) + * <jk>public</jk> Map<String,Object> getMyExtraStuff() { + * ... + * } + * } + * </p> + * <p> + * Similar rules apply for value types and swaps. The property values optionally can be any serializable type + * or use swaps. + * <p class='bcode'> + * <jc>// A serializable type other than Object.</jc> + * <jk>public class</jk> BeanWithDynaFieldWithListValues { + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>) + * <jk>public</jk> Map<String,List<String>> getMyExtraStuff() { + * ... + * } + * } + * + * <jc>// A swapped value.</jc> + * <jk>public class</jk> BeanWithDynaFieldWithSwappedValues { + * + * <ja>@BeanProperty</ja>(name=<js>"*"</js>, swap=CalendarSwap.<jsf>ISO8601DTZ</jsf>.<jk>class</jk>) + * <jk>public</jk> Map<String,Calendar> getMyExtraStuff() { + * ... + * } + * } + * </p> + * <p class='info'> + * Note that if you're not interested in these additional properties, you can also use the {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting + * to ignore values that don't fit into existing properties. + * </p> */ String name() default ""; http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/f8ee4864/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 6cfe84b..96a8b5c 100644 --- a/juneau-core/src/main/javadoc/overview.html +++ b/juneau-core/src/main/javadoc/overview.html @@ -5819,6 +5819,7 @@ <li>{@link org.apache.juneau.http.AcceptEncoding} <li>{@link org.apache.juneau.http.ContentType} </ul> + <li>Support for dynamic beans. See {@link org.apache.juneau.annotation.BeanProperty#name() @BeanProperty.name()}. </ul> <h6 class='topic'>org.apache.juneau.rest</h6> @@ -7573,7 +7574,7 @@ <li>New {@link org.apache.juneau.ClassMeta#isInstance(Object)} method. <li>Performance improvements when using the {@link org.apache.juneau.BeanMap#add(String,Object)} method. Array properties are stored in a temporary list cache until {@link org.apache.juneau.BeanMap#getBean()} is called. - <li>New {@link org.apache.juneau.BeanPropertyMeta#add(BeanMap,Object)} method for adding values to Collection and array properties. + <li>New <code><del>BeanPropertyMeta.add(BeanMap,Object)</del></code> method for adding values to Collection and array properties. <li>Config INI files now support keys with name <js>"*"</js>. </ul>
