/*
* Copyright 2014 Daniel Bechler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.danielbechler.diff.path;
import de.danielbechler.diff.selector.BeanPropertyElementSelector;
import de.danielbechler.diff.selector.CollectionItemElementSelector;
import de.danielbechler.diff.selector.ElementSelector;
import de.danielbechler.diff.selector.MapKeyElementSelector;
import de.danielbechler.diff.selector.RootElementSelector;
import de.danielbechler.util.Assert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* @author Daniel Bechler
*/
public final class NodePath implements Comparable<NodePath>
{
private final List<ElementSelector> elementSelectors;
private NodePath(final List<ElementSelector> elementSelectors)
{
this.elementSelectors = Collections.unmodifiableList(elementSelectors);
}
public boolean isParentOf(final NodePath nodePath)
{
final List<ElementSelector> otherElementSelectors = nodePath.getElementSelectors();
if (elementSelectors.size() < otherElementSelectors.size())
{
return otherElementSelectors.subList(0, elementSelectors.size()).equals(elementSelectors);
}
return false;
}
public boolean isChildOf(final NodePath nodePath)
{
final List<ElementSelector> otherElementSelectors = nodePath.getElementSelectors();
if (elementSelectors.size() > otherElementSelectors.size())
{
return elementSelectors.subList(0, otherElementSelectors.size()).equals(otherElementSelectors);
}
return false;
}
public ElementSelector getLastElementSelector()
{
return elementSelectors.get(elementSelectors.size() - 1);
}
@Override
public int hashCode()
{
return elementSelectors.hashCode();
}
@Override
public boolean equals(final Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
final NodePath that = (NodePath) o;
if (!elementSelectors.equals(that.elementSelectors))
{
return false;
}
return true;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
final Iterator<ElementSelector> iterator = elementSelectors.iterator();
ElementSelector previousElementSelector = null;
while (iterator.hasNext())
{
final ElementSelector elementSelector = iterator.next();
if (elementSelector instanceof RootElementSelector)
{
sb.append("/");
}
else if (elementSelector instanceof CollectionItemElementSelector || elementSelector instanceof MapKeyElementSelector)
{
sb.append(elementSelector);
}
else if (previousElementSelector instanceof RootElementSelector)
{
sb.append(elementSelector);
}
else
{
sb.append('/');
sb.append(elementSelector);
}
previousElementSelector = elementSelector;
}
return sb.toString();
}
public int compareTo(final NodePath that)
{
if (this.getElementSelectors().size() <= that.getElementSelectors().size())
{
return -1;
}
else if (this.matches(that))
{
return 0;
}
else if (this.getElementSelectors().size() > that.getElementSelectors().size())
{
return 1;
}
else
{
return 1;
}
}
public boolean matches(final NodePath nodePath)
{
return nodePath.equals(this);
}
public static AppendableBuilder startBuildingFrom(final NodePath nodePath)
{
Assert.notNull(nodePath, "propertyPath");
return new AppendableBuilderImpl(new ArrayList<ElementSelector>(nodePath.getElementSelectors()));
}
public List<ElementSelector> getElementSelectors()
{
return elementSelectors;
}
public static NodePath with(final String propertyName, final String... additionalPropertyNames)
{
return startBuilding().propertyName(propertyName, additionalPropertyNames).build();
}
public static AppendableBuilder startBuilding()
{
final List<ElementSelector> elementSelectors1 = new LinkedList<ElementSelector>();
elementSelectors1.add(RootElementSelector.getInstance());
return new AppendableBuilderImpl(elementSelectors1);
}
public static NodePath withRoot()
{
return startBuilding().build();
}
public static interface AppendableBuilder
{
AppendableBuilder element(ElementSelector elementSelector);
AppendableBuilder propertyName(String name, String... names);
<T> AppendableBuilder collectionItem(T item);
<K> AppendableBuilder mapKey(K key);
NodePath build();
}
private static final class AppendableBuilderImpl implements AppendableBuilder
{
private final List<ElementSelector> elementSelectors;
public AppendableBuilderImpl(final List<ElementSelector> elementSelectors)
{
Assert.notEmpty(elementSelectors, "elementSelectors");
this.elementSelectors = new LinkedList<ElementSelector>(elementSelectors);
}
public AppendableBuilder element(final ElementSelector elementSelector)
{
Assert.notNull(elementSelector, "elementSelector");
elementSelectors.add(elementSelector);
return this;
}
public AppendableBuilder propertyName(final String name, final String... names)
{
elementSelectors.add(new BeanPropertyElementSelector(name));
for (final String s : names)
{
elementSelectors.add(new BeanPropertyElementSelector(s));
}
return this;
}
public <T> AppendableBuilder collectionItem(final T item)
{
elementSelectors.add(new CollectionItemElementSelector(item));
return this;
}
public <K> AppendableBuilder mapKey(final K key)
{
Assert.notNull(key, "key");
elementSelectors.add(new MapKeyElementSelector(key));
return this;
}
public NodePath build()
{
if (elementSelectors.isEmpty())
{
throw new IllegalStateException("A property path cannot be empty");
}
else if (!(elementSelectors.get(0) instanceof RootElementSelector))
{
throw new IllegalStateException("A property path must start with a root element");
}
else if (elementCount(RootElementSelector.class) > 1)
{
throw new IllegalStateException("A property path cannot contain multiple root elements");
}
return new NodePath(elementSelectors);
}
private int elementCount(final Class<? extends ElementSelector> type)
{
assert type != null : "Type must not be null";
int count = 0;
for (final ElementSelector elementSelector : elementSelectors)
{
if (type.isAssignableFrom(elementSelector.getClass()))
{
count++;
}
}
return count;
}
}
}