/** * Exposes a document as a boolean, suitable for holding muted state. * * @param router router for the muted document * @return muted state. */ private static <E> ObservableBasicValue<Boolean> createMuted( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBoolean.create(router, router.getDocument().getDocumentElement(), MUTED_TAG, MUTED_ATTR); }
/** * Creates a basic value. * * @param router router for the document holding the value state * @param container container on which the value state is stored * @param tag tag name to use for child elements of the container * @param valueAttrName name to use for value attributes */ public static <E> DocumentBasedBoolean<E> create( DocumentEventRouter<? super E, E, ?> router, E container, String tag, String valueAttrName) { DocumentBasedBoolean<E> value = new DocumentBasedBoolean<E>(router, container, tag, valueAttrName); router.addChildListener(container, value); value.load(); return value; }
private void changeValue(E newElement) { Boolean oldValue = get(); if (newElement != null) { valueElement = newElement; // It's a positive element if it has the right tagname and does not have // value = false. Note, absence of attributes is interpreted as a positive // element. value = !FALSE.equals(getDocument().getAttribute(valueElement, valueAttr)); } else { valueElement = null; value = null; } Boolean newValue = get(); maybeTriggerOnValueChanged(oldValue, newValue); }
@Override public void onElementRemoved(E oldElement) { if (!tag.equals(getDocument().getTagName(oldElement))) { return; } redundantElements.remove(oldElement); if (oldElement == valueElement) { // Reference value is removed. Find new best value from redundant collection. changeValue(extractLastRedundant()); } }
/** * Deletes redundant elements. */ private void cleanup() { Boolean beforeValue = get(); // Delete all elements identified as redundant. We don't delete _every_ // element in the document in order that this type can be embedded // collaboratively in the same document as other types. Collection<E> toDelete = new ArrayList<E>(); toDelete.addAll(redundantElements); ObservableMutableDocument<? super E, E, ?> doc = getDocument(); for (E e : toDelete) { doc.deleteNode(e); } // Callbacks should have emptied the redundant collection. assert redundantElements.isEmpty(); // Check that cleanup did not change the interpretation. assert equals(beforeValue, get()); }
public void testInitialStateIsNull() { assertNull(target.get()); }
@Override public void set(Boolean newValue) { Boolean oldValue = get(); if (equals(oldValue, newValue)) { return; } if (newValue != null) { // Add an element to reflect new value. getDocument().createChildElement(container, tag, new AttributesImpl(valueAttr, Serializer.BOOLEAN.toString(newValue))); cleanup(); } else { // Erase all elements. Cleanup first, so that removal of real element // does not promote a redundant element temporarily. cleanup(); getDocument().deleteNode(valueElement); } assert equals(newValue, get()); }
public void testWriteNullCleansUp() { createTargetOn(Arrays.asList(false, true, null)); target.set(null); assertSubstrate(Arrays.<Boolean>asList()); }
/** * Loads state from the substrate document. */ private void load() { ObservableMutableDocument<? super E, E, ?> document = getDocument(); E child = DocHelper.getFirstChildElement(document, container); while (child != null) { onElementAdded(child); child = DocHelper.getNextSiblingElement(document, child); } }
@Override public void onElementAdded(E newElement) { ObservableMutableDocument<? super E, E, ?> document = getDocument(); assert container.equals(document.getParentElement(newElement)); if (!tag.equals(document.getTagName(newElement))) { return; } // Possibly changing an existing value? if (valueElement != null) { if (document.getLocation(newElement) < document.getLocation(valueElement)) { // New element loses. redundantElements.add(newElement); } else { // New element wins. redundantElements.add(valueElement); changeValue(newElement); } } else { // New element is the new value. changeValue(newElement); } }
/** * Notifies listeners if oldValue and newValue are different. */ private void maybeTriggerOnValueChanged(Boolean oldValue, Boolean newValue) { if (!equals(oldValue, newValue)) { for (Listener<Boolean> listener : listeners) { listener.onValueChanged(oldValue, newValue); } } }
private E extractLastRedundant() { E maxElement = null; int maxLocation = -1; for (E element : redundantElements) { // NOTE(user): there is a possible bug here, which will be fixed by // not individualising the element events in the ElementListener // interface. int location = getDocument().getLocation(element); if (location > maxLocation) { maxLocation = location; maxElement = element; } } redundantElements.remove(maxElement); return maxElement; }
public void testInitialStateIsNull() { assertNull(target.get()); }
public void testWriteNullCleansUp() { createTargetOn(Arrays.asList(false, true, null)); target.set(null); assertSubstrate(Arrays.<Boolean>asList()); }
public void testBackwardsCompatibleWithCorruptFalse() { createTargetOn(Arrays.asList(false, false)); assertFalse(target.get()); }
/** * Exposes a document as a boolean, suitable for holding muted state. * * @param router router for the muted document * @return muted state. */ private static <E> ObservableBasicValue<Boolean> createCleared( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBoolean.create(router, router.getDocument().getDocumentElement(), CLEARED_TAG, CLEARED_ATTR); }
public void testWriteTrueCleansUp() { createTargetOn(Arrays.asList(false, false)); target.set(true); assertSubstrate(Arrays.asList(true)); }