Implemented #599, ability to skip duplicate module registrations
diff --git a/release-notes/VERSION b/release-notes/VERSION
index e04225d..bf992f6 100644
--- a/release-notes/VERSION
+++ b/release-notes/VERSION
@@ -38,6 +38,7 @@
 #597: Improve error messaging for cases where JSON Creator returns null (which
   is illegal)
  (contributed by Aurelien L)
+#599: Add a simple mechanism for avoiding multiple registrations of the same module
 #607: Allow (re)config of `JsonParser.Feature`s via `ObjectReader`
 #608: Allow (re)config of `JsonGenerator.Feature`s via `ObjectWriter`
 #614: Add a mechanism for using `@JsonCreator.mode` for resolving possible ambiguity between
diff --git a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
index e7fbf21..83c304a 100644
--- a/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
+++ b/src/main/java/com/fasterxml/jackson/databind/MapperFeature.java
@@ -274,7 +274,33 @@
      * 
      * @since 2.1
      */
-    USE_WRAPPER_NAME_AS_PROPERTY_NAME(false)
+    USE_WRAPPER_NAME_AS_PROPERTY_NAME(false),
+    
+    /*
+    /******************************************************
+    /* Other features
+    /******************************************************
+     */
+
+    /**
+     * Feature that determines whether multiple registrations of same module
+     * should be ignored or not; if enabled, only the first registration call
+     * results in module being called, and possible duplicate calls are silently
+     * ignored; if disabled, no checking is done and all registration calls are
+     * dispatched to module.
+     *<p>
+     * Definition of "same module" is based on using {@link Module#getTypeId()};
+     * modules with same non-null <code>type id</code> are considered same for
+     * purposes of duplicate registration. This also avoids having to keep track
+     * of actual module instances; only ids will be kept track of (and only if
+     * this feature is enabled).
+     *<p>
+     * Feature is enabled by default.
+     *
+     * @since 2.5
+     */
+    IGNORE_DUPLICATE_MODULE_REGISTRATIONS(true)
+    
     ;
 
     private final boolean _defaultState;
diff --git a/src/main/java/com/fasterxml/jackson/databind/Module.java b/src/main/java/com/fasterxml/jackson/databind/Module.java
index 1b268a5..37ca295 100644
--- a/src/main/java/com/fasterxml/jackson/databind/Module.java
+++ b/src/main/java/com/fasterxml/jackson/databind/Module.java
@@ -28,7 +28,7 @@
      */
     
     /**
-     * Method that returns identifier for module; this can be used by Jackson
+     * Method that returns a display that can be used by Jackson
      * for informational purposes, as well as in associating extensions with
      * module that provides them.
      */
@@ -41,6 +41,23 @@
     @Override
     public abstract Version version();
 
+    /**
+     * Method that returns an id that may be used to determine if two {@link Module}
+     * instances are considered to be of same type, for purpose of preventing
+     * multiple registrations of "same type of" module
+     * (see {@link com.fasterxml.jackson.databind.MapperFeature#IGNORE_DUPLICATE_MODULE_REGISTRATIONS})
+     * If `null` is returned, every instance is considered unique.
+     * If non-null value is returned, equality of id Objects is used to check whether
+     * modules should be considered to be "of same type"
+     *<p>
+     * Default implementation returns value of class name ({@link Class#getName}).
+     *
+     * @since 2.5
+     */
+    public Object getTypeId() {
+        return getClass().getName();
+    }
+    
     /*
     /**********************************************************
     /* Life-cycle: registration
diff --git a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
index 9271bad..93f6534 100644
--- a/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
+++ b/src/main/java/com/fasterxml/jackson/databind/ObjectMapper.java
@@ -324,6 +324,22 @@
 
     /*
     /**********************************************************
+    /* Module-related
+    /**********************************************************
+     */
+
+    /**
+     * Set of module types (as per {@link Module#getTypeId()} that have been
+     * registered; kept track of iff {@link MapperFeature#IGNORE_DUPLICATE_MODULE_REGISTRATIONS}
+     * is enabled, so that duplicate registration calls can be ignored
+     * (to avoid adding same handlers multiple times, mostly).
+     * 
+     * @since 2.5
+     */
+    protected Set<Object> _registeredModuleTypes;
+    
+    /*
+    /**********************************************************
     /* Caching
     /**********************************************************
      */
@@ -601,6 +617,19 @@
      */
     public ObjectMapper registerModule(Module module)
     {
+        if (isEnabled(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS)) {
+            Object typeId = module.getTypeId();
+            if (typeId != null) {
+                if (_registeredModuleTypes == null) {
+                    _registeredModuleTypes = new HashSet<Object>();
+                }
+                // try adding; if already had it, should skip
+                if (!_registeredModuleTypes.add(typeId)) {
+                    return this;
+                }
+            }
+        }
+        
         /* Let's ensure we have access to name and version information, 
          * even if we do not have immediate use for either. This way we know
          * that they will be available from beginning
@@ -756,7 +785,7 @@
             
             @Override
             public void setMixInAnnotations(Class<?> target, Class<?> mixinSource) {
-                mapper.addMixInAnnotations(target, mixinSource);
+                mapper.addMixIn(target, mixinSource);
             }
             
             @Override
@@ -3067,7 +3096,6 @@
      * Method called to configure the generator as necessary and then
      * call write functionality
      */
-    @SuppressWarnings("deprecation")
     protected final void _configAndWriteValue(JsonGenerator jgen, Object value)
         throws IOException
     {
diff --git a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
index e451098..de0c6fe 100644
--- a/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
+++ b/src/main/java/com/fasterxml/jackson/databind/cfg/DeserializerFactoryConfig.java
@@ -12,7 +12,7 @@
 public class DeserializerFactoryConfig
     implements java.io.Serializable // since 2.1
 {
-    private static final long serialVersionUID = 3683541151102256824L;
+    private static final long serialVersionUID = 1L; // since 2.5
 
     protected final static Deserializers[] NO_DESERIALIZERS = new Deserializers[0];
     protected final static BeanDeserializerModifier[] NO_MODIFIERS = new BeanDeserializerModifier[0];
@@ -47,7 +47,6 @@
      */
     protected final BeanDeserializerModifier[] _modifiers;
 
-
     /**
      * List of objects that may be able to resolve abstract types to
      * concrete types. Used by functionality like "mr Bean" to materialize
@@ -63,7 +62,7 @@
      * or to support post-constructor functionality.
      */
     protected final ValueInstantiators[] _valueInstantiators;
-    
+
     /**
      * Constructor for creating basic configuration with no additional
      * handlers.
@@ -106,6 +105,7 @@
         return new DeserializerFactoryConfig(all, _additionalKeyDeserializers, _modifiers,
                 _abstractTypeResolvers, _valueInstantiators);
     }
+
     /**
      * Fluent/factory method used to construct a configuration object that
      * has same key deserializer providers as this instance, plus one specified
diff --git a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
index 2c20c1e..01fa522 100644
--- a/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
+++ b/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java
@@ -807,7 +807,7 @@
         if (typeStr != null) {
             throw new IllegalArgumentException("Can not deserialize Class "+type.getName()+" (of type "+typeStr+") as a Bean");
         }
-    	return true;
+        return true;
     }
 
     /**
@@ -821,7 +821,7 @@
         if (status == null) {
             BeanDescription desc = config.introspectClassAnnotations(type);
             status = config.getAnnotationIntrospector().isIgnorableType(desc.getClassInfo());
-            // We default to 'false', ie. not ignorable
+            // We default to 'false', i.e. not ignorable
             if (status == null) {
                 status = Boolean.FALSE;
             }
diff --git a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
index 6a33afb..f6485b8 100644
--- a/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
+++ b/src/main/java/com/fasterxml/jackson/databind/module/SimpleModule.java
@@ -6,7 +6,6 @@
 import java.util.Map;
 
 import com.fasterxml.jackson.core.Version;
-
 import com.fasterxml.jackson.databind.*;
 import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
 import com.fasterxml.jackson.databind.deser.ValueInstantiator;
@@ -30,8 +29,7 @@
     extends Module
     implements java.io.Serializable
 {
-    // at 2.4.0:
-    private static final long serialVersionUID = -8905749147637667249L;
+    private static final long serialVersionUID = 1L; // 2.5.0
 
     protected final String _name;
     protected final Version _version;
@@ -94,8 +92,11 @@
      * use actual name and version number information.
      */
     public SimpleModule() {
-        // when passing 'this', can not chain constructors...
-        _name = "SimpleModule-"+System.identityHashCode(this);
+        // can't chain when making reference to 'this'
+        // note: generate different name for direct instantiation, sub-classing
+        _name = (getClass() == SimpleModule.class) ?
+                "SimpleModule-"+System.identityHashCode(this)
+                : getClass().getName();
         _version = Version.unknownVersion();
     }
     
@@ -162,6 +163,19 @@
             _serializers = new SimpleSerializers(serializers);
         }
     }
+
+    /**
+     * Since instances are likely to be custom, implementation returns
+     * <code>null</code> if (but only if!) this class is directly instantiated;
+     * but class name (default impl) for sub-classes.
+     */
+    @Override
+    public Object getTypeId() {
+        if (getClass() == SimpleModule.class) {
+            return null;
+        }
+        return super.getTypeId();
+    }
     
     /*
     /**********************************************************
diff --git a/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
new file mode 100644
index 0000000..171b867
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/databind/module/TestDuplicateRegistration.java
@@ -0,0 +1,56 @@
+package com.fasterxml.jackson.databind.module;
+
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.*;
+
+public class TestDuplicateRegistration extends BaseMapTest
+{
+    static class MyModule extends Module {
+        public int regCount;
+        
+        public MyModule() {
+            super();
+        }
+
+        @Override
+        public String getModuleName() {
+            return "TestModule";
+        }
+
+        @Override
+        public Version version() {
+            return Version.unknownVersion();
+        }
+
+        @Override
+        public void setupModule(SetupContext context) {
+            ++regCount;
+        }
+    }
+
+    public void testDuplicateRegistration() throws Exception
+    {
+        // by default, duplicate registration should be prevented
+        ObjectMapper mapper = new ObjectMapper();
+        assertTrue(mapper.isEnabled(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS));
+        MyModule module = new MyModule();
+        mapper.registerModule(module);
+        mapper.registerModule(module);
+        mapper.registerModule(module);
+        assertEquals(1, module.regCount);
+
+        // but may be allowed by changing setting
+        mapper.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
+        mapper.registerModule(module);
+        assertEquals(2, module.regCount);
+
+        // and ditto for a new instance
+        ObjectMapper mapper2 = new ObjectMapper();
+        mapper2.disable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS);
+        MyModule module2 = new MyModule();
+        mapper.registerModule(module2);
+        mapper.registerModule(module2);
+        mapper.registerModule(module2);
+        assertEquals(3, module2.regCount);
+    }
+}