1 /*
2  * Copyright 2011-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.data.mapping.context;
17
18 import lombok.EqualsAndHashCode;
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.stream.Collectors;
25
26 import org.springframework.core.convert.converter.Converter;
27 import org.springframework.data.mapping.PersistentProperty;
28 import org.springframework.data.mapping.PersistentPropertyPath;
29 import org.springframework.data.util.TypeInformation;
30 import org.springframework.lang.Nullable;
31 import org.springframework.util.Assert;
32 import org.springframework.util.StringUtils;
33
34 /**
35  * Abstraction of a path of {@link PersistentProperty}s.
36  *
37  * @author Oliver Gierke
38  * @author Christoph Strobl
39  */

40 @EqualsAndHashCode
41 class DefaultPersistentPropertyPath<P extends PersistentProperty<P>> implements PersistentPropertyPath<P> {
42
43     private static final Converter<PersistentProperty<?>, String> DEFAULT_CONVERTER = (source) -> source.getName();
44     private static final String DEFAULT_DELIMITER = ".";
45
46     private final List<P> properties;
47
48     /**
49      * Creates a new {@link DefaultPersistentPropertyPath} for the given {@link PersistentProperty}s.
50      *
51      * @param properties must not be {@literal null}.
52      */

53     public DefaultPersistentPropertyPath(List<P> properties) {
54
55         Assert.notNull(properties, "Properties must not be null!");
56
57         this.properties = properties;
58     }
59
60     /**
61      * Creates an empty {@link DefaultPersistentPropertyPath}.
62      *
63      * @return
64      */

65     public static <T extends PersistentProperty<T>> DefaultPersistentPropertyPath<T> empty() {
66         return new DefaultPersistentPropertyPath<T>(Collections.emptyList());
67     }
68
69     /**
70      * Appends the given {@link PersistentProperty} to the current {@link PersistentPropertyPath}.
71      *
72      * @param property must not be {@literal null}.
73      * @return a new {@link DefaultPersistentPropertyPath} with the given property appended to the current one.
74      * @throws IllegalArgumentException in case the property is not a property of the type of the current leaf property.
75      */

76     public DefaultPersistentPropertyPath<P> append(P property) {
77
78         Assert.notNull(property, "Property must not be null!");
79
80         if (isEmpty()) {
81             return new DefaultPersistentPropertyPath<>(Collections.singletonList(property));
82         }
83
84         @SuppressWarnings("null")
85         Class<?> leafPropertyType = getLeafProperty().getActualType();
86
87         Assert.isTrue(property.getOwner().getType().equals(leafPropertyType),
88                 () -> String.format("Cannot append property %s to type %s!", property.getName(), leafPropertyType.getName()));
89
90         List<P> properties = new ArrayList<>(this.properties);
91         properties.add(property);
92
93         return new DefaultPersistentPropertyPath<>(properties);
94     }
95
96     /*
97      * (non-Javadoc)
98      * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath()
99      */

100     @Nullable
101     public String toDotPath() {
102         return toPath(DEFAULT_DELIMITER, DEFAULT_CONVERTER);
103     }
104
105     /*
106      * (non-Javadoc)
107      * @see org.springframework.data.mapping.context.PersistentPropertyPath#toDotPath(org.springframework.core.convert.converter.Converter)
108      */

109     @Nullable
110     public String toDotPath(Converter<? super P, String> converter) {
111         return toPath(DEFAULT_DELIMITER, converter);
112     }
113
114     /*
115      * (non-Javadoc)
116      * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String)
117      */

118     @Nullable
119     public String toPath(String delimiter) {
120         return toPath(delimiter, DEFAULT_CONVERTER);
121     }
122
123     /*
124      * (non-Javadoc)
125      * @see org.springframework.data.mapping.context.PersistentPropertyPath#toPath(java.lang.String, org.springframework.core.convert.converter.Converter)
126      */

127     @Nullable
128     public String toPath(String delimiter, Converter<? super P, String> converter) {
129
130         Assert.hasText(delimiter, "Delimiter must not be null or empty!");
131         Assert.notNull(converter, "Converter must not be null!");
132
133         String result = properties.stream() //
134                 .map(converter::convert) //
135                 .filter(StringUtils::hasText) //
136                 .collect(Collectors.joining(delimiter));
137
138         return result.isEmpty() ? null : result;
139     }
140
141     /*
142      * (non-Javadoc)
143      * @see org.springframework.data.mapping.context.PersistentPropertyPath#getLeafProperty()
144      */

145     @Nullable
146     public P getLeafProperty() {
147         return properties.isEmpty() ? null : properties.get(properties.size() - 1);
148     }
149
150     /*
151      * (non-Javadoc)
152      * @see org.springframework.data.mapping.context.PersistentPropertyPath#getBaseProperty()
153      */

154     @Nullable
155     public P getBaseProperty() {
156         return properties.isEmpty() ? null : properties.get(0);
157     }
158
159     /*
160      * (non-Javadoc)
161      * @see org.springframework.data.mapping.context.PersistentPropertyPath#isBasePathOf(org.springframework.data.mapping.context.PersistentPropertyPath)
162      */

163     public boolean isBasePathOf(PersistentPropertyPath<P> path) {
164
165         Assert.notNull(path, "PersistentPropertyPath must not be null!");
166
167         Iterator<P> iterator = path.iterator();
168
169         for (P property : this) {
170
171             if (!iterator.hasNext()) {
172                 return false;
173             }
174
175             P reference = iterator.next();
176
177             if (!property.equals(reference)) {
178                 return false;
179             }
180         }
181
182         return true;
183     }
184
185     /*
186      * (non-Javadoc)
187      * @see org.springframework.data.mapping.context.PersistentPropertyPath#getExtensionForBaseOf(org.springframework.data.mapping.context.PersistentPropertyPath)
188      */

189     public PersistentPropertyPath<P> getExtensionForBaseOf(PersistentPropertyPath<P> base) {
190
191         if (!base.isBasePathOf(this)) {
192             return this;
193         }
194
195         List<P> result = new ArrayList<>();
196         Iterator<P> iterator = iterator();
197
198         for (int i = 0; i < base.getLength(); i++) {
199             iterator.next();
200         }
201
202         while (iterator.hasNext()) {
203             result.add(iterator.next());
204         }
205
206         return new DefaultPersistentPropertyPath<>(result);
207     }
208
209     /*
210      * (non-Javadoc)
211      * @see org.springframework.data.mapping.context.PersistentPropertyPath#getParentPath()
212      */

213     public PersistentPropertyPath<P> getParentPath() {
214
215         int size = properties.size();
216
217         return size == 0 ? this : new DefaultPersistentPropertyPath<>(properties.subList(0, size - 1));
218     }
219
220     /*
221      * (non-Javadoc)
222      * @see org.springframework.data.mapping.context.PersistentPropertyPath#getLength()
223      */

224     public int getLength() {
225         return properties.size();
226     }
227
228     /*
229      * (non-Javadoc)
230      * @see java.lang.Iterable#iterator()
231      */

232     public Iterator<P> iterator() {
233         return properties.iterator();
234     }
235
236     /**
237      * Returns whether the current path contains a property of the given type.
238      *
239      * @param type can be {@literal null}.
240      * @return
241      */

242     public boolean containsPropertyOfType(@Nullable TypeInformation<?> type) {
243
244         return type == null //
245                 ? false //
246                 : properties.stream() //
247                         .anyMatch(property -> type.equals(property.getTypeInformation().getActualType()));
248     }
249
250     /*
251      * (non-Javadoc)
252      * @see java.lang.Object#toString()
253      */

254     @Override
255     @Nullable
256     public String toString() {
257         return toDotPath();
258     }
259 }
260