001package gudusoft.gsqlparser.slpc.model; 002 003import java.math.BigInteger; 004import java.util.ArrayList; 005import java.util.Collections; 006import java.util.LinkedHashMap; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010 011/** 012 * Immutable JSON object used by the SLPC reference model. 013 * 014 * <p>The contract intentionally distinguishes an absent member from a member 015 * whose value is {@code null}. A generic immutable tree keeps that distinction 016 * and also lets the Java model follow the versioned JSON Schema without a 017 * second, drifting set of 150+ mutable DTO classes.</p> 018 */ 019public final class SlpcObject { 020 021 private final Map<String, Object> values; 022 023 private SlpcObject(Map<String, Object> source) { 024 LinkedHashMap<String, Object> copy = new LinkedHashMap<String, Object>(); 025 for (Map.Entry<String, Object> entry : source.entrySet()) { 026 if (entry.getKey() == null) { 027 throw new IllegalArgumentException("SLPC object member name must not be null"); 028 } 029 copy.put(entry.getKey(), freeze(entry.getValue())); 030 } 031 this.values = Collections.unmodifiableMap(copy); 032 } 033 034 public static Builder builder() { 035 return new Builder(); 036 } 037 038 public boolean contains(String name) { 039 return values.containsKey(name); 040 } 041 042 public Object get(String name) { 043 return values.get(name); 044 } 045 046 public String string(String name) { 047 Object value = values.get(name); 048 return value instanceof String ? (String) value : null; 049 } 050 051 public BigInteger integer(String name) { 052 Object value = values.get(name); 053 return value instanceof BigInteger ? (BigInteger) value : null; 054 } 055 056 public SlpcObject object(String name) { 057 Object value = values.get(name); 058 return value instanceof SlpcObject ? (SlpcObject) value : null; 059 } 060 061 public List<Object> array(String name) { 062 Object value = values.get(name); 063 if (!(value instanceof List<?>)) { 064 return null; 065 } 066 @SuppressWarnings("unchecked") 067 List<Object> result = (List<Object>) value; 068 return result; 069 } 070 071 public Set<String> names() { 072 return values.keySet(); 073 } 074 075 public Map<String, Object> asMap() { 076 return values; 077 } 078 079 public Builder toBuilder() { 080 Builder builder = new Builder(); 081 builder.values.putAll(values); 082 return builder; 083 } 084 085 @Override 086 public boolean equals(Object other) { 087 return other instanceof SlpcObject && values.equals(((SlpcObject) other).values); 088 } 089 090 @Override 091 public int hashCode() { 092 return values.hashCode(); 093 } 094 095 private static Object freeze(Object value) { 096 if (value == null || value instanceof String || value instanceof Boolean 097 || value instanceof BigInteger || value instanceof SlpcObject) { 098 return value; 099 } 100 if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { 101 return BigInteger.valueOf(((Number) value).longValue()); 102 } 103 if (value instanceof Map<?, ?>) { 104 Builder builder = builder(); 105 for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) { 106 if (!(entry.getKey() instanceof String)) { 107 throw new IllegalArgumentException("SLPC object member name must be a string"); 108 } 109 builder.put((String) entry.getKey(), entry.getValue()); 110 } 111 return builder.build(); 112 } 113 if (value instanceof Iterable<?>) { 114 ArrayList<Object> copy = new ArrayList<Object>(); 115 for (Object item : (Iterable<?>) value) { 116 copy.add(freeze(item)); 117 } 118 return Collections.unmodifiableList(copy); 119 } 120 throw new IllegalArgumentException("Unsupported SLPC value type: " + value.getClass().getName()); 121 } 122 123 public static final class Builder { 124 private final LinkedHashMap<String, Object> values = new LinkedHashMap<String, Object>(); 125 126 public Builder put(String name, Object value) { 127 if (name == null) { 128 throw new IllegalArgumentException("SLPC object member name must not be null"); 129 } 130 values.put(name, value); 131 return this; 132 } 133 134 /** Adds an explicitly present JSON null. */ 135 public Builder putNull(String name) { 136 return put(name, null); 137 } 138 139 /** Adds the member only when the value is non-null. */ 140 public Builder putIfPresent(String name, Object value) { 141 if (value != null) { 142 put(name, value); 143 } 144 return this; 145 } 146 147 public Builder remove(String name) { 148 values.remove(name); 149 return this; 150 } 151 152 public SlpcObject build() { 153 return new SlpcObject(values); 154 } 155 } 156}