blob: 432488220f3d882655e24fa47972d32baaa01a38 [file] [log] [blame]
/*
* Copyright (c) 2019 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
*/
package org.eclipse.scout.rt.dataobject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
public final class DataObjectVisitors {
private DataObjectVisitors() {
}
/**
* Visits all nodes and calls on each element of given type the provided consumer. Matching nodes are visited
* <b>not</b> recursively. If a node matches child nodes of this node are not visited.
*/
public static <T> void forEach(Object root, Class<? extends T> elementType, Consumer<T> consumer) {
forEach(root, new P_TypedElementConsumer<>(elementType, consumer, false));
}
/**
* Visits all nodes and calls on each element of given type the provided consumer. Matching nodes are visited
* recursively. If a node matches, child nodes of this node are also visited.
*/
public static <T> void forEachRec(Object root, Class<? extends T> elementType, Consumer<T> consumer) {
forEach(root, new P_TypedElementConsumer<>(elementType, consumer, true));
}
/**
* Visits all nodes and calls on each element of given type the provided consumer. If consumer returns {@code true}
* then matching node is visited recursively.
*/
public static <T> void forEachRecIf(Object root, Class<? extends T> elementType, Predicate<T> consumer) {
forEach(root, new P_TypedElementConsumer<>(elementType, consumer));
}
private static void forEach(Object root, Predicate<Object> elementConsumer) {
new P_DataObjectVisitor(elementConsumer).visit(root);
}
/**
* Visits all nodes and calls on each element of given type the provided operator. The current value will be replace
* with the new value from calling the operator. Children of nodes that match element type are not visited.
* <p>
* Note: this operation might fail if data object contains unmodifiable collections or maps.
*/
public static <T> void replaceEach(Object root, Class<? extends T> elementType, UnaryOperator<T> operator) {
new P_ReplaceDataObjectVisitor<>(elementType, operator).visit(root);
}
private static final class P_DataObjectVisitor extends AbstractDataObjectVisitor {
private final Predicate<Object> m_elementConsumer;
private P_DataObjectVisitor(Predicate<Object> elementConsumer) {
m_elementConsumer = elementConsumer;
}
@Override
protected <T> void caseNode(T node, Consumer<T> chain) {
if (m_elementConsumer.test(node)) {
super.caseNode(node, chain); // call chain
}
}
}
private static final class P_TypedElementConsumer<T> implements Predicate<Object> {
private final Class<? extends T> m_elementType;
private final Predicate<T> m_elementConsumer;
private P_TypedElementConsumer(Class<? extends T> elementType, Consumer<T> elementConsumer, boolean recursive) {
this(elementType, o -> {
elementConsumer.accept(o);
return recursive;
});
}
private P_TypedElementConsumer(Class<? extends T> elementType, Predicate<T> elementConsumer) {
m_elementType = elementType;
m_elementConsumer = elementConsumer;
}
@Override
public boolean test(Object t) {
if (m_elementType.isInstance(t)) {
return m_elementConsumer.test(m_elementType.cast(t));
}
return true;
}
}
private static final class P_ReplaceDataObjectVisitor<T> extends AbstractDataObjectVisitor {
private final Class<? extends T> m_elementType;
private final UnaryOperator<T> m_operator;
public P_ReplaceDataObjectVisitor(Class<? extends T> elementType, UnaryOperator<T> operator) {
m_elementType = elementType;
m_operator = operator;
}
@Override
protected void caseCollection(Collection<?> collection) {
if (collection instanceof List) {
// order is important
updateList((List<?>) collection);
}
else {
// order can be ignored
updateCollection(collection);
}
}
@Override
protected void caseMap(Map<?, ?> map) {
updateMap(map);
}
@Override
protected void caseDoEntity(IDoEntity entity) {
for (DoNode<?> node : entity.allNodes().values()) {
updateDoNode(node);
}
}
@Override
protected void caseDoList(DoList<?> doList) {
updateList(doList.get());
}
private <LT> void updateList(List<LT> list) {
ListIterator<LT> it = list.listIterator();
while (it.hasNext()) {
LT value = it.next();
LT newValue = applyOperatorOrVisit(value);
if (value != newValue) {
it.remove();
it.add(newValue);
}
}
}
private <CT> void updateCollection(Collection<CT> collection) {
List<CT> newValues = null;
Iterator<CT> it = collection.iterator();
while (it.hasNext()) {
CT value = it.next();
CT newValue = applyOperatorOrVisit(value);
if (value != newValue) {
it.remove();
if (newValues == null) {
newValues = new ArrayList<>();
}
newValues.add(newValue);
}
}
if (newValues != null) {
collection.addAll(newValues);
}
}
private <K, V> void updateMap(Map<K, V> map) {
Map<K, V> newEntries = null;
Iterator<Entry<K, V>> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry<K, V> entry = it.next();
K key = entry.getKey();
K newKey = applyOperatorOrVisit(key);
V value = entry.getValue();
V newValue = applyOperatorOrVisit(value);
if (newKey != key || newValue != value) {
it.remove();
if (newEntries == null) {
newEntries = new HashMap<>();
}
newEntries.put(newKey, newValue);
}
}
if (newEntries != null) {
map.putAll(newEntries);
}
}
private <NT> void updateDoNode(DoNode<NT> node) {
node.set(applyOperatorOrVisit(node.get()));
}
@SuppressWarnings("unchecked")
private <OT> OT applyOperatorOrVisit(OT o) {
if (m_elementType.isInstance(o)) {
return (OT) m_operator.apply(m_elementType.cast(o));
}
else {
visit(o);
return o;
}
}
}
}