Repository: eclipse/leshan
Pull request #1085 (2021-09-03 16:10:46)

Add version param to "create" command of leshan-client-demo
This aims to implements #1080.

The new parameters looks like : 
```
Usage:  create <objectId> [<version>]
Enable a new Object
      <objectId>    Id of the LWM2M object to enable
      [<version>]   Version of the LwM2M object to enable, if not precised the
                      most recent one is picked

```

E.g : 
```
create 10 1.0
     DefaultRegistrationEngine 2021-09-03 18:10:17,738 [INFO] Triggering registration update...
              LeshanClientDemo 2021-09-03 18:10:17,740 [INFO] Object 10 v1.0 enabled.
     DefaultRegistrationEngine 2021-09-03 18:10:17,742 [INFO] Trying to update registration to coap://localhost:5683 (response timeout 120000ms)...
     DefaultRegistrationEngine 2021-09-03 18:10:17,752 [INFO] Registration update succeed.
```



Patch:
diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
index 4b52e2b4..0bcca56d 100644
--- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
+++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/engine/DefaultRegistrationEngine.java
@@ -44,7 +44,7 @@ import org.eclipse.leshan.client.servers.ServerInfo;
 import org.eclipse.leshan.client.servers.ServersInfoExtractor;
 import org.eclipse.leshan.client.util.LinkFormatHelper;
 import org.eclipse.leshan.core.Link;
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.LwM2mId;
 import org.eclipse.leshan.core.ResponseCode;
 import org.eclipse.leshan.core.request.BindingMode;
@@ -309,7 +309,7 @@ public class DefaultRegistrationEngine implements RegistrationEngine {
         LOG.info("Trying to register to {} ...", server.getUri());
         RegisterRequest request = null;
         try {
-            Version lwM2mVersion = Version.lastSupported();
+            LwM2mVersion lwM2mVersion = LwM2mVersion.lastSupported();
             EnumSet<BindingMode> supportedBindingMode = ServersInfoExtractor
                     .getDeviceSupportedBindingMode(objectEnablers.get(LwM2mId.DEVICE), 0);
             Link[] links = LinkFormatHelper.getClientDescription(objectEnablers.values(), null,
diff --git a/leshan-client-demo/logback-config.xml b/leshan-client-demo/logback-config.xml
index 9a49efee..d294726a 100644
--- a/leshan-client-demo/logback-config.xml
+++ b/leshan-client-demo/logback-config.xml
@@ -33,5 +33,6 @@ Contributors:
 	<logger name="org.eclipse.leshan.server.bootstrap.DefaultBootstrapHandler" level="DEBUG" />
 	<logger name="org.eclipse.leshan.client.object.Security" level="DEBUG" />
 	<logger name="org.eclipse.leshan.client.object.Server" level="DEBUG" />
+	<logger name="org.eclipse.leshan.core.model.LwM2mModelRepository" level="TRACE"/>
 
 </configuration>
\ No newline at end of file
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
index 4bbb12cc..f763666d 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/LeshanClientDemo.java
@@ -49,10 +49,9 @@ import org.eclipse.leshan.core.californium.DefaultEndpointFactory;
 import org.eclipse.leshan.core.demo.LwM2mDemoConstant;
 import org.eclipse.leshan.core.demo.cli.ShortErrorMessageHandler;
 import org.eclipse.leshan.core.demo.cli.interactive.InteractiveCLI;
-import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.LwM2mModelRepository;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.core.model.ObjectModel;
-import org.eclipse.leshan.core.model.StaticModel;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
 import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
 import org.slf4j.Logger;
@@ -88,11 +87,11 @@ public class LeshanClientDemo {
 
         try {
             // Create Client
-            LwM2mModel model = createModel(cli);
-            final LeshanClient client = createClient(cli, model);
+            LwM2mModelRepository repository = createModel(cli);
+            final LeshanClient client = createClient(cli, repository);
 
             // Print commands help
-            InteractiveCLI console = new InteractiveCLI(new InteractiveCommands(client, model));
+            InteractiveCLI console = new InteractiveCLI(new InteractiveCommands(client, repository));
             console.showHelp();
 
             // Start the client
@@ -121,24 +120,24 @@ public class LeshanClientDemo {
         }
     }
 
-    private static LwM2mModel createModel(LeshanClientDemoCLI cli) throws Exception {
+    private static LwM2mModelRepository createModel(LeshanClientDemoCLI cli) throws Exception {
 
-        List<ObjectModel> models = ObjectLoader.loadDefault();
+        List<ObjectModel> models = ObjectLoader.loadAllDefault();
         models.addAll(ObjectLoader.loadDdfResources("/models", LwM2mDemoConstant.modelPaths));
         if (cli.main.modelsFolder != null) {
             models.addAll(ObjectLoader.loadObjectsFromDir(cli.main.modelsFolder, true));
         }
 
-        return new StaticModel(models);
+        return new LwM2mModelRepository(models);
     }
 
-    public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModel model) throws Exception {
+    public static LeshanClient createClient(LeshanClientDemoCLI cli, LwM2mModelRepository repository) throws Exception {
         // create Leshan client from command line option
         final MyLocation locationInstance = new MyLocation(cli.location.position.latitude,
                 cli.location.position.longitude, cli.location.scaleFactor);
 
         // Initialize object list
-        final ObjectsInitializer initializer = new ObjectsInitializer(model);
+        final ObjectsInitializer initializer = new ObjectsInitializer(repository.getLwM2mModel());
         if (cli.main.bootstrap) {
             if (cli.identity.isPSK()) {
                 initializer.setInstancesForObject(SECURITY, pskBootstrap(cli.main.url,
@@ -310,12 +309,12 @@ public class LeshanClientDemo {
         client.getObjectTree().addListener(new ObjectsListenerAdapter() {
             @Override
             public void objectRemoved(LwM2mObjectEnabler object) {
-                LOG.info("Object {} disabled.", object.getId());
+                LOG.info("Object {} v{} disabled.", object.getId(), object.getObjectModel().version);
             }
 
             @Override
             public void objectAdded(LwM2mObjectEnabler object) {
-                LOG.info("Object {} enabled.", object.getId());
+                LOG.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version);
             }
         });
 
diff --git a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java
index dc9304e8..59e998eb 100644
--- a/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java
+++ b/leshan-client-demo/src/main/java/org/eclipse/leshan/client/demo/cli/interactive/InteractiveCommands.java
@@ -12,9 +12,13 @@ import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectsInitializer;
+import org.eclipse.leshan.core.LwM2m.Version;
 import org.eclipse.leshan.core.LwM2mId;
+import org.eclipse.leshan.core.demo.cli.converters.VersionConverter;
 import org.eclipse.leshan.core.demo.cli.interactive.JLineInteractiveCommands;
-import org.eclipse.leshan.core.model.LwM2mModel;
+import org.eclipse.leshan.core.model.LwM2mModelRepository;
+import org.eclipse.leshan.core.model.ObjectModel;
+import org.eclipse.leshan.core.model.StaticModel;
 
 import jline.console.ConsoleReader;
 import picocli.CommandLine;
@@ -39,11 +43,11 @@ public class InteractiveCommands implements Runnable, JLineInteractiveCommands {
     private PrintWriter out;
 
     private LeshanClient client;
-    private LwM2mModel model;
+    private LwM2mModelRepository repository;
 
-    public InteractiveCommands(LeshanClient client, LwM2mModel model) {
+    public InteractiveCommands(LeshanClient client, LwM2mModelRepository repository) {
         this.client = client;
-        this.model = model;
+        this.repository = repository;
     }
 
     @Override
@@ -63,9 +67,15 @@ public class InteractiveCommands implements Runnable, JLineInteractiveCommands {
     @Command(name = "create", description = "Enable a new Object", headerHeading = "%n", footer = "")
     static class CreateCommand implements Runnable {
 
-        @Parameters(description = "Id of the LWM2M object to enable")
+        @Parameters(description = "Id of the LWM2M object to enable", index = "0")
         private Integer objectId;
 
+        @Parameters(description = "Version of the LwM2M object to enable, if not precised the most recent one is picked",
+                    index = "1",
+                    arity = "0..1",
+                    converter = VersionConverter.class)
+        private Version version;
+
         @ParentCommand
         InteractiveCommands parent;
 
@@ -74,14 +84,28 @@ public class InteractiveCommands implements Runnable, JLineInteractiveCommands {
             if (parent.client.getObjectTree().getObjectEnabler(objectId) != null) {
                 parent.out.printf("Object %d already enabled.%n", objectId);
                 parent.out.flush();
-            } else if (parent.model.getObjectModel(objectId) == null) {
-                parent.out.printf("Unable to enable Object %d : there no model for this.%n", objectId);
-                parent.out.flush();
             } else {
-                ObjectsInitializer objectsInitializer = new ObjectsInitializer(parent.model);
-                objectsInitializer.setDummyInstancesForObject(objectId);
-                LwM2mObjectEnabler object = objectsInitializer.create(objectId);
-                parent.client.getObjectTree().addObjectEnabler(object);
+                ObjectModel objectModel;
+                if (version != null)
+                    objectModel = parent.repository.getObjectModel(objectId, version);
+                else {
+                    objectModel = parent.repository.getObjectModel(objectId);
+                }
+                if (objectModel == null) {
+                    if (version == null) {
+                        parent.out.printf("Unable to enable Object %d : there no model for this object.%n", objectId);
+                    } else {
+                        parent.out.printf(
+                                "Unable to enable Object %d : there no model for this object in version %s.%n",
+                                objectId, version);
+                    }
+                    parent.out.flush();
+                } else {
+                    ObjectsInitializer objectsInitializer = new ObjectsInitializer(new StaticModel(objectModel));
+                    objectsInitializer.setDummyInstancesForObject(objectId);
+                    LwM2mObjectEnabler object = objectsInitializer.create(objectId);
+                    parent.client.getObjectTree().addObjectEnabler(object);
+                }
             }
         }
     }
diff --git a/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/cli/converters/VersionConverter.java b/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/cli/converters/VersionConverter.java
new file mode 100644
index 00000000..fb087af3
--- /dev/null
+++ b/leshan-core-demo/src/main/java/org/eclipse/leshan/core/demo/cli/converters/VersionConverter.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.core.demo.cli.converters;
+
+import org.eclipse.leshan.core.LwM2m.Version;
+
+import picocli.CommandLine.ITypeConverter;
+import picocli.CommandLine.TypeConversionException;
+
+public class VersionConverter implements ITypeConverter<Version> {
+
+    @Override
+    public Version convert(String input) {
+        String err = Version.validate(input);
+        if (err != null)
+            throw new TypeConversionException(err);
+
+        return new Version(input);
+    }
+};
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
index 397a8827..0a26c65a 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2m.java
@@ -20,40 +20,105 @@ public interface LwM2m {
     /**
      * Version of LWM2M specification.
      */
-    public class Version implements Comparable<Version> {
+    public class LwM2mVersion extends Version {
 
-        public static Version V1_0 = new Version("1.0", true);
-        public static Version V1_1 = new Version("1.1", true);
-        private static Version[] supportedVersions = new Version[] { V1_0, V1_1 };
+        public static LwM2mVersion V1_0 = new LwM2mVersion("1.0", true);
+        public static LwM2mVersion V1_1 = new LwM2mVersion("1.1", true);
+        private static LwM2mVersion[] supportedVersions = new LwM2mVersion[] { V1_0, V1_1 };
 
-        private String value;
-        private boolean supported;
+        private final boolean supported;
 
-        protected Version(String version, boolean supported) {
-            this.value = version;
+        protected LwM2mVersion(String version, boolean supported) {
+            super(version);
             this.supported = supported;
         }
 
-        @Override
-        public String toString() {
-            return value;
-        }
-
         public boolean isSupported() {
             return supported;
         }
 
-        public static Version get(String version) {
-            for (Version constantVersion : supportedVersions) {
-                if (constantVersion.value.equals(version)) {
+        public static LwM2mVersion get(String version) {
+            for (LwM2mVersion constantVersion : supportedVersions) {
+                if (constantVersion.toString().equals(version)) {
                     return constantVersion;
                 }
             }
-            return new Version(version, false);
+            return new LwM2mVersion(version, false);
         }
 
         public static boolean isSupported(String version) {
-            return get(version).isSupported();
+            for (LwM2mVersion constantVersion : supportedVersions) {
+                if (constantVersion.toString().equals(version)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public static LwM2mVersion getDefault() {
+            return V1_0;
+        }
+
+        public static LwM2mVersion lastSupported() {
+            return V1_1;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + (supported ? 1231 : 1237);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            LwM2mVersion other = (LwM2mVersion) obj;
+            if (supported != other.supported)
+                return false;
+            return true;
+        }
+    }
+
+    /**
+     * Generic class to handle Version (e.g. Object versioning)
+     */
+    public class Version implements Comparable<Version> {
+
+        public static final Version MAX = new Version(Short.MAX_VALUE, Short.MIN_VALUE);
+
+        protected final Short major;
+        protected final Short minor;
+
+        public Version(Short major, Short minor) {
+            this.major = major;
+            this.minor = minor;
+        }
+
+        public Version(String version) {
+            try {
+                String[] versionPart = version.split("\\.");
+                this.major = Short.parseShort(versionPart[0]);
+                this.minor = Short.parseShort(versionPart[1]);
+            } catch (RuntimeException e) {
+                String err = Version.validate(version);
+                if (err != null) {
+                    throw new IllegalArgumentException(err);
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%d.%d", major, minor);
         }
 
         public static String validate(String version) {
@@ -78,19 +143,12 @@ public interface LwM2m {
             return null;
         }
 
-        public static Version getDefault() {
-            return V1_0;
-        }
-
-        public static Version lastSupported() {
-            return V1_1;
-        }
-
         @Override
         public int hashCode() {
             final int prime = 31;
             int result = 1;
-            result = prime * result + ((value == null) ? 0 : value.hashCode());
+            result = prime * result + ((major == null) ? 0 : major.hashCode());
+            result = prime * result + ((minor == null) ? 0 : minor.hashCode());
             return result;
         }
 
@@ -103,28 +161,25 @@ public interface LwM2m {
             if (getClass() != obj.getClass())
                 return false;
             Version other = (Version) obj;
-            if (value == null) {
-                if (other.value != null)
+            if (major == null) {
+                if (other.major != null)
                     return false;
-            } else if (!value.equals(other.value))
+            } else if (!major.equals(other.major))
+                return false;
+            if (minor == null) {
+                if (other.minor != null)
+                    return false;
+            } else if (!minor.equals(other.minor))
                 return false;
             return true;
         }
 
         @Override
         public int compareTo(Version other) {
-            String[] versionPart = this.value.split("\\.");
-            String[] otherVersionPart = other.value.split("\\.");
-
-            short major = Short.parseShort(versionPart[0]);
-            short oMajor = Short.parseShort(otherVersionPart[0]);
-            if (major != oMajor)
-                return major - oMajor;
-
-            short minor = Short.parseShort(versionPart[1]);
-            short oMinor = Short.parseShort(otherVersionPart[1]);
+            if (major != other.major)
+                return major - other.major;
 
-            return minor - oMinor;
+            return minor - other.minor;
         }
 
         public boolean newerThan(Version version) {
@@ -136,7 +191,7 @@ public interface LwM2m {
         }
 
         public boolean newerThan(String version) {
-            return newerThan(Version.get(version));
+            return newerThan(new Version(version));
         }
     }
 
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileParser.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileParser.java
index 75cce85c..91f90c76 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileParser.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileParser.java
@@ -28,7 +28,7 @@ import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.model.ResourceModel.Operations;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 import org.eclipse.leshan.core.util.StringUtils;
@@ -113,7 +113,7 @@ public class DDFFileParser {
             Document document = builder.parse(inputStream);
 
             // Get DDF file validator
-            Version lwm2mVersion;
+            LwM2mVersion lwm2mVersion;
             DDFFileValidator ddfFileValidator;
             if (ddfValidatorFactory != null) {
                 lwm2mVersion = ddfValidatorFactory.extractLWM2MVersion(document, streamName);
@@ -143,7 +143,7 @@ public class DDFFileParser {
         }
     }
 
-    private ObjectModel parseObject(Node object, String streamName, Version schemaVersion, boolean validate)
+    private ObjectModel parseObject(Node object, String streamName, LwM2mVersion schemaVersion, boolean validate)
             throws InvalidDDFFileException {
 
         Node objectType = object.getAttributes().getNamedItem("ObjectType");
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileValidatorFactory.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileValidatorFactory.java
index 711e2422..bca6f831 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileValidatorFactory.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DDFFileValidatorFactory.java
@@ -15,7 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.model;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.w3c.dom.Document;
 
 /**
@@ -31,10 +31,10 @@ public interface DDFFileValidatorFactory {
      * 
      * @return the version of the minimal LWM2M version supported by this DDF file.
      */
-    Version extractLWM2MVersion(Document document, String documentName) throws InvalidDDFFileException;
+    LwM2mVersion extractLWM2MVersion(Document document, String documentName) throws InvalidDDFFileException;
 
     /**
-     * Create a {@link DDFFileValidator} for the given LWM2M {@link Version}
+     * Create a {@link DDFFileValidator} for the given LWM2M {@link LwM2mVersion}
      */
-    DDFFileValidator create(Version lwm2mVersion);
+    DDFFileValidator create(LwM2mVersion lwm2mVersion);
 }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidator.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidator.java
index 0719ac47..fcb86dc2 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidator.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidator.java
@@ -26,7 +26,7 @@ import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.util.Validate;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
@@ -49,17 +49,17 @@ public class DefaultDDFFileValidator implements DDFFileValidator {
      * Create a {@link DDFFileValidator} using the LWM2M v1.1 schema.
      */
     public DefaultDDFFileValidator() {
-        this(Version.V1_1);
+        this(LwM2mVersion.V1_1);
     }
 
     /**
-     * Create a {@link DDFFileValidator} using schema corresponding to LWM2M {@link Version}.
+     * Create a {@link DDFFileValidator} using schema corresponding to LWM2M {@link LwM2mVersion}.
      */
-    public DefaultDDFFileValidator(Version version) {
+    public DefaultDDFFileValidator(LwM2mVersion version) {
         Validate.notNull(version, "version must not be null");
-        if (Version.V1_0.equals(version)) {
+        if (LwM2mVersion.V1_0.equals(version)) {
             schema = LWM2M_V1_0_SCHEMA_PATH;
-        } else if (Version.V1_1.equals(version)) {
+        } else if (LwM2mVersion.V1_1.equals(version)) {
             schema = LWM2M_V1_1_SCHEMA_PATH;
         } else {
             throw new IllegalStateException(String.format("Unsupported version %s", version));
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidatorFactory.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidatorFactory.java
index 1efb01aa..ab3d3bad 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidatorFactory.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultDDFFileValidatorFactory.java
@@ -15,7 +15,7 @@
  *******************************************************************************/
 package org.eclipse.leshan.core.model;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
@@ -29,7 +29,7 @@ import org.w3c.dom.NodeList;
 public class DefaultDDFFileValidatorFactory implements DDFFileValidatorFactory {
 
     @Override
-    public Version extractLWM2MVersion(Document document, String DocumentName) throws InvalidDDFFileException {
+    public LwM2mVersion extractLWM2MVersion(Document document, String DocumentName) throws InvalidDDFFileException {
         NodeList nodes = document.getElementsByTagName("LWM2M");
         if (nodes.getLength() != 1) {
             throw new InvalidDDFFileException("DDF file %s should have 1 <LWM2M> element.", DocumentName);
@@ -43,9 +43,9 @@ public class DefaultDDFFileValidatorFactory implements DDFFileValidatorFactory {
         String schemaLocation = schemaLocationAttr.getTextContent();
         if (schemaLocation != null) {
             if (schemaLocation.endsWith("LWM2M.xsd")) {
-                return Version.V1_0;
+                return LwM2mVersion.V1_0;
             } else if (schemaLocation.endsWith("LWM2M-v1_1.xsd")) {
-                return Version.V1_1;
+                return LwM2mVersion.V1_1;
             }
         }
         throw new InvalidDDFFileException("unsupported value [%s] for attribute 'xsi:noNamespaceSchemaLocation' in %s",
@@ -53,7 +53,7 @@ public class DefaultDDFFileValidatorFactory implements DDFFileValidatorFactory {
     }
 
     @Override
-    public DDFFileValidator create(Version lwm2mVersion) {
+    public DDFFileValidator create(LwM2mVersion lwm2mVersion) {
         return new DefaultDDFFileValidator(lwm2mVersion);
     }
 }
\ No newline at end of file
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultObjectModelValidator.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultObjectModelValidator.java
index ec5e4407..63127781 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultObjectModelValidator.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/DefaultObjectModelValidator.java
@@ -17,6 +17,7 @@ package org.eclipse.leshan.core.model;
 
 import java.util.List;
 
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.LwM2m.Version;
 import org.eclipse.leshan.core.model.ResourceModel.Type;
 
@@ -116,7 +117,7 @@ public class DefaultObjectModelValidator implements ObjectModelValidator {
                     "Model for Resource %s(%d) in %s is invalid : a none executable resource MUST have a type.",
                     resource.name, resource.id, modelName, resource.type);
         }
-        if (lwm2mVersion.equals(Version.V1_0.toString()))
+        if (lwm2mVersion.equals(LwM2mVersion.V1_0.toString()))
             switch (resource.type) {
             case NONE:
             case STRING:
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/LwM2mModelRepository.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/LwM2mModelRepository.java
index b48f2f68..82b2f139 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/LwM2mModelRepository.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/LwM2mModelRepository.java
@@ -17,11 +17,11 @@ package org.eclipse.leshan.core.model;
 
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.NavigableMap;
 import java.util.TreeMap;
 
+import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.node.LwM2mNodeUtil;
 import org.eclipse.leshan.core.util.Validate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -33,9 +33,72 @@ import org.slf4j.LoggerFactory;
 public class LwM2mModelRepository {
     private static final Logger LOG = LoggerFactory.getLogger(LwM2mModelRepository.class);
 
+    private static class Key implements Comparable<Key> {
+        Integer id;
+        Version version;
+
+        public Key(Integer id, Version version) {
+            this.id = id;
+            this.version = version;
+        }
+
+        @Override
+        public int compareTo(Key o) {
+            // handle null
+            if (o == null) {
+                return 1;
+            }
+            // compare id
+            int res = Integer.compare(this.id, o.id);
+            if (res != 0) {
+                return res;
+            }
+            // compare version
+            else {
+                return this.version.compareTo(o.version);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((id == null) ? 0 : id.hashCode());
+            result = prime * result + ((version == null) ? 0 : version.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            Key other = (Key) obj;
+            if (id == null) {
+                if (other.id != null)
+                    return false;
+            } else if (!id.equals(other.id))
+                return false;
+            if (version == null) {
+                if (other.version != null)
+                    return false;
+            } else if (!version.equals(other.version))
+                return false;
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("key[%s/%s]", id, version);
+        }
+
+    }
+
     // This map contains all the object models available. Different version could be used.
-    // This map is indexed by a string composed of objectid and version (objectid##version)
-    private final Map<String, ObjectModel> objects;
+    private final NavigableMap<Key, ObjectModel> objects;
 
     public LwM2mModelRepository(ObjectModel... objectModels) {
         this(Arrays.asList(objectModels));
@@ -45,38 +108,95 @@ public class LwM2mModelRepository {
         if (objectModels == null) {
             objects = new TreeMap<>();
         } else {
-            Map<String, ObjectModel> map = new HashMap<>();
+            NavigableMap<Key, ObjectModel> map = new TreeMap<>();
             for (ObjectModel model : objectModels) {
-                String key = getKey(model);
+                // validate Key
+                validateKey(model.id, model.version);
+
+                // create Key
+                Key key = getKey(model.id, model.version);
                 if (key == null) {
                     throw new IllegalArgumentException(
                             String.format("Model %s is invalid : object id is missing.", model));
                 }
+
+                // add to the map
                 ObjectModel old = map.put(key, model);
                 if (old != null) {
                     LOG.debug("Model already exists for object {} in version {}. Overriding it.", model.id,
                             model.version);
                 }
             }
-            objects = Collections.unmodifiableMap(map);
+            // TODO use unmodifiableNavigableMap when we pass to java8.
+            objects = map;
         }
     }
 
-    public ObjectModel getObjectModel(Integer objectId, String version) {
-        Validate.notNull(objectId, "objectid must not be null");
-        Validate.notNull(version, "version must not be null");
+    private void validateKey(Integer id, String version) {
+        LwM2mNodeUtil.validateObjectId(id);
+        String err = Version.validate(version);
+        if (err != null) {
+            throw new IllegalArgumentException(
+                    String.format("Invalid version %s for object %d : %s", version, id, err));
+        }
+    }
 
+    public ObjectModel getObjectModel(Integer objectId, String version) {
+        validateKey(objectId, version);
         return objects.get(getKey(objectId, version));
     }
 
-    private String getKey(ObjectModel objectModel) {
-        return getKey(objectModel.id, objectModel.version);
+    public ObjectModel getObjectModel(Integer objectId, Version version) {
+        LwM2mNodeUtil.validateObjectId(objectId);
+        Validate.notNull(version, "version must not be null");
+
+        return objects.get(getKey(objectId, version));
     }
 
-    private String getKey(Integer objectId, String version) {
-        if (objectId == null) {
+    /**
+     * @return most recent version of the model.
+     */
+    public ObjectModel getObjectModel(Integer objectId) {
+        LwM2mNodeUtil.validateObjectId(objectId);
+        
+        Key floorKey = objects.floorKey(getKey(objectId, Version.MAX));
+        if (floorKey == null || !floorKey.id.equals(objectId)) {
             return null;
         }
-        return objectId + "##" + version;
+        return objects.get(floorKey);
+    }
+
+    private Key getKey(Integer objectId, String version) {
+        return getKey(objectId, new Version(version));
+    }
+
+    private Key getKey(Integer objectId, Version version) {
+        return new Key(objectId, version);
+    }
+
+    /**
+     * Create a {@link LwM2mModel} with the last version of each Objects.
+     */
+    public LwM2mModel getLwM2mModel() {
+        return new LwM2mModel() {
+            @Override
+            public ResourceModel getResourceModel(int objectId, int resourceId) {
+                ObjectModel objectModel = getObjectModel(objectId);
+                if (objectModel == null)
+                    return null;
+
+                return objectModel.resources.get(resourceId);
+            }
+
+            @Override
+            public Collection<ObjectModel> getObjectModels() {
+                throw new UnsupportedOperationException("not implemented");
+            }
+
+            @Override
+            public ObjectModel getObjectModel(int objectId) {
+                return LwM2mModelRepository.this.getObjectModel(objectId);
+            }
+        };
     }
-}
+}
\ No newline at end of file
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java b/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java
index 43f501d2..c7077b9e 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/model/ObjectLoader.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.LwM2m.Version;
 import org.eclipse.leshan.core.util.StringUtils;
 import org.slf4j.Logger;
@@ -43,14 +44,14 @@ public class ObjectLoader {
      * Load last embedded version of default LWM2M objects. So the list contain only one model by object.
      */
     public static List<ObjectModel> loadDefault() {
-        return loadDefault(Version.V1_1);
+        return loadDefault(LwM2mVersion.V1_1);
     }
 
     /**
      * Load embedded version of default LWM2M objects for a given version of LWM2M. So the list contain only one model
      * by object.
      */
-    public static List<ObjectModel> loadDefault(Version requiredVersion) {
+    public static List<ObjectModel> loadDefault(LwM2mVersion requiredVersion) {
         String errorMsg = Version.validate(requiredVersion.toString());
         if (errorMsg != null)
             throw new IllegalStateException(String.format("Invalid version : %s", errorMsg));
@@ -61,11 +62,11 @@ public class ObjectLoader {
             Map<Integer, ObjectModel> models = new TreeMap<>();
             for (ObjectModel model : loadDdfResources("/models/", ddfpaths)) {
                 // skip model not compatible with the given version
-                if (Version.get(model.lwm2mVersion).newerThan(requiredVersion))
+                if (LwM2mVersion.get(model.lwm2mVersion).newerThan(requiredVersion))
                     continue;
 
                 ObjectModel previousModel = models.get(model.id);
-                if (previousModel == null || Version.get(model.version).newerThan(previousModel.version)) {
+                if (previousModel == null || new Version(model.version).newerThan(previousModel.version)) {
                     models.put(model.id, model);
                 }
             }
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java
index 273636e3..993c0d7c 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/BindingMode.java
@@ -17,7 +17,7 @@ package org.eclipse.leshan.core.request;
 
 import java.util.EnumSet;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 
 /**
  * Transport binding and Queue Mode
@@ -43,16 +43,16 @@ public enum BindingMode {
      * @param targetVersion the target LWM2M version
      * @return null if the BindingMode value is compatible with the given LWM2M version, else return an error message.
      */
-    public String isValidFor(Version targetVersion) {
+    public String isValidFor(LwM2mVersion targetVersion) {
         switch (this) {
         case T:
         case N:
-            if (targetVersion.olderThan(Version.V1_1)) {
+            if (targetVersion.olderThan(LwM2mVersion.V1_1)) {
                 return String.format("%s is supported since LWM2M 1.1", this);
             }
             break;
         case Q:
-            if (targetVersion.newerThan(Version.V1_0)) {
+            if (targetVersion.newerThan(LwM2mVersion.V1_0)) {
                 return String.format("%s is not supported since LWM2M 1.1", this);
             }
             break;
@@ -84,7 +84,7 @@ public enum BindingMode {
      * @param targetVersion the target LWM2M version
      * @return null if the bindings are compatible with the given LWM2M version, else return an error message.
      */
-    public static String isValidFor(EnumSet<BindingMode> bindings, Version targetVersion) {
+    public static String isValidFor(EnumSet<BindingMode> bindings, LwM2mVersion targetVersion) {
         for (BindingMode binding : bindings) {
             String err = binding.isValidFor(targetVersion);
             if (err != null)
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormat.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormat.java
index b150201b..0f73fd0a 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormat.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/ContentFormat.java
@@ -23,7 +23,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 
 /**
  * Data format defined by the LWM2M specification
@@ -44,14 +44,14 @@ public class ContentFormat implements Comparable<ContentFormat> {
     public static final int SENML_CBOR_CODE = 112;
 
     public static final ContentFormat TLV = new ContentFormat("TLV", "application/vnd.oma.lwm2m+tlv", TLV_CODE,
-            Arrays.asList(Version.V1_0));
+            Arrays.asList(LwM2mVersion.V1_0));
     public static final ContentFormat JSON = new ContentFormat("JSON", "application/vnd.oma.lwm2m+json", JSON_CODE);
     public static final ContentFormat TEXT = new ContentFormat("TEXT", "text/plain", TEXT_CODE,
-            Arrays.asList(Version.V1_1));
+            Arrays.asList(LwM2mVersion.V1_1));
     public static final ContentFormat OPAQUE = new ContentFormat("OPAQUE", "application/octet-stream", OPAQUE_CODE,
-            Arrays.asList(Version.V1_1));
+            Arrays.asList(LwM2mVersion.V1_1));
     public static final ContentFormat LINK = new ContentFormat("LINK", "application/link-format", LINK_CODE,
-            Arrays.asList(Version.V1_1));
+            Arrays.asList(LwM2mVersion.V1_1));
     public static final ContentFormat SENML_JSON = new ContentFormat("SENML_JSON", "application/senml+json",
             SENML_JSON_CODE);
     public static final ContentFormat SENML_CBOR = new ContentFormat("SENML_CBOR", "application/senml+cbor",
@@ -61,14 +61,14 @@ public class ContentFormat implements Comparable<ContentFormat> {
     public static final ContentFormat DEFAULT = TLV;
 
     public static final ContentFormat knownContentFormat[] = new ContentFormat[] { TLV, JSON, SENML_JSON, SENML_CBOR,
-                            TEXT, OPAQUE, CBOR, LINK };
+            TEXT, OPAQUE, CBOR, LINK };
 
     private final String name;
     private final String mediaType;
     private final int code;
-    private Set<Version> mandatoryForClient; // lwm2m version where this content format is mandatory at client side
+    private Set<LwM2mVersion> mandatoryForClient; // lwm2m version where this content format is mandatory at client side
 
-    public ContentFormat(String name, String mediaType, int code, Collection<Version> mandatory) {
+    public ContentFormat(String name, String mediaType, int code, Collection<LwM2mVersion> mandatory) {
         this.name = name;
         this.mediaType = mediaType;
         this.code = code;
@@ -102,9 +102,9 @@ public class ContentFormat implements Comparable<ContentFormat> {
     }
 
     /**
-     * @return True is this {@link ContentFormat} is mandatory at client side for the given LWM2M {@link Version}.
+     * @return True is this {@link ContentFormat} is mandatory at client side for the given LWM2M {@link LwM2mVersion}.
      */
-    public boolean isMandatoryForClient(Version lwM2mVersion) {
+    public boolean isMandatoryForClient(LwM2mVersion lwM2mVersion) {
         return this.mandatoryForClient.contains(lwM2mVersion);
     }
 
@@ -193,14 +193,14 @@ public class ContentFormat implements Comparable<ContentFormat> {
 
     /**
      * From a list of {@link ContentFormat} of a client, return only the optional ones for a given LWM2M
-     * {@link Version}. In other words we remove all {@link ContentFormat} which is considered as Mandatory.
+     * {@link LwM2mVersion}. In other words we remove all {@link ContentFormat} which is considered as Mandatory.
      * 
      * @param contentFormat A list of all supported {@link ContentFormat} for a given device.
      * @param lwm2mVersion The LWM2M version targeted
      * @return only optional {@link ContentFormat}
      */
     public static List<ContentFormat> getOptionalContentFormatForClient(Collection<ContentFormat> contentFormat,
-            Version lwm2mVersion) {
+            LwM2mVersion lwm2mVersion) {
         List<ContentFormat> optionalFormat = new ArrayList<>();
         for (ContentFormat supportedFormat : contentFormat) {
             if (!supportedFormat.isMandatoryForClient(lwm2mVersion)) {
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java
index b049005e..c04b0db4 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/RegisterRequest.java
@@ -22,6 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.leshan.core.Link;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.LwM2m.Version;
 import org.eclipse.leshan.core.request.exception.InvalidRequestException;
 import org.eclipse.leshan.core.response.RegisterResponse;
@@ -94,15 +95,15 @@ public class RegisterRequest extends AbstractLwM2mRequest<RegisterResponse> impl
         }
 
         if (bindingMode != null) {
-            err = BindingMode.isValidFor(bindingMode, Version.get(lwVersion));
+            err = BindingMode.isValidFor(bindingMode, LwM2mVersion.get(lwVersion));
             if (err != null) {
                 throw new InvalidRequestException("Invalid Binding mode: %s", err);
             }
         }
 
         // handle queue mode param
-        Version version = Version.get(lwVersion);
-        if (version.equals(Version.V1_0)) {
+        LwM2mVersion version = LwM2mVersion.get(lwVersion);
+        if (version.equals(LwM2mVersion.V1_0)) {
             if (queueMode != null)
                 throw new InvalidRequestException("QueueMode is not defined in LWM2M v1.0");
             else
diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
index 50e7da83..92e8937a 100644
--- a/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
+++ b/leshan-core/src/main/java/org/eclipse/leshan/core/request/UpdateRequest.java
@@ -22,7 +22,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.eclipse.leshan.core.Link;
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.request.exception.InvalidRequestException;
 import org.eclipse.leshan.core.response.UpdateResponse;
 
@@ -108,7 +108,7 @@ public class UpdateRequest extends AbstractLwM2mRequest<UpdateResponse> implemen
         return additionalAttributes;
     }
 
-    public void validate(Version targetedVersion) {
+    public void validate(LwM2mVersion targetedVersion) {
         if (bindingMode != null) {
             String err = BindingMode.isValidFor(bindingMode, targetedVersion);
             if (err != null) {
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
index cfd33f71..16a73a34 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/Registration.java
@@ -30,7 +30,7 @@ import java.util.Set;
 import java.util.TreeSet;
 
 import org.eclipse.leshan.core.Link;
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.attributes.Attribute;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.node.LwM2mPath;
@@ -60,7 +60,7 @@ public class Registration {
 
     private final String smsNumber;
 
-    private final Version lwM2mVersion;
+    private final LwM2mVersion lwM2mVersion;
 
     private final EnumSet<BindingMode> bindingMode;
 
@@ -229,7 +229,7 @@ public class Registration {
         return smsNumber;
     }
 
-    public Version getLwM2mVersion() {
+    public LwM2mVersion getLwM2mVersion() {
         return lwM2mVersion;
     }
 
@@ -314,7 +314,7 @@ public class Registration {
     }
 
     public boolean usesQueueMode() {
-        if (lwM2mVersion.olderThan(Version.V1_1))
+        if (lwM2mVersion.olderThan(LwM2mVersion.V1_1))
             return bindingMode.contains(BindingMode.Q);
         else
             return queueMode;
@@ -479,7 +479,7 @@ public class Registration {
         private String smsNumber;
         private EnumSet<BindingMode> bindingMode;
         private Boolean queueMode;
-        private Version lwM2mVersion = Version.getDefault();
+        private LwM2mVersion lwM2mVersion = LwM2mVersion.getDefault();
         private Link[] objectLinks;
         private String rootPath;
         private Set<ContentFormat> supportedContentFormats;
@@ -563,7 +563,7 @@ public class Registration {
             return this;
         }
 
-        public Builder lwM2mVersion(Version lwM2mVersion) {
+        public Builder lwM2mVersion(LwM2mVersion lwM2mVersion) {
             this.lwM2mVersion = lwM2mVersion;
             return this;
         }
@@ -728,9 +728,9 @@ public class Registration {
             // Define Default value
             rootPath = rootPath == null ? "/" : rootPath;
             lifeTimeInSec = lifeTimeInSec == null ? DEFAULT_LIFETIME_IN_SEC : lifeTimeInSec;
-            lwM2mVersion = lwM2mVersion == null ? Version.getDefault() : lwM2mVersion;
+            lwM2mVersion = lwM2mVersion == null ? LwM2mVersion.getDefault() : lwM2mVersion;
             bindingMode = bindingMode == null ? EnumSet.of(BindingMode.U) : bindingMode;
-            queueMode = queueMode == null && lwM2mVersion.newerThan(Version.V1_0) ? Boolean.FALSE : queueMode;
+            queueMode = queueMode == null && lwM2mVersion.newerThan(LwM2mVersion.V1_0) ? Boolean.FALSE : queueMode;
             registrationDate = registrationDate == null ? new Date() : registrationDate;
             lastUpdate = lastUpdate == null ? new Date() : lastUpdate;
 
diff --git a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
index edbdfc30..2cac674b 100644
--- a/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
+++ b/leshan-server-core/src/main/java/org/eclipse/leshan/server/registration/RegistrationHandler.java
@@ -19,7 +19,7 @@ package org.eclipse.leshan.server.registration;
 
 import java.util.Date;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.request.DeregisterRequest;
 import org.eclipse.leshan.core.request.Identity;
 import org.eclipse.leshan.core.request.RegisterRequest;
@@ -57,10 +57,10 @@ public class RegistrationHandler {
                 registrationIdProvider.getRegistrationId(registerRequest), registerRequest.getEndpointName(), sender);
         builder.extractDataFromObjectLink(true);
 
-        builder.lwM2mVersion(Version.get(registerRequest.getLwVersion())).lifeTimeInSec(registerRequest.getLifetime())
-                .bindingMode(registerRequest.getBindingMode()).queueMode(registerRequest.getQueueMode())
-                .objectLinks(registerRequest.getObjectLinks()).smsNumber(registerRequest.getSmsNumber())
-                .registrationDate(new Date()).lastUpdate(new Date())
+        builder.lwM2mVersion(LwM2mVersion.get(registerRequest.getLwVersion()))
+                .lifeTimeInSec(registerRequest.getLifetime()).bindingMode(registerRequest.getBindingMode())
+                .queueMode(registerRequest.getQueueMode()).objectLinks(registerRequest.getObjectLinks())
+                .smsNumber(registerRequest.getSmsNumber()).registrationDate(new Date()).lastUpdate(new Date())
                 .additionalRegistrationAttributes(registerRequest.getAdditionalAttributes());
 
         // We must check if the client is using the right identity.
diff --git a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
index b056fdd3..8453fc4f 100644
--- a/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
+++ b/leshan-server-redis/src/main/java/org/eclipse/leshan/server/redis/serialization/RegistrationSerDes.java
@@ -23,7 +23,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 
 import org.eclipse.leshan.core.Link;
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.request.BindingMode;
 import org.eclipse.leshan.core.request.ContentFormat;
 import org.eclipse.leshan.server.registration.Registration;
@@ -120,9 +120,9 @@ public class RegistrationSerDes {
         b.lifeTimeInSec(jObj.getLong("lt", 0));
         String versionAsString = jObj.getString("ver", null);
         if (versionAsString == null) {
-            b.lwM2mVersion(Version.getDefault());
+            b.lwM2mVersion(LwM2mVersion.getDefault());
         } else {
-            b.lwM2mVersion(Version.get(versionAsString));
+            b.lwM2mVersion(LwM2mVersion.get(versionAsString));
         }
         b.registrationDate(new Date(jObj.getLong("regDate", 0)));
         if (jObj.get("sms") != null) {


Test patch:
diff --git a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
index f343bf23..09b62a93 100644
--- a/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
+++ b/leshan-client-core/src/test/java/org/eclipse/leshan/client/util/LinkFormatHelperTest.java
@@ -33,7 +33,7 @@ import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
 import org.eclipse.leshan.client.resource.LwM2mObjectEnabler;
 import org.eclipse.leshan.client.resource.ObjectEnabler;
 import org.eclipse.leshan.core.Link;
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.model.ObjectLoader;
 import org.eclipse.leshan.core.model.ObjectModel;
 import org.eclipse.leshan.core.request.ContentFormat;
@@ -303,7 +303,7 @@ public class LinkFormatHelperTest {
     }
 
     private ObjectModel getObjectModel(int id) {
-        List<ObjectModel> objectModels = ObjectLoader.loadDefault(Version.V1_0);
+        List<ObjectModel> objectModels = ObjectLoader.loadDefault(LwM2mVersion.V1_0);
         for (ObjectModel objectModel : objectModels) {
             if (objectModel.id == id)
                 return objectModel;
@@ -315,7 +315,7 @@ public class LinkFormatHelperTest {
      * Gets a default object model by id and manipulates its version.
      */
     private ObjectModel getVersionedObjectModel(int id, String version) {
-        List<ObjectModel> objectModels = ObjectLoader.loadDefault(Version.V1_0);
+        List<ObjectModel> objectModels = ObjectLoader.loadDefault(LwM2mVersion.V1_0);
         for (ObjectModel om : objectModels) {
             if (om.id == id)
                 return new ObjectModel(om.id, om.name, om.description, version, om.multiple, om.mandatory,
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/VersionTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/VersionTest.java
index 8f02041d..40fc13dc 100644
--- a/leshan-core/src/test/java/org/eclipse/leshan/core/VersionTest.java
+++ b/leshan-core/src/test/java/org/eclipse/leshan/core/VersionTest.java
@@ -17,25 +17,26 @@ package org.eclipse.leshan.core;
 
 import static org.junit.Assert.*;
 
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.LwM2m.Version;
 import org.junit.Test;
 
 public class VersionTest {
     @Test
     public void is_supported_tests() {
-        assertTrue(Version.isSupported("1.0"));
-        assertTrue(Version.isSupported("1.1"));
-        assertFalse(Version.isSupported("1.2"));
-        assertFalse(Version.isSupported(""));
-        assertFalse(Version.isSupported(null));
+        assertTrue(LwM2mVersion.isSupported("1.0"));
+        assertTrue(LwM2mVersion.isSupported("1.1"));
+        assertFalse(LwM2mVersion.isSupported("1.2"));
+        assertFalse(LwM2mVersion.isSupported(""));
+        assertFalse(LwM2mVersion.isSupported(null));
     }
 
     @Test
     public void compare_tests() {
-        assertTrue(Version.get("1.0").compareTo(Version.get("1.2")) < 0);
-        assertTrue(Version.get("0.9").compareTo(Version.get("1.2")) < 0);
-        assertTrue(Version.get("1.2").compareTo(Version.get("1.2")) == 0);
-        assertTrue(Version.get("1.3").compareTo(Version.get("1.2")) > 0);
-        assertTrue(Version.get("2.0").compareTo(Version.get("1.2")) > 0);
+        assertTrue(new Version("1.0").compareTo(new Version("1.2")) < 0);
+        assertTrue(new Version("0.9").compareTo(new Version("1.2")) < 0);
+        assertTrue(new Version("1.2").compareTo(new Version("1.2")) == 0);
+        assertTrue(new Version("1.3").compareTo(new Version("1.2")) > 0);
+        assertTrue(new Version("2.0").compareTo(new Version("1.2")) > 0);
     }
 }
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/model/LwM2mModelRespositotyTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/model/LwM2mModelRespositotyTest.java
new file mode 100644
index 00000000..ceab433c
--- /dev/null
+++ b/leshan-core/src/test/java/org/eclipse/leshan/core/model/LwM2mModelRespositotyTest.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Sierra Wireless and others.
+ * 
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
+ * 
+ * The Eclipse Public License is available at
+ *    http://www.eclipse.org/legal/epl-v20.html
+ * and the Eclipse Distribution License is available at
+ *    http://www.eclipse.org/org/documents/edl-v10.html.
+ * 
+ * Contributors:
+ *     Sierra Wireless - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.leshan.core.model;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.leshan.core.model.ResourceModel.Operations;
+import org.eclipse.leshan.core.model.ResourceModel.Type;
+import org.junit.Test;
+
+public class LwM2mModelRespositotyTest {
+
+    @Test
+    public void validate_get_specific_version() throws InvalidModelException, InvalidDDFFileException, IOException {
+
+        // create repository
+        List<ObjectModel> models = new ArrayList<ObjectModel>();
+        models.add(createModel(0, "1.0"));
+        models.add(createModel(1, "1.0"));
+        models.add(createModel(1, "1.1"));
+        models.add(createModel(2, "1.0"));
+        models.add(createModel(2, "1.1"));
+        LwM2mModelRepository repository = new LwM2mModelRepository(models);
+
+        // validate get specific version
+        ObjectModel objectModel = repository.getObjectModel(1, "1.1");
+        assertEquals(objectModel.id, (Integer) 1);
+        assertEquals(objectModel.version, "1.1");
+
+        objectModel = repository.getObjectModel(2, "1.0");
+        assertEquals(objectModel.id, (Integer) 2);
+        assertEquals(objectModel.version, "1.0");
+
+        objectModel = repository.getObjectModel(3, "1.0");
+        assertNull(objectModel);
+
+        objectModel = repository.getObjectModel(2, "1.2");
+        assertNull(objectModel);
+    }
+
+    @Test
+    public void validate_get_last_version() throws InvalidModelException, InvalidDDFFileException, IOException {
+
+        // create repository
+        List<ObjectModel> models = new ArrayList<ObjectModel>();
+        models.add(createModel(0, "1.0"));
+        models.add(createModel(1, "1.0"));
+        models.add(createModel(1, "1.1"));
+        models.add(createModel(2, "1.0"));
+        models.add(createModel(2, "1.1"));
+        LwM2mModelRepository repository = new LwM2mModelRepository(models);
+
+        // validate get most recent version
+        ObjectModel objectModel = repository.getObjectModel(0);
+        assertEquals(objectModel.id, (Integer) 0);
+        assertEquals(objectModel.version, "1.0");
+
+        objectModel = repository.getObjectModel(1);
+        assertEquals(objectModel.id, (Integer) 1);
+        assertEquals(objectModel.version, "1.1");
+
+        objectModel = repository.getObjectModel(2);
+        assertEquals(objectModel.id, (Integer) 2);
+        assertEquals(objectModel.version, "1.1");
+
+        objectModel = repository.getObjectModel(3);
+        assertNull(objectModel);
+    }
+
+    private ObjectModel createModel(Integer objectId, String version) {
+        ResourceModel resourceModel = new ResourceModel(0, "a resource", Operations.R, false, false, Type.BOOLEAN, null,
+                null, null);
+        return new ObjectModel(objectId, "Object " + objectId, null, version, false, false, resourceModel);
+
+    }
+}
diff --git a/leshan-core/src/test/java/org/eclipse/leshan/core/request/ContentFormatTest.java b/leshan-core/src/test/java/org/eclipse/leshan/core/request/ContentFormatTest.java
index 6f25adb5..580f2484 100644
--- a/leshan-core/src/test/java/org/eclipse/leshan/core/request/ContentFormatTest.java
+++ b/leshan-core/src/test/java/org/eclipse/leshan/core/request/ContentFormatTest.java
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals;
 import java.util.Arrays;
 import java.util.List;
 
-import org.eclipse.leshan.core.LwM2m.Version;
+import org.eclipse.leshan.core.LwM2m.LwM2mVersion;
 import org.eclipse.leshan.core.node.codec.CodecException;
 import org.junit.Test;
 
@@ -31,7 +31,7 @@ public class ContentFormatTest {
     public void get_optional_content_format_for_v1_0() throws CodecException {
 
         List<ContentFormat> optionalContentFormat = ContentFormat
-                .getOptionalContentFormatForClient(Arrays.asList(TLV, JSON, TEXT, OPAQUE), Version.V1_0);
+                .getOptionalContentFormatForClient(Arrays.asList(TLV, JSON, TEXT, OPAQUE), LwM2mVersion.V1_0);
 
         assertEquals(Arrays.asList(JSON, TEXT, OPAQUE), optionalContentFormat);
     }
@@ -39,7 +39,7 @@ public class ContentFormatTest {
     @Test
     public void get_optional_content_format_for_v1_1() throws CodecException {
         List<ContentFormat> optionalContentFormat = ContentFormat.getOptionalContentFormatForClient(
-                Arrays.asList(TLV, JSON, SENML_JSON, SENML_CBOR, TEXT, OPAQUE, CBOR, LINK), Version.V1_1);
+                Arrays.asList(TLV, JSON, SENML_JSON, SENML_CBOR, TEXT, OPAQUE, CBOR, LINK), LwM2mVersion.V1_1);
 
         assertEquals(Arrays.asList(TLV, JSON, SENML_JSON, SENML_CBOR, CBOR), optionalContentFormat);