Implemented [JACKSON-201]

diff --git a/release-notes/CREDITS b/release-notes/CREDITS
index ea0c5c7..f32a8c9 100644
--- a/release-notes/CREDITS
+++ b/release-notes/CREDITS
@@ -217,9 +217,14 @@
    [1.3.1]
 
 Mike Pilone
+  * Suggested [JACKSON-201]: Allow serialization of "empty beans" (classes
+    without getters), if SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS is
+    disabled; or if class has recognized Jackson annotation
+   [1.4.0]
   * Reported [JACKSON-202]: Non-public fields not deserialized properly
     with JAXB annotations
    [1.3.1]
+  
 
 Stephen Friedrich
   * Reported additional issues with [JACKSON-203]
diff --git a/release-notes/VERSION b/release-notes/VERSION
index ec510ba..c250a56 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -26,6 +26,10 @@
   * [JACKSON-192] Added basic delegate implementations (JsonParserDelegate,
     JsonGeneratorDelegate) to make it easier to override core parser and
     generate behavior
+  * [JACKSON-201] Allow serialization of "empty beans" (classes without
+    getters), if SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS is
+    disabled; or if class has recognized Jackson annotation
+   (suggested by Mike P)
 
   Other:
 
diff --git a/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java b/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
index 954e8fa..637a361 100644
--- a/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
+++ b/src/mapper/java/org/codehaus/jackson/map/SerializationConfig.java
@@ -151,6 +151,23 @@
          */
         ,WRAP_ROOT_VALUE(false)
 
+        /**
+         * Feature that determines what happens when no accessors are
+         * found for a type (and there are no annotations to indicate
+         * it is meant to be serialized). If enabled (default), an
+         * exception is thrown to indicate these as non-serializable
+         * types; if disabled, they are serialized as empty Objects,
+         * i.e. without any properties.
+         *<p>
+         * Note that empty types that this feature has only effect on
+         * those "empty" beans that do not have any recognized annotations
+         * (like <code>@JsonSerialize</code>): ones that do have annotations
+         * do not result in an exception being thrown.
+         *
+         * @since 1.4
+         */
+         ,FAIL_ON_EMPTY_BEANS(true)
+
         // // // Features for datatype-specific serialization
 
         /**
diff --git a/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java b/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
index 944c2bd..76fbcc1 100644
--- a/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
+++ b/src/mapper/java/org/codehaus/jackson/map/introspect/AnnotatedClass.java
@@ -172,6 +172,8 @@
     ///////////////////////////////////////////////////////
      */
 
+    public boolean hasAnnotations() { return _classAnnotations.size() > 0; }
+
     public AnnotatedConstructor getDefaultConstructor() { return _defaultConstructor; }
 
     public List<AnnotatedConstructor> getConstructors()
diff --git a/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java b/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
index b8f9f4c..2f7c055 100644
--- a/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
+++ b/src/mapper/java/org/codehaus/jackson/map/introspect/BasicBeanDescription.java
@@ -49,6 +49,16 @@
 
     public AnnotatedClass getClassInfo() { return _classInfo; }
 
+    public Class<?> classDescribed() { return _classInfo.getAnnotated(); }
+
+    /**
+     * Method for checking whether class being described has any
+     * annotations recognized by registered annotation introspector.
+     */
+    public boolean hasKnownClassAnnotations() {
+        return _classInfo.hasAnnotations();
+    }
+
     public AnnotatedMethod findMethod(String name, Class<?>[] paramTypes)
     {
         return _classInfo.findMethod(name, paramTypes);
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
index 91ad16c..3ede7b7 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializer.java
@@ -25,16 +25,14 @@
     extends SerializerBase<Object>
     implements ResolvableSerializer, SchemaAware
 {
+    final static BeanPropertyWriter[] NO_PROPS = new BeanPropertyWriter[0];
+
     final protected String _className;
 
     final protected BeanPropertyWriter[] _props;
 
     public BeanSerializer(Class<?> type, BeanPropertyWriter[] props)
     {
-        // sanity check
-        if (props.length == 0) {
-            throw new IllegalArgumentException("Can not create BeanSerializer for type that has no properties");
-        }
         _props = props;
         // let's store this for debugging
         _className = type.getName();
@@ -45,6 +43,15 @@
         this(type, props.toArray(new BeanPropertyWriter[props.size()]));
     }
 
+    /**
+     * Method for constructing dummy bean deserializer; one that
+     * never outputs any properties
+     */
+    public static BeanSerializer createDummy(Class<?> forType)
+    {
+        return new BeanSerializer(forType, NO_PROPS);
+    }
+
     public void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
         throws IOException, JsonGenerationException
     {
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
index 96c63c7..32e512f 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/BeanSerializerFactory.java
@@ -142,7 +142,7 @@
             ser = findSerializerFromAnnotation(config, valueMethod);
             return new JsonValueSerializer(valueMethod.getAnnotated(), ser);
         }
-        return constructBeanSerializer(type, config, beanDesc);
+        return constructBeanSerializer(config, beanDesc);
     }
 
     /*
@@ -164,20 +164,27 @@
         return (ClassUtil.canBeABeanType(type) == null) && !ClassUtil.isProxyType(type);
     }
 
-    protected JsonSerializer<Object> constructBeanSerializer(Class<?> type, SerializationConfig config,
+    protected JsonSerializer<Object> constructBeanSerializer(SerializationConfig config,
                                                              BasicBeanDescription beanDesc)
     {
         // First: any detectable (auto-detect, annotations) properties to serialize?
         List<BeanPropertyWriter> props = findBeanProperties(config, beanDesc);
         if (props == null || props.size() == 0) {
             // No properties, no serializer
+            /* 27-Nov-2009, tatu: Except that as per [JACKSON-201], we are
+             *   ok with that as long as it has a recognized class annotation
+             *  (which may come from a mix-in too)
+             */
+            if (beanDesc.hasKnownClassAnnotations()) {
+                return BeanSerializer.createDummy(beanDesc.classDescribed());
+            }
             return null;
         }
         // Any properties to suppress?
         props = filterBeanProperties(config, beanDesc, props);
         // And finally: do they need to be sorted in some special way?
         props = sortBeanProperties(config, beanDesc, props);
-        return new BeanSerializer(type, props);
+        return new BeanSerializer(beanDesc.classDescribed(), props);
     }
 
     /**
diff --git a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
index 5f8b6cf..5cdcbe2 100644
--- a/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
+++ b/src/mapper/java/org/codehaus/jackson/map/ser/StdSerializerProvider.java
@@ -48,12 +48,15 @@
     {
         @Override
         public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
-            throws JsonMappingException
+            throws IOException, JsonMappingException
         {
-            /* 18-Feb-2009, tatu: Let's suggest the most likely reason
-             *   for failure to find a serializer
-             */
-            throw new JsonMappingException("No serializer found for class "+value.getClass().getName()+" (and no bean properties discovered to create bean serializer)");
+            // 27-Nov-2009, tatu: As per [JACKSON-201] may or may not fail...
+            if (provider.isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS)) {
+                throw new JsonMappingException("No serializer found for class "+value.getClass().getName()+" and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )");
+            }
+            // But if it's fine, we'll just output empty JSON Object:
+            jgen.writeStartObject();
+            jgen.writeEndObject();
         }
     };
 
diff --git a/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java b/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java
new file mode 100644
index 0000000..437f1c1
--- /dev/null
+++ b/src/test/org/codehaus/jackson/map/ser/TestEmptyClass.java
@@ -0,0 +1,49 @@
+package org.codehaus.jackson.map.ser;
+
+import org.codehaus.jackson.map.*;
+import org.codehaus.jackson.map.annotate.*;
+
+public class TestEmptyClass
+    extends BaseMapTest
+{
+    static class Empty { }
+
+    @JsonSerialize
+    static class EmptyWithAnno { }
+
+    /**
+     * Test to check that [JACKSON-201] works if there is a recognized
+     * annotation (which indicates type is serializable)
+     */
+    public void testEmptyWithAnnotations() throws Exception
+    {
+        ObjectMapper mapper = new ObjectMapper();
+        // First: without annotations, should complain
+        try {
+            serializeAsString(mapper, new Empty());
+        } catch (JsonMappingException e) {
+            verifyException(e, "No serializer found for class");
+        }
+
+        // But not if there is a recognized annotation
+        assertEquals("{}", serializeAsString(mapper, new EmptyWithAnno()));
+
+        // Including class annotation through mix-ins
+        mapper = new ObjectMapper();
+        mapper.getSerializationConfig().addMixInAnnotations(Empty.class, EmptyWithAnno.class);
+        assertEquals("{}", serializeAsString(mapper, new Empty()));
+    }
+
+    /**
+     * Alternative it is possible to use a feature to allow
+     * serializing empty classes, too
+     */
+    public void testEmptyWithFeature() throws Exception
+    {
+        ObjectMapper mapper = new ObjectMapper();
+        // should be enabled by default
+        assertTrue(mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS));
+        mapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
+        assertEquals("{}", serializeAsString(mapper, new Empty()));
+    }
+}