/*
* Copyright 2011-2017 the original author or authors.
*
* 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 org.springframework.data.mapping.context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Abstraction of a path of {@link PersistentProperty}s.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
class DefaultPersistentPropertyPath<T extends PersistentProperty<T>> implements PersistentPropertyPath<T> {
private enum PropertyNameConverter implements Converter<PersistentProperty<?>, String> {
INSTANCE;
public String convert(PersistentProperty<?> source) {
return source.getName();
}
}
private final List<T> properties;
/**
* Creates a new {@link DefaultPersistentPropertyPath} for the given {@link PersistentProperty}s.
*
* @param properties must not be {@literal null}.
*/
public DefaultPersistentPropertyPath(List<T> properties) {
Assert.notNull(properties, "Properties must not be null!");
this.properties = properties;
}
/**
* Creates an empty {@link DefaultPersistentPropertyPath}.
*
* @return
*/
public static <T extends PersistentProperty<T>> DefaultPersistentPropertyPath<T> empty() {
return new DefaultPersistentPropertyPath<>(Collections.<T> emptyList());
}
/**
* Appends the given {@link PersistentProperty} to the current {@link PersistentPropertyPath}.
*
* @param property must not be {@literal null}.
* @return a new {@link DefaultPersistentPropertyPath} with the given property appended to the current one.
* @throws IllegalArgumentException in case the property is not a property of the type of the current leaf property.
*/
public DefaultPersistentPropertyPath<T> append(T property) {
Assert.notNull(property, "Property must not be null!");
if (isEmpty()) {
return new DefaultPersistentPropertyPath<>(Collections.singletonList(property));
}
Class<?> leafPropertyType = getLeafProperty().getActualType();
Assert.isTrue(property.getOwner().getType().equals(leafPropertyType),
String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName()));
List<T> properties = new ArrayList<>(this.properties);
properties.add(property);
return new DefaultPersistentPropertyPath<>(properties);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath()
*/
public String toDotPath() {
return toPath(null, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath(org.springframework.core.convert.converter.Converter)
*/
public String toDotPath(Converter<? super T, String> converter) {
return toPath(null, converter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String)
*/
public String toPath(String delimiter) {
return toPath(delimiter, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String, org.springframework.core.convert.converter.Converter)
*/
public String toPath(String delimiter, Converter<? super T, String> converter) {
@SuppressWarnings("unchecked")
Converter<? super T, String> converterToUse = converter == null
? PropertyNameConverter.INSTANCE : converter;
String delimiterToUse = delimiter == null ? "." : delimiter;
List<String> result = new ArrayList<>();
for (T property : properties) {
String convert = converterToUse.convert(property);
if (StringUtils.hasText(convert)) {
result.add(convert);
}
}
return result.isEmpty() ? null : StringUtils.collectionToDelimitedString(result, delimiterToUse);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#getLeafProperty()
*/
public T getLeafProperty() {
return properties.get(properties.size() - 1);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#getBaseProperty()
*/
public T getBaseProperty() {
return properties.get(0);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#isBasePathOf(org.springframework.data.mapping.context.PersistentPropertyPath)
*/
public boolean isBasePathOf(PersistentPropertyPath<T> path) {
if (path == null) {
return false;
}
Iterator<T> iterator = path.iterator();
for (T property : this) {
if (!iterator.hasNext()) {
return false;
}
T reference = iterator.next();
if (!property.equals(reference)) {
return false;
}
}
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#getExtensionForBaseOf(org.springframework.data.mapping.context.PersistentPropertyPath)
*/
public PersistentPropertyPath<T> getExtensionForBaseOf(PersistentPropertyPath<T> base) {
if (!base.isBasePathOf(this)) {
return this;
}
List<T> result = new ArrayList<>();
Iterator<T> iterator = iterator();
for (int i = 0; i < base.getLength(); i++) {
iterator.next();
}
while (iterator.hasNext()) {
result.add(iterator.next());
}
return new DefaultPersistentPropertyPath<>(result);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#getParentPath()
*/
public PersistentPropertyPath<T> getParentPath() {
int size = properties.size();
if (size <= 1) {
return this;
}
return new DefaultPersistentPropertyPath<>(properties.subList(0, size - 1));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#getLength()
*/
public int getLength() {
return properties.size();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator<T> iterator() {
return properties.iterator();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.PersistentPropertyPath#isEmpty()
*/
public boolean isEmpty() {
return properties.isEmpty();
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !getClass().equals(obj.getClass())) {
return false;
}
DefaultPersistentPropertyPath<?> that = (DefaultPersistentPropertyPath<?>) obj;
return this.properties.equals(that.properties);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return properties.hashCode();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return toDotPath();
}
}