package ameba.message.internal;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.*;
/**
* This is a Tree like structure of paths and properties that can be used for
* defining which parts of an object graph to render in JSON or XML, and can
* also be used to define which parts to select and fetch for an ORM query.
* <p>
* It provides a way of parsing a string representation of nested path
* properties and applying that to both what to fetch (ORM query) and what to
* render (JAX-RS JSON / XML).
* </p>
*
* @author icode
*
*/
public class BeanPathProperties {
private final Map<String, Props> pathMap;
/**
* Construct an empty BeanPathProperties.
*/
public BeanPathProperties() {
this.pathMap = Maps.newLinkedHashMap();
this.pathMap.put(null, new Props(this, null, null));
}
/**
* Construct for creating copy.
*/
private BeanPathProperties(BeanPathProperties orig) {
this.pathMap = new LinkedHashMap<>(orig.pathMap.size());
Set<Map.Entry<String, Props>> entrySet = orig.pathMap.entrySet();
for (Map.Entry<String, Props> e : entrySet) {
pathMap.put(e.getKey(), e.getValue().copy(this));
}
}
/**
* Parse and return a BeanPathProperties from nested string format like
* (a,b,c(d,e),f(g)) where "c" is a path containing "d" and "e" and "f" is a
* path containing "g" and the root path contains "a","b","c" and "f".
*
* @param source source path
* @return path properties
*/
public static BeanPathProperties parse(String source) {
return PathPropertiesParser.parse(source);
}
/**
* Create a copy of this instance so that it can be modified.
* <p>
* For example, you may want to create a copy to add extra properties to a
* path so that they are fetching in a ORM query but perhaps not rendered by
* default. That is, use a BeanPathProperties for JSON or XML rendering, but
* create a copy, add some extra properties and then use that copy to define
* an ORM query.
* </p>
*
* @return path properties
*/
public BeanPathProperties copy() {
return new BeanPathProperties(this);
}
/**
* Return true if there are no paths defined.
*
* @return true is empty
*/
public boolean isEmpty() {
return pathMap.isEmpty();
}
/**
* <p>toString.</p>
*
* @return a {@link java.lang.String} object.
*/
public String toString() {
return pathMap.toString();
}
/**
* Return true if the path is defined and has properties.
*
* @param path path
* @return true is has path
*/
public boolean hasPath(String path) {
Props props = pathMap.get(path);
return props != null && !props.isEmpty();
}
/**
* Get the properties for a given path.
*
* @param path path
* @return properties
*/
public Set<String> getProperties(String path) {
Props props = pathMap.get(path);
return props == null ? null : props.getProperties();
}
/**
* <p>addToPath.</p>
*
* @param path a {@link java.lang.String} object.
* @param property a {@link java.lang.String} object.
*/
public void addToPath(String path, String property) {
Props props = pathMap.computeIfAbsent(path, k -> new Props(this, null, path));
props.getProperties().add(property);
}
/**
* Set the properties for a given path.
*
* @param path path
* @param properties properties set
*/
public void put(String path, Set<String> properties) {
pathMap.put(path, new Props(this, null, path, properties));
}
/**
* Remove a path returning the properties set for that path.
*
* @param path path
* @return properties set
*/
public Set<String> remove(String path) {
Props props = pathMap.remove(path);
return props == null ? null : props.getProperties();
}
/**
* Return a shallow copy of the paths.
*
* @return path set
*/
public Set<String> getPaths() {
return Sets.newLinkedHashSet(pathMap.keySet());
}
/**
* <p>getPathProps.</p>
*
* @return a {@link java.util.Collection} object.
*/
public Collection<Props> getPathProps() {
return pathMap.values();
}
/**
* Each these path properties as fetch paths to the query.
*
* @param each each process
*/
public void each(Each<Props> each) {
for (Map.Entry<String, Props> entry : pathMap.entrySet()) {
Props props = entry.getValue();
each.execute(props);
}
}
/**
* <p>getRootProperties.</p>
*
* @return a {@link ameba.message.internal.BeanPathProperties.Props} object.
*/
public Props getRootProperties() {
return pathMap.get(null);
}
public interface Each<PROPS> {
void execute(PROPS props);
}
public static class Props {
private final BeanPathProperties owner;
private final String parentPath;
private final String path;
private final Set<String> propSet;
private Props(BeanPathProperties owner, String parentPath, String path, Set<String> propSet) {
this.owner = owner;
this.path = path;
this.parentPath = parentPath;
this.propSet = propSet;
}
private Props(BeanPathProperties owner, String parentPath, String path) {
this(owner, parentPath, path, new LinkedHashSet<>());
}
/**
* Create a shallow copy of this Props instance.
*
* @param newOwner new owner
* @return properties
*/
public Props copy(BeanPathProperties newOwner) {
return new Props(newOwner, parentPath, path, Sets.newLinkedHashSet(propSet));
}
public String getPath() {
return path;
}
public String toString() {
return propSet.toString();
}
public boolean isEmpty() {
return propSet.isEmpty();
}
/**
* Return the properties for this property set.
*
* @return property set
*/
public Set<String> getProperties() {
return propSet;
}
/**
* Return the properties as a comma delimited string.
*
* @return properties string
*/
public String getPropertiesAsString() {
StringBuilder sb = new StringBuilder();
Iterator<String> it = propSet.iterator();
boolean hasNext = it.hasNext();
while (hasNext) {
sb.append(it.next());
hasNext = it.hasNext();
if (hasNext) {
sb.append(",");
}
}
return sb.toString();
}
/**
* Return the parent path
*
* @return parent props
*/
protected Props getParent() {
return owner.pathMap.get(parentPath);
}
/**
* Add a child Property set.
*
* @param subpath subpath
* @return prop
*/
protected Props addChild(String subpath) {
subpath = subpath.trim();
addProperty(subpath);
// build the subPath
String p = path == null ? subpath : path + "." + subpath;
Props nested = new Props(owner, path, p);
owner.pathMap.put(p, nested);
return nested;
}
/**
* Add a properties to include for this path.
*
* @param property property
*/
protected void addProperty(String property) {
propSet.add(property.trim());
}
}
}