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}