Java tutorial
/** * Copyright 2018 Red Hat, Inc, and individual contributors. * * 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/LICENSE-2.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 org.wildfly.swarm.microprofile.openapi.api.util; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.eclipse.microprofile.openapi.models.Constructible; import org.eclipse.microprofile.openapi.models.Extensible; import org.eclipse.microprofile.openapi.models.Reference; import org.eclipse.microprofile.openapi.models.responses.APIResponses; import org.eclipse.microprofile.openapi.models.security.SecurityRequirement; import org.eclipse.microprofile.openapi.models.servers.Server; import org.eclipse.microprofile.openapi.models.tags.Tag; import org.wildfly.swarm.microprofile.openapi.api.models.OpenAPIImpl; /** * Used to merge two OAI data models into a single one. The MP+OAI 1.0 spec * requires that any or all of the various mechanisms for producing an OAI document * can be used. When more than one mechanism is used, each mechanism produces an * OpenAPI document. These multiple documents must then be sensibly merged into * a final result. * * @author eric.wittmann@gmail.com */ public class MergeUtil { private static final Set<String> EXCLUDED_PROPERTIES = new HashSet<>(); static { EXCLUDED_PROPERTIES.add("class"); } /** * Constructor. */ private MergeUtil() { } /** * Merges two documents and returns the result. * @param document1 * @param document2 */ public static final OpenAPIImpl merge(OpenAPIImpl document1, OpenAPIImpl document2) { return mergeObjects(document1, document2); } /** * Generic merge of two objects of the same type. * @param object1 * @param object2 */ @SuppressWarnings({ "rawtypes" }) public static <T> T mergeObjects(T object1, T object2) { if (object1 == null && object2 != null) { return object2; } if (object1 != null && object2 == null) { return object1; } if (object1 == null && object2 == null) { return null; } // It's uncommon, but in some cases (like Link Parameters or Examples) the values could // be different types. In this case, just take the 2nd one (the override). if (!object1.getClass().equals(object2.getClass())) { return object2; } PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(object1); for (PropertyDescriptor descriptor : descriptors) { if (EXCLUDED_PROPERTIES.contains(descriptor.getName())) { continue; } Class ptype = descriptor.getPropertyType(); if (Map.class.isAssignableFrom(ptype)) { try { Map values1 = (Map) descriptor.getReadMethod().invoke(object1); Map values2 = (Map) descriptor.getReadMethod().invoke(object2); Map newValues = mergeMaps(values1, values2); descriptor.getWriteMethod().invoke(object1, newValues); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } else if (List.class.isAssignableFrom(ptype)) { try { List values1 = (List) descriptor.getReadMethod().invoke(object1); List values2 = (List) descriptor.getReadMethod().invoke(object2); List newValues = mergeLists(values1, values2); descriptor.getWriteMethod().invoke(object1, newValues); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } else if (Constructible.class.isAssignableFrom(ptype)) { try { Object val1 = descriptor.getReadMethod().invoke(object1); Object val2 = descriptor.getReadMethod().invoke(object2); Object newValue = mergeObjects(val1, val2); if (newValue != null) { descriptor.getWriteMethod().invoke(object1, newValue); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } else { try { Object newValue = descriptor.getReadMethod().invoke(object2); if (newValue != null) { descriptor.getWriteMethod().invoke(object1, newValue); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e); } } } return object1; } /** * Merges two Maps. Any values missing from Map1 but present in Map2 will be added. If a value * is present in both maps, it will be overridden or merged. * @param values1 * @param values2 */ @SuppressWarnings({ "rawtypes", "unchecked" }) private static Map mergeMaps(Map values1, Map values2) { if (values1 == null && values2 == null) { return null; } if (values1 != null && values2 == null) { return values1; } if (values1 == null && values2 != null) { return values2; } for (Object key : values2.keySet()) { if (values1.containsKey(key)) { Object pval1 = values1.get(key); Object pval2 = values2.get(key); if (pval1 instanceof Map) { values1.put(key, mergeMaps((Map) pval1, (Map) pval2)); } else if (pval1 instanceof List) { values1.put(key, mergeLists((List) pval1, (List) pval2)); } else if (pval1 instanceof Constructible) { values1.put(key, mergeObjects(pval1, pval2)); } else { values1.put(key, pval2); } } else { Object pval2 = values2.get(key); values1.put(key, pval2); } } if (values1 instanceof Constructible) { if (values1 instanceof Reference) { Reference ref1 = (Reference) values1; Reference ref2 = (Reference) values2; if (ref2.getRef() != null) { ref1.setRef(ref2.getRef()); } } if (values1 instanceof Extensible) { Extensible extensible1 = (Extensible) values1; Extensible extensible2 = (Extensible) values2; extensible1.setExtensions(mergeMaps(extensible1.getExtensions(), extensible2.getExtensions())); } if (values1 instanceof APIResponses) { APIResponses responses1 = (APIResponses) values1; APIResponses responses2 = (APIResponses) values2; responses1.defaultValue(mergeObjects(responses1.getDefault(), responses2.getDefault())); } } return values1; } /** * Merges two Lists. Any values missing from List1 but present in List2 will be added. Depending on * the type of list, further processing and de-duping may be required. * @param values1 * @param values2 */ @SuppressWarnings({ "rawtypes", "unchecked" }) private static List mergeLists(List values1, List values2) { if (values1 == null && values2 == null) { return null; } if (values1 != null && values2 == null) { return values1; } if (values1 == null && values2 != null) { return values2; } if (values1.get(0) instanceof String) { return mergeStringLists(values1, values2); } if (values1.get(0) instanceof Tag) { return mergeTagLists(values1, values2); } if (values1.get(0) instanceof Server) { return mergeServerLists(values1, values2); } if (values1.get(0) instanceof SecurityRequirement) { return mergeSecurityRequirementLists(values1, values2); } values1.addAll(values2); return values1; } /** * Merge a list of strings. In all cases, string lists are really sets. So this is just * combining the two lists and then culling duplicates. * @param values1 * @param values2 */ private static List<String> mergeStringLists(List<String> values1, List<String> values2) { Set<String> set = new LinkedHashSet<String>(); set.addAll(values1); set.addAll(values2); return new ArrayList<String>(set); } /** * Merge two lists of Tags. Tags are a special case because they are named and you cannot * have two Tags with the same name. This will append any tags from values2 that don't * exist in values1. It will *merge* any tags found in values2 that already exist in * values1. * @param values1 * @param values2 */ private static List<Tag> mergeTagLists(List<Tag> values1, List<Tag> values2) { for (Tag value2 : values2) { Tag match = null; for (Tag value1 : values1) { if (value1.getName() != null && value1.getName().equals(value2.getName())) { match = value1; break; } } if (match == null) { values1.add(value2); } else { mergeObjects(match, value2); } } return values1; } /** * Merge two lists of Servers. Servers are a special case because they must be unique * by the 'url' property each must have. * @param values1 * @param values2 */ private static List<Server> mergeServerLists(List<Server> values1, List<Server> values2) { for (Server value2 : values2) { Server match = null; for (Server value1 : values1) { if (value1.getUrl() != null && value1.getUrl().equals(value2.getUrl())) { match = value1; break; } } if (match == null) { values1.add(value2); } else { mergeObjects(match, value2); } } return values1; } /** * Merge two lists of Security Requirements. Security Requirement lists are are a * special case because * values1. * @param values1 * @param values2 */ private static List<SecurityRequirement> mergeSecurityRequirementLists(List<SecurityRequirement> values1, List<SecurityRequirement> values2) { for (SecurityRequirement value2 : values2) { if (values1.contains(value2)) { continue; } values1.add(value2); } return values1; } }