001/* 002 * VM-Operator 003 * Copyright (C) 2023 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.vmoperator.util; 020 021import com.google.gson.JsonArray; 022import com.google.gson.JsonElement; 023import com.google.gson.JsonObject; 024import com.google.gson.JsonPrimitive; 025import java.math.BigInteger; 026import java.util.Collections; 027import java.util.List; 028import java.util.Optional; 029import java.util.function.Supplier; 030 031/** 032 * Utility class for pointing to elements on a Gson (Json) tree. 033 */ 034@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", 035 "PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal", "PMD.GodClass" }) 036public class GsonPtr { 037 038 private final JsonElement position; 039 040 private GsonPtr(JsonElement root) { 041 this.position = root; 042 } 043 044 /** 045 * Create a new instance pointing to the given element. 046 * 047 * @param root the root 048 * @return the Gson pointer 049 */ 050 @SuppressWarnings("PMD.ShortMethodName") 051 public static GsonPtr to(JsonElement root) { 052 return new GsonPtr(root); 053 } 054 055 /** 056 * Create a new instance pointing to the {@link JsonElement} 057 * selected by the given selectors. If a selector of type 058 * {@link String} denotes a non-existant member of a 059 * {@link JsonObject}, a new member (of type {@link JsonObject} 060 * is added. 061 * 062 * @param selectors the selectors 063 * @return the Gson pointer 064 */ 065 @SuppressWarnings({ "PMD.ShortMethodName", "PMD.PreserveStackTrace" }) 066 public GsonPtr to(Object... selectors) { 067 JsonElement element = position; 068 for (Object sel : selectors) { 069 if (element instanceof JsonObject obj 070 && sel instanceof String member) { 071 element = Optional.ofNullable(obj.get(member)).orElseGet(() -> { 072 @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") 073 var child = new JsonObject(); 074 obj.add(member, child); 075 return child; 076 }); 077 continue; 078 } 079 if (element instanceof JsonArray arr 080 && sel instanceof Integer index) { 081 try { 082 element = arr.get(index); 083 } catch (IndexOutOfBoundsException e) { 084 throw new IllegalStateException("Selected array index" 085 + " may not be empty."); 086 } 087 continue; 088 } 089 throw new IllegalStateException("Invalid selection"); 090 } 091 return new GsonPtr(element); 092 } 093 094 /** 095 * Returns {@link JsonElement} that the pointer points to. 096 * 097 * @return the result 098 */ 099 public JsonElement get() { 100 return position; 101 } 102 103 /** 104 * Returns {@link JsonElement} that the pointer points to, 105 * casted to the given type. 106 * 107 * @param <T> the generic type 108 * @param cls the cls 109 * @return the result 110 */ 111 @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" }) 112 public <T extends JsonElement> T get(Class<T> cls) { 113 if (cls.isAssignableFrom(position.getClass())) { 114 return cls.cast(position); 115 } 116 throw new IllegalArgumentException("Not positioned at element" 117 + " of desired type."); 118 } 119 120 /** 121 * Returns the selected {@link JsonElement}, cast to the class 122 * specified. 123 * 124 * @param <T> the generic type 125 * @param cls the cls 126 * @param selectors the selectors 127 * @return the optional 128 */ 129 @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" }) 130 public <T extends JsonElement> Optional<T> 131 get(Class<T> cls, Object... selectors) { 132 JsonElement element = position; 133 for (Object sel : selectors) { 134 if (element instanceof JsonObject obj 135 && sel instanceof String member) { 136 element = obj.get(member); 137 if (element == null) { 138 return Optional.empty(); 139 } 140 continue; 141 } 142 if (element instanceof JsonArray arr 143 && sel instanceof Integer index) { 144 try { 145 element = arr.get(index); 146 } catch (IndexOutOfBoundsException e) { 147 return Optional.empty(); 148 } 149 continue; 150 } 151 return Optional.empty(); 152 } 153 if (cls.isAssignableFrom(element.getClass())) { 154 return Optional.of(cls.cast(element)); 155 } 156 return Optional.empty(); 157 } 158 159 /** 160 * Returns the String value of the selected {@link JsonPrimitive}. 161 * 162 * @param selectors the selectors 163 * @return the as string 164 */ 165 public Optional<String> getAsString(Object... selectors) { 166 return get(JsonPrimitive.class, selectors) 167 .map(JsonPrimitive::getAsString); 168 } 169 170 /** 171 * Returns the Integer value of the selected {@link JsonPrimitive}. 172 * 173 * @param selectors the selectors 174 * @return the as string 175 */ 176 public Optional<Integer> getAsInt(Object... selectors) { 177 return get(JsonPrimitive.class, selectors) 178 .map(JsonPrimitive::getAsInt); 179 } 180 181 /** 182 * Returns the Integer value of the selected {@link JsonPrimitive}. 183 * 184 * @param selectors the selectors 185 * @return the as string 186 */ 187 public Optional<BigInteger> getAsBigInteger(Object... selectors) { 188 return get(JsonPrimitive.class, selectors) 189 .map(JsonPrimitive::getAsBigInteger); 190 } 191 192 /** 193 * Returns the Long value of the selected {@link JsonPrimitive}. 194 * 195 * @param selectors the selectors 196 * @return the as string 197 */ 198 public Optional<Long> getAsLong(Object... selectors) { 199 return get(JsonPrimitive.class, selectors) 200 .map(JsonPrimitive::getAsLong); 201 } 202 203 /** 204 * Returns the boolean value of the selected {@link JsonPrimitive}. 205 * 206 * @param selectors the selectors 207 * @return the boolean 208 */ 209 public Optional<Boolean> getAsBoolean(Object... selectors) { 210 return get(JsonPrimitive.class, selectors) 211 .map(JsonPrimitive::getAsBoolean); 212 } 213 214 /** 215 * Returns the elements of the selected {@link JsonArray} as list. 216 * 217 * @param <T> the generic type 218 * @param cls the cls 219 * @param selectors the selectors 220 * @return the list 221 */ 222 @SuppressWarnings("unchecked") 223 public <T extends JsonElement> List<T> getAsListOf(Class<T> cls, 224 Object... selectors) { 225 return get(JsonArray.class, selectors).map(a -> (List<T>) a.asList()) 226 .orElse(Collections.emptyList()); 227 } 228 229 /** 230 * Sets the selected value. This pointer must point to a 231 * {@link JsonObject} or {@link JsonArray}. The selector must 232 * be a {@link String} or an integer respectively. 233 * 234 * @param selector the selector 235 * @param value the value 236 * @return the Gson pointer 237 */ 238 public GsonPtr set(Object selector, JsonElement value) { 239 if (position instanceof JsonObject obj 240 && selector instanceof String member) { 241 obj.add(member, value); 242 return this; 243 } 244 if (position instanceof JsonArray arr 245 && selector instanceof Integer index) { 246 if (index >= arr.size()) { 247 arr.add(value); 248 } else { 249 arr.set(index, value); 250 } 251 return this; 252 } 253 throw new IllegalStateException("Invalid selection"); 254 } 255 256 /** 257 * Short for `set(selector, new JsonPrimitive(value))`. 258 * 259 * @param selector the selector 260 * @param value the value 261 * @return the gson ptr 262 * @see #set(Object, JsonElement) 263 */ 264 public GsonPtr set(Object selector, String value) { 265 return set(selector, new JsonPrimitive(value)); 266 } 267 268 /** 269 * Short for `set(selector, new JsonPrimitive(value))`. 270 * 271 * @param selector the selector 272 * @param value the value 273 * @return the gson ptr 274 * @see #set(Object, JsonElement) 275 */ 276 public GsonPtr set(Object selector, Long value) { 277 return set(selector, new JsonPrimitive(value)); 278 } 279 280 /** 281 * Short for `set(selector, new JsonPrimitive(value))`. 282 * 283 * @param selector the selector 284 * @param value the value 285 * @return the gson ptr 286 * @see #set(Object, JsonElement) 287 */ 288 public GsonPtr set(Object selector, BigInteger value) { 289 return set(selector, new JsonPrimitive(value)); 290 } 291 292 /** 293 * Same as {@link #set(Object, JsonElement)}, but sets the value 294 * only if it doesn't exist yet, else returns the existing value. 295 * If this pointer points to a {@link JsonArray} and the selector 296 * if larger than or equal to the size of the array, the supplied 297 * value will be appended. 298 * 299 * @param <T> the generic type 300 * @param selector the selector 301 * @param supplier the supplier of the missing value 302 * @return the existing or supplied value 303 */ 304 @SuppressWarnings("unchecked") 305 public <T extends JsonElement> T 306 computeIfAbsent(Object selector, Supplier<T> supplier) { 307 if (position instanceof JsonObject obj 308 && selector instanceof String member) { 309 return Optional.ofNullable((T) obj.get(member)).orElseGet(() -> { 310 var res = supplier.get(); 311 obj.add(member, res); 312 return res; 313 }); 314 } 315 if (position instanceof JsonArray arr 316 && selector instanceof Integer index) { 317 if (index >= arr.size()) { 318 var res = supplier.get(); 319 arr.add(res); 320 return res; 321 } 322 return (T) arr.get(index); 323 } 324 throw new IllegalStateException("Invalid selection"); 325 } 326 327 /** 328 * Short for `computeIfAbsent(selector, () -> new JsonPrimitive(value))`. 329 * 330 * @param selector the selector 331 * @param value the value 332 * @return the Gson pointer 333 */ 334 public GsonPtr getOrSet(Object selector, String value) { 335 computeIfAbsent(selector, () -> new JsonPrimitive(value)); 336 return this; 337 } 338 339}