Repository: wicket
Updated Branches:
  refs/heads/WICKET-4972-PropertyResourceModel [created] fd6ed4d15


PropertyResourceModel as alternative to StringResourceModel


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/fd6ed4d1
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/fd6ed4d1
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/fd6ed4d1

Branch: refs/heads/WICKET-4972-PropertyResourceModel
Commit: fd6ed4d15e125dc93e1e068a73b37b59f740eb5c
Parents: adcb7a6
Author: svenmeier <[email protected]>
Authored: Thu Sep 25 14:51:18 2014 +0200
Committer: svenmeier <[email protected]>
Committed: Thu Sep 25 14:52:09 2014 +0200

----------------------------------------------------------------------
 .../wicket/model/PropertyResourceModel.java     | 589 +++++++++++++++++++
 .../wicket/model/StringResourceModel.java       |  17 +-
 ...ropertyResourceModelTest$TestPage.properties |  27 +
 .../wicket/model/PropertyResourceModelTest.java | 340 +++++++++++
 .../resource/loader/PropertiesResolverTest.java |   3 +-
 .../examples/ajax/builtin/RatingsPage.java      |   4 +-
 .../wicket/examples/compref/LabelPage.java      |   6 +-
 .../examples/compref/LabelPage.properties       |   2 +-
 .../examples/compref/LabelPage_ko.properties    |   2 +-
 .../examples/compref/LabelPage_no.properties    |   2 +-
 .../org/apache/wicket/examples/pub/Home.java    |   4 +-
 .../org/apache/wicket/examples/pub2/Home.java   |   4 +-
 .../wicket/examples/wizard/NewUserWizard.java   |  10 +-
 .../html/form/upload/UploadProgressBar.java     |   5 +-
 .../html/form/upload/UploadStatusResource.java  |   5 +-
 .../repeater/data/table/NavigatorLabel.java     |   6 +-
 .../wicket/extensions/rating/RatingPanel.java   |  19 +-
 .../extensions/rating/RatingPanel.properties    |   4 +-
 .../extensions/rating/RatingPanel_da.properties |   4 +-
 .../extensions/rating/RatingPanel_es.properties |   4 +-
 .../rating/RatingPanel_fr.properties.xml        |   4 +-
 .../extensions/rating/RatingPanel_hu.properties |   4 +-
 .../extensions/rating/RatingPanel_ko.properties |   4 +-
 .../extensions/rating/RatingPanel_nl.properties |   4 +-
 .../extensions/rating/RatingPanel_no.properties |   4 +-
 .../extensions/rating/RatingPanel_pl.properties |   4 +-
 .../rating/RatingPanel_ru.properties.xml        |   4 +-
 .../apache/wicket/util/collections/MiniMap.java | 104 ++++
 28 files changed, 1127 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-core/src/main/java/org/apache/wicket/model/PropertyResourceModel.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/model/PropertyResourceModel.java 
b/wicket-core/src/main/java/org/apache/wicket/model/PropertyResourceModel.java
new file mode 100644
index 0000000..9476d0d
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/model/PropertyResourceModel.java
@@ -0,0 +1,589 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "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.wicket.model;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Component;
+import org.apache.wicket.Localizer;
+import org.apache.wicket.Session;
+import 
org.apache.wicket.core.util.string.interpolator.PropertyVariableInterpolator;
+import org.apache.wicket.resource.loader.ComponentStringResourceLoader;
+import org.apache.wicket.util.collections.MicroMap;
+import org.apache.wicket.util.lang.Args;
+
+
+/**
+ * This model class encapsulates the full power of localization support within 
the Wicket framework.
+ * It combines the flexible Wicket resource loading mechanism with property 
expressions. This
+ * combination should be able to solve any dynamic localization requirement 
that a project has.
+ * <p>
+ * The model should be created with four parameters, which are described in 
detail below:
+ * <ul>
+ * <li><b>resourceKey </b>- This is the most important parameter as it 
contains the key that should
+ * be used to obtain resources from any string resource loaders. This 
parameter is mandatory: a null
+ * value will throw an exception. Typically it will contain an ordinary string 
such as
+ * &quot;label.username&quot;. To add extra power to the key functionality the 
key may also contain
+ * a property expression which will be evaluated if the model parameter (see 
below) is not null.
+ * This allows keys to be changed dynamically as the application is running. 
For example, the key
+ * could be &quot;product.${product.id}&quot; which prior to rendering will 
call
+ * model.getObject().getProduct().getId() and substitute this value into the 
resource key before is
+ * is passed to the loader.
+ * <li><b>component </b>- This parameter should be a component that the string 
resource is relative
+ * to. In a simple application this will usually be the Page on which the 
component resides. For
+ * reusable components/containers that are packaged with their own string 
resource bundles it should
+ * be the actual component/container rather than the page. For more 
information on this please see
+ * {@link org.apache.wicket.resource.loader.ComponentStringResourceLoader}. 
The relative component
+ * may actually be {@code null} if this model is wrapped on assignment (
+ * {@link IComponentAssignedModel}) or when all resource loading is to be done 
from a global
+ * resource loader. However, we recommend that a relative component is still 
supplied even in the
+ * latter case in order to 'future proof' your application with regards to 
changing resource loading
+ * strategies.
+ * <li><b>model</b>- This parameter is mandatory if either the resourceKey or 
the found string
+ * resource (see below) contain property expressions. Where property 
expressions are present they
+ * will all be evaluated relative to this model object. If there are no 
property expressions present
+ * then this model parameter may be <code>null</code><br>
+ * Alternatively you can pass a map of models, each identified by a String 
which is used as a prefix
+ * for all property expressions.
+ * <li><b>defaultValue</b>- a default to be used if the string resource is not 
defined.
+ * </ul>
+ * <p>
+ * <b>Example 1 </b>
+ * <p>
+ * In its simplest form, the model can be used as follows:
+ * 
+ * <pre>
+ * public class MyPage extends WebPage&lt;Void&gt;
+ * {
+ *     public MyPage(final PageParameters parameters)
+ *     {
+ *             add(new Label(&quot;username&quot;, new 
PropertyResourceModel(&quot;label.username&quot;, this, null)));
+ *     }
+ * }
+ * </pre>
+ * 
+ * Where the resource bundle for the page contains the entry 
<code>label.username=Username</code>
+ * <p>
+ * <b>Example 2 </b>
+ * <p>
+ * In this example, the resource key is selected based on the evaluation of a 
property expression:
+ * 
+ * <pre>
+ * public class MyPage extends WebPage&lt;Void&gt;
+ * {
+ *     public MyPage(final PageParameters parameters)
+ *     {
+ *         WeatherStation ws = new WeatherStation();
+ *         add(new Label(&quot;weatherMessage&quot;,
+ *             new PropertyResourceModel(&quot;weather.${currentStatus}&quot;, 
this, new Model&lt;WeatherStation&gt;(ws)));
+ *     }
+ * }
+ * </pre>
+ * 
+ * Which will call the WeatherStation.getCurrentStatus() method each time the 
string resource model
+ * is used and where the resource bundle for the page contains the entries:
+ * 
+ * <pre>
+ * weather.sunny=Don't forget sunscreen!
+ * weather.raining=You might need an umbrella
+ * weather.snowing=Got your skis?
+ * weather.overcast=Best take a coat to be safe
+ * </pre>
+ * 
+ * <p>
+ * <b>Example 3 </b>
+ * <p>
+ * In this example the found resource string contains a property expression 
that is substituted via
+ * the model:
+ * 
+ * <pre>
+ * public class MyPage extends WebPage&lt;Void&gt;
+ * {
+ *     public MyPage(final PageParameters parameters)
+ *     {
+ *         WeatherStation ws = new WeatherStation();
+ *         add(new Label(&quot;weatherMessage&quot;,
+ *             new PropertyResourceModel(&quot;weather.message&quot;, this, 
new Model&lt;WeatherStation&gt;(ws)));
+ *     }
+ * }
+ * </pre>
+ * 
+ * Where the resource bundle contains the entry <code>weather.message=Weather 
station reports that
+ * the temperature is ${currentTemperature} ${units}</code>
+ * <p>
+ * <b>Example 4 </b>
+ * <p>
+ * This is an example of the most complex and powerful use of the string 
resource model with
+ * multiple nested models:
+ * 
+ * <pre>
+ * public class MyPage extends WebPage&lt;Void&gt;
+ * {
+ *     public MyPage(final PageParameters parameters)
+ *     {
+ *             WeatherStation ws = new WeatherStation();
+ * 
+ *             Map&lt;String, IModel&lt;?&gt;&gt; models = new 
HashMap&lt;&gt;();
+ *             models.put(&quot;date&quot;, Model.of(new Date()));
+ *             models.put(&quot;currentStatus&quot;, new 
PropertyModel&lt;?&gt;(ws, &quot;currentStatus&quot;));
+ *             models.put(&quot;ws&quot;, Model.of(ws));
+ *             add(new Label(&quot;weatherMessage&quot;, new 
PropertyResourceModel(&quot;weather.detail&quot;, this, models)));
+ *     }
+ * }
+ * </pre>
+ * 
+ * In the resource bundle all property expressions are prefixed with the 
identifier of the
+ * respective model:
+ * 
+ * <pre>
+ * weather.detail=The report for ${date}, shows the temperature as 
${ws.currentStatus} ${ws.unit} \
+ *     and the weather to be ${currentStatus}
+ * </pre>
+ * 
+ * @see ComponentStringResourceLoader for additional information especially on 
the component search
+ *      order
+ * 
+ * @author Chris Turner
+ * @author svenmeier
+ */
+public class PropertyResourceModel extends LoadableDetachableModel<String>
+       implements
+               IComponentAssignedModel<String>
+{
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * The models for property resolving.
+        */
+       private final Map<String, IModel<?>> models;
+
+       /** The relative component used for lookups. */
+       private final Component component;
+
+       /** The key of message to get. */
+       private final String resourceKey;
+
+       /** The default value of the message. */
+       private final IModel<String> defaultValue;
+
+       @Override
+       public IWrapModel<String> wrapOnAssignment(Component component)
+       {
+               return new AssignmentWrapper(component);
+       }
+
+       private class AssignmentWrapper extends LoadableDetachableModel<String>
+               implements
+                       IWrapModel<String>
+       {
+               private static final long serialVersionUID = 1L;
+
+               private final Component component;
+
+               /**
+                * Construct.
+                * 
+                * @param component
+                */
+               public AssignmentWrapper(Component component)
+               {
+                       this.component = component;
+               }
+
+               @Override
+               public void detach()
+               {
+                       super.detach();
+
+                       PropertyResourceModel.this.detach();
+               }
+
+               @Override
+               protected void onDetach()
+               {
+                       if (PropertyResourceModel.this.component == null)
+                       {
+                               PropertyResourceModel.this.onDetach();
+                       }
+               }
+
+               @Override
+               protected String load()
+               {
+                       if (PropertyResourceModel.this.component != null)
+                       {
+                               // ignore assignment if component was specified 
explicitly
+                               return PropertyResourceModel.this.getObject();
+                       }
+                       else
+                       {
+                               return getString(component);
+                       }
+               }
+
+               @Override
+               public void setObject(String object)
+               {
+                       PropertyResourceModel.this.setObject(object);
+               }
+
+               @Override
+               public IModel<String> getWrappedModel()
+               {
+                       return PropertyResourceModel.this;
+               }
+       }
+
+       /**
+        * Creates a new string resource model using the supplied parameters.
+        * 
+        * @param resourceKey
+        *            The resource key for this string resource
+        * @param model
+        *            An optional model to use for property substitutions
+        */
+       public PropertyResourceModel(final String resourceKey, final IModel<?> 
model)
+       {
+               this(resourceKey, null, model, null);
+       }
+
+
+       /**
+        * Creates a new string resource model using the supplied parameters.
+        * <p>
+        * The relative component parameter should generally be supplied, as 
without it resources can
+        * not be obtained from resource bundles that are held relative to a 
particular component or
+        * page. However, for application that use only global resources then 
this parameter may be
+        * null.
+        * 
+        * @param resourceKey
+        *            The resource key for this string resource
+        * @param component
+        *            The component that the resource is relative to
+        * @param model
+        *            An optional model to use for property substitutions
+        */
+       public PropertyResourceModel(final String resourceKey, final Component 
component,
+               final IModel<?> model)
+       {
+               this(resourceKey, component, model, null);
+       }
+
+       /**
+        * Creates a new string resource model using the supplied parameters.
+        * 
+        * @param resourceKey
+        *            The resource key for this string resource
+        * @param model
+        *            An optional model to use for property substitutions
+        * @param defaultValue
+        *            The default value if the resource key is not found.
+        */
+       public PropertyResourceModel(final String resourceKey, final IModel<?> 
model,
+               final IModel<String> defaultValue)
+       {
+               this(resourceKey, null, model, defaultValue);
+       }
+
+       /**
+        * Creates a new string resource model using the supplied parameters.
+        * <p>
+        * The relative component parameter should generally be supplied, as 
without it resources can
+        * not be obtained from resource bundles that are held relative to a 
particular component or
+        * page. However, for application that use only global resources then 
this parameter may be
+        * null.
+        * 
+        * @param resourceKey
+        *            The resource key for this string resource
+        * @param component
+        *            The component that the resource is relative to
+        * @param model
+        *            An optional model to use for property substitutions
+        * @param defaultValue
+        *            The default value if the resource key is not found.
+        */
+       public PropertyResourceModel(final String resourceKey, final Component 
component,
+               final IModel<?> model, final IModel<String> defaultValue)
+       {
+               this(resourceKey, component, new MicroMap<String, 
IModel<?>>("", model), defaultValue);
+       }
+
+       /**
+        * Creates a new string resource model using the supplied parameters.
+        * <p>
+        * The relative component parameter should generally be supplied, as 
without it resources can
+        * not be obtained from resource bundles that are held relative to a 
particular component or
+        * page. However, for application that use only global resources then 
this parameter may be
+        * null.
+        * 
+        * @param resourceKey
+        *            The resource key for this string resource
+        * @param component
+        *            The component that the resource is relative to
+        * @param models
+        *            A map of models to use for property substitutions
+        * @param defaultValue
+        *            The default value if the resource key is not found.
+        */
+       public PropertyResourceModel(final String resourceKey, final Component 
component,
+               final Map<String, IModel<?>> models, final IModel<String> 
defaultValue)
+       {
+               Args.notNull(resourceKey, "resourceKey");
+               Args.notNull(models, "models");
+
+               this.resourceKey = resourceKey;
+               this.component = component;
+               this.models = models;
+               this.defaultValue = defaultValue;
+       }
+
+
+       /**
+        * Gets the localizer that is being used by this string resource model.
+        * 
+        * @return The localizer
+        */
+       public Localizer getLocalizer()
+       {
+               return Application.get().getResourceSettings().getLocalizer();
+       }
+
+       /**
+        * Gets the string currently represented by this model. The string that 
is returned may vary for
+        * each call to this method depending on the values contained in the 
model and an the parameters
+        * that were passed when this string resource model was created.
+        * 
+        * @return The string
+        */
+       public final String getString()
+       {
+               return getString(component);
+       }
+
+       private String getString(final Component component)
+       {
+
+               final Localizer localizer = getLocalizer();
+               IModel<?> target = getTargetModel();
+               String defaultVal = defaultValue != null ? 
defaultValue.getObject() : null;
+
+               return localizer.getString(getResourceKey(), component, target, 
defaultVal);
+       }
+
+       /**
+        * @return The locale to use when formatting the resource value
+        */
+       protected Locale getLocale()
+       {
+               final Locale locale;
+               if (component != null)
+               {
+                       locale = component.getLocale();
+               }
+               else
+               {
+                       locale = Session.exists() ? Session.get().getLocale() : 
Locale.getDefault();
+               }
+               return locale;
+       }
+
+       /**
+        * This method just returns debug information, so it won't return the 
localized string. Please
+        * use getString() for that.
+        * 
+        * @return The string for this model object
+        */
+       @Override
+       public String toString()
+       {
+               StringBuilder sb = new StringBuilder("PropertyResourceModel[");
+               sb.append("key:");
+               sb.append(resourceKey);
+               sb.append(",default:");
+               sb.append(defaultValue);
+               return sb.toString();
+       }
+
+       /**
+        * Get the model to use as target for all property expressions.
+        */
+       private IModel<?> getTargetModel()
+       {
+               if (models == null)
+               {
+                       return null;
+               }
+               else if (models instanceof MicroMap)
+               {
+                       // a single model only, use it as is
+                       return models.values().iterator().next();
+               }
+               else
+               {
+                       // a wrapper which returns the object contained in each 
model
+                       return new AbstractReadOnlyModel<Map<String, Object>>()
+                       {
+                               @Override
+                               public Map<String, Object> getObject()
+                               {
+                                       return new Map<String, Object>()
+                                       {
+                                               @Override
+                                               public int size()
+                                               {
+                                                       return models.size();
+                                               }
+
+                                               @Override
+                                               public boolean isEmpty()
+                                               {
+                                                       return models.isEmpty();
+                                               }
+
+                                               @Override
+                                               public boolean 
containsKey(Object key)
+                                               {
+                                                       return 
models.containsKey(key);
+                                               }
+
+                                               @Override
+                                               public boolean 
containsValue(Object value)
+                                               {
+                                                       return 
models.containsValue(value);
+                                               }
+
+                                               @Override
+                                               public Object get(Object key)
+                                               {
+                                                       IModel<?> value = 
models.get(key);
+                                                       if (value == null)
+                                                       {
+                                                               return value;
+                                                       }
+                                                       return 
value.getObject();
+                                               }
+
+                                               @Override
+                                               public Object put(String key, 
Object value)
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+
+                                               @Override
+                                               public Object remove(Object key)
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+
+                                               @Override
+                                               public void putAll(Map<? 
extends String, ? extends Object> m)
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+
+                                               @Override
+                                               public void clear()
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+
+                                               @Override
+                                               public Set<String> keySet()
+                                               {
+                                                       return models.keySet();
+                                               }
+
+                                               @Override
+                                               public Collection<Object> 
values()
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+
+                                               @Override
+                                               public 
Set<java.util.Map.Entry<String, Object>> entrySet()
+                                               {
+                                                       throw new 
UnsupportedOperationException();
+                                               }
+                                       };
+                               }
+                       };
+               }
+       }
+
+       /**
+        * Gets the resource key for this string resource. If the resource key 
contains property
+        * expressions and the model is not null then the returned value is the 
actual resource key with
+        * all substitutions undertaken.
+        * 
+        * @return The (possibly substituted) resource key
+        */
+       protected final String getResourceKey()
+       {
+               IModel<?> target = getTargetModel();
+
+               if (target == null)
+               {
+                       return resourceKey;
+               }
+               else
+               {
+                       return new PropertyVariableInterpolator(resourceKey, 
target.getObject()).toString();
+               }
+       }
+
+       /**
+        * Gets the string that this string resource model currently represents.
+        * <p>
+        * Note: This method is used only if this model is used directly 
without assignment to a
+        * component, it is not called by the assignment wrapper returned from
+        * {@link #wrapOnAssignment(Component)}.
+        */
+       @Override
+       protected final String load()
+       {
+               return getString();
+       }
+
+       @Override
+       public final void detach()
+       {
+               super.detach();
+
+               for (IModel<?> model : models.values())
+               {
+                       if (model != null)
+                       {
+                               model.detach();
+                       }
+               }
+
+               if (defaultValue != null)
+               {
+                       defaultValue.detach();
+               }
+       }
+
+       @Override
+       public void setObject(String object)
+       {
+               throw new UnsupportedOperationException();
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-core/src/main/java/org/apache/wicket/model/StringResourceModel.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/model/StringResourceModel.java 
b/wicket-core/src/main/java/org/apache/wicket/model/StringResourceModel.java
index 07e2080..e170af7 100644
--- a/wicket-core/src/main/java/org/apache/wicket/model/StringResourceModel.java
+++ b/wicket-core/src/main/java/org/apache/wicket/model/StringResourceModel.java
@@ -85,10 +85,10 @@ import org.apache.wicket.util.string.Strings;
  * <pre>
  * public class MyPage extends WebPage&lt;Void&gt;
  * {
- *    public MyPage(final PageParameters parameters)
- *    {
- *        add(new Label(&quot;username&quot;, new 
StringResourceModel(&quot;label.username&quot;, this, null)));
- *    }
+ *     public MyPage(final PageParameters parameters)
+ *     {
+ *             add(new Label(&quot;username&quot;, new 
StringResourceModel(&quot;label.username&quot;, this, null)));
+ *     }
  * }
  * </pre>
  * 
@@ -101,7 +101,7 @@ import org.apache.wicket.util.string.Strings;
  * <pre>
  * public class MyPage extends WebPage&lt;Void&gt;
  * {
- *     public MyPage(final PageParameters parameters)
+ *     public MyPage(final PageParameters parameters)
  *     {
  *         WeatherStation ws = new WeatherStation();
  *         add(new Label(&quot;weatherMessage&quot;,
@@ -129,7 +129,7 @@ import org.apache.wicket.util.string.Strings;
  * <pre>
  * public class MyPage extends WebPage&lt;Void&gt;
  * {
- *     public MyPage(final PageParameters parameters)
+ *     public MyPage(final PageParameters parameters)
  *     {
  *         WeatherStation ws = new WeatherStation();
  *         add(new Label(&quot;weatherMessage&quot;,
@@ -149,7 +149,7 @@ import org.apache.wicket.util.string.Strings;
  * <pre>
  * public class MyPage extends WebPage&lt;Void&gt;
  * {
- *     public MyPage(final PageParameters parameters)
+ *     public MyPage(final PageParameters parameters)
  *     {
  *         WeatherStation ws = new WeatherStation();
  *         IModel&lt;WeatherStation&gt; model = new 
Model&lt;WeatherStation&gt;(ws);
@@ -178,7 +178,10 @@ import org.apache.wicket.util.string.Strings;
  *      order
  * 
  * @author Chris Turner
+ * 
+ * @deprecated use {@link PropertyResourceModel} which works without {@link 
MessageFormat}
  */
+@Deprecated
 public class StringResourceModel extends LoadableDetachableModel<String>
        implements
                IComponentAssignedModel<String>

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest$TestPage.properties
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest$TestPage.properties
 
b/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest$TestPage.properties
new file mode 100644
index 0000000..70734fe
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest$TestPage.properties
@@ -0,0 +1,27 @@
+#
+# $Id$
+# $Revision$
+# $Date$
+#
+# ====================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#  http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "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.
+#
+simple.text=Simple text
+wrappedOnAssignment.text=Non-wrapped text
+resourceModelWithoutComponent.wrappedOnAssignment.text=Wrapped text
+weather.sunny=It's sunny, wear sunscreen
+weather.raining=It's raining, take an umbrella
+weather.message=Weather station "${name}" reports that the temperature is 
${currentTemperature} ${units}
+weather.detail=The report for ${time}, shows the temperature as 
${station.currentTemperature} ${station.units} and the weather to be 
${station.currentStatus}
+
+weather.25.7=Twenty-five dot seven
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest.java
new file mode 100644
index 0000000..d7f197e
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/model/PropertyResourceModelTest.java
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "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.wicket.model;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Session;
+import org.apache.wicket.WicketTestCase;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.util.collections.MiniMap;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test cases for the {@link PropertyResourceModel}.
+ * 
+ * @author Chris Turner
+ * @author svenmeier
+ */
+public class PropertyResourceModelTest extends WicketTestCase
+{
+       private WebPage page;
+
+       private WeatherStation ws;
+
+       private IModel<WeatherStation> wsModel;
+
+       /**
+        * @throws Exception
+        */
+       @Before
+       public void before() throws Exception
+       {
+               page = new TestPage();
+               ws = new WeatherStation();
+               wsModel = new Model<WeatherStation>(ws);
+       }
+
+
+       /** */
+       @Test
+       public void getSimpleResource()
+       {
+               PropertyResourceModel model = new 
PropertyResourceModel("simple.text", page, null);
+               assertEquals("Text should be as expected", "Simple text", 
model.getString());
+               assertEquals("Text should be as expected", "Simple text", 
model.getObject());
+       }
+
+       /** */
+       @Test
+       public void getWrappedOnAssignmentResource()
+       {
+               Label label1 = new Label("resourceModelWithComponent", new 
PropertyResourceModel(
+                       "wrappedOnAssignment.text", page, null));
+               page.add(label1);
+               assertEquals("Text should be as expected", "Non-wrapped text",
+                       label1.getDefaultModelObject());
+
+               Label label2 = new Label("resourceModelWithoutComponent", new 
PropertyResourceModel(
+                       "wrappedOnAssignment.text", (Component)null, null));
+               page.add(label2);
+               assertEquals("Text should be as expected", "Wrapped text",
+                       label2.getDefaultModelObject());
+       }
+
+       /** */
+       @Test(expected = IllegalArgumentException.class)
+       public void nullResourceKey()
+       {
+               new PropertyResourceModel(null, page, null);
+       }
+
+       /** */
+       @Test
+       public void getSimpleResourceWithKeySubstitution()
+       {
+               PropertyResourceModel model = new 
PropertyResourceModel("weather.${currentStatus}", page,
+                       wsModel);
+               assertEquals("Text should be as expected", "It's sunny, wear 
sunscreen",
+                       model.getString());
+               ws.setCurrentStatus("raining");
+               assertEquals("Text should be as expected", "It's raining, take 
an umbrella",
+                       model.getString());
+       }
+
+       /** */
+       @Test
+       public void getSimpleResourceWithKeySubstitutionForNonString()
+       {
+               // German uses comma (,) as decimal separator
+               Session.get().setLocale(Locale.GERMAN);
+
+               PropertyResourceModel model = new 
PropertyResourceModel("weather.${currentTemperature}",
+                       page,
+                       wsModel);
+               assertEquals("Text should be as expected", "Twenty-five dot 
seven",
+                       model.getString());
+       }
+
+       /** */
+       @Test
+       public void getPropertySubstitutedResource()
+       {
+               tester.getSession().setLocale(Locale.ENGLISH);
+               PropertyResourceModel model = new 
PropertyResourceModel("weather.message", page, wsModel);
+               assertEquals(
+                       "Text should be as expected",
+                       "Weather station \"Europe's main weather station\" 
reports that the temperature is 25.7 \u00B0C",
+                       model.getString());
+               ws.setCurrentTemperature(11.5);
+               assertEquals(
+                       "Text should be as expected",
+                       "Weather station \"Europe's main weather station\" 
reports that the temperature is 11.5 \u00B0C",
+                       model.getString());
+       }
+
+       /** */
+       @Test
+       public void substitutionParametersResource()
+       {
+               tester.getSession().setLocale(Locale.ENGLISH);
+
+               Calendar cal = Calendar.getInstance();
+               cal.set(2004, Calendar.OCTOBER, 15, 13, 21);
+
+               PropertyResourceModel model = new 
PropertyResourceModel("weather.detail", page,
+                       MiniMap.<String, IModel<?>> of(
+                       "time", new PropertyModel<Date>(cal, "time"),
+                       "station", wsModel),
+                       null);
+
+               assertEquals(
+                       "Text should be as expected",
+                       "The report for 10/15/04, shows the temperature as 25.7 
°C and the weather to be sunny",
+                       model.getString());
+               ws.setCurrentStatus("raining");
+               ws.setCurrentTemperature(11.568);
+               assertEquals(
+                       "Text should be as expected",
+                       "The report for 10/15/04, shows the temperature as 
11.568 °C and the weather to be raining",
+                       model.getString());
+       }
+
+       /** */
+       @Test(expected = UnsupportedOperationException.class)
+       public void setObject()
+       {
+               PropertyResourceModel model = new 
PropertyResourceModel("simple.text", page, null);
+               model.setObject("Some value");
+       }
+
+       /** */
+       @Test
+       public void detachAttachDetachableModel()
+       {
+               IModel<WeatherStation> wsDetachModel = new 
LoadableDetachableModel<WeatherStation>()
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected WeatherStation load()
+                       {
+                               return new WeatherStation();
+                       }
+
+
+               };
+
+               PropertyResourceModel model = new 
PropertyResourceModel("simple.text", page, wsDetachModel);
+               model.getObject();
+               assertNotNull(model.getLocalizer());
+               model.detach();
+       }
+
+       /**
+        * https://issues.apache.org/jira/browse/WICKET-4323
+        */
+       @Test
+       public void detachSubstituteModelFromAssignmentWrapper()
+       {
+               IModel<WeatherStation> nullOnDetachModel = new 
Model<WeatherStation>()
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void detach()
+                       {
+                               setObject(null);
+                       }
+               };
+
+               nullOnDetachModel.setObject(ws);
+               Label label1 = new Label("resourceModelWithComponent", new 
PropertyResourceModel(
+                       "wrappedOnAssignment.text", page, nullOnDetachModel));
+               page.add(label1);
+               label1.getDefaultModelObject();
+               label1.detach();
+               assertNull(nullOnDetachModel.getObject());
+
+               nullOnDetachModel.setObject(ws);
+               Label label2 = new Label("resourceModelWithoutComponent", new 
PropertyResourceModel(
+                       "wrappedOnAssignment.text", nullOnDetachModel));
+               page.add(label2);
+               label2.getDefaultModelObject();
+               label2.detach();
+               assertNull(nullOnDetachModel.getObject());
+       }
+
+       /**
+        * https://issues.apache.org/jira/browse/WICKET-5176
+        */
+       @Test
+       public void detachEvenNotAttached() {
+               Wicket5176Model wrappedModel = new Wicket5176Model();
+               PropertyResourceModel stringResourceModel = new 
PropertyResourceModel("test",
+                       (Component)null, wrappedModel);
+               assertFalse(stringResourceModel.isAttached());
+               assertTrue(wrappedModel.isAttached());
+               stringResourceModel.detach();
+               assertFalse(wrappedModel.isAttached());
+       }
+
+       private static class Wicket5176Model implements IModel {
+               private boolean attached = true;
+
+               @Override
+               public Object getObject() {
+                       return null;
+               }
+
+               @Override
+               public void setObject(Object object) {
+               }
+
+               @Override
+               public void detach() {
+                       attached = false;
+               }
+
+               private boolean isAttached() {
+                       return attached;
+               }
+       }
+
+       /**
+        * Inner class used for testing.
+        */
+       public static class WeatherStation implements Serializable
+       {
+               private static final long serialVersionUID = 1L;
+
+               private final String name = "Europe's main weather station";
+
+               private String currentStatus = "sunny";
+
+               private double currentTemperature = 25.7;
+
+               /**
+                * @return status
+                */
+               public String getCurrentStatus()
+               {
+                       return currentStatus;
+               }
+
+               /**
+                * @param currentStatus
+                */
+               public void setCurrentStatus(String currentStatus)
+               {
+                       this.currentStatus = currentStatus;
+               }
+
+               /**
+                * @return current temp
+                */
+               public double getCurrentTemperature()
+               {
+                       return currentTemperature;
+               }
+
+               /**
+                * @param currentTemperature
+                */
+               public void setCurrentTemperature(double currentTemperature)
+               {
+                       this.currentTemperature = currentTemperature;
+               }
+
+               /**
+                * @return units
+                */
+               public String getUnits()
+               {
+                       return "\u00B0C";
+               }
+
+               /**
+                * @return name
+                */
+               public String getName()
+               {
+                       return name;
+               }
+       }
+
+       /**
+        * Test page.
+        */
+       public static class TestPage extends WebPage
+       {
+               private static final long serialVersionUID = 1L;
+
+               /**
+                * Construct.
+                */
+               public TestPage()
+               {
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-core/src/test/java/org/apache/wicket/resource/loader/PropertiesResolverTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/resource/loader/PropertiesResolverTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/resource/loader/PropertiesResolverTest.java
index 8c5c6a9..37a4dfb 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/resource/loader/PropertiesResolverTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/resource/loader/PropertiesResolverTest.java
@@ -22,7 +22,6 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.repeater.RepeatingView;
-import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.util.tester.WicketTester;
 import org.junit.Assert;
@@ -121,7 +120,7 @@ public class PropertiesResolverTest extends Assert
 
                private String lookup(String key, Component anchor)
                {
-                       return new StringResourceModel(key, anchor, null, 
(String)null).getString();
+                       return anchor.getString(key);
                }
        }
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/RatingsPage.java
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/RatingsPage.java
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/RatingsPage.java
index 6ad0b40..d0e5f30 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/RatingsPage.java
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/RatingsPage.java
@@ -16,7 +16,6 @@
  */
 package org.apache.wicket.examples.ajax.builtin;
 
-import org.apache.wicket.util.io.IClusterable;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.extensions.rating.RatingPanel;
 import org.apache.wicket.markup.html.link.Link;
@@ -27,6 +26,7 @@ import org.apache.wicket.request.IRequestHandler;
 import 
org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
 import org.apache.wicket.request.resource.PackageResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.io.IClusterable;
 
 
 /**
@@ -188,7 +188,7 @@ public class RatingsPage extends BasePage
                });
 
                add(new RatingPanel("rating2", new 
PropertyModel<Integer>(rating2, "rating"),
-                       new Model<Integer>(5), new 
PropertyModel<Integer>(rating2, "nrOfVotes"),
+                       new Model<Integer>(5), null,
                        new PropertyModel<Boolean>(this, "hasVoted"), true)
                {
                        @Override

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.java
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.java
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.java
index 47007d5..8764c49 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.java
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.java
@@ -21,7 +21,7 @@ import java.util.Date;
 import org.apache.wicket.examples.WicketExamplePage;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 
 
 /**
@@ -59,8 +59,8 @@ public class LabelPage extends WicketExamplePage
                // We use key 'label.current.locale' and provide a the current 
locale
                // for
                // parameter substitution.
-               StringResourceModel stringResourceModel = new 
StringResourceModel("label.current.locale",
-                       this, null, getLocale());
+               PropertyResourceModel stringResourceModel = new 
PropertyResourceModel(
+                       "label.current.locale", this, Model.of(getLocale()));
                add(new Label("resourceLabel", stringResourceModel));
 
                // and here we add a label that contains markup. Normally, this 
markup

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.properties
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.properties
index 9deb6b6..895c72a 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage.properties
@@ -12,4 +12,4 @@
 #  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.
-label.current.locale=Did you know your current browser''s locale is {0}?
+label.current.locale=Did you know your current browser's locale is ${}?

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_ko.properties
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_ko.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_ko.properties
index bf088ce..081dcde 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_ko.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_ko.properties
@@ -12,4 +12,4 @@
 #  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.
-label.current.locale=\ub2f9\uc2e0\uc774 \ud604\uc7ac \uc0ac\uc6a9\ud558\uace0 
\uacc4\uc2e0 \ube0c\ub77c\uc6b0\uc800\uc758 \ub85c\ucf00\uc77c\uc774 
{0}\uc784\uc744 \uc54c\uace0 \uacc4\uc168\uc2b5\ub2c8\uae4c?
+label.current.locale=\ub2f9\uc2e0\uc774 \ud604\uc7ac \uc0ac\uc6a9\ud558\uace0 
\uacc4\uc2e0 \ube0c\ub77c\uc6b0\uc800\uc758 \ub85c\ucf00\uc77c\uc774 
${}\uc784\uc744 \uc54c\uace0 \uacc4\uc168\uc2b5\ub2c8\uae4c?

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_no.properties
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_no.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_no.properties
index 9b72fcb..fea202f 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_no.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LabelPage_no.properties
@@ -12,4 +12,4 @@
 #  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.
-label.current.locale=Visste du at språket til nettleseren din i øyeblikket 
er {0}?
+label.current.locale=Visste du at språket til nettleseren din i øyeblikket 
er ${}?

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/pub/Home.java
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/pub/Home.java 
b/wicket-examples/src/main/java/org/apache/wicket/examples/pub/Home.java
index 7e1230c..b481213 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/pub/Home.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/pub/Home.java
@@ -23,7 +23,7 @@ import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.image.Image;
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.PackageResourceReference;
 import org.apache.wicket.util.value.ValueMap;
@@ -58,7 +58,7 @@ public final class Home extends WicketExamplePage
                // variable ${user} will be regconized as a property variable, 
and will
                // be substituted with the given model (the wrapped map). Hence,
                // ${user} will be replaced by map.get('user'), which is 
'Jonathan'.
-               StringResourceModel labelModel = new 
StringResourceModel("salutation", this,
+               PropertyResourceModel labelModel = new 
PropertyResourceModel("salutation", this,
                        new Model<>(map));
 
                // Add the label with the dynamic model

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/pub2/Home.java
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/pub2/Home.java 
b/wicket-examples/src/main/java/org/apache/wicket/examples/pub2/Home.java
index c65460f..31c1b8b 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/pub2/Home.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/pub2/Home.java
@@ -22,7 +22,7 @@ import org.apache.wicket.examples.WicketExamplePage;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.util.value.ValueMap;
 
@@ -54,7 +54,7 @@ public final class Home extends WicketExamplePage
                // variable ${user} will be regconized as a property variable, 
and will
                // be substituted with the given model (the wrapped map). Hence,
                // ${user} will be replaced by map.get('user'), which is 
'Jonathan'.
-               StringResourceModel labelModel = new 
StringResourceModel("salutation", this,
+               PropertyResourceModel labelModel = new 
PropertyResourceModel("salutation", this,
                        new Model<>(map));
 
                // Add the label with the dynamic model

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-examples/src/main/java/org/apache/wicket/examples/wizard/NewUserWizard.java
----------------------------------------------------------------------
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/wizard/NewUserWizard.java
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/wizard/NewUserWizard.java
index 139392e..e7d07f0 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/wizard/NewUserWizard.java
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/wizard/NewUserWizard.java
@@ -38,7 +38,7 @@ import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.validation.ValidationError;
 import org.apache.wicket.validation.validator.EmailAddressValidator;
 
@@ -68,8 +68,8 @@ public class NewUserWizard extends Wizard
                        super(true);
                        IModel<User> userModel = new Model<>(user);
                        setTitleModel(new ResourceModel("confirmation.title"));
-                       setSummaryModel(new 
StringResourceModel("confirmation.summary", this, userModel));
-                       setContentModel(new 
StringResourceModel("confirmation.content", this, userModel));
+                       setSummaryModel(new 
PropertyResourceModel("confirmation.summary", this, userModel));
+                       setContentModel(new 
PropertyResourceModel("confirmation.content", this, userModel));
                }
        }
 
@@ -84,7 +84,7 @@ public class NewUserWizard extends Wizard
                public UserDetailsStep()
                {
                        setTitleModel(new ResourceModel("confirmation.title"));
-                       setSummaryModel(new 
StringResourceModel("userdetails.summary", this, new Model<>(
+                       setSummaryModel(new 
PropertyResourceModel("userdetails.summary", this, new Model<>(
                                user)));
                        add(new RequiredTextField<>("user.firstName"));
                        add(new RequiredTextField<>("user.lastName"));
@@ -129,7 +129,7 @@ public class NewUserWizard extends Wizard
                public UserRolesStep()
                {
                        super(new ResourceModel("userroles.title"), null);
-                       setSummaryModel(new 
StringResourceModel("userroles.summary", this,
+                       setSummaryModel(new 
PropertyResourceModel("userroles.summary", this,
                                new Model<>(user)));
                        final ListMultipleChoice<String> rolesChoiceField = new 
ListMultipleChoice<>(
                                "user.roles", allRoles);

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
index f6d3f4b..e27076d 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadProgressBar.java
@@ -30,8 +30,7 @@ import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.upload.FileUploadField;
 import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.request.resource.CssResourceReference;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
@@ -233,7 +232,7 @@ public class UploadProgressBar extends Panel
 
                final String uploadFieldId = (uploadField == null) ? "" : 
uploadField.getMarkupId();
 
-               final String status = new 
StringResourceModel(RESOURCE_STARTING, this, (IModel<?>)null).getString();
+               final String status = new 
PropertyResourceModel(RESOURCE_STARTING, this, null).getString();
 
                CharSequence url = urlFor(ref, 
UploadStatusResource.newParameter(getPage().getId()));
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadStatusResource.java
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadStatusResource.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadStatusResource.java
index 92d9a4b..b0d8dd4 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadStatusResource.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/form/upload/UploadStatusResource.java
@@ -19,9 +19,8 @@ package 
org.apache.wicket.extensions.ajax.markup.html.form.upload;
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.Application;
-import org.apache.wicket.Component;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.protocol.http.servlet.MultipartServletWebRequestImpl;
 import org.apache.wicket.protocol.http.servlet.UploadInfo;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
@@ -103,7 +102,7 @@ class UploadStatusResource extends AbstractResource
                {
                        status = info.getPercentageComplete() +
                                "|" +
-                               new StringResourceModel(RESOURCE_STATUS, 
(Component)null, Model.of(info)).getString();
+ new PropertyResourceModel(RESOURCE_STATUS, null, Model.of(info)).getString();
                }
                return status;
        }

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/NavigatorLabel.java
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/NavigatorLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/NavigatorLabel.java
index 283118e..ef4799a 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/NavigatorLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/data/table/NavigatorLabel.java
@@ -17,11 +17,11 @@
 package org.apache.wicket.extensions.markup.html.repeater.data.table;
 
 
-import org.apache.wicket.util.io.IClusterable;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.navigation.paging.IPageableItems;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
+import org.apache.wicket.util.io.IClusterable;
 
 
 /**
@@ -46,7 +46,7 @@ public class NavigatorLabel extends Label
        public NavigatorLabel(final String id, final IPageableItems pageable)
        {
                super(id);
-               setDefaultModel(new StringResourceModel("NavigatorLabel", this,
+               setDefaultModel(new PropertyResourceModel("NavigatorLabel", 
this,
                        new Model<>(new LabelModelObject(pageable))));
        }
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.java
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.java
index 127a386..5961d10 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.java
@@ -16,6 +16,8 @@
  */
 package org.apache.wicket.extensions.rating;
 
+import java.util.Map;
+
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -29,12 +31,13 @@ import org.apache.wicket.markup.html.list.LoopItem;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.model.PropertyResourceModel;
 import org.apache.wicket.request.IRequestHandler;
 import 
org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
 import org.apache.wicket.request.resource.CssResourceReference;
 import org.apache.wicket.request.resource.PackageResourceReference;
 import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.collections.MiniMap;
 
 /**
  * Rating component that generates a number of stars where a user can click on 
to rate something.
@@ -358,18 +361,20 @@ public abstract class RatingPanel extends Panel
        protected Component newRatingLabel(final String id, final IModel<? 
extends Number> rating,
                final IModel<Integer> nrOfVotes)
        {
-               IModel<String> model;
+               String resourceKey;
+               Map<String, IModel<?>> models;
                if (nrOfVotes == null)
                {
-                       Object[] parameters = new Object[] { rating };
-                       model = new StringResourceModel("rating.simple", this, 
null, parameters);
+                       resourceKey = "rating.simple";
+                       models = MiniMap.<String, IModel<?>> of("rated", 
rating);
                }
                else
                {
-                       Object[] parameters = new Object[] { rating, nrOfVotes 
};
-                       model = new StringResourceModel("rating.complete", 
this, null, parameters);
+                       resourceKey = "rating.complete";
+                       models = MiniMap.<String, IModel<?>> of("rated", 
rating, "votes", nrOfVotes);
                }
-               return new Label(id, model);
+
+               return new Label(id, new PropertyResourceModel(resourceKey, 
this, models, null));
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.properties
index 5647d5b..b2b6d80 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=Rated {0,number,#.#}
-rating.complete=Rated {0,number,#.#} from {1,number,#} votes
+rating.simple=Rated ${rated}
+rating.complete=Rated ${rated} from ${votes} votes

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_da.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_da.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_da.properties
index 37efc75..6be4ee0 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_da.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_da.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.complete = Vurderet til {0,number,#.#} ud af {1,number,#} stemmer
-rating.simple   = Vurderet til {0,number,#.#}
+rating.complete = Vurderet til ${rated} ud af ${votes} stemmer
+rating.simple   = Vurderet til ${rated}

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_es.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_es.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_es.properties
index abc72ff..6633334 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_es.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_es.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=Valorado {0,number,#.#}
-rating.complete=Valorado {0,number,#.#} de {1,number,#} votos
+rating.simple=Valorado ${rated}
+rating.complete=Valorado ${rated} de ${votes} votos

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_fr.properties.xml
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_fr.properties.xml
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_fr.properties.xml
index 030b3bf..11a8dc0 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_fr.properties.xml
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_fr.properties.xml
@@ -17,6 +17,6 @@
 -->
 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd";>
 <properties>
-    <entry key="rating.complete">Noté {0,number,#.#} avec {1,number,#} 
votes</entry>
-    <entry key="rating.simple">Noté {0,number,#.#}</entry>
+    <entry key="rating.complete">Noté ${rated} avec ${votes} votes</entry>
+    <entry key="rating.simple">Noté ${rated}</entry>
 </properties>

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_hu.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_hu.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_hu.properties
index 1b23dcf..0c6fc90 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_hu.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_hu.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=Oszt\u00E1lyzat\: {0,number,\#.\#}
-rating.complete=Oszt\u00E1lyzat\: {0,number,\#.\#}, {1,number,\#} szavazat 
alapj\u00E1n
+rating.simple=Oszt\u00E1lyzat\: ${rated}
+rating.complete=Oszt\u00E1lyzat\: ${rated}, ${votes} szavazat alapj\u00E1n

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ko.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ko.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ko.properties
index b0f6b2c..f86fbbf 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ko.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ko.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=\ub4f1\uae09 {0,number,#.#}
-rating.complete=\ub4f1\uae09 {0,number,#.#} (\ud22c\ud45c\uc218 {1,number,#})
+rating.simple=\ub4f1\uae09 ${rated}
+rating.complete=\ub4f1\uae09 ${rated} (\ud22c\ud45c\uc218 ${votes})

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_nl.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_nl.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_nl.properties
index 7e12dde..98c4e44 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_nl.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_nl.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=Waardering {0,number,#.#}
-rating.complete=Waardering {0,number,#.#} uit {1,number,#} stemmen
+rating.simple=Waardering ${rated}
+rating.complete=Waardering ${rated} uit ${votes} stemmen

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_no.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_no.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_no.properties
index df2147e..79c7cb8 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_no.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_no.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=KÃ¥ret {0,number,#.#}
-rating.complete=KÃ¥ret {0,number,#.#} fra {1,number,#} stemmer
+rating.simple=KÃ¥ret ${rated}
+rating.complete=KÃ¥ret ${rated} fra ${votes} stemmer

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_pl.properties
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_pl.properties
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_pl.properties
index e46ecf7..2db7484 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_pl.properties
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_pl.properties
@@ -12,5 +12,5 @@
 #  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.
-rating.simple=Ocena {0,number,#.#}
-rating.complete=Ocena {0,number,#.#} z {1,number,#} g\u0142os\u00f3w
+rating.simple=Ocena ${rated}
+rating.complete=Ocena ${rated} z ${votes} g\u0142os\u00f3w

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ru.properties.xml
----------------------------------------------------------------------
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ru.properties.xml
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ru.properties.xml
index 47637a9..c962f03 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ru.properties.xml
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/rating/RatingPanel_ru.properties.xml
@@ -17,6 +17,6 @@
 -->
 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd";>
 <properties>
-       <entry key="rating.simple">Уровень оценки 
{0,number,#.#}</entry>
-       <entry key="rating.complete">Уровень оценки {0,number,#.#} 
из {1,number,#} голос(ов)(а)</entry>
+       <entry key="rating.simple">Уровень оценки ${rated}</entry>
+       <entry key="rating.complete">Уровень оценки ${rated} из 
${votes} голос(ов)(а)</entry>
 </properties>

http://git-wip-us.apache.org/repos/asf/wicket/blob/fd6ed4d1/wicket-util/src/main/java/org/apache/wicket/util/collections/MiniMap.java
----------------------------------------------------------------------
diff --git 
a/wicket-util/src/main/java/org/apache/wicket/util/collections/MiniMap.java 
b/wicket-util/src/main/java/org/apache/wicket/util/collections/MiniMap.java
index c931466..8a3484f 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/collections/MiniMap.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/collections/MiniMap.java
@@ -540,4 +540,108 @@ public class MiniMap<K, V> implements Map<K, V>, 
Serializable
 
                return -1;
        }
+
+       /**
+        * A map with two entries.
+        * 
+        * @param key
+        *            key of entry
+        * @param value
+        *            value of entry
+        * @return map
+        */
+       public static <K, V> Map<K, V> of(K key, V value)
+       {
+               MiniMap<K, V> map = new MiniMap<>(1);
+
+               map.put(key, value);
+
+               return map;
+       }
+
+       /**
+        * A map with two entries.
+        * 
+        * @param key1
+        *            key of first entry
+        * @param value1
+        *            value of first entry
+        * @param key2
+        *            key of second entry
+        * @param value2
+        *            value of second entry
+        * @return map
+        */
+       public static <K, V> Map<K, V> of(K key1, V value1, K key2, V value2)
+       {
+               MiniMap<K, V> map = new MiniMap<>(2);
+
+               map.put(key1, value1);
+               map.put(key2, value2);
+
+               return map;
+       }
+
+       /**
+        * A map with two entries.
+        * 
+        * @param key1
+        *            key of first entry
+        * @param value1
+        *            value of first entry
+        * @param key2
+        *            key of second entry
+        * @param value2
+        *            value of second entry
+        * @param key3
+        *            key of third entry
+        * @param value3
+        *            value of third entry
+        * @return map
+        */
+       public static <K, V> Map<K, V> of(K key1, V value1, K key2, V value2, K 
key3, V value3)
+       {
+               MiniMap<K, V> map = new MiniMap<>(3);
+
+               map.put(key1, value1);
+               map.put(key2, value2);
+               map.put(key3, value3);
+
+               return map;
+       }
+
+       /**
+        * A map with two entries.
+        * 
+        * @param key1
+        *            key of first entry
+        * @param value1
+        *            value of first entry
+        * @param key2
+        *            key of second entry
+        * @param value2
+        *            value of second entry
+        * @param key3
+        *            key of third entry
+        * @param value3
+        *            value of third entry
+        * @param key4
+        *            key of third entry
+        * @param value4
+        *            value of third entry
+        * @return map
+        */
+       public static <K, V> Map<K, V> of(K key1, V value1, K key2, V value2, K 
key3, V value3, K key4,
+               V value4)
+       {
+               MiniMap<K, V> map = new MiniMap<>(4);
+
+               map.put(key1, value1);
+               map.put(key2, value2);
+               map.put(key3, value3);
+               map.put(key4, value4);
+
+               return map;
+       }
+
 }

Reply via email to