package fr.openwide.core.commons.util.fieldpath; import java.io.Serializable; import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.text.StrMatcher; import org.apache.commons.lang3.text.StrTokenizer; import org.bindgen.Bindable; import org.bindgen.Binding; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * A symbolic representation of the "field path", i.e. the chain of properties that need to be accessed in order to get * a certain value from an object. * <p>The "FieldPath" may be represented as a character string, such as: * <ul> * <li>"": the root fieldPath * <li>".myProperty": the path from the root to "myProperty" * <li>".myProperty.subProperty": the path from the root to the "subProperty" property of "myProperty" * <li>".myCollectionProperty[*]": the path from the root to an element of the (collection) property "myCollectionProperty" * <li>".myCollectionProperty[*].subProperty": the path from the root to the "subProperty" property of an element of the (collection) property "myCollectionProperty" * </ul> * <p>Note that these are <em>symbolic</em> representations, and as such they are not suitable for automatic retrieval * of values from a given root. For this sort of treatments, prefer the use of {@link Binding Bindings} */ @Bindable public class FieldPath implements Iterable<FieldPathComponent>, Serializable { // NOSONAR: this class CANNOT be final because of the ROOT static field. private static final long serialVersionUID = 2486324046309761966L; public static final FieldPath ROOT = new FieldPath(ImmutableList.<FieldPathComponent>of()) { private static final long serialVersionUID = 1L; private Object readResolve() { return ROOT; } }; private final List<FieldPathComponent> components; private static final StrMatcher DELIMITER_MATCHER = StrMatcher.charSetMatcher('.', '[', ']'); private static final String ITEM_TOKEN = "*"; public static final FieldPath fromString(String string) { if (StringUtils.isBlank(string)) { return null; } else { List<FieldPathComponent> components = Lists.newLinkedList(); StrTokenizer tokenizer = new StrTokenizer(string, DELIMITER_MATCHER); for (String token : tokenizer.getTokenList()) { if (ITEM_TOKEN.equals(token)) { components.add(FieldPathComponent.ITEM); } else { components.add(new FieldPathPropertyComponent(token)); } } return new FieldPath(components); } } public static final FieldPath fromBinding(Binding<?> binding) { if (binding == null) { return null; } else { List<FieldPathComponent> components = Lists.newLinkedList(); Binding<?> currentBinding = binding; Binding<?> parentBinding = currentBinding.getParentBinding(); while (parentBinding != null) { components.add(0, new FieldPathPropertyComponent(currentBinding.getName())); currentBinding = parentBinding; parentBinding = currentBinding.getParentBinding(); } return new FieldPath(components); } } public static FieldPath of(Iterable<FieldPathComponent> components) { return new FieldPath(components); } public static FieldPath of(FieldPathComponent ... components) { return new FieldPath(ImmutableList.copyOf(components)); } private FieldPath(Iterable<FieldPathComponent> components) { super(); this.components = ImmutableList.copyOf(components); } private FieldPath(Iterable<FieldPathComponent> components, Iterable<FieldPathComponent> addedComponents) { super(); this.components = ImmutableList.<FieldPathComponent>builder().addAll(components).addAll(addedComponents).build(); } private FieldPath(Iterable<FieldPathComponent> components, FieldPathComponent addedComponent) { super(); this.components = ImmutableList.<FieldPathComponent>builder().addAll(components).add(addedComponent).build(); } @Override public Iterator<FieldPathComponent> iterator() { return components.iterator(); } public int size() { return components.size(); } protected Optional<FieldPathComponent> lastComponent() { int size = components.size(); if (size > 0) { return Optional.of(components.get(size - 1)); } else { return Optional.absent(); } } public Optional<FieldPath> parent() { int size = components.size(); if (size > 0) { return Optional.of(new FieldPath(components.subList(0, size - 1))); } else { return Optional.absent(); } } public boolean isRoot() { return components.isEmpty(); } public boolean isItem() { FieldPathComponent lastComponent = lastComponent().orNull(); return lastComponent == FieldPathComponent.ITEM; } public Optional<FieldPath> container() { if (isItem()) { return parent(); } else { return Optional.absent(); } } public Optional<FieldPath> rootContainer() { int itemIndex = components.indexOf(FieldPathComponent.ITEM); if (itemIndex >= 0) { return Optional.of(new FieldPath(components.subList(0, itemIndex))); } else { return Optional.absent(); } } public FieldPath item() { return new FieldPath(components, FieldPathComponent.ITEM); } public boolean startsWith(FieldPath other) { if (other.components.size() > components.size()) { return false; } else { return components.subList(0, other.components.size()).equals(other.components); } } public Optional<FieldPath> relativeTo(FieldPath other) { if (startsWith(other)) { return Optional.of(new FieldPath(components.subList(other.components.size(), components.size()))); } else { return Optional.absent(); } } public Optional<FieldPath> relativeToParent() { if (isRoot()) { return Optional.absent(); } else { return relativeTo(parent().get()); } } public FieldPath append(FieldPath other) { return new FieldPath(components, other.components); } public FieldPath append(FieldPathComponent component) { return new FieldPath(components, component); } public FieldPath compose(FieldPath other) { return new FieldPath(other.components, components); } @Override public boolean equals(Object obj) { if (obj instanceof FieldPath) { FieldPath other = (FieldPath) obj; return new EqualsBuilder() .append(components, other.components) .build(); } else { return false; } } @Override public int hashCode() { return new HashCodeBuilder() .append(components) .build(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); for (FieldPathComponent component : components) { component.appendTo(builder); } return builder.toString(); } }