001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyrighted [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2015 ForgeRock AS.
015 */
016
017package org.forgerock.json;
018
019import java.io.File;
020import java.net.URI;
021import java.nio.charset.Charset;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.TreeSet;
030import java.util.UUID;
031import java.util.regex.Pattern;
032
033/**
034 * Subclass of {@link JsonValue} that checks all keys are accessed.
035 */
036public class JsonValueKeyAccessChecker extends JsonValue {
037
038    private final JsonValue delegate;
039    private final Set<String> accessedKeyNames = new HashSet<>();
040    private final Map<JsonPointer, JsonValueKeyAccessChecker> subCheckers = new HashMap<>();
041
042    /**
043     * Constructs a {@link JsonValueKeyAccessChecker}.
044     *
045     * @param delegate The delegate to check
046     */
047    public JsonValueKeyAccessChecker(JsonValue delegate) {
048        super(null);
049        this.delegate = delegate;
050        if (delegate != null) {
051            for (JsonValue value : delegate) {
052                wrap(value);
053            }
054        }
055    }
056
057    private JsonValue access(JsonValue val) {
058        String keyName = val.getPointer().leaf();
059        if (keyName != null) {
060            accessedKeyNames.add(keyName);
061        }
062        return val;
063    }
064
065    private JsonValue wrap(JsonValue val) {
066        if (val == this.delegate) {
067            return this;
068        } else if (val.isMap() || val.isList()) {
069            // wrap the list and map to ensure the key access check is propagated
070            JsonValueKeyAccessChecker checker = subCheckers.get(val.getPointer());
071            if (checker == null) {
072                checker = new JsonValueKeyAccessChecker(val);
073                subCheckers.put(val.getPointer(), checker);
074            }
075            return checker;
076        }
077        // there is no key access to check, just return the value itself
078        return val;
079    }
080
081    /** {@inheritDoc} */
082    @Override
083    public JsonValue recordKeyAccesses() {
084        return this;
085    }
086
087    /** {@inheritDoc} */
088    @Override
089    public void verifyAllKeysAccessed() {
090        StringBuilder errors = new StringBuilder();
091        verifyAllKeysAccessed(errors);
092        if (errors.length() > 0) {
093            throw new JsonException(errors.toString());
094        }
095    }
096
097    private void verifyAllKeysAccessed(StringBuilder errors) {
098        final Set<String> unaccessedKeys = isList() ? Collections.<String>emptySet()
099                            : new TreeSet<>(this.delegate.keys());
100        unaccessedKeys.removeAll(this.accessedKeyNames);
101        if (!unaccessedKeys.isEmpty()) {
102            if (errors.length() > 0) {
103                errors.append("\n");
104            }
105            errors.append(getPointer()).append(": ").append("Unused keys: " + unaccessedKeys);
106        }
107
108        for (JsonValueKeyAccessChecker subChecker : this.subCheckers.values()) {
109            // do not report unaccessed keys located under an already reported unaccessed key
110            if (!unaccessedKeys.contains(subChecker.getPointer().leaf())) {
111                subChecker.verifyAllKeysAccessed(errors);
112            }
113        }
114    }
115
116    /** {@inheritDoc} */
117    @Override
118    public JsonValue add(final int index, final Object object) {
119        throw new UnsupportedOperationException();
120    }
121
122    /** {@inheritDoc} */
123    @Override
124    public JsonValue add(final JsonPointer pointer, final Object object) {
125        throw new UnsupportedOperationException();
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    public JsonValue add(final Object object) {
131        throw new UnsupportedOperationException();
132    }
133
134    /** {@inheritDoc} */
135    @Override
136    public JsonValue add(final String key, final Object object) {
137        throw new UnsupportedOperationException();
138    }
139
140    /** {@inheritDoc} */
141    @Override
142    public JsonValue addPermissive(final JsonPointer pointer, final Object object) {
143        throw new UnsupportedOperationException();
144    }
145
146    /** {@inheritDoc} */
147    @Override
148    public void applyTransformers() {
149        throw new UnsupportedOperationException();
150    }
151
152    /** {@inheritDoc} */
153    @Override
154    public Boolean asBoolean() {
155        return this.delegate.asBoolean();
156    }
157
158    /** {@inheritDoc} */
159    @Override
160    public Charset asCharset() {
161        return this.delegate.asCharset();
162    }
163
164    /** {@inheritDoc} */
165    @Override
166    public Double asDouble() {
167        return this.delegate.asDouble();
168    }
169
170    /** {@inheritDoc} */
171    @Override
172    public <T extends Enum<T>> T asEnum(final Class<T> type) {
173        return this.delegate.asEnum(type);
174    }
175
176    /** {@inheritDoc} */
177    @Override
178    public File asFile() {
179        return this.delegate.asFile();
180    }
181
182    /** {@inheritDoc} */
183    @Override
184    public Integer asInteger() {
185        return this.delegate.asInteger();
186    }
187
188    /** {@inheritDoc} */
189    @Override
190    public List<Object> asList() {
191        return this.delegate.asList();
192    }
193
194    /** {@inheritDoc} */
195    @Override
196    public <E> List<E> asList(final Class<E> type) {
197        return this.delegate.asList(type);
198    }
199
200    /** {@inheritDoc} */
201    @Override
202    public Long asLong() {
203        return this.delegate.asLong();
204    }
205
206    /** {@inheritDoc} */
207    @Override
208    public Map<String, Object> asMap() {
209        return this.delegate.asMap();
210    }
211
212    /** {@inheritDoc} */
213    @Override
214    public Number asNumber() {
215        return this.delegate.asNumber();
216    }
217
218    /** {@inheritDoc} */
219    @Override
220    public Pattern asPattern() {
221        return this.delegate.asPattern();
222    }
223
224    /** {@inheritDoc} */
225    @Override
226    public JsonPointer asPointer() {
227        return this.delegate.asPointer();
228    }
229
230    /** {@inheritDoc} */
231    @Override
232    public String asString() {
233        return this.delegate.asString();
234    }
235
236    /** {@inheritDoc} */
237    @Override
238    public URI asURI() {
239        return this.delegate.asURI();
240    }
241
242    /** {@inheritDoc} */
243    @Override
244    public UUID asUUID() {
245        return this.delegate.asUUID();
246    }
247
248    /** {@inheritDoc} */
249    @Override
250    public void clear() {
251        throw new UnsupportedOperationException();
252    }
253
254    /** {@inheritDoc} */
255    @Override
256    public JsonValue clone() {
257        return wrap(this.delegate.clone());
258    }
259
260    /** {@inheritDoc} */
261    @Override
262    public boolean contains(final Object object) {
263        return this.delegate.contains(object);
264    }
265
266    /** {@inheritDoc} */
267    @Override
268    public JsonValue copy() {
269        return wrap(this.delegate.copy());
270    }
271
272    /** {@inheritDoc} */
273    @Override
274    public JsonValue defaultTo(final Object object) {
275        return wrap(this.delegate.defaultTo(object));
276    }
277
278    /** {@inheritDoc} */
279    @Override
280    public JsonValue expect(final Class<?> type) {
281        return wrap(this.delegate.expect(type));
282    }
283
284    /** {@inheritDoc} */
285    @Override
286    public JsonValue get(final int index) {
287        return wrap(access(this.delegate.get(index)));
288    }
289
290    /** {@inheritDoc} */
291    @Override
292    public JsonValue get(final JsonPointer pointer) {
293        return wrap(access(this.delegate.get(pointer)));
294    }
295
296    /** {@inheritDoc} */
297    @Override
298    public JsonValue get(final String key) {
299        return wrap(access(this.delegate.get(key)));
300    }
301
302    /** {@inheritDoc} */
303    @Override
304    public Object getObject() {
305        return this.delegate.getObject();
306    }
307
308    /** {@inheritDoc} */
309    @Override
310    public JsonPointer getPointer() {
311        return this.delegate.getPointer();
312    }
313
314    /** {@inheritDoc} */
315    @Override
316    public List<JsonTransformer> getTransformers() {
317        return this.delegate.getTransformers();
318    }
319
320    /** {@inheritDoc} */
321    @Override
322    public boolean isBoolean() {
323        return this.delegate.isBoolean();
324    }
325
326    /** {@inheritDoc} */
327    @Override
328    public boolean isDefined(final String key) {
329        return this.delegate.isDefined(key);
330    }
331
332    /** {@inheritDoc} */
333    @Override
334    public boolean isList() {
335        return this.delegate.isList();
336    }
337
338    /** {@inheritDoc} */
339    @Override
340    public boolean isMap() {
341        return this.delegate.isMap();
342    }
343
344    /** {@inheritDoc} */
345    @Override
346    public boolean isNull() {
347        return this.delegate.isNull();
348    }
349
350    /** {@inheritDoc} */
351    @Override
352    public boolean isNumber() {
353        return this.delegate.isNumber();
354    }
355
356    /** {@inheritDoc} */
357    @Override
358    public boolean isString() {
359        return this.delegate.isString();
360    }
361
362    /** {@inheritDoc} */
363    @Override
364    public Iterator<JsonValue> iterator() {
365        final Iterator<JsonValue> delegate = this.delegate.iterator();
366        return new Iterator<JsonValue>() {
367
368            @Override
369            public boolean hasNext() {
370                return delegate.hasNext();
371            }
372
373            @Override
374            public JsonValue next() {
375                return wrap(access(delegate.next()));
376            }
377
378            @Override
379            public void remove() {
380                throw new UnsupportedOperationException();
381            }
382        };
383    }
384
385    /** {@inheritDoc} */
386    @Override
387    public Set<String> keys() {
388        return this.delegate.keys();
389    }
390
391    /** {@inheritDoc} */
392    @Override
393    public JsonValue put(final int index, final Object object) {
394        throw new UnsupportedOperationException();
395    }
396
397    /** {@inheritDoc} */
398    @Override
399    public JsonValue put(final JsonPointer pointer, final Object object) {
400        throw new UnsupportedOperationException();
401    }
402
403    /** {@inheritDoc} */
404    @Override
405    public JsonValue put(final String key, final Object object) {
406        throw new UnsupportedOperationException();
407    }
408
409    /** {@inheritDoc} */
410    @Override
411    public JsonValue putPermissive(final JsonPointer pointer, final Object object) {
412        throw new UnsupportedOperationException();
413    }
414
415    /** {@inheritDoc} */
416    @Override
417    public void remove(final int index) {
418        throw new UnsupportedOperationException();
419    }
420
421    /** {@inheritDoc} */
422    @Override
423    public void remove(final JsonPointer pointer) {
424        throw new UnsupportedOperationException();
425    }
426
427    /** {@inheritDoc} */
428    @Override
429    public void remove(final String key) {
430        throw new UnsupportedOperationException();
431    }
432
433    /** {@inheritDoc} */
434    @Override
435    public JsonValue required() {
436        return wrap(this.delegate.required());
437    }
438
439    /** {@inheritDoc} */
440    @Override
441    public void setObject(final Object object) {
442        throw new UnsupportedOperationException();
443    }
444
445    /** {@inheritDoc} */
446    @Override
447    public int size() {
448        return this.delegate.size();
449    }
450
451    /** {@inheritDoc} */
452    @Override
453    public boolean equals(Object obj) {
454        return this.delegate.equals(obj);
455    }
456
457    /** {@inheritDoc} */
458    @Override
459    public int hashCode() {
460        return this.delegate.hashCode();
461    }
462
463    /** {@inheritDoc} */
464    @Override
465    public String toString() {
466        return this.delegate.toString();
467    }
468}