• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.34
today-context/src/main/java/infra/ui/ModelMap.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.ui;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.io.Serial;
23
import java.util.Collection;
24
import java.util.LinkedHashMap;
25
import java.util.Map;
26

27
import infra.core.Conventions;
28
import infra.lang.Assert;
29
import infra.util.StringUtils;
30

31
/**
32
 * Implementation of {@link java.util.Map} for use when building model data for use
33
 * with UI tools. Supports chained calls and generation of model attribute names.
34
 *
35
 * <p>This is an implementation class exposed to handler methods by Web MVC, typically via
36
 * a declaration of the {@link Model} interface. There is no need to
37
 * build it within user code; a plain {@link ModelMap} or even a just
38
 * a regular {@link Map} with String keys will be good enough to return a user model.
39
 *
40
 * @author Rob Harrop
41
 * @author Juergen Hoeller
42
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
43
 * @see Conventions#getVariableName
44
 * @since 4.0 2022/4/8 22:58
45
 */
46
public class ModelMap extends LinkedHashMap<String, Object> implements Model {
47

48
  @Serial
49
  private static final long serialVersionUID = 1L;
50

51
  /**
52
   * Construct a new, empty {@code ModelMap}.
53
   */
54
  public ModelMap() { }
3✔
55

56
  /**
57
   * Construct a new {@code ModelMap} containing the supplied attribute
58
   * under the supplied name.
59
   *
60
   * @see #addAttribute(String, Object)
61
   */
62
  public ModelMap(String attributeName, @Nullable Object attributeValue) {
2✔
63
    addAttribute(attributeName, attributeValue);
5✔
64
  }
1✔
65

66
  /**
67
   * Construct a new {@code ModelMap} containing the supplied attribute.
68
   * Uses attribute name generation to generate the key for the supplied model
69
   * object.
70
   *
71
   * @see #addAttribute(Object)
72
   */
73
  public ModelMap(Object attributeValue) {
2✔
74
    addAttribute(attributeValue);
4✔
75
  }
1✔
76

77
  /**
78
   * Add the supplied attribute under the supplied name.
79
   *
80
   * @param attributeName the name of the model attribute (never {@code null})
81
   * @param attributeValue the model attribute value (can be {@code null})
82
   */
83
  @Override
84
  public ModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
85
    Assert.notNull(attributeName, "Model attribute name is required");
3✔
86
    put(attributeName, attributeValue);
5✔
87
    return this;
2✔
88
  }
89

90
  /**
91
   * Add the supplied attribute to this {@code Map} using a
92
   * {@link Conventions#getVariableName generated name}.
93
   * <p><i>Note: Empty {@link Collection Collections} are not added to
94
   * the model when using this method because we cannot correctly determine
95
   * the true convention name. View code should check for {@code null} rather
96
   * than for empty collections as is already done by JSTL tags.</i>
97
   *
98
   * @param attributeValue the model attribute value (never {@code null})
99
   */
100
  @Override
101
  public ModelMap addAttribute(Object attributeValue) {
102
    Assert.notNull(attributeValue, "Model object is required");
3✔
103
    if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) {
7!
104
      return this;
2✔
105
    }
106
    return addAttribute(Conventions.getVariableName(attributeValue), attributeValue);
6✔
107
  }
108

109
  /**
110
   * Copy all attributes in the supplied {@code Collection} into this
111
   * {@code Map}, using attribute name generation for each element.
112
   *
113
   * @see #addAttribute(Object)
114
   */
115
  @Override
116
  public ModelMap addAllAttributes(@Nullable Collection<?> attributeValues) {
117
    if (attributeValues != null) {
2✔
118
      for (Object attributeValue : attributeValues) {
9✔
119
        addAttribute(attributeValue);
4✔
120
      }
1✔
121
    }
122
    return this;
2✔
123
  }
124

125
  /**
126
   * Copy all attributes in the supplied {@code Map} into this {@code Map}.
127
   *
128
   * @see #addAttribute(String, Object)
129
   */
130
  @Override
131
  public ModelMap addAllAttributes(@Nullable Map<String, ?> attributes) {
132
    if (attributes != null) {
2✔
133
      putAll(attributes);
3✔
134
    }
135
    return this;
2✔
136
  }
137

138
  /**
139
   * Copy all attributes in the supplied {@code Map} into this {@code Map},
140
   * with existing objects of the same name taking precedence (i.e. not getting
141
   * replaced).
142
   */
143
  @Override
144
  public ModelMap mergeAttributes(@Nullable Map<String, ?> attributes) {
145
    if (attributes != null) {
2✔
146
      for (Map.Entry<String, ?> entry : attributes.entrySet()) {
11✔
147
        String key = entry.getKey();
4✔
148
        if (!containsKey(key)) {
4✔
149
          put(key, entry.getValue());
6✔
150
        }
151
      }
1✔
152
    }
153
    return this;
2✔
154
  }
155

156
  /**
157
   * Does this model contain an attribute of the given name?
158
   *
159
   * @param attributeName the name of the model attribute (never {@code null})
160
   * @return whether this model contains a corresponding attribute
161
   */
162
  @Override
163
  public boolean containsAttribute(String attributeName) {
164
    return containsKey(attributeName);
4✔
165
  }
166

167
  /**
168
   * Return the attribute value for the given name, if any.
169
   *
170
   * @param attributeName the name of the model attribute (never {@code null})
171
   * @return the corresponding attribute value, or {@code null} if none
172
   */
173
  @Nullable
174
  @Override
175
  public Object getAttribute(String attributeName) {
176
    return get(attributeName);
4✔
177
  }
178

179
  @Override
180
  public void setAttribute(String name, @Nullable Object value) {
181
    put(name, value);
5✔
182
  }
1✔
183

184
  @Override
185
  public Object removeAttribute(String name) {
186
    return remove(name);
4✔
187
  }
188

189
  @Override
190
  public Map<String, Object> asMap() {
191
    return this;
2✔
192
  }
193

194
  @Override
195
  public String[] getAttributeNames() {
196
    return StringUtils.toStringArray(keySet());
×
197
  }
198

199
  @Override
200
  public Iterable<String> attributeNames() {
201
    return keySet();
×
202
  }
203

204
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc