001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common; 019 020import com.google.common.collect.ImmutableMap; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026 027// TODO Move ObjectTreeSorter to a (TBD) dev.enola.common.testlib testonly module 028public final class ObjectTreeSorter { 029 private ObjectTreeSorter() {} 030 031 @SuppressWarnings("unchecked") 032 public static Object sortByKeyIfMap(Object object) { 033 if (object instanceof Map) { 034 var map = (Map<String, Object>) object; 035 return sortByKey(map); 036 } else if (object instanceof List list) { 037 // NB: We cannot use an ImmutableList here, because null is permitted! 038 var newList = new ArrayList<>(list.size()); 039 for (var element : list) { 040 var sorted = sortByKeyIfMap(element); 041 newList.add(sorted); 042 } 043 sortListByID(newList); 044 return newList; 045 } 046 return object; 047 } 048 049 private static Map<String, Object> sortByKey(Map<String, Object> map) { 050 List<String> keys = new ArrayList<>(map.keySet()); 051 Collections.sort(keys); 052 053 var newMap = ImmutableMap.<String, Object>builderWithExpectedSize(keys.size()); 054 for (var key : keys) { 055 newMap.put(key, sortByKeyIfMap(map.get(key))); 056 } 057 return newMap.build(); 058 } 059 060 private static void sortListByID(List<Object> list) { 061 Collections.sort( 062 list, 063 (o1, o2) -> { 064 // skipcq: JAVA-C1003 065 if (o1 instanceof Map m1 && o2 instanceof Map m2) { 066 var oid1 = m1.get("@id"); 067 var oid2 = m2.get("@id"); 068 // skipcq: JAVA-C1003 069 if (oid1 instanceof String id1 && oid2 instanceof String id2) 070 return id1.compareTo(id2); 071 return 0; 072 } else return 0; 073 }); 074 } 075}