This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 99aec78  JUNEAU-185
99aec78 is described below

commit 99aec786eb9f2a8cfd0c65f6611783a59d7df2c3
Author: JamesBognar <[email protected]>
AuthorDate: Wed Mar 11 19:55:40 2020 -0400

    JUNEAU-185
    
    ResourceDescriptions needs an append(url, label, description) method.
---
 .../apache/juneau/BeanConfigAnnotationTest.java    |  20 +-
 .../java/org/apache/juneau/BeanMapErrorsTest.java  |  75 ++++---
 .../src/main/java/org/apache/juneau/BeanMap.java   |  24 ++-
 .../src/main/java/org/apache/juneau/BeanMeta.java  |  89 +++++----
 .../org/apache/juneau/utils/ReflectionMap.java     |   3 +
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |  19 ++
 .../juneau/rest/annotation/RestMethodBpiTest.java  | 221 ++++++++++++++++++++-
 .../rest/headers/ResourceDescriptionTest.java}     |  31 +--
 .../juneau/rest/helper/ResourceDescription.java    |  39 +++-
 .../juneau/rest/helper/ResourceDescriptions.java   |  12 ++
 10 files changed, 423 insertions(+), 110 deletions(-)

diff --git 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanConfigAnnotationTest.java
 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanConfigAnnotationTest.java
index af72ef2..804d55d 100644
--- 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanConfigAnnotationTest.java
+++ 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanConfigAnnotationTest.java
@@ -26,10 +26,12 @@ import org.apache.juneau.reflect.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.transform.*;
 import org.junit.*;
+import org.junit.runners.*;
 
 /**
  * Tests the @BeanConfig annotation.
  */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class BeanConfigAnnotationTest {
 
        private static void check(String expected, Object o) {
@@ -158,7 +160,7 @@ public class BeanConfigAnnotationTest {
        static ClassInfo a = ClassInfo.of(A.class);
 
        @Test
-       public void basic() throws Exception {
+       public void a01_basic() throws Exception {
                AnnotationList al = a.getAnnotationList();
                BeanTraverseSession bc = 
JsonSerializer.create().applyAnnotations(al, sr).build().createSession();
 
@@ -209,7 +211,7 @@ public class BeanConfigAnnotationTest {
        static ClassInfo b = ClassInfo.of(B.class);
 
        @Test
-       public void noValues() throws Exception {
+       public void b01_noValues() throws Exception {
                AnnotationList al = b.getAnnotationList();
                JsonSerializer bc = 
JsonSerializer.create().applyAnnotations(al, sr).build();
                check("PUBLIC", bc.getBeanClassVisibility());
@@ -261,7 +263,7 @@ public class BeanConfigAnnotationTest {
        static ClassInfo c = ClassInfo.of(C.class);
 
        @Test
-       public void noAnnotation() throws Exception {
+       public void c01_noAnnotation() throws Exception {
                AnnotationList al = c.getAnnotationList();
                JsonSerializer bc = 
JsonSerializer.create().applyAnnotations(al, sr).build();
                check("PUBLIC", bc.getBeanClassVisibility());
@@ -327,7 +329,7 @@ public class BeanConfigAnnotationTest {
        private static ClassInfo d = ClassInfo.of(D.class);
 
        @Test
-       public void beanBpiBpxCombined_noBeanConfig() throws Exception {
+       public void d01_beanBpiBpxCombined_noBeanConfig() throws Exception {
                String json = SimpleJson.DEFAULT.toString(D.create());
                assertEquals("{a:1,c:3}", json);
                D d = SimpleJson.DEFAULT.read(json, D.class);
@@ -336,7 +338,7 @@ public class BeanConfigAnnotationTest {
        }
 
        @Test
-       public void beanBpiBpxCombined_beanConfigOverride() throws Exception {
+       public void d02_beanBpiBpxCombined_beanConfigOverride() throws 
Exception {
                AnnotationList al = d.getAnnotationList();
                JsonSerializer js = 
JsonSerializer.create().simple().applyAnnotations(al, sr).build();
                JsonParser jp = JsonParser.create().applyAnnotations(al, 
sr).build();
@@ -349,7 +351,7 @@ public class BeanConfigAnnotationTest {
        }
 
        @Test
-       public void beanBpiBpxCombined_beanContextBuilderOverride() throws 
Exception {
+       public void d03_beanBpiBpxCombined_beanContextBuilderOverride() throws 
Exception {
                Bean ba = new BeanAnnotation("D").bpi("b,c,d").bpx("c");
                JsonSerializer js = 
JsonSerializer.create().simple().annotations(ba).build();
                JsonParser jp = JsonParser.create().annotations(ba).build();
@@ -387,7 +389,7 @@ public class BeanConfigAnnotationTest {
        private static ClassInfo e = ClassInfo.of(E.class);
 
        @Test
-       public void beanBpiBpxCombined_multipleBeanAnnotations_noBeanConfig() 
throws Exception {
+       public void 
e01_beanBpiBpxCombined_multipleBeanAnnotations_noBeanConfig() throws Exception {
                String json = SimpleJson.DEFAULT.toString(E.create());
                assertEquals("{a:1,c:3}", json);
                E e = SimpleJson.DEFAULT.read(json, E.class);
@@ -396,7 +398,7 @@ public class BeanConfigAnnotationTest {
        }
 
        @Test
-       public void 
beanBpiBpxCombined_multipleBeanAnnotations_beanConfigOverride() throws 
Exception {
+       public void 
e02_beanBpiBpxCombined_multipleBeanAnnotations_beanConfigOverride() throws 
Exception {
                AnnotationList al = e.getAnnotationList();
                JsonSerializer js = 
JsonSerializer.create().simple().applyAnnotations(al, sr).build();
                JsonParser jp = JsonParser.create().applyAnnotations(al, 
sr).build();
@@ -409,7 +411,7 @@ public class BeanConfigAnnotationTest {
        }
 
        @Test
-       public void 
beanBpiBpxCombined_multipleBeanAnnotations_beanContextBuilderOverride() throws 
Exception {
+       public void 
e03_beanBpiBpxCombined_multipleBeanAnnotations_beanContextBuilderOverride() 
throws Exception {
                Bean ba = new BeanAnnotation("E").bpi("b,c,d").bpx("c");
                JsonSerializer js = 
JsonSerializer.create().simple().annotations(ba).build();
                JsonParser jp = JsonParser.create().annotations(ba).build();
diff --git 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanMapErrorsTest.java
 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanMapErrorsTest.java
index 9842600..7245423 100644
--- 
a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanMapErrorsTest.java
+++ 
b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/BeanMapErrorsTest.java
@@ -14,6 +14,8 @@ package org.apache.juneau;
 
 import static org.junit.Assert.*;
 
+import java.util.stream.*;
+
 import org.apache.juneau.annotation.*;
 import org.junit.*;
 
@@ -24,39 +26,47 @@ public class BeanMapErrorsTest {
 
        
//-----------------------------------------------------------------------------------------------------------------
        // @Beanp(name) on method not in @Bean(properties)
+       // Shouldn't be found in keySet()/entrySet() but should be found in 
containsKey()/get()
        
//-----------------------------------------------------------------------------------------------------------------
        @Test
        public void beanPropertyMethodNotInBeanProperties() {
                BeanContext bc = BeanContext.DEFAULT;
 
-               try {
-                       bc.getClassMeta(A1.class);
-                       fail();
-               } catch (Exception e) {
-                       assertEquals("org.apache.juneau.BeanMapErrorsTest$A1: 
Found @Beanp(\"f2\") but name was not found in @Bean(properties)", 
e.getMessage());
-               }
+               BeanMap<A1> bm = bc.createBeanSession().newBeanMap(A1.class);
+               assertTrue(bm.containsKey("f2"));
+               assertEquals(-1, bm.get("f2"));
+               bm.put("f2", -2);
+               assertEquals(-2, bm.get("f2"));
+               assertFalse(bm.keySet().contains("f2"));
+               assertFalse(bm.entrySet().stream().map(x -> 
x.getKey()).collect(Collectors.toList()).contains("f2"));
        }
 
        @Bean(bpi="f1")
        public static class A1 {
                public int f1;
+               private int f2 = -1;
 
                @Beanp("f2")
                public int f2() {
-                       return -1;
+                       return f2;
                };
+
+               public void setF2(int f2) {
+                       this.f2 = f2;
+               }
        }
 
        @Test
        public void beanPropertyMethodNotInBeanProperties_usingConfig() {
                BeanContext bc = 
BeanContext.create().applyAnnotations(B1.class).build();
 
-               try {
-                       bc.getClassMeta(B1.class);
-                       fail();
-               } catch (Exception e) {
-                       assertEquals("org.apache.juneau.BeanMapErrorsTest$B1: 
Found @Beanp(\"f2\") but name was not found in @Bean(properties)", 
e.getMessage());
-               }
+               BeanMap<B1> bm = bc.createBeanSession().newBeanMap(B1.class);
+               assertTrue(bm.containsKey("f2"));
+               assertEquals(-1, bm.get("f2"));
+               bm.put("f2", -2);
+               assertEquals(-2, bm.get("f2"));
+               assertFalse(bm.keySet().contains("f2"));
+               assertFalse(bm.entrySet().stream().map(x -> 
x.getKey()).collect(Collectors.toList()).contains("f2"));
        }
 
        @BeanConfig(
@@ -69,10 +79,16 @@ public class BeanMapErrorsTest {
        )
        public static class B1 {
                public int f1;
+               private int f2 = -1;
 
+               @Beanp("f2")
                public int f2() {
-                       return -1;
+                       return f2;
                };
+
+               public void setF2(int f2) {
+                       this.f2 = f2;
+               }
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -82,31 +98,34 @@ public class BeanMapErrorsTest {
        public void beanPropertyFieldNotInBeanProperties() {
                BeanContext bc = BeanContext.DEFAULT;
 
-               try {
-                       bc.getClassMeta(A2.class);
-                       fail();
-               } catch (Exception e) {
-                       assertEquals("org.apache.juneau.BeanMapErrorsTest$A2: 
Found @Beanp(\"f2\") but name was not found in @Bean(properties)", 
e.getMessage());
-               }
+               BeanMap<A2> bm = bc.createBeanSession().newBeanMap(A2.class);
+               assertTrue(bm.containsKey("f2"));
+               assertEquals(-1, bm.get("f2"));
+               bm.put("f2", -2);
+               assertEquals(-2, bm.get("f2"));
+               assertFalse(bm.keySet().contains("f2"));
+               assertFalse(bm.entrySet().stream().map(x -> 
x.getKey()).collect(Collectors.toList()).contains("f2"));
        }
+
        @Bean(bpi="f1")
        public static class A2 {
                public int f1;
 
                @Beanp("f2")
-               public int f2;
+               public int f2 = -1;
        }
 
        @Test
        public void beanPropertyFieldNotInBeanProperties_usingBeanConfig() {
                BeanContext bc = 
BeanContext.create().applyAnnotations(B2.class).build();
 
-               try {
-                       bc.getClassMeta(B2.class);
-                       fail();
-               } catch (Exception e) {
-                       assertEquals("org.apache.juneau.BeanMapErrorsTest$B2: 
Found @Beanp(\"f2\") but name was not found in @Bean(properties)", 
e.getMessage());
-               }
+               BeanMap<B2> bm = bc.createBeanSession().newBeanMap(B2.class);
+               assertTrue(bm.containsKey("f2"));
+               assertEquals(-1, bm.get("f2"));
+               bm.put("f2", -2);
+               assertEquals(-2, bm.get("f2"));
+               assertFalse(bm.keySet().contains("f2"));
+               assertFalse(bm.entrySet().stream().map(x -> 
x.getKey()).collect(Collectors.toList()).contains("f2"));
        }
 
        @BeanConfig(
@@ -120,6 +139,6 @@ public class BeanMapErrorsTest {
        public static class B2 {
                public int f1;
 
-               public int f2;
+               public int f2 = -1;
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index b704bc4..28d22cf 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -155,6 +155,12 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
                        if (cm.isOptional() && pMeta.get(this, pMeta.getName()) 
== null)
                                pMeta.set(this, pMeta.getName(), 
cm.getOptionalDefault());
                }
+               // Do the same for hidden fields.
+               for (BeanPropertyMeta pMeta : 
this.meta.hiddenProperties.values()) {
+                       ClassMeta<?> cm = pMeta.getClassMeta();
+                       if (cm.isOptional() && pMeta.get(this, pMeta.getName()) 
== null)
+                               pMeta.set(this, pMeta.getName(), 
cm.getOptionalDefault());
+               }
 
                return b;
        }
@@ -237,7 +243,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
         */
        @Override /* Map */
        public Object put(String property, Object value) {
-               BeanPropertyMeta p = meta.properties.get(property);
+               BeanPropertyMeta p = getPropertyMeta(property);
                if (p == null) {
                        if (meta.ctx.isIgnoreUnknownBeanProperties())
                                return null;
@@ -245,7 +251,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
                        if (property.equals(beanTypePropertyName))
                                return null;
 
-                       p = meta.properties.get("*");
+                       p = getPropertyMeta("*");
                        if (p == null)
                                throw new BeanRuntimeException(meta.c, "Bean 
property ''{0}'' not found.", property);
                }
@@ -254,6 +260,13 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
                return p.set(this, property, value);
        }
 
+       @Override /* Map */
+       public boolean containsKey(Object property) {
+               if (getPropertyMeta(emptyIfNull(property)) != null)
+                       return true;
+               return super.containsKey(property);
+       }
+
        /**
         * Add a value to a collection or array property.
         *
@@ -265,7 +278,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
         * @param value The value to add to the collection or array.
         */
        public void add(String property, Object value) {
-               BeanPropertyMeta p = meta.properties.get(property);
+               BeanPropertyMeta p = getPropertyMeta(property);
                if (p == null) {
                        if (meta.ctx.isIgnoreUnknownBeanProperties())
                                return;
@@ -431,10 +444,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
         * @return Metadata on the specified property, or <jk>null</jk> if that 
property does not exist.
         */
        public BeanPropertyMeta getPropertyMeta(String propertyName) {
-               BeanPropertyMeta bpMeta = meta.properties.get(propertyName);
-               if (bpMeta == null)
-                       bpMeta = meta.dynaProperty;
-               return bpMeta;
+               return meta.getPropertyMeta(propertyName);
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 5fd5ebb..f24f082 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -71,6 +71,9 @@ public class BeanMeta<T> {
        /** The properties on the target class. */
        protected final Map<String,BeanPropertyMeta> properties;
 
+       /** The hidden properties on the target class. */
+       protected final Map<String,BeanPropertyMeta> hiddenProperties;
+
        /** The getter properties on the target class. */
        protected final Map<Method,String> getterProps;
 
@@ -121,6 +124,7 @@ public class BeanMeta<T> {
                this.beanFilter = beanFilter;
                this.dictionaryName = b.dictionaryName;
                this.properties = unmodifiableMap(b.properties);
+               this.hiddenProperties = unmodifiableMap(b.hiddenProperties);
                this.getterProps = unmodifiableMap(b.getterProps);
                this.setterProps = unmodifiableMap(b.setterProps);
                this.dynaProperty = b.dynaProperty;
@@ -139,7 +143,7 @@ public class BeanMeta<T> {
                BeanContext ctx;
                BeanFilter beanFilter;
                String[] pNames;
-               Map<String,BeanPropertyMeta> properties;
+               Map<String,BeanPropertyMeta> properties, hiddenProperties = new 
LinkedHashMap<>();
                Map<Method,String> getterProps = new HashMap<>();
                Map<Method,String> setterProps = new HashMap<>();
                BeanPropertyMeta dynaProperty;
@@ -174,11 +178,13 @@ public class BeanMeta<T> {
                                List<Class<?>> bdClasses = new ArrayList<>();
                                if (beanFilter != null && 
beanFilter.getBeanDictionary() != null)
                                        
bdClasses.addAll(Arrays.asList(beanFilter.getBeanDictionary()));
-                               Bean bean = classMeta.getAnnotation(Bean.class);
-                               if (bean != null) {
-                                       if (! bean.typeName().isEmpty())
-                                               
bdClasses.add(classMeta.innerClass);
-                               }
+
+                               boolean hasTypeName = false;
+                               for (Bean b : 
classMeta.getAnnotationsParentFirst(Bean.class))
+                                       if (! b.typeName().isEmpty())
+                                               hasTypeName = true;
+                               if (hasTypeName)
+                                       bdClasses.add(classMeta.innerClass);
                                this.beanRegistry = new BeanRegistry(ctx, null, 
bdClasses.toArray(new Class<?>[bdClasses.size()]));
 
                                for (Bean b : 
classMeta.getAnnotationsParentFirst(Bean.class))
@@ -357,8 +363,8 @@ public class BeanMeta<T> {
 
                                } else /* Use 'better' introspection */ {
 
-                                       for (Field f : findBeanFields(ctx, c2, 
stopClass, fVis, filterProps)) {
-                                               String name = 
findPropertyName(f, fixedBeanProps);
+                                       for (Field f : findBeanFields(ctx, c2, 
stopClass, fVis)) {
+                                               String name = 
findPropertyName(f);
                                                if (name != null) {
                                                        if (! 
normalProps.containsKey(name))
                                                                
normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
@@ -366,7 +372,7 @@ public class BeanMeta<T> {
                                                }
                                        }
 
-                                       List<BeanMethod> bms = 
findBeanMethods(ctx, c2, stopClass, mVis, fixedBeanProps, filterProps, 
propertyNamer, fluentSetters);
+                                       List<BeanMethod> bms = 
findBeanMethods(ctx, c2, stopClass, mVis, propertyNamer, fluentSetters);
 
                                        // Iterate through all the getters.
                                        for (BeanMethod bm : bms) {
@@ -481,31 +487,44 @@ public class BeanMeta<T> {
                                        Set<String> bfbpi = beanFilter.getBpi();
                                        Set<String> bfbpx = beanFilter.getBpx();
 
-                                       if (bpx.isEmpty() && ! bfbpx.isEmpty()) 
{
-
-                                               for (String k : bfbpx)
-                                                       properties.remove(k);
-
-                                       // Only include specified properties if 
BeanFilter.includeKeys is specified.
-                                       // Note that the order must match 
includeKeys.
-                                       } else if (! bfbpi.isEmpty()) {
+                                       if (bpi.isEmpty() && ! bfbpi.isEmpty()) 
{
+                                               // Only include specified 
properties if BeanFilter.includeKeys is specified.
+                                               // Note that the order must 
match includeKeys.
                                                Map<String,BeanPropertyMeta> 
properties2 = new LinkedHashMap<>();
                                                for (String k : bfbpi) {
                                                        if 
(properties.containsKey(k))
-                                                               
properties2.put(k, properties.get(k));
+                                                               
properties2.put(k, properties.remove(k));
                                                }
+                                               
hiddenProperties.putAll(properties);
                                                properties = properties2;
                                        }
+                                       if (bpx.isEmpty() && ! bfbpx.isEmpty()) 
{
+                                               for (String k : bfbpx) {
+                                                       hiddenProperties.put(k, 
properties.remove(k));
+                                               }
+                                       }
+                               }
+
+                               if (! bpi.isEmpty()) {
+                                       Map<String,BeanPropertyMeta> 
properties2 = new LinkedHashMap<>();
+                                       for (String k : bpi) {
+                                               if (properties.containsKey(k))
+                                                       properties2.put(k, 
properties.remove(k));
+                                       }
+                                       hiddenProperties.putAll(properties);
+                                       properties = properties2;
                                }
 
                                for (String ep : bpx)
-                                       properties.remove(ep);
+                                       hiddenProperties.put(ep, 
properties.remove(ep));
 
                                if (pNames != null) {
                                        Map<String,BeanPropertyMeta> 
properties2 = new LinkedHashMap<>();
                                        for (String k : pNames) {
                                                if (properties.containsKey(k))
                                                        properties2.put(k, 
properties.get(k));
+                                               else
+                                                       hiddenProperties.put(k, 
properties.get(k));
                                        }
                                        properties = properties2;
                                }
@@ -544,21 +563,15 @@ public class BeanMeta<T> {
                 * Returns the property name of the specified field if it's a 
valid property.
                 * Returns null if the field isn't a valid property.
                 */
-               private String findPropertyName(Field f, Set<String> 
fixedBeanProps) {
+               private String findPropertyName(Field f) {
                        @SuppressWarnings("deprecation")
                        BeanProperty px = f.getAnnotation(BeanProperty.class);
                        List<Beanp> lp = ctx.getAnnotations(Beanp.class, f);
                        List<Name> ln = ctx.getAnnotations(Name.class, f);
                        String name = bpName(px, lp, ln);
-                       if (isNotEmpty(name)) {
-                               if (fixedBeanProps.isEmpty() || 
fixedBeanProps.contains(name))
-                                       return name;
-                               return null;  // Could happen if filtered via 
BEAN_bpi/BEAN_bpx.
-                       }
-                       name = propertyNamer.getPropertyName(f.getName());
-                       if (fixedBeanProps.isEmpty() || 
fixedBeanProps.contains(name))
+                       if (isNotEmpty(name))
                                return name;
-                       return null;
+                       return propertyNamer.getPropertyName(f.getName());
                }
        }
 
@@ -670,7 +683,7 @@ public class BeanMeta<T> {
         * @param fixedBeanProps Only include methods whose properties are in 
this list.
         * @param pn Use this property namer to determine property names from 
the method names.
         */
-       static final List<BeanMethod> findBeanMethods(BeanContext ctx, Class<?> 
c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, Set<String> 
filterProps, PropertyNamer pn, boolean fluentSetters) {
+       static final List<BeanMethod> findBeanMethods(BeanContext ctx, Class<?> 
c, Class<?> stopClass, Visibility v, PropertyNamer pn, boolean fluentSetters) {
                List<BeanMethod> l = new LinkedList<>();
 
                for (ClassInfo c2 : findClasses(c, stopClass)) {
@@ -700,9 +713,6 @@ public class BeanMeta<T> {
                                MethodType methodType = UNKNOWN;
                                String bpName = bpName(px, lp, ln);
 
-                               if (! (isEmpty(bpName) || filterProps.isEmpty() 
|| filterProps.contains(bpName)))
-                                       throw new BeanRuntimeException(c, 
"Found @Beanp(\"{0}\") but name was not found in @Bean(properties)", bpName);
-
                                if (pt.size() == 0) {
                                        if ("*".equals(bpName)) {
                                                if 
(rt.isChildOf(Collection.class)) {
@@ -769,12 +779,8 @@ public class BeanMeta<T> {
                                        throw new BeanRuntimeException(c, 
"Found @Beanp(\"*\") but could not determine method type on method ''{0}''.", 
m.getSimpleName());
 
                                if (methodType != UNKNOWN) {
-                                       if (bpName != null && ! 
bpName.isEmpty()) {
+                                       if (bpName != null && ! 
bpName.isEmpty())
                                                n = bpName;
-                                               if (! fixedBeanProps.isEmpty())
-                                                       if (! 
fixedBeanProps.contains(n))
-                                                               n = null;  // 
Could happen if filtered via BEAN_bpi/BEAN_bpx
-                                       }
                                        if (n != null)
                                                l.add(new BeanMethod(n, 
methodType, m.inner()));
                                }
@@ -783,7 +789,7 @@ public class BeanMeta<T> {
                return l;
        }
 
-       static final Collection<Field> findBeanFields(BeanContext ctx, Class<?> 
c, Class<?> stopClass, Visibility v, Set<String> filterProps) {
+       static final Collection<Field> findBeanFields(BeanContext ctx, Class<?> 
c, Class<?> stopClass, Visibility v) {
                List<Field> l = new LinkedList<>();
                for (ClassInfo c2 : findClasses(c, stopClass)) {
                        for (FieldInfo f : c2.getDeclaredFields()) {
@@ -795,15 +801,10 @@ public class BeanMeta<T> {
                                @SuppressWarnings("deprecation")
                                BeanProperty px = 
f.getAnnotation(BeanProperty.class);
                                List<Beanp> lp = 
ctx.getAnnotations(Beanp.class, f);
-                               List<Name> ln = ctx.getAnnotations(Name.class, 
f);
-                               String bpName = bpName(px, lp, ln);
 
                                if (! (v.isVisible(f.inner()) || px != null || 
lp.size() > 0))
                                        continue;
 
-                               if (! (isEmpty(bpName) || filterProps.isEmpty() 
|| filterProps.contains(bpName)))
-                                       throw new BeanRuntimeException(c, 
"Found @Beanp(\"{0}\") but name was not found in @Bean(properties)", bpName);
-
                                l.add(f.inner());
                        }
                }
@@ -872,6 +873,8 @@ public class BeanMeta<T> {
        public BeanPropertyMeta getPropertyMeta(String name) {
                BeanPropertyMeta bpm = properties.get(name);
                if (bpm == null)
+                       bpm = hiddenProperties.get(name);
+               if (bpm == null)
                        bpm = dynaProperty;
                return bpm;
        }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
index 85150b4..63f76f5 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/ReflectionMap.java
@@ -174,6 +174,7 @@ public class ReflectionMap<V> {
                 *      <ul>
                 *              <li>Full class name (e.g. 
<js>"com.foo.MyClass"</js>).
                 *              <li>Simple class name (e.g. <js>"MyClass"</js>).
+                *              <li>All classes (e.g. <js>"*"</js>).
                 *              <li>Full method name (e.g. 
<js>"com.foo.MyClass.myMethod"</js>).
                 *              <li>Simple method name (e.g. 
<js>"MyClass.myMethod"</js>).
                 *              <li>A comma-delimited list of anything on this 
list.
@@ -601,6 +602,8 @@ public class ReflectionMap<V> {
                String cSimple = c.getSimpleName(), cFull = c.getName();
                if (isEquals(simpleName, cSimple) || isEquals(fullName, cFull))
                        return true;
+               if ("*".equals(simpleName))
+                       return true;
                if (cFull.indexOf('$') != -1) {
                        Package p = c.getPackage();
                        if (p != null)
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html 
b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index cbb7676..8afc505 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -110,6 +110,23 @@
        String json = ws.toString(addressBean);  <jc>// Will print 
street,city,state</jc>
                </p>
        <li>
+               Bean maps now have the concept of "hidden" properties 
(properties that aren't serialized but otherwise accessible).
+               <br>For example, the {@link oaj.html.annotation.Html#link()} 
can now reference hidden properties:
+               <p class='bpcode w800'>
+       <ja>@Bean</ja>(bpi=<js>"a"</js>) <jc>// Will be overridden</jc>
+       <jk>public class</jk> MyBean {
+               
+               <ja>@Html</ja>(link=<js>"servlet:/{b}"</js>)
+               <jk>public</jk> String <jf>a</jf>;
+               
+               <jk>public</jk> String <jf>b</jf>;  <jc>// Not serialized, but 
referenced in link on a.</jc>  
+                       
+       }
+               </p>
+               <br>
+               The general rule for the {@link oaj.BeanMap} class is that 
<c>get()</c>,<c>put()</c>, and <c>containsKey()</c>
+               will work against hidden properties, but <c>keySet()</c> and 
<c>entrySet()</c> will skip them.
+       <li>
                Several bug fixes in the {@link HtmlSerializer} and {@link 
HtmlParser} classes around the handling of 
                collections and arrays of beans with 
<c><ja>@Bean</ja>(typeName)</c> annotations.
        <li>
@@ -137,6 +154,8 @@
                the 
<c><ja>@ConfigurationContext</ja>(initializers=JuneauRestInitializer.<jk>class</jk>)</c>
 when unit testing
                using <ja>@SpringBootTest</ja>.
        <li>
+               New {@link 
oajr.helper.ResourceDescription(String,String,String)} constructor and {@link 
oajr.helper.ResourceDescriptions#append(String,String,String)} method.
+       <li>
                New {@link oajr.helper.Hyperlink} class.
 </ul>
 
diff --git 
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodBpiTest.java
 
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodBpiTest.java
index affdbc8..6e87579 100644
--- 
a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodBpiTest.java
+++ 
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation/RestMethodBpiTest.java
@@ -43,6 +43,21 @@ public class RestMethodBpiTest {
                public Object a03() throws Exception {
                        return new MyBeanA().init();
                }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanA: a,_b")
+               public Object a04() throws Exception {
+                       return new MyBeanA().init();
+               }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanA: a")
+               public Object a05() throws Exception {
+                       return new MyBeanA().init();
+               }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanA: _b")
+               public Object a06() throws Exception {
+                       return new MyBeanA().init();
+               }
        }
        static MockRest a = MockRest.build(A.class);
 
@@ -71,6 +86,31 @@ public class RestMethodBpiTest {
                a.get("/a03").urlEnc().execute().assertBody("_b=foo");
        }
 
+       @Test
+       public void a04() throws Exception {
+               
a.get("/a04").json().execute().assertBody("{\"a\":1,\"_b\":\"foo\"}");
+               
a.get("/a04").xml().execute().assertBodyContains("<object><a>1</a><_b>foo</_b></object>");
+               
a.get("/a04").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr><tr><td>_b</td><td>foo</td></tr></table>");
+               a.get("/a04").uon().execute().assertBody("(a=1,_b=foo)");
+               a.get("/a04").urlEnc().execute().assertBody("a=1&_b=foo");
+       }
+       @Test
+       public void a05() throws Exception {
+               a.get("/a05").json().execute().assertBody("{\"a\":1}");
+               
a.get("/a05").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
a.get("/a05").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               a.get("/a05").uon().execute().assertBody("(a=1)");
+               a.get("/a05").urlEnc().execute().assertBody("a=1");
+       }
+       @Test
+       public void a06() throws Exception {
+               a.get("/a06").json().execute().assertBody("{\"_b\":\"foo\"}");
+               
a.get("/a06").xml().execute().assertBodyContains("<object><_b>foo</_b></object>");
+               
a.get("/a06").html().execute().assertBodyContains("<table><tr><td>_b</td><td>foo</td></tr></table>");
+               a.get("/a06").uon().execute().assertBody("(_b=foo)");
+               a.get("/a06").urlEnc().execute().assertBody("_b=foo");
+       }
+       
        
//=================================================================================================================
        // BPX on normal bean
        
//=================================================================================================================
@@ -90,6 +130,21 @@ public class RestMethodBpiTest {
                public Object b03() throws Exception {
                        return new MyBeanA().init();
                }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanA: a,_b")
+               public Object b04() throws Exception {
+                       return new MyBeanA().init();
+               }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanA: a")
+               public Object b05() throws Exception {
+                       return new MyBeanA().init();
+               }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanA: _b")
+               public Object b06() throws Exception {
+                       return new MyBeanA().init();
+               }
        }
        static MockRest b = MockRest.build(B.class);
 
@@ -117,6 +172,30 @@ public class RestMethodBpiTest {
                b.get("/b03").uon().execute().assertBody("(a=1)");
                b.get("/b03").urlEnc().execute().assertBody("a=1");
        }
+       @Test
+       public void b04() throws Exception {
+               b.get("/b04").json().execute().assertBody("{}");
+               b.get("/b04").xml().execute().assertBodyContains("<object/>");
+               
b.get("/b04").html().execute().assertBodyContains("<table></table>");
+               b.get("/b04").uon().execute().assertBody("()");
+               b.get("/b04").urlEnc().execute().assertBody("");
+       }
+       @Test
+       public void b05() throws Exception {
+               b.get("/b05").json().execute().assertBody("{\"_b\":\"foo\"}");
+               
b.get("/b05").xml().execute().assertBodyContains("<object><_b>foo</_b></object>");
+               
b.get("/b05").html().execute().assertBodyContains("<table><tr><td>_b</td><td>foo</td></tr></table>");
+               b.get("/b05").uon().execute().assertBody("(_b=foo)");
+               b.get("/b05").urlEnc().execute().assertBody("_b=foo");
+       }
+       @Test
+       public void b06() throws Exception {
+               b.get("/b06").json().execute().assertBody("{\"a\":1}");
+               
b.get("/b06").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
b.get("/b06").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               b.get("/b06").uon().execute().assertBody("(a=1)");
+               b.get("/b06").urlEnc().execute().assertBody("a=1");
+       }
 
        
//=================================================================================================================
        // BPI on bean using @Bean(properties)
@@ -137,16 +216,31 @@ public class RestMethodBpiTest {
                public Object c03() throws Exception {
                        return new MyBeanB().init();
                }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanB: a,_b")
+               public Object c04() throws Exception {
+                       return new MyBeanB().init();
+               }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanB: a")
+               public Object c05() throws Exception {
+                       return new MyBeanB().init();
+               }
+               @RestMethod
+               @BeanConfig(bpi="MyBeanB: _b")
+               public Object c06() throws Exception {
+                       return new MyBeanB().init();
+               }
        }
        static MockRest c = MockRest.build(C.class);
 
        @Test
        public void c01() throws Exception {
-               
c.get("/c01").json().execute().assertBody("{\"_b\":\"foo\",\"a\":1}");
-               
c.get("/c01").xml().execute().assertBodyContains("<object><_b>foo</_b><a>1</a></object>");
-               
c.get("/c01").html().execute().assertBodyContains("<table><tr><td>_b</td><td>foo</td></tr><tr><td>a</td><td>1</td></tr></table>");
-               c.get("/c01").uon().execute().assertBody("(_b=foo,a=1)");
-               c.get("/c01").urlEnc().execute().assertBody("_b=foo&a=1");
+               
c.get("/c01").json().execute().assertBody("{\"a\":1,\"_b\":\"foo\"}");
+               
c.get("/c01").xml().execute().assertBodyContains("<object><a>1</a><_b>foo</_b></object>");
+               
c.get("/c01").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr><tr><td>_b</td><td>foo</td></tr></table>");
+               c.get("/c01").uon().execute().assertBody("(a=1,_b=foo)");
+               c.get("/c01").urlEnc().execute().assertBody("a=1&_b=foo");
        }
        @Test
        public void c02() throws Exception {
@@ -164,6 +258,30 @@ public class RestMethodBpiTest {
                c.get("/c03").uon().execute().assertBody("(_b=foo)");
                c.get("/c03").urlEnc().execute().assertBody("_b=foo");
        }
+       @Test
+       public void c04() throws Exception {
+               
c.get("/c04").json().execute().assertBody("{\"a\":1,\"_b\":\"foo\"}");
+               
c.get("/c04").xml().execute().assertBodyContains("<object><a>1</a><_b>foo</_b></object>");
+               
c.get("/c04").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr><tr><td>_b</td><td>foo</td></tr></table>");
+               c.get("/c04").uon().execute().assertBody("(a=1,_b=foo)");
+               c.get("/c04").urlEnc().execute().assertBody("a=1&_b=foo");
+       }
+       @Test
+       public void c05() throws Exception {
+               c.get("/c05").json().execute().assertBody("{\"a\":1}");
+               
c.get("/c05").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
c.get("/c05").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               c.get("/c05").uon().execute().assertBody("(a=1)");
+               c.get("/c05").urlEnc().execute().assertBody("a=1");
+       }
+       @Test
+       public void c06() throws Exception {
+               c.get("/c06").json().execute().assertBody("{\"_b\":\"foo\"}");
+               
c.get("/c06").xml().execute().assertBodyContains("<object><_b>foo</_b></object>");
+               
c.get("/c06").html().execute().assertBodyContains("<table><tr><td>_b</td><td>foo</td></tr></table>");
+               c.get("/c06").uon().execute().assertBody("(_b=foo)");
+               c.get("/c06").urlEnc().execute().assertBody("_b=foo");
+       }
 
        
//=================================================================================================================
        // BPX on bean using @Bean(properties)
@@ -184,6 +302,21 @@ public class RestMethodBpiTest {
                public Object d03() throws Exception {
                        return new MyBeanB().init();
                }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanB: a,_b")
+               public Object d04() throws Exception {
+                       return new MyBeanB().init();
+               }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanB: a")
+               public Object d05() throws Exception {
+                       return new MyBeanB().init();
+               }
+               @RestMethod
+               @BeanConfig(bpx="MyBeanB: _b")
+               public Object d06() throws Exception {
+                       return new MyBeanB().init();
+               }
        }
        static MockRest d = MockRest.build(D.class);
 
@@ -211,6 +344,30 @@ public class RestMethodBpiTest {
                d.get("/d03").uon().execute().assertBody("(a=1)");
                d.get("/d03").urlEnc().execute().assertBody("a=1");
        }
+       @Test
+       public void d04() throws Exception {
+               d.get("/d04").json().execute().assertBody("{}");
+               d.get("/d04").xml().execute().assertBodyContains("<object/>");
+               
d.get("/d04").html().execute().assertBodyContains("<table></table>");
+               d.get("/d04").uon().execute().assertBody("()");
+               d.get("/d04").urlEnc().execute().assertBody("");
+       }
+       @Test
+       public void d05() throws Exception {
+               d.get("/d05").json().execute().assertBody("{\"_b\":\"foo\"}");
+               
d.get("/d05").xml().execute().assertBodyContains("<object><_b>foo</_b></object>");
+               
d.get("/d05").html().execute().assertBodyContains("<table><tr><td>_b</td><td>foo</td></tr></table>");
+               d.get("/d05").uon().execute().assertBody("(_b=foo)");
+               d.get("/d05").urlEnc().execute().assertBody("_b=foo");
+       }
+       @Test
+       public void d06() throws Exception {
+               d.get("/d06").json().execute().assertBody("{\"a\":1}");
+               
d.get("/d06").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
d.get("/d06").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               d.get("/d06").uon().execute().assertBody("(a=1)");
+               d.get("/d06").urlEnc().execute().assertBody("a=1");
+       }
 
        
//=================================================================================================================
        // BPI meta-matching
@@ -223,6 +380,11 @@ public class RestMethodBpiTest {
                public Object e01() throws Exception {
                        return new MyBeanA().init();
                }
+               @RestMethod
+               @BeanConfig(bpi="*: a")
+               public Object e02() throws Exception {
+                       return new MyBeanA().init();
+               }
        }
        static MockRest e = MockRest.build(E.class);
 
@@ -234,6 +396,14 @@ public class RestMethodBpiTest {
                e.get("/e01").uon().execute().assertBody("(a=1)");
                e.get("/e01").urlEnc().execute().assertBody("a=1");
        }
+       @Test
+       public void e02() throws Exception {
+               e.get("/e02").json().execute().assertBody("{\"a\":1}");
+               
e.get("/e02").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
e.get("/e02").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               e.get("/e02").uon().execute().assertBody("(a=1)");
+               e.get("/e02").urlEnc().execute().assertBody("a=1");
+       }
 
        
//=================================================================================================================
        // BPI fully-qualified class name
@@ -246,6 +416,11 @@ public class RestMethodBpiTest {
                public Object f01() throws Exception {
                        return new MyBeanA().init();
                }
+               @RestMethod
+               
@BeanConfig(bpi="org.apache.juneau.rest.annotation.RestMethodBpiTest$MyBeanA: 
a")
+               public Object f02() throws Exception {
+                       return new MyBeanA().init();
+               }
        }
        static MockRest f = MockRest.build(F.class);
 
@@ -257,6 +432,14 @@ public class RestMethodBpiTest {
                f.get("/f01").uon().execute().assertBody("(a=1)");
                f.get("/f01").urlEnc().execute().assertBody("a=1");
        }
+       @Test
+       public void f02() throws Exception {
+               f.get("/f02").json().execute().assertBody("{\"a\":1}");
+               
f.get("/f02").xml().execute().assertBodyContains("<object><a>1</a></object>");
+               
f.get("/f02").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr></table>");
+               f.get("/f02").uon().execute().assertBody("(a=1)");
+               f.get("/f02").urlEnc().execute().assertBody("a=1");
+       }
 
        
//=================================================================================================================
        // Negative matching
@@ -275,6 +458,18 @@ public class RestMethodBpiTest {
                        // Should not match.  We don't support meta-matches in 
class names.
                        return new MyBeanA().init();
                }
+               @RestMethod
+               @BeanConfig(bpi="MyBean: a")
+               public Object g03() throws Exception {
+                       // Should not match.
+                       return new MyBeanA().init();
+               }
+               @RestMethod
+               @BeanConfig(bpi="MyBean*: a")
+               public Object g04() throws Exception {
+                       // Should not match.  We don't support meta-matches in 
class names.
+                       return new MyBeanA().init();
+               }
        }
        static MockRest g = MockRest.build(G.class);
 
@@ -294,6 +489,22 @@ public class RestMethodBpiTest {
                g.get("/g02").uon().execute().assertBody("(a=1,_b=foo)");
                g.get("/g02").urlEnc().execute().assertBody("a=1&_b=foo");
        }
+       @Test
+       public void g03() throws Exception {
+               
g.get("/g03").json().execute().assertBody("{\"a\":1,\"_b\":\"foo\"}");
+               
g.get("/g03").xml().execute().assertBodyContains("<object><a>1</a><_b>foo</_b></object>");
+               
g.get("/g03").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr><tr><td>_b</td><td>foo</td></tr></table>");
+               g.get("/g03").uon().execute().assertBody("(a=1,_b=foo)");
+               g.get("/g03").urlEnc().execute().assertBody("a=1&_b=foo");
+       }
+       @Test
+       public void g04() throws Exception {
+               
g.get("/g04").json().execute().assertBody("{\"a\":1,\"_b\":\"foo\"}");
+               
g.get("/g04").xml().execute().assertBodyContains("<object><a>1</a><_b>foo</_b></object>");
+               
g.get("/g04").html().execute().assertBodyContains("<table><tr><td>a</td><td>1</td></tr><tr><td>_b</td><td>foo</td></tr></table>");
+               g.get("/g04").uon().execute().assertBody("(a=1,_b=foo)");
+               g.get("/g04").urlEnc().execute().assertBody("a=1&_b=foo");
+       }
 
        
//=================================================================================================================
        // Beans
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
 
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/headers/ResourceDescriptionTest.java
similarity index 67%
copy from 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
copy to 
juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/headers/ResourceDescriptionTest.java
index e9a6143..bb192f6 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
+++ 
b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/headers/ResourceDescriptionTest.java
@@ -10,25 +10,26 @@
 // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
 // * specific language governing permissions and limitations under the 
License.                                              *
 // 
***************************************************************************************************************************
-package org.apache.juneau.rest.helper;
+package org.apache.juneau.rest.headers;
 
-import java.util.*;
+import static org.junit.Assert.*;
+
+import org.apache.juneau.marshall.*;
+import org.apache.juneau.rest.helper.*;
+import org.junit.*;
+import org.junit.runners.*;
 
 /**
- * A list of {@link ResourceDescription} objects.
+ * Validates the handling of the Accept-Charset header.
  */
-public class ResourceDescriptions extends ArrayList<ResourceDescription> {
-       private static final long serialVersionUID = 1L;
+@SuppressWarnings({})
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ResourceDescriptionTest {
 
-       /**
-        * Adds a new {@link ResourceDescription} to this list.
-        *
-        * @param name The name of the child resource.
-        * @param description The description of the child resource.
-        * @return This object (for method chaining).
-        */
-       public ResourceDescriptions append(String name, String description) {
-               super.add(new ResourceDescription(name, description));
-               return this;
+       @Test
+       public void a01_basic() throws Exception {
+               ResourceDescription rd = new 
ResourceDescription("a","b?c=d&e=f","g");
+               assertEquals("<table><tr><td>name</td><td><a 
href=\"/b?c=d&amp;e=f\">a</a></td></tr><tr><td>description</td><td>g</td></tr></table>",
 Html.DEFAULT.toString(rd));
+               assertEquals("{name:'a',description:'g'}", 
SimpleJson.DEFAULT.toString(rd));
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescription.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescription.java
index b7f3e21..2801dab 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescription.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescription.java
@@ -36,16 +36,30 @@ import org.apache.juneau.jsonschema.annotation.Schema;
 @Response(schema=@Schema(ignore=true))
 public final class ResourceDescription implements 
Comparable<ResourceDescription> {
 
-       private String name, description;
+       private String name, uri, description;
 
        /**
-        * Constructor.
+        * Constructor for when the name and uri are the same.
         *
         * @param name The name of the child resource.
         * @param description The description of the child resource.
         */
        public ResourceDescription(String name, String description) {
                this.name = name;
+               this.uri = name;
+               this.description = description;
+       }
+
+       /**
+        * Constructor for when the name and uri are different.
+        *
+        * @param name The name of the child resource.
+        * @param uri The uri of the child resource.
+        * @param description The description of the child resource.
+        */
+       public ResourceDescription(String name, String uri, String description) 
{
+               this.name = name;
+               this.uri = uri;
                this.description = description;
        }
 
@@ -57,12 +71,21 @@ public final class ResourceDescription implements 
Comparable<ResourceDescription
         *
         * @return The name.
         */
-       @Html(link="servlet:/{name}")
+       @Html(link="servlet:/{uri}")
        public String getName() {
                return name;
        }
 
        /**
+        * Returns the uri on this label.
+        *
+        * @return The name.
+        */
+       public String getUri() {
+               return uri == null ? name : uri;
+       }
+
+       /**
         * Sets the name field on this label to a new value.
         *
         * @param name The new name.
@@ -93,6 +116,16 @@ public final class ResourceDescription implements 
Comparable<ResourceDescription
                return this;
        }
 
+       /**
+        * Sets the uri field on this label to a new value.
+        *
+        * @param uri The new uri.
+        * @return This object (for method chaining).
+        */
+       public ResourceDescription uri(String uri) {
+               this.uri = uri;
+               return this;
+       }
 
        @Override /* Comparable */
        public int compareTo(ResourceDescription o) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
index e9a6143..9cf9fcf 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/helper/ResourceDescriptions.java
@@ -31,4 +31,16 @@ public class ResourceDescriptions extends 
ArrayList<ResourceDescription> {
                super.add(new ResourceDescription(name, description));
                return this;
        }
+       /**
+        * Adds a new {@link ResourceDescription} to this list when the uri is 
different from the name.
+        *
+        * @param name The name of the child resource.
+        * @param uri The URI of the child resource.
+        * @param description The description of the child resource.
+        * @return This object (for method chaining).
+        */
+       public ResourceDescriptions append(String name, String uri, String 
description) {
+               super.add(new ResourceDescription(name, uri, description));
+               return this;
+       }
 }

Reply via email to