package org.sef4j.core.helpers.proptree.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.sef4j.core.helpers.proptree.dto.PropTreeNodeDTO;
import org.sef4j.core.util.ICopySupport;
/**
* copy Mapper for PropTreeNode -> PropTreeNodeDTO
*
* <PRE>
* PropTreeNode +---------------------+ PropTreeNodeDTO
* | \ | PropTreeNodeMapper | | \
* | + prop1: value1 ----> | + prop1:mapper | --> | + prop1: MappedValue1
* | + prop2: value2 | | | ... (some props skipped)
* | ... +---------------------+ |
* | |
* + child1 + child1
* + child11 + child11
* .. .. (some child skipped)
* + child2 + child2
* + child21 + child21
* .. .. (some props skipped)
* </PRE>
*
* delegate to PropTreeValueMapper for mapping property values
* see also Orika Mapper sub-class OrikaPropTreeValueMapper for copying some key-value properties
*/
public class PropTreeNodeDTOMapper {
public static class PropMapperEntry {
public final String propName;
public final String destPropName;
public final PropTreeValueMapper mapper;
public final PropTreeValuePredicate<Object> propPredicate;
public final Predicate<Object> valuePredicate;
@SuppressWarnings("unchecked")
public PropMapperEntry(String propName, String destPropName,
PropTreeValueMapper mapper,
PropTreeValuePredicate<?> propPredicate,
Predicate<?> valuePredicate
) {
this.propName = propName;
if (destPropName == null) destPropName = propName;
this.destPropName = destPropName;
this.mapper = mapper;
this.propPredicate = (PropTreeValuePredicate<Object>) propPredicate;
this.valuePredicate = (Predicate<Object>) valuePredicate;
}
}
private final PropMapperEntry[] propMapperEntries;
private final int maxDepth;
private final Predicate<PropTreeNode> nodePredicate;
private final Predicate<PropTreeNode> recurseNodePredicate;
// ------------------------------------------------------------------------
protected PropTreeNodeDTOMapper(Builder b) {
this.propMapperEntries = b.propMapperEntries.toArray(new PropMapperEntry[b.propMapperEntries.size()]);
this.maxDepth = b.maxDepth;
this.nodePredicate = b.nodePredicate;
this.recurseNodePredicate = b.recurseNodePredicate;
}
public static class Builder {
private List<PropMapperEntry> propMapperEntries = new ArrayList<PropMapperEntry>();
private int maxDepth = -1;
private Predicate<PropTreeNode> nodePredicate;
private Predicate<PropTreeNode> recurseNodePredicate;
public PropTreeNodeDTOMapper build() {
return new PropTreeNodeDTOMapper(this);
}
public Builder withPropMapperEntries(PropMapperEntry... p) {
this.propMapperEntries.addAll(Arrays.asList(p));
return this;
}
public Builder withMaxDepth(int p) {
this.maxDepth = p;
return this;
}
public Builder withNodePredicate(Predicate<PropTreeNode> p) {
this.nodePredicate = p;
return this;
}
public Builder withRecuseNodePredicate(Predicate<PropTreeNode> p) {
this.recurseNodePredicate = p;
return this;
}
}
// ------------------------------------------------------------------------
public PropTreeNodeDTO map(PropTreeNode src) {
PropTreeNodeDTO res = PropTreeNodeDTO.newRoot();
recursiveCopyToDTO(src, res, maxDepth);
return res;
}
protected void recursiveCopyToDTO(PropTreeNode src, PropTreeNodeDTO dest, int remainDepth) {
if (nodePredicate == null || nodePredicate.test(src)) {
// Notice: map propValues may change in another thread => should copy to apply filtering then conversion...
copyPropValues(src, dest);
}
if (remainDepth == -1 || remainDepth > 0) {
int childMaxDepth = (remainDepth == -1)? -1 : remainDepth-1;
for(Map.Entry<String,PropTreeNode> e : src.getChildMap().entrySet()) { // read-only ref (copy-on-write field)
String childName = e.getKey();
if (childName == null) {
continue; //should not occur!!
}
PropTreeNode srcChild = e.getValue();
// *** recurse ***
if (srcChild.getParent() == null || srcChild.getParent().getParent() == null
|| recurseNodePredicate == null || recurseNodePredicate.test(srcChild)) {
PropTreeNodeDTO destChild = dest.getOrCreateChild(childName);
recursiveCopyToDTO(srcChild, destChild, childMaxDepth);
}
}
}
}
@SuppressWarnings("unchecked")
protected void copyPropValues(PropTreeNode src, PropTreeNodeDTO dest) {
Map<String, Object> propsMap = src.getPropsMap(); // read-only ref (copy-on-write field)
for (PropMapperEntry e : propMapperEntries) {
String propName = e.propName;
Object propValue = propsMap.get(propName);
if (propValue == null) {
continue; // value not present on this node
}
// propValue may change in another thread => create local immutable copy to apply filtering then conversion...
if (propValue instanceof ICopySupport) {
propValue = ((ICopySupport<Object>) propValue).copy();
}
if (e.propPredicate != null && ! e.propPredicate.test(src, propName, propValue)) {
continue; // ignore this prop!
}
if (e.valuePredicate != null && ! e.valuePredicate.test(propValue)) {
continue; // ignore this prop!
}
Object destPropValue = e.mapper.mapProp(src, propName, propValue);
if (destPropValue != null) {
dest.putProp(e.destPropName, destPropValue);
}
}
}
}