001/* 002 * Configurate 003 * Copyright (C) zml and Configurate contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ninja.leaping.configurate; 018 019import com.google.common.collect.ImmutableList; 020import com.google.common.collect.ImmutableMap; 021import com.google.common.reflect.TypeParameter; 022import com.google.common.reflect.TypeToken; 023import ninja.leaping.configurate.objectmapping.ObjectMappingException; 024import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; 025import org.checkerframework.checker.nullness.qual.NonNull; 026import org.checkerframework.checker.nullness.qual.Nullable; 027 028import java.util.Collection; 029import java.util.Collections; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Objects; 034import java.util.function.Function; 035import java.util.function.Supplier; 036 037import static java.util.Objects.requireNonNull; 038 039/** 040 * Simple implementation of {@link ConfigurationNode}. 041 */ 042public class SimpleConfigurationNode implements ConfigurationNode { 043 044 /** 045 * The options determining the behaviour of this node 046 */ 047 @NonNull 048 private final ConfigurationOptions options; 049 050 /** 051 * If this node is attached to a wider configuration structure 052 */ 053 volatile boolean attached; 054 055 /** 056 * Path of this node. 057 * 058 * Internally, may only be modified when an operation that adds or removes a node at the same 059 * or higher level in the node tree 060 */ 061 @Nullable 062 volatile Object key; 063 064 /** 065 * The parent of this node 066 */ 067 @Nullable 068 private SimpleConfigurationNode parent; 069 070 /** 071 * The current value of this node 072 */ 073 @NonNull 074 private volatile ConfigValue value; 075 076 /** 077 * Create a new node with no parent and {@link ConfigurationOptions#defaults() default} options 078 * 079 * @return The newly created node 080 * @deprecated Use {@link ConfigurationNode#root()} instead 081 */ 082 @Deprecated 083 @NonNull 084 public static SimpleConfigurationNode root() { 085 return root(ConfigurationOptions.defaults()); 086 } 087 088 /** 089 * Create a new node with no parent and defined options 090 * 091 * @param options The options to use in this node. 092 * @return The newly created node 093 * @deprecated Use {@link ConfigurationNode#root(ConfigurationOptions)} instead 094 */ 095 @Deprecated 096 @NonNull 097 public static SimpleConfigurationNode root(@NonNull ConfigurationOptions options) { 098 return new SimpleConfigurationNode(null, null, options); 099 } 100 101 protected SimpleConfigurationNode(@Nullable Object key, @Nullable SimpleConfigurationNode parent, @NonNull ConfigurationOptions options) { 102 requireNonNull(options, "options"); 103 this.key = key; 104 this.options = options; 105 this.parent = parent; 106 this.value = NullConfigValue.instance(); 107 108 // if the parent is null, this node is a root node, and is therefore "attached" 109 if (parent == null) { 110 attached = true; 111 } 112 } 113 114 protected SimpleConfigurationNode(@Nullable SimpleConfigurationNode parent, SimpleConfigurationNode copyOf) { 115 this.options = copyOf.options; 116 this.attached = true; // copies are always attached 117 this.key = copyOf.key; 118 this.parent = parent; 119 this.value = copyOf.value.copy(this); 120 } 121 122 /** 123 * Handles the copying of applied defaults, if enabled. 124 * 125 * @param defValue the default value 126 * @param <V> the value type 127 * @return the same value 128 */ 129 private <V> V storeDefault(V defValue) { 130 if (defValue != null && getOptions().shouldCopyDefaults()) { 131 setValue(defValue); 132 } 133 return defValue; 134 } 135 136 private <V> V storeDefault(TypeToken<V> type, V defValue) throws ObjectMappingException { 137 if (defValue != null && getOptions().shouldCopyDefaults()) { 138 setValue(type, defValue); 139 } 140 return defValue; 141 } 142 143 @Override 144 public Object getValue(Object def) { 145 Object ret = value.getValue(); 146 return ret == null ? storeDefault(def) : ret; 147 } 148 149 @Override 150 public Object getValue(@NonNull Supplier<Object> defSupplier) { 151 Object ret = value.getValue(); 152 return ret == null ? storeDefault(defSupplier.get()) : ret; 153 } 154 155 @Override 156 public <T> T getValue(@NonNull Function<Object, T> transformer, T def) { 157 T ret = transformer.apply(getValue()); 158 return ret == null ? storeDefault(def) : ret; 159 } 160 161 @Override 162 public <T> T getValue(@NonNull Function<Object, T> transformer, @NonNull Supplier<T> defSupplier) { 163 T ret = transformer.apply(getValue()); 164 return ret == null ? storeDefault(defSupplier.get()) : ret; 165 } 166 167 @NonNull 168 @Override 169 public <T> List<T> getList(@NonNull Function<Object, T> transformer) { 170 final ImmutableList.Builder<T> ret = ImmutableList.builder(); 171 ConfigValue value = this.value; 172 if (value instanceof ListConfigValue) { 173 // transform each value individually if the node is a list 174 for (SimpleConfigurationNode o : value.iterateChildren()) { 175 T transformed = transformer.apply(o.getValue()); 176 if (transformed != null) { 177 ret.add(transformed); 178 } 179 } 180 } else { 181 // transfer the value as a whole 182 T transformed = transformer.apply(value.getValue()); 183 if (transformed != null) { 184 ret.add(transformed); 185 } 186 } 187 188 return ret.build(); 189 } 190 191 @Override 192 public <T> List<T> getList(@NonNull Function<Object, T> transformer, List<T> def) { 193 List<T> ret = getList(transformer); 194 return ret.isEmpty() ? storeDefault(def) : ret; 195 } 196 197 @Override 198 public <T> List<T> getList(@NonNull Function<Object, T> transformer, @NonNull Supplier<List<T>> defSupplier) { 199 List<T> ret = getList(transformer); 200 return ret.isEmpty() ? storeDefault(defSupplier.get()) : ret; 201 } 202 203 @Override 204 public <T> List<T> getList(@NonNull TypeToken<T> type, List<T> def) throws ObjectMappingException { 205 List<T> ret = getValue(new TypeToken<List<T>>() {} 206 .where(new TypeParameter<T>() {}, type), def); 207 return ret.isEmpty() ? storeDefault(def) : ret; 208 } 209 210 @Override 211 public <T> List<T> getList(@NonNull TypeToken<T> type, @NonNull Supplier<List<T>> defSupplier) throws ObjectMappingException { 212 List<T> ret = getValue(new TypeToken<List<T>>(){}.where(new TypeParameter<T>(){}, type), defSupplier); 213 return ret.isEmpty() ? storeDefault(defSupplier.get()) : ret; 214 } 215 216 @Override 217 @SuppressWarnings("unchecked") 218 public <T> T getValue(@NonNull TypeToken<T> type, T def) throws ObjectMappingException { 219 Object value = getValue(); 220 if (value == null) { 221 return storeDefault(type, def); 222 } 223 224 TypeSerializer<?> serial = getOptions().getSerializers().get(type); 225 if (serial == null) { 226 if (type.getRawType().isInstance(value)) { 227 return (T) type.getRawType().cast(value); 228 } else { 229 return storeDefault(type, def); 230 } 231 } 232 return (T) serial.deserialize(type, this); 233 } 234 235 @Override 236 @SuppressWarnings("unchecked") 237 public <T> T getValue(@NonNull TypeToken<T> type, @NonNull Supplier<T> defSupplier) throws ObjectMappingException { 238 Object value = getValue(); 239 if (value == null) { 240 return storeDefault(type, defSupplier.get()); 241 } 242 243 TypeSerializer<?> serial = getOptions().getSerializers().get(type); 244 if (serial == null) { 245 if (type.getRawType().isInstance(value)) { 246 return (T) type.getRawType().cast(value); 247 } else { 248 return storeDefault(type, defSupplier.get()); 249 } 250 } 251 return (T) serial.deserialize(type, this); 252 } 253 254 @NonNull 255 @Override 256 public SimpleConfigurationNode setValue(@Nullable Object newValue) { 257 // if the value to be set is a configuration node already, unwrap and store the raw data 258 if (newValue instanceof ConfigurationNode) { 259 ConfigurationNode newValueAsNode = (ConfigurationNode) newValue; 260 if (newValueAsNode == this) { // this would be a no-op whoop 261 return this; 262 } 263 264 if (newValueAsNode.isList()) { 265 // handle list 266 attachIfNecessary(); 267 ListConfigValue newList = new ListConfigValue(this); 268 synchronized (newValueAsNode) { 269 newList.setValue(newValueAsNode.getChildrenList()); 270 } 271 this.value = newList; 272 return this; 273 274 } else if (newValueAsNode.isMap()) { 275 // handle map 276 attachIfNecessary(); 277 MapConfigValue newMap = new MapConfigValue(this); 278 synchronized (newValueAsNode) { 279 newMap.setValue(newValueAsNode.getChildrenMap()); 280 } 281 this.value = newMap; 282 return this; 283 284 } else { 285 // handle scalar/null 286 newValue = newValueAsNode.getValue(); 287 } 288 } 289 290 // if the new value is null, handle detaching from this nodes parent 291 if (newValue == null) { 292 if (parent == null) { 293 clear(); 294 } else { 295 parent.removeChild(key); 296 } 297 return this; 298 } 299 300 insertNewValue(newValue, false); 301 return this; 302 } 303 304 /** 305 * Handles the process of setting a new value for this node. 306 * 307 * @param newValue The new value 308 * @param onlyIfNull If the insertion should only take place if the current value is null 309 */ 310 private void insertNewValue(Object newValue, boolean onlyIfNull) { 311 attachIfNecessary(); 312 313 synchronized (this) { 314 ConfigValue oldValue, value; 315 oldValue = value = this.value; 316 317 if (onlyIfNull && !(oldValue instanceof NullConfigValue)){ 318 return; 319 } 320 321 // init new config value backing for the new value type if necessary 322 if (newValue instanceof Collection) { 323 if (!(value instanceof ListConfigValue)) { 324 value = new ListConfigValue(this); 325 } 326 } else if (newValue instanceof Map) { 327 if (!(value instanceof MapConfigValue)) { 328 value = new MapConfigValue(this); 329 } 330 } else if (!(value instanceof ScalarConfigValue)) { 331 value = new ScalarConfigValue(this); 332 } 333 334 // insert the data into the config value 335 value.setValue(newValue); 336 337 /*if (oldValue != null && oldValue != value) { 338 oldValue.clear(); 339 }*/ 340 this.value = value; 341 } 342 } 343 344 @NonNull 345 @Override 346 public ConfigurationNode mergeValuesFrom(@NonNull ConfigurationNode other) { 347 if (other.isMap()) { 348 ConfigValue oldValue, newValue; 349 synchronized (this) { 350 oldValue = newValue = value; 351 352 // ensure the current type is applicable. 353 if (!(oldValue instanceof MapConfigValue)) { 354 if (oldValue instanceof NullConfigValue) { 355 newValue = new MapConfigValue(this); 356 } else { 357 return this; 358 } 359 } 360 361 // merge values from 'other' 362 for (Map.Entry<Object, ? extends ConfigurationNode> ent : other.getChildrenMap().entrySet()) { 363 SimpleConfigurationNode currentChild = newValue.getChild(ent.getKey()); 364 // Never allow null values to overwrite non-null values 365 if ((currentChild != null && currentChild.getValue() != null) && ent.getValue().getValue() == null) { 366 continue; 367 } 368 369 // create a new child node for the value 370 SimpleConfigurationNode newChild = this.createNode(ent.getKey()); 371 newChild.attached = true; 372 newChild.setValue(ent.getValue()); 373 // replace the existing value, if absent 374 SimpleConfigurationNode existing = newValue.putChildIfAbsent(ent.getKey(), newChild); 375 // if an existing value was present, attempt to merge the new value into it 376 if (existing != null) { 377 existing.mergeValuesFrom(newChild); 378 } 379 } 380 this.value = newValue; 381 } 382 } else if (other.getValue() != null) { 383 // otherwise, replace the value of this node, only if currently null 384 insertNewValue(other.getValue(), true); 385 } 386 return this; 387 } 388 389 @NonNull 390 @Override 391 public SimpleConfigurationNode getNode(@NonNull Object @NonNull... path) { 392 SimpleConfigurationNode pointer = this; 393 for (Object el : path) { 394 pointer = pointer.getChild(el, false); 395 } 396 return pointer; 397 } 398 399 @Override 400 public @NonNull SimpleConfigurationNode getNode(@NonNull Iterable<?> path) { 401 SimpleConfigurationNode pointer = this; 402 for (Object el : path) { 403 pointer = pointer.getChild(el, false); 404 } 405 return pointer; 406 } 407 408 @Override 409 public boolean isVirtual() { 410 return !attached; 411 } 412 413 @NonNull 414 @Override 415 public ValueType getValueType() { 416 return this.value.getType(); 417 } 418 419 @NonNull 420 @Override 421 @SuppressWarnings("unchecked") 422 public List<? extends SimpleConfigurationNode> getChildrenList() { 423 ConfigValue value = this.value; 424 return value instanceof ListConfigValue ? ImmutableList.copyOf(((ListConfigValue) value).values.get()) : Collections.emptyList(); 425 } 426 427 @NonNull 428 @Override 429 @SuppressWarnings("unchecked") 430 public Map<Object, ? extends SimpleConfigurationNode> getChildrenMap() { 431 ConfigValue value = this.value; 432 return value instanceof MapConfigValue ? ImmutableMap.copyOf(((MapConfigValue) value).values) : Collections.emptyMap(); 433 } 434 435 @Override 436 public boolean isEmpty() { 437 return this.value.isEmpty(); 438 } 439 440 /** 441 * Gets a child node, relative to this. 442 * 443 * @param key The key 444 * @param attach If the resultant node should be automatically attached 445 * @return The child node 446 */ 447 protected SimpleConfigurationNode getChild(Object key, boolean attach) { 448 SimpleConfigurationNode child = value.getChild(key); 449 450 // child doesn't currently exist 451 if (child == null) { 452 if (attach) { 453 // attach ourselves first 454 attachIfNecessary(); 455 // insert the child node into the value 456 SimpleConfigurationNode existingChild = value.putChildIfAbsent(key, (child = createNode(key))); 457 if (existingChild != null) { 458 child = existingChild; 459 } else { 460 attachChild(child); 461 } 462 } else { 463 // just create a new virtual (detached) node 464 child = createNode(key); 465 } 466 } 467 468 return child; 469 } 470 471 @Override 472 public boolean removeChild(@NonNull Object key) { 473 return detachIfNonNull(value.putChild(key, null)) != null; 474 } 475 476 private static SimpleConfigurationNode detachIfNonNull(SimpleConfigurationNode node) { 477 if (node != null) { 478 node.attached = false; 479 node.clear(); 480 } 481 return node; 482 } 483 484 @NonNull 485 @Override 486 @Deprecated 487 public SimpleConfigurationNode getAppendedNode() { 488 // the appended node can have a key of -1 489 // the "real" key will be determined when the node is inserted into a list config value 490 return getChild(-1, false); 491 } 492 493 @Nullable 494 @Override 495 public Object getKey() { 496 return this.key; 497 } 498 499 @NonNull 500 @Override 501 public Object[] getPath() { 502 LinkedList<Object> pathElements = new LinkedList<>(); 503 ConfigurationNode pointer = this; 504 if (pointer.getParent() == null) { 505 return new Object[]{}; // we're the root node, no key here 506 } 507 508 do { 509 pathElements.addFirst(pointer.getKey()); 510 } while ((pointer = pointer.getParent()).getParent() != null); 511 return pathElements.toArray(); 512 } 513 514 @Nullable 515 public SimpleConfigurationNode getParent() { 516 return this.parent; 517 } 518 519 @NonNull 520 @Override 521 public ConfigurationOptions getOptions() { 522 return this.options; 523 } 524 525 @NonNull 526 @Override 527 public SimpleConfigurationNode copy() { 528 return copy(null); 529 } 530 531 @NonNull 532 protected SimpleConfigurationNode copy(@Nullable SimpleConfigurationNode parent) { 533 return new SimpleConfigurationNode(parent, this); 534 } 535 536 /** 537 * The same as {@link #getParent()} - but ensuring that 'parent' is attached via 538 * {@link #attachChildIfAbsent(SimpleConfigurationNode)}. 539 * 540 * @return The parent 541 */ 542 SimpleConfigurationNode getParentEnsureAttached() { 543 SimpleConfigurationNode parent = this.parent; 544 if (parent.isVirtual()) { 545 parent = parent.getParentEnsureAttached().attachChildIfAbsent(parent); 546 547 } 548 return this.parent = parent; 549 } 550 551 protected void attachIfNecessary() { 552 if (!attached) { 553 getParentEnsureAttached().attachChild(this); 554 } 555 } 556 557 protected SimpleConfigurationNode createNode(Object path) { 558 return new SimpleConfigurationNode(path, this, options); 559 } 560 561 protected SimpleConfigurationNode attachChildIfAbsent(SimpleConfigurationNode child) { 562 return attachChild(child, true); 563 } 564 565 private void attachChild(SimpleConfigurationNode child) { 566 attachChild(child, false); 567 } 568 569 /** 570 * Attaches a child to this node 571 * 572 * @param child The child 573 * @return The resultant value 574 */ 575 private SimpleConfigurationNode attachChild(SimpleConfigurationNode child, boolean onlyIfAbsent) { 576 // ensure this node is attached 577 if (isVirtual()) { 578 throw new IllegalStateException("This parent is not currently attached. This is an internal state violation."); 579 } 580 581 // ensure the child actually is a child 582 if (!child.getParentEnsureAttached().equals(this)) { 583 throw new IllegalStateException("Child " + child + " path is not a direct parent of me (" + this + "), cannot attach"); 584 } 585 586 // update the value 587 ConfigValue oldValue, newValue; 588 synchronized (this) { 589 newValue = oldValue = this.value; 590 591 // if the existing value isn't a map, we need to update it's type 592 if (!(oldValue instanceof MapConfigValue)) { 593 if (child.key instanceof Integer) { 594 // if child.key is an integer, we can infer that the type of this node should be a list 595 if (oldValue instanceof NullConfigValue) { 596 // if the oldValue was null, we can just replace it with an empty list 597 newValue = new ListConfigValue(this); 598 } else if (!(oldValue instanceof ListConfigValue)) { 599 // if the oldValue contained a value, we add it as the first element of the 600 // new list 601 newValue = new ListConfigValue(this, oldValue.getValue()); 602 } 603 } else { 604 // if child.key isn't an integer, assume map 605 newValue = new MapConfigValue(this); 606 } 607 } 608 609 /// now the value has been updated to an appropriate type, we can insert the value 610 if (onlyIfAbsent) { 611 SimpleConfigurationNode oldChild = newValue.putChildIfAbsent(child.key, child); 612 if (oldChild != null) { 613 return oldChild; 614 } 615 } else { 616 detachIfNonNull(newValue.putChild(child.key, child)); 617 } 618 this.value = newValue; 619 } 620 621 if (newValue != oldValue) { 622 oldValue.clear(); 623 } 624 child.attached = true; 625 return child; 626 } 627 628 protected void clear() { 629 synchronized (this) { 630 ConfigValue oldValue = this.value; 631 value = NullConfigValue.instance(); 632 oldValue.clear(); 633 } 634 } 635 636 @Override 637 public <S, T, E extends Exception> T visit(ConfigurationVisitor<S, T, E> visitor, S state) throws E { 638 return visitInternal(visitor, state); 639 } 640 641 @Override 642 public <S, T> T visit(ConfigurationVisitor.Safe<S, T> visitor, S state) { 643 try { 644 return visitInternal(visitor, state); 645 } catch (VisitorSafeNoopException e) { 646 throw new Error("Exception was thrown on a Safe visitor"); 647 } 648 } 649 650 private <S, T, E extends Exception> T visitInternal(ConfigurationVisitor<S, T, E> visitor, S state) throws E { 651 visitor.beginVisit(this, state); 652 if (!(this.value instanceof NullConfigValue)) { // only visit if we have an actual value 653 LinkedList<Object> toVisit = new LinkedList<>(); 654 toVisit.add(this); 655 656 @Nullable Object active; 657 while ((active = toVisit.pollFirst()) != null) { 658 // try to pop a node from the stack, or handle the node exit if applicable 659 @Nullable SimpleConfigurationNode current = VisitorNodeEnd.popFromVisitor(active, visitor, state); 660 if (current == null) { 661 continue; 662 } 663 664 visitor.enterNode(current, state); 665 ConfigValue value = current.value; 666 if (value instanceof MapConfigValue) { 667 visitor.enterMappingNode(current, state); 668 toVisit.addFirst(new VisitorNodeEnd(current, true)); 669 toVisit.addAll(0, ((MapConfigValue) value).values.values()); 670 } else if (value instanceof ListConfigValue) { 671 visitor.enterListNode(current, state); 672 toVisit.addFirst(new VisitorNodeEnd(current, false)); 673 toVisit.addAll(0, ((ListConfigValue) value).values.get()); 674 } else if (value instanceof ScalarConfigValue) { 675 visitor.enterScalarNode(current, state); 676 } else { 677 throw new IllegalStateException("Unknown value type " + value.getClass()); 678 } 679 } 680 } 681 return visitor.endVisit(state); 682 } 683 684 @Override 685 public boolean equals(Object o) { 686 if (this == o) return true; 687 if (!(o instanceof SimpleConfigurationNode)) return false; 688 SimpleConfigurationNode that = (SimpleConfigurationNode) o; 689 690 return Objects.equals(this.key, that.key) && Objects.equals(this.value, that.value); 691 } 692 693 @Override 694 public int hashCode() { 695 return Objects.hashCode(key) ^ Objects.hashCode(value); 696 } 697 698 @Override 699 public String toString() { 700 return "AbstractConfigurationNode{key=" + key + ", value=" + value + '}'; 701 } 702}