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

link-intersystems / lis-commons / #320

22 May 2024 05:03AM UTC coverage: 90.326% (+0.1%) from 90.223%
#320

push

renelink
Added documentation and refactored property predicates.

18 of 18 new or added lines in 5 files covered. (100.0%)

11 existing lines in 2 files now uncovered.

7712 of 8538 relevant lines covered (90.33%)

0.9 hits per line

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

92.5
/lis-commons-beans/src/main/java/com/link_intersystems/beans/BeanMapDecorator.java
1
package com.link_intersystems.beans;
2

3
import java.io.Serializable;
4
import java.util.*;
5
import java.util.function.Predicate;
6

7
import static java.util.Objects.requireNonNull;
8

9
/**
10
 * Decorates a bean as a Map.
11
 */
12
public class BeanMapDecorator extends AbstractMap<String, Object> implements Serializable {
13

14
    private static final long serialVersionUID = -6218854323681382593L;
15
    private Bean<?> bean;
16

17
    public BeanMapDecorator(Bean<?> bean) {
1✔
18
        this.bean = requireNonNull(bean);
1✔
19
    }
1✔
20

21
    public static Object indexSetter(int index, Object value) {
22
        return new IndexSetter(index, value);
1✔
23
    }
24

25
    private PropertyDescList getProperties() {
26
        BeanClass<?> beanClass = bean.getBeanClass();
1✔
27
        return beanClass.getProperties();
1✔
28
    }
29

30
    public int size() {
31
        return getProperties().size();
1✔
32
    }
33

34
    public boolean containsKey(Object key) {
35
        if (key == null) {
1✔
36
            return false;
1✔
37
        }
38
        String propertyName = key.toString();
1✔
39
        BeanClass<?> beanClass = bean.getBeanClass();
1✔
40
        return beanClass.getProperties().filter(PropertyDesc.NONE_INDEXED).containsProperty(propertyName);
1✔
41
    }
42

43
    public Object get(Object key) {
44
        if (key == null) {
1✔
45
            return null;
1✔
46
        }
47
        String propertyName = key.toString();
1✔
48

49

50
        PropertyDescList properties = getProperties();
1✔
51
        PropertyDesc propertyDesc = properties.getByName(propertyName);
1✔
52

53
        return get(propertyDesc);
1✔
54
    }
55

56
    private Object get(PropertyDesc propertyDesc) {
57
        if (propertyDesc == null) {
1✔
58
            return null;
1✔
59
        }
60
        BeanClass<?> beanClass = bean.getBeanClass();
1✔
61
        String propertyName = propertyDesc.getName();
1✔
62
        boolean isIndexedProperty = beanClass.getProperties().filter(PropertyDesc.INDEXED).containsProperty(propertyName);
1✔
63

64
        if (isIndexedProperty) {
1✔
65
            IndexedProperty property = (IndexedProperty) filteredByDesc(Property.INDEXED, propertyDesc);
1✔
66
            checkReadAccess(property);
1✔
67
            return new IndexedValue(property);
1✔
68
        } else {
69
            Property property = filteredByDesc(Property.NONE_INDEXED, propertyDesc);
1✔
70
            checkReadAccess(property);
1✔
71
            return property.getValue();
1✔
72
        }
73
    }
74

75
    private Property filteredByDesc(Predicate<? super Property> predicate, PropertyDesc propertyDesc) {
76
        return bean.getProperties().filter(predicate).getByDesc(propertyDesc);
1✔
77
    }
78

79
    /**
80
     * @see BeanMapDecorator#indexSetter(int, Object) to set indexed property values.
81
     */
82
    public Object put(String key, Object value) {
83
        if (key == null) {
1✔
84
            throw new IllegalArgumentException(
1✔
85
                    "BeanMapDecorator does not support putting 'null' keys, because a bean can never have a 'null' property.");
86
        }
87
        BeanClass<?> beanClass = bean.getBeanClass();
1✔
88
        PropertyDescList properties = beanClass.getProperties();
1✔
89
        PropertyDesc propertyDesc = properties.getByName(key);
1✔
90

91
        if (propertyDesc == null) {
1✔
92
            throw new NoSuchPropertyException(
1✔
93
                    bean.getBeanClass().getType(),
1✔
94
                    key);
95
        }
96

97
        return put(propertyDesc, value);
1✔
98
    }
99

100
    private Object put(PropertyDesc propertyDesc, Object value) {
101
        Object previousValue = null;
1✔
102

103
        if (propertyDesc instanceof IndexedPropertyDesc) {
1✔
104
            if (!(value instanceof IndexSetter)) {
1✔
105
                throw new IllegalArgumentException(
1✔
106
                        "Property named "
107
                                + propertyDesc.getName()
1✔
108
                                + " is an indexed property. To set an indexed property's value you must use "
109
                                + IndexSetter.class.getSimpleName() + " to wrap the value.");
1✔
110
            }
111
            IndexSetter indexedValueSet = IndexSetter.class.cast(value);
1✔
112
            IndexedProperty indexedProperty = (IndexedProperty) filteredByDesc(Property.INDEXED, propertyDesc);
1✔
113
            checkWriteAccess(indexedProperty);
1✔
114
            Object element = indexedValueSet.getElement();
1✔
115
            indexedProperty.setValue(indexedValueSet.getIndex(), element);
1✔
116
        } else {
1✔
117
            Property property = filteredByDesc(Property.NONE_INDEXED, propertyDesc);
1✔
118
            checkWriteAccess(property);
1✔
119
            previousValue = getValueIfReadable(propertyDesc.getName());
1✔
120
            property.setValue(value);
1✔
121

122
        }
123

124
        return previousValue;
1✔
125
    }
126

127
    private Object getValueIfReadable(String propertyName) {
128
        Property property = bean.getProperties().getByName(propertyName);
1✔
129
        if (property.getPropertyDesc().isReadable()) {
1✔
130
            return property.getValue();
1✔
131
        }
UNCOV
132
        return null;
×
133
    }
134

135
    private void checkWriteAccess(IndexedProperty property) {
136
        if (!property.getPropertyDesc().isIndexedWritable()) {
1✔
UNCOV
137
            throw new UnsupportedOperationException(
×
138
                    "BeanMapDecorator can not put property "
139
                            + property
140
                            + ", because the indexed property has no indexed "
141
                            + "setter method, e.g. void setter(int index, PropertyType value); ");
142
        }
143
    }
1✔
144

145
    private void checkWriteAccess(Property property) {
146
        if (!property.getPropertyDesc().isWritable()) {
1✔
UNCOV
147
            throw new UnsupportedOperationException(
×
148
                    "BeanMapDecorator can not put property " + property
149
                            + ", because the property is not writable");
150
        }
151
    }
1✔
152

153
    private void checkReadAccess(IndexedProperty property) {
154
        if (!property.getPropertyDesc().isIndexedReadable()) {
1✔
UNCOV
155
            throw new UnsupportedOperationException(
×
156
                    "BeanMapDecorator can not get property "
157
                            + property
158
                            + ", because the indexed property has no indexed "
159
                            + "getter method, e.g. PropertyType getter(int index);");
160
        }
161
    }
1✔
162

163
    private void checkReadAccess(Property property) {
164
        if (!property.getPropertyDesc().isReadable()) {
1✔
UNCOV
165
            throw new UnsupportedOperationException(
×
166
                    "BeanMapDecorator can not get property " + property
167
                            + ", because the property is not readable");
168
        }
169
    }
1✔
170

171
    public Object remove(Object key) {
UNCOV
172
        throw new UnsupportedOperationException(
×
173
                "BeanMapDecorator does not support remove");
174
    }
175

176
    /**
177
     * {@inheritDoc}
178
     *
179
     * <p>
180
     * This implementation iterates over the specified map's <tt>entrySet()</tt>
181
     * collection, and calls this map's <tt>put</tt> operation once for each
182
     * entry returned by the iteration.
183
     *
184
     * <p>
185
     * Note that this implementation throws an
186
     * <tt>UnsupportedOperationException</tt> if this map does not support the
187
     * <tt>put</tt> operation and the specified map is nonempty.
188
     *
189
     * @throws UnsupportedOperationException {@inheritDoc}
190
     * @throws ClassCastException            {@inheritDoc}
191
     * @throws NullPointerException          {@inheritDoc}
192
     * @throws IllegalArgumentException      {@inheritDoc}
193
     */
194
    public void putAll(Map<? extends String, ? extends Object> m) {
195
        for (Map.Entry<? extends String, ? extends Object> e : m.entrySet())
1✔
196
            put(e.getKey(), e.getValue());
1✔
197
    }
1✔
198

199
    public void clear() {
UNCOV
200
        throw new UnsupportedOperationException(
×
201
                "A BeanMapDecorator can not be cleared, because"
202
                        + " properties of a bean belong to the bean's"
203
                        + " class and therefore can not be removed at runtime.");
204
    }
205

206
    public Set<String> keySet() {
207
        BeanClass<?> beanClass = bean.getBeanClass();
1✔
208
        PropertyDescList allProperties = beanClass.getProperties();
1✔
209
        return new LinkedHashSet<>(allProperties.getPropertyNames());
1✔
210
    }
211

212
    public Collection<Object> values() {
213
        Collection<Object> values = new AbstractCollection<Object>() {
1✔
214

215
            @Override
216
            public Iterator<Object> iterator() {
217
                Iterator<PropertyDesc> readablePropertyDescs = bean
1✔
218
                        .getBeanClass()
1✔
219
                        .getProperties().stream()
1✔
220
                        .filter(PropertyDesc::isReadable)
1✔
221
                        .iterator();
1✔
222

223
                return new Iterator<Object>() {
1✔
224

225
                    public boolean hasNext() {
226
                        return readablePropertyDescs.hasNext();
1✔
227
                    }
228

229
                    public Object next() {
230
                        PropertyDesc propertyDesc = readablePropertyDescs.next();
1✔
231
                        if (propertyDesc instanceof IndexedPropertyDesc) {
1✔
232
                            IndexedProperty indexedProperty = (IndexedProperty) filteredByDesc(Property.INDEXED, propertyDesc);
1✔
233
                            return new IndexedValue(indexedProperty);
1✔
234
                        } else {
235
                            return propertyDesc.getPropertyValue(bean.getBeanObject());
1✔
236
                        }
237
                    }
238

239
                    public void remove() {
UNCOV
240
                        throw new UnsupportedOperationException("Bean properties can not be removed.");
×
241
                    }
242
                };
243
            }
244

245
            @Override
246
            public int size() {
247
                return bean.getBeanClass().getProperties().getPropertyNames().size();
1✔
248
            }
249
        };
250
        return values;
1✔
251
    }
252

253
    public Set<java.util.Map.Entry<String, Object>> entrySet() {
254
        Set<java.util.Map.Entry<String, Object>> entries = new AbstractSet<Map.Entry<String, Object>>() {
1✔
255

256
            @Override
257
            public Iterator<java.util.Map.Entry<String, Object>> iterator() {
258
                Iterator<PropertyDesc> propertyDescIterator = bean.getBeanClass().getProperties().iterator();
1✔
259

260
                return new Iterator<Map.Entry<String, Object>>() {
1✔
261

262

263
                    public boolean hasNext() {
264
                        return propertyDescIterator.hasNext();
1✔
265
                    }
266

267
                    public java.util.Map.Entry<String, Object> next() {
268
                        PropertyDesc propertyDesc = propertyDescIterator.next();
1✔
269

270
                        return new Map.Entry<String, Object>() {
1✔
271

272
                            public Object setValue(Object value) {
273
                                return put(propertyDesc, value);
1✔
274
                            }
275

276
                            public Object getValue() {
277
                                return get(propertyDesc);
1✔
278
                            }
279

280
                            public String getKey() {
281
                                return propertyDesc.getName();
1✔
282
                            }
283
                        };
284
                    }
285

286
                    public void remove() {
UNCOV
287
                        throw new UnsupportedOperationException();
×
288
                    }
289
                };
290
            }
291

292
            @Override
293
            public int size() {
294
                return BeanMapDecorator.this.size();
1✔
295
            }
296
        };
297
        return entries;
1✔
298
    }
299

300
    public boolean containsValue(Object value) {
301
        return values().contains(value);
1✔
302
    }
303

304
    public static final class IndexedValue {
305

306
        private final IndexedProperty indexedProperty;
307

308
        private IndexedValue(IndexedProperty indexedProperty) {
1✔
309
            this.indexedProperty = indexedProperty;
1✔
310
        }
1✔
311

312
        public Object getElement(int index) {
313
            return indexedProperty.getValue(index);
1✔
314
        }
315

316
    }
317

318
    /**
319
     * Use {@link BeanMapDecorator#indexSetter(int, Object)} to create an instance.
320
     */
321
    public static final class IndexSetter {
322

323
        private final int index;
324
        private final Object element;
325

326
        private IndexSetter(int index, Object element) {
1✔
327
            this.index = index;
1✔
328
            this.element = element;
1✔
329
        }
1✔
330

331
        public int getIndex() {
332
            return index;
1✔
333
        }
334

335
        public Object getElement() {
336
            return element;
1✔
337
        }
338

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

© 2026 Coveralls, Inc