/* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * 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/LICENSE2.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 com.sebastian_daschner.jaxrs_analyzer.analysis.results; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeIdentifier; import com.sebastian_daschner.jaxrs_analyzer.model.rest.TypeRepresentation; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonValue; import java.util.HashMap; import java.util.Map; /** * Analyzes {@code JsonValue}s to derive the actual JSON representations. * Equal JSON structures (i.e. objects or arrays) will result in the same dynamically identified representation. * * @author Sebastian Daschner */ class DynamicTypeAnalyzer { /** * The type representation storage where all analyzed types have to be added. This will be created by the caller. */ private final Map<TypeIdentifier, TypeRepresentation> typeRepresentations; DynamicTypeAnalyzer(final Map<TypeIdentifier, TypeRepresentation> typeRepresentations) { this.typeRepresentations = typeRepresentations; } /** * Analyzes the given JSON value. * * @param jsonValue The JSON value to analyze * @return The type identifier */ TypeIdentifier analyze(final JsonValue jsonValue) { return analyzeInternal(jsonValue); } private TypeIdentifier analyzeInternal(final JsonValue jsonValue) { switch (jsonValue.getValueType()) { case ARRAY: return analyzeInternal((JsonArray) jsonValue); case OBJECT: return analyzeInternal((JsonObject) jsonValue); case STRING: return TypeIdentifier.ofType(Types.STRING); case NUMBER: return TypeIdentifier.ofType(Types.DOUBLE); case TRUE: case FALSE: return TypeIdentifier.ofType(Types.PRIMITIVE_BOOLEAN); case NULL: return TypeIdentifier.ofType(Types.OBJECT); default: throw new IllegalArgumentException("Unknown JSON value type provided"); } } private TypeIdentifier analyzeInternal(final JsonArray jsonArray) { final TypeIdentifier containedIdentifier = jsonArray.isEmpty() ? TypeIdentifier.ofType(Types.OBJECT) : analyzeInternal(jsonArray.get(0)); final TypeRepresentation containedRepresentation = typeRepresentations.getOrDefault(containedIdentifier, TypeRepresentation.ofConcrete(containedIdentifier)); final TypeIdentifier existingCollection = findExistingCollection(containedRepresentation); if (existingCollection != null) { return existingCollection; } final TypeIdentifier identifier = TypeIdentifier.ofDynamic(); typeRepresentations.put(identifier, TypeRepresentation.ofCollection(identifier, containedRepresentation)); return identifier; } private TypeIdentifier analyzeInternal(final JsonObject jsonObject) { final HashMap<String, TypeIdentifier> properties = jsonObject.entrySet().stream() .collect(HashMap::new, (m, v) -> m.put(v.getKey(), analyze(v.getValue())), Map::putAll); final TypeIdentifier existing = findExistingType(properties); if (existing != null) return existing; final TypeIdentifier identifier = TypeIdentifier.ofDynamic(); typeRepresentations.put(identifier, TypeRepresentation.ofConcrete(identifier, properties)); return identifier; } private TypeIdentifier findExistingCollection(final TypeRepresentation containedRepresentation) { return typeRepresentations.entrySet().stream().filter(e -> e.getValue() instanceof TypeRepresentation.CollectionTypeRepresentation) .filter(e -> e.getKey().getType().equals(Types.JSON)) .filter(e -> ((TypeRepresentation.CollectionTypeRepresentation) e.getValue()).contentEquals(containedRepresentation)) .map(Map.Entry::getKey).findAny().orElse(null); } private TypeIdentifier findExistingType(final HashMap<String, TypeIdentifier> properties) { return typeRepresentations.entrySet().stream().filter(e -> e.getValue() instanceof TypeRepresentation.ConcreteTypeRepresentation) .filter(e -> e.getKey().getType().equals(Types.JSON)) .filter(e -> ((TypeRepresentation.ConcreteTypeRepresentation) e.getValue()).contentEquals(properties)) .map(Map.Entry::getKey).findAny().orElse(null); } }